-
Notifications
You must be signed in to change notification settings - Fork 95
[Server] issues-68 No Ability to set outputSchema #88
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
4f9df3d
e2243bc
2dbd411
709a815
4486776
9ff6516
54a5d14
6100ffc
d347c84
e4a82f7
e5a4cb5
0963d24
f1b471a
42b5261
48a19b9
7190bee
f41ea86
55f8608
8885d29
ca18caf
757b959
67cc641
fb3c1e6
ff26b4f
9be9e38
85549cb
09596c0
d3a0791
ed16e6b
654a111
49a3aea
dad0f50
fe64e80
a8612f6
8f825f3
e148515
660f150
5804ba9
f08705a
773c3a2
8779b6d
7fa4aec
e6a3fe1
c526b4f
2060906
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -136,4 +136,51 @@ public function getParamTypeString(?Param $paramTag): ?string | |||||
|
|
||||||
| return null; | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * Gets the return type string from a Return tag. | ||||||
| */ | ||||||
| public function getReturnTypeString(?DocBlock $docBlock): ?string | ||||||
| { | ||||||
| if (!$docBlock) { | ||||||
| return null; | ||||||
| } | ||||||
|
|
||||||
| $returnTags = $docBlock->getTagsByName('return'); | ||||||
| if (empty($returnTags)) { | ||||||
|
||||||
| if (empty($returnTags)) { | |
| if ([] === $returnTags) { |
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we check for TagWithType instead of using method_exists?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and should be inverted to:
if (!$returnTag instanceof TagWithType) {
return null;
}it's better to continue with the style of early exits - like you did in the beginning of the method
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
similar comments would apply to this method like with getReturnTypeString
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -79,6 +79,36 @@ public function generate(\ReflectionMethod|\ReflectionFunction $reflection): arr | |
| return $this->buildSchemaFromParameters($parametersInfo, $methodSchema); | ||
| } | ||
|
|
||
| /** | ||
| * Generates a JSON Schema object (as a PHP array) for a method's or function's return type. | ||
| * | ||
| * @return array<string, mixed>|null | ||
| */ | ||
| public function generateOutputSchema(\ReflectionMethod|\ReflectionFunction $reflection): ?array | ||
| { | ||
| $docComment = $reflection->getDocComment() ?: null; | ||
| $docBlock = $this->docBlockParser->parseDocBlock($docComment); | ||
|
|
||
| $docBlockReturnType = $this->docBlockParser->getReturnTypeString($docBlock); | ||
| $returnDescription = $this->docBlockParser->getReturnDescription($docBlock); | ||
|
|
||
| $reflectionReturnType = $reflection->getReturnType(); | ||
| $reflectionReturnTypeString = $reflectionReturnType | ||
| ? $this->getTypeStringFromReflection($reflectionReturnType, $reflectionReturnType->allowsNull()) | ||
| : null; | ||
|
|
||
| // Use DocBlock with generics, otherwise reflection, otherwise DocBlock | ||
| $returnTypeString = ($docBlockReturnType && str_contains($docBlockReturnType, '<')) | ||
| ? $docBlockReturnType | ||
| : ($reflectionReturnTypeString ?: $docBlockReturnType); | ||
|
|
||
| if (!$returnTypeString || 'void' === strtolower($returnTypeString)) { | ||
| return null; | ||
| } | ||
|
|
||
| return $this->buildOutputSchemaFromType($returnTypeString, $returnDescription); | ||
| } | ||
|
|
||
|
Comment on lines
83
to
113
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please add tests for this method |
||
| /** | ||
| * Extracts method-level or function-level Schema attribute. | ||
| * | ||
|
|
@@ -784,4 +814,36 @@ private function mapSimpleTypeToJsonSchema(string $type): string | |
| default => \in_array(strtolower($type), ['datetime', 'datetimeinterface']) ? 'string' : 'object', | ||
| }; | ||
| } | ||
|
|
||
| /** | ||
| * Builds an output schema from a return type string. | ||
| * | ||
| * @return array<string, mixed> | ||
| */ | ||
| private function buildOutputSchemaFromType(string $returnTypeString, ?string $description): array | ||
| { | ||
| // Handle array types - treat as object with additionalProperties | ||
| if (str_contains($returnTypeString, 'array')) { | ||
| $schema = [ | ||
| 'type' => 'object', | ||
| 'additionalProperties' => ['type' => 'mixed'], | ||
| ]; | ||
| } else { | ||
| // Handle other types - wrap in object for MCP compatibility | ||
| $mappedType = $this->mapSimpleTypeToJsonSchema($returnTypeString); | ||
| $schema = [ | ||
| 'type' => 'object', | ||
| 'properties' => [ | ||
| 'result' => ['type' => $mappedType], | ||
| ], | ||
| 'required' => ['result'], | ||
| ]; | ||
| } | ||
|
|
||
| if ($description) { | ||
| $schema['description'] = $description; | ||
| } | ||
|
|
||
| return $schema; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,34 +23,46 @@ | |
| * properties: array<string, mixed>, | ||
| * required: string[]|null | ||
| * } | ||
| * @phpstan-type ToolOutputSchema array{ | ||
| * type: 'object', | ||
| * properties: array<string, mixed>, | ||
| * required: string[]|null | ||
| * } | ||
| * @phpstan-type ToolData array{ | ||
| * name: string, | ||
| * inputSchema: ToolInputSchema, | ||
| * description?: string|null, | ||
| * annotations?: ToolAnnotationsData, | ||
| * outputSchema?: ToolOutputSchema, | ||
| * } | ||
| * | ||
| * @author Kyrian Obikwelu <[email protected]> | ||
| */ | ||
| class Tool implements \JsonSerializable | ||
| { | ||
| /** | ||
| * @param string $name the name of the tool | ||
| * @param string|null $description A human-readable description of the tool. | ||
| * This can be used by clients to improve the LLM's understanding of | ||
| * available tools. It can be thought of like a "hint" to the model. | ||
| * @param ToolInputSchema $inputSchema a JSON Schema object (as a PHP array) defining the expected 'arguments' for the tool | ||
| * @param ToolAnnotations|null $annotations optional additional tool information | ||
| * @param string $name the name of the tool | ||
| * @param string|null $description A human-readable description of the tool. | ||
| * This can be used by clients to improve the LLM's understanding of | ||
| * available tools. It can be thought of like a "hint" to the model. | ||
| * @param ToolInputSchema $inputSchema a JSON Schema object (as a PHP array) defining the expected 'arguments' for the tool | ||
| * @param ToolAnnotations|null $annotations optional additional tool information | ||
| * @param ToolOutputSchema|null $outputSchema optional JSON Schema object (as a PHP array) defining the expected output structure | ||
| */ | ||
| public function __construct( | ||
| public readonly string $name, | ||
| public readonly array $inputSchema, | ||
| public readonly ?string $description, | ||
| public readonly ?ToolAnnotations $annotations, | ||
| public readonly ?array $outputSchema = null, | ||
| ) { | ||
| if (!isset($inputSchema['type']) || 'object' !== $inputSchema['type']) { | ||
| throw new InvalidArgumentException('Tool inputSchema must be a JSON Schema of type "object".'); | ||
| } | ||
|
|
||
| if (null !== $outputSchema && (!isset($outputSchema['type']) || 'object' !== $outputSchema['type'])) { | ||
| throw new InvalidArgumentException('Tool outputSchema must be a JSON Schema of type "object" or null.'); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -71,11 +83,21 @@ public static function fromArray(array $data): self | |
| $data['inputSchema']['properties'] = new \stdClass(); | ||
| } | ||
|
|
||
| if (isset($data['outputSchema']) && \is_array($data['outputSchema'])) { | ||
| if (!isset($data['outputSchema']['type']) || 'object' !== $data['outputSchema']['type']) { | ||
| throw new InvalidArgumentException('Tool outputSchema must be of type "object".'); | ||
| } | ||
| if (isset($data['outputSchema']['properties']) && \is_array($data['outputSchema']['properties']) && empty($data['outputSchema']['properties'])) { | ||
| $data['outputSchema']['properties'] = new \stdClass(); | ||
| } | ||
| } | ||
|
|
||
| return new self( | ||
| $data['name'], | ||
| $data['inputSchema'], | ||
| isset($data['description']) && \is_string($data['description']) ? $data['description'] : null, | ||
| isset($data['annotations']) && \is_array($data['annotations']) ? ToolAnnotations::fromArray($data['annotations']) : null | ||
| isset($data['annotations']) && \is_array($data['annotations']) ? ToolAnnotations::fromArray($data['annotations']) : null, | ||
| $data['outputSchema'] | ||
| ); | ||
| } | ||
|
|
||
|
|
@@ -85,6 +107,7 @@ public static function fromArray(array $data): self | |
| * inputSchema: ToolInputSchema, | ||
| * description?: string, | ||
| * annotations?: ToolAnnotations, | ||
| * outputSchema?: ToolOutputSchema, | ||
| * } | ||
| */ | ||
| public function jsonSerialize(): array | ||
|
|
@@ -99,6 +122,9 @@ public function jsonSerialize(): array | |
| if (null !== $this->annotations) { | ||
| $data['annotations'] = $this->annotations; | ||
| } | ||
| if (null !== $this->outputSchema) { | ||
| $data['outputSchema'] = $this->outputSchema; | ||
| } | ||
|
|
||
| return $data; | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
always like to be explicit