Skip to content

Commit

Permalink
Caching of schemas and properties to avoid duplicate type analysis; B…
Browse files Browse the repository at this point in the history
…ug fixes in function calling
  • Loading branch information
ddebowczyk committed Mar 4, 2024
1 parent 40ed002 commit a50c2b7
Show file tree
Hide file tree
Showing 13 changed files with 195 additions and 161 deletions.
15 changes: 7 additions & 8 deletions examples/ClassificationMulticlass/run.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,27 +18,26 @@ enum Label : string {
}

/** Represents analysed ticket data */
class Ticket {
class TicketLabels {
/** @var Label[] */
public array $ticketLabels = [];
public array $labels = [];
}

// Perform single-label classification on the input text.
function multi_classify(string $data) : Ticket {
function multi_classify(string $data) : TicketLabels {
return (new Instructor())->respond(
messages: [[
"role" => "user",
"content" => "Classify following support ticket: {$data}",
"content" => "Label following support ticket: {$data}",
]],
responseModel: Ticket::class,
model: "gpt-3.5-turbo-0613",
responseModel: TicketLabels::class,
);
}

// Test single-label classification
$ticket = "My account is locked and I can't access my billing info.";
$prediction = multi_classify($ticket);

assert(in_array(Label::TECH_ISSUE, $prediction->classLabels));
assert(in_array(Label::BILLING, $prediction->classLabels));
assert(in_array(Label::TECH_ISSUE, $prediction->labels));
assert(in_array(Label::BILLING, $prediction->labels));
dump($prediction);
9 changes: 5 additions & 4 deletions examples/RestatingInstructions/run.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,25 @@
*/
class Role
{
/** Restate the instructions and rules to correctly determine the title. */
/** Restate instructions and rules, so you can correctly determine the title. */
public string $instructions;
/** Role description */
public string $description;
/* Most likely job title */
public string $title;
}

/** Details of analyzed user. The key information we're looking for is appropriate role data. */
class UserDetail
{
public string $name;
public int $age;
public Role $role;
}

$user = (new Instructor)->respond(
messages: [["role" => "user", "content" => "I'm Jason, I'm 28 yo. I am responsible for driving growth of our company."]],
$instructor = new Instructor;
$user = ($instructor)->respond(
messages: [["role" => "user", "content" => "I'm Jason, I'm 28 yo. I am the head of Apex Software, responsible for driving growth of our company."]],
responseModel: UserDetail::class,
);

dump($user);
10 changes: 9 additions & 1 deletion src/Instructor.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
* Use respond() method to generate structured responses from LLM calls.
*/
class Instructor {
private LLM $llm;
protected LLM $llm;
protected $messages;
public $retryPrompt = "Recall function correctly, fix following errors:";

public function __construct(
Expand Down Expand Up @@ -89,4 +90,11 @@ public function json() : string {
public function response() : array {
return $this->llm->response();
}

/**
* Most recent request sent to LLM
*/
public function request() : array {
return $this->llm->request();
}
}
10 changes: 8 additions & 2 deletions src/LLMs/OpenAI/LLM.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class LLM implements CanCallFunction
{
private Client $client;
private CreateResponse $response;
private array $request;

public function __construct(
string $apiKey = '',
Expand All @@ -34,22 +35,27 @@ public function callFunction(
string $model = 'gpt-4-0125-preview',
array $options = []
) : string {
$this->response = $this->client->chat()->create(array_merge([
$this->request = array_merge([
'model' => $model,
'messages' => $messages,
'tools' => [$functionSchema],
'tool_choice' => [
'type' => 'function',
'function' => ['name' => $functionName]
]
], $options));
], $options);
$this->response = $this->client->chat()->create($this->request);
return $this->data();
}

public function response() : array {
return $this->response->toArray();
}

public function request() : array {
return $this->request;
}

public function data() : string {
return $this->response->choices[0]->message->toolCalls[0]->function->arguments ?? '';
}
Expand Down
12 changes: 10 additions & 2 deletions src/PropertyMap.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,15 @@ class PropertyMap
{
private $map = [];

public function get(string $class, string $property) : Schema
{
public function register(string $class, string $property, Schema $schema) {
$this->map[$class][$property] = $schema;
}

public function get(string $class, string $property) : Schema {
return $this->map[$class][$property];
}

public function has(string $class, string $property) : bool {
return isset($this->map[$class][$property]);
}
}
2 changes: 2 additions & 0 deletions src/Schema/PropertyInfoBased/Data/Schema/ObjectRefSchema.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace Cognesy\Instructor\Schema\PropertyInfoBased\Data\Schema;

use Cognesy\Instructor\Schema\PropertyInfoBased\Data\Reference;

class ObjectRefSchema extends Schema
{
private string $defsLabel = 'definitions';
Expand Down
3 changes: 2 additions & 1 deletion src/Schema/PropertyInfoBased/Data/Schema/ObjectSchema.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,10 @@ public function toArray(callable $refCallback = null) : array
}
return array_filter([
'type' => 'object',
'title' => $this->name,
'description' => $this->description,
'properties' => $propertyDefs,
'required' => $this->required,
'description' => $this->description,
]);
}
}
114 changes: 74 additions & 40 deletions src/Schema/PropertyInfoBased/Factories/SchemaFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Cognesy\Instructor\Schema\PropertyInfoBased\Factories;

use Cognesy\Instructor\PropertyMap;
use Cognesy\Instructor\Schema\PropertyInfoBased\Data\Schema\ArraySchema;
use Cognesy\Instructor\Schema\PropertyInfoBased\Data\Schema\EnumSchema;
use Cognesy\Instructor\Schema\PropertyInfoBased\Data\Schema\ObjectRefSchema;
Expand All @@ -10,6 +11,7 @@
use Cognesy\Instructor\Schema\PropertyInfoBased\Data\Schema\Schema;
use Cognesy\Instructor\Schema\PropertyInfoBased\Data\TypeDetails;
use Cognesy\Instructor\Schema\PropertyInfoBased\Utils\ClassInfo;
use Cognesy\Instructor\SchemaMap;

/**
* Factory for creating schema objects from class names
Expand All @@ -21,8 +23,13 @@ class SchemaFactory
{
/** @var bool allows to render schema with object properties inlined or referenced */
protected $useObjectReferences = false;
protected SchemaMap $schemaMap;
protected PropertyMap $propertyMap;

public function __construct() {}
public function __construct() {
$this->schemaMap = new SchemaMap;
$this->propertyMap = new PropertyMap;
}

/**
* Extracts the schema from a class and constructs a function call
Expand All @@ -31,7 +38,59 @@ public function __construct() {}
*/
public function schema(string $anyType) : Schema
{
return $this->makeSchema((new TypeDetailsFactory)->fromTypeName($anyType), '', '');
if (!$this->schemaMap->has($anyType)) {
$this->schemaMap->register($anyType, $this->makeSchema((new TypeDetailsFactory)->fromTypeName($anyType)));
}
return $this->schemaMap->get($anyType);
}

public function property(string $class, string $property) : Schema
{
if (!$this->propertyMap->has($class, $property)) {
$this->propertyMap->register($class, $property, $this->getPropertySchema($class, $property));
}
return $this->propertyMap->get($class, $property);
}

/**
* Gets all the property schemas of a class
*
* @param string $class
* @return Schema[]
*/
protected function getPropertySchemas(string $class) : array {
$properties = (new ClassInfo)->getProperties($class);
$propertySchemas = [];
foreach ($properties as $property) {
$propertySchemas[$property] = $this->property($class, $property);
}
return $propertySchemas;
}

/**
* Gets the schema of a property
*
* @param string $class
* @param string $property
* @return Schema
*/
protected function getPropertySchema(string $class, string $property) : Schema {
$propertyInfoType = (new ClassInfo)->getType($class, $property);
$type = (new TypeDetailsFactory)->fromPropertyInfo($propertyInfoType);
$description = $this->getPropertyDescription($type, $class, $property);
return $this->makePropertySchema($type, $property, $description);
}

protected function getPropertyDescription(TypeDetails $type, string $class, string $property) : string{
if (in_array($type->type, ['object', 'enum'])) {
$classDescription = (new ClassInfo)->getClassDescription($type->class);
} else {
$classDescription = '';
}
return implode("\n", array_filter([
(new ClassInfo)->getPropertyDescription($class, $property),
$classDescription,
]));
}

/**
Expand All @@ -42,24 +101,28 @@ public function schema(string $anyType) : Schema
* @param string $description
* @return Schema
*/
protected function makeSchema(TypeDetails $type, string $name, string $description) : Schema
protected function makeSchema(TypeDetails $type) : Schema
{
return match ($type->type) {
'object' => new ObjectSchema(
$type,
$name,
$description,
$type->class,
(new ClassInfo)->getClassDescription($type->class),
$this->getPropertySchemas($type->class),
(new ClassInfo)->getRequiredProperties($type->class),
),
'enum' => new EnumSchema($type, $name, $description),
'enum' => new EnumSchema(
$type,
$type->class,
(new ClassInfo)->getClassDescription($type->class),
),
'array' => new ArraySchema(
$type,
$name,
$description,
$this->makePropertySchema($type, $name, $description),
'',
'',
$this->makePropertySchema($type, 'item', 'Array item'),
),
'int', 'string', 'bool', 'float' => new ScalarSchema($type, $name, $description),
'int', 'string', 'bool', 'float' => new ScalarSchema($type, 'value', 'Correctly extracted value'),
default => throw new \Exception('Unknown type: '.$type->type),
};
}
Expand All @@ -81,7 +144,7 @@ protected function makePropertySchema(TypeDetails $type, string $name, string $d
$type,
$name,
$description,
$this->makePropertySchema($type->nestedType, '', ''),
$this->makePropertySchema($type->nestedType, 'item', 'Array item'),
),
'int', 'string', 'bool', 'float' => new ScalarSchema($type, $name, $description),
default => throw new \Exception('Unknown type: ' . $type->type),
Expand Down Expand Up @@ -109,33 +172,4 @@ protected function makePropertyObject(TypeDetails $type, string $name, string $d
(new ClassInfo)->getRequiredProperties($type->class),
);
}

/**
* Gets all the property schemas of a class
*
* @param string $class
* @return Schema[]
*/
protected function getPropertySchemas(string $class) : array {
$properties = (new ClassInfo)->getProperties($class);
$propertySchemas = [];
foreach ($properties as $property) {
$propertySchemas[$property] = $this->getPropertySchema($class, $property);
}
return $propertySchemas;
}

/**
* Gets the schema of a property
*
* @param string $class
* @param string $property
* @return Schema
*/
protected function getPropertySchema(string $class, string $property) : Schema {
$propertyInfoType = (new ClassInfo)->getType($class, $property);
$propertyDescription = (new ClassInfo)->getDescription($class, $property);
$type = (new TypeDetailsFactory)->fromPropertyInfo($propertyInfoType);
return $this->makePropertySchema($type, $property, $propertyDescription);
}
}
8 changes: 7 additions & 1 deletion src/Schema/PropertyInfoBased/Utils/ClassInfo.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,13 @@ public function getProperties(string $class) : array {
return $this->extractor()->getProperties($class) ?? [];
}

public function getDescription(string $class, string $property): string {
public function getClassDescription(string $class) : string {
// get class description from PHPDoc
$reflection = new ReflectionClass($class);
return DocstringUtils::descriptionsOnly($reflection->getDocComment());
}

public function getPropertyDescription(string $class, string $property): string {
$extractor = $this->extractor();
return trim(implode(' ', [
$extractor->getShortDescription($class, $property),
Expand Down
46 changes: 46 additions & 0 deletions src/Schema/PropertyInfoBased/Utils/DocstringUtils.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php
namespace Cognesy\Instructor\Schema\PropertyInfoBased\Utils;

use Cognesy\Instructor\Utils\Pipeline;

class DocstringUtils
{
public static function descriptionsOnly(string $code): string
{
return (new Pipeline())
->through(fn($code) => self::removeMarkers($code))
->through(fn($code) => self::removeAnnotations($code))
->then(fn($code) => trim($code))
->process($code);
}

public static function removeMarkers(string $code): string
{
// Pattern to match comment markers
$pattern = '/(\/\*\*|\*\/|\/\/|#)/';

// Remove comment markers from the string
$cleanedString = preg_replace($pattern, '', $code);

// Optional: Clean up extra asterisks and whitespace from multiline comments
$cleanedString = preg_replace('/^\s*\*\s?/m', '', $cleanedString);

return $cleanedString;
}

public static function removeAnnotations(string $code): string
{
$lines = explode("\n", $code);
$cleanedLines = [];
foreach ($lines as $line) {
$trimmed = trim($line);
if (empty($trimmed)) {
continue;
}
if ($trimmed[0] !== '@') {
$cleanedLines[] = $line;
}
}
return implode("\n", $cleanedLines);
}
}
Loading

0 comments on commit a50c2b7

Please sign in to comment.