Skip to content

Commit e55bf30

Browse files
authored
Merge pull request #23 from firefliesai/chore-sc-70927-improve-test-readme
test(llm-tool): add complex structured tool test
2 parents d38f9a7 + 6a58629 commit e55bf30

File tree

5 files changed

+126
-7
lines changed

5 files changed

+126
-7
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ All notable changes to Schema Forge will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [1.0.3] - 2025-03-28
9+
10+
1. update README.md
11+
812
## [1.0.3] - 2025-03-27
913

1014
1. Add reflect-metadata v0.1.14 backward-compatibility.

README.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,14 @@ Make sure to enable experimental decorators in your `tsconfig.json`:
145145

146146
## Quick Start
147147

148+
> **Important**: The `@ToolMeta` decorator is required when using `classToOpenAITool`, `classToAnthropicTool`, or any LLM-specific converter function, but is optional when using just `classToJsonSchema`. If you only need to generate JSON Schema without LLM integration, you can omit the `@ToolMeta` decorator.
149+
150+
> **Note on Type Specifications**: TypeScript's type system automatically infers most property types, but there are two cases where you must explicitly specify types in the `@ToolProp` decorator:
151+
> - **Arrays**: Use `items: { type: 'string' }` or `items: { type: CustomClass }` for arrays
152+
> - **Enums**: Use `enum: ['value1', 'value2']` or `enum: EnumType` for enumerated values
153+
>
154+
> See examples below for details.
155+
148156
### Define a Class with Decorators
149157

150158
```typescript
@@ -438,6 +446,56 @@ class User {
438446
}
439447
```
440448

449+
### When to Specify Types Explicitly
450+
451+
Schema Forge uses TypeScript's reflection capabilities to automatically infer most property types, but there are two specific cases where you **must** provide explicit type information:
452+
453+
1. **Arrays**: TypeScript's type reflection can determine that a property is an array, but it cannot identify the element type
454+
```typescript
455+
// INCORRECT - will not properly identify element type
456+
@ToolProp({ description: 'List of tags' })
457+
tags: string[]; // TypeScript knows this is Array, but not that elements are strings
458+
459+
// CORRECT - explicitly specify element type
460+
@ToolProp({
461+
description: 'List of tags',
462+
items: { type: 'string' } // Required for primitive arrays
463+
})
464+
tags: string[];
465+
466+
// CORRECT - for arrays of custom classes
467+
@ToolProp({
468+
description: 'Previous addresses',
469+
items: { type: Address } // Pass the class directly
470+
})
471+
previousAddresses: Address[];
472+
```
473+
474+
2. **Enums**: TypeScript enums need explicit handling to generate proper schema enumeration values
475+
```typescript
476+
enum UserRole { Admin = 'admin', User = 'user', Guest = 'guest' }
477+
478+
// INCORRECT - will not include enum values in schema
479+
@ToolProp({ description: 'User role' })
480+
role: UserRole;
481+
482+
// CORRECT - explicitly specify enum
483+
@ToolProp({
484+
description: 'User role',
485+
enum: UserRole // Pass the enum directly
486+
})
487+
role: UserRole;
488+
489+
// CORRECT - alternatively, provide values directly
490+
@ToolProp({
491+
description: 'User role',
492+
enum: ['admin', 'user', 'guest']
493+
})
494+
role: string;
495+
```
496+
497+
All other primitive types (string, number, boolean) and custom classes are automatically inferred without additional type specification.
498+
441499
### Using TypeScript Enums
442500

443501
Schema Forge works well with native TypeScript enums:

package-lock.json

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@firefliesai/schema-forge",
3-
"version": "1.0.3",
3+
"version": "1.0.4",
44
"main": "dist/lib/index.js",
55
"types": "dist/lib/index.d.ts",
66
"exports": {

src/llm-tool.spec.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import { GoogleGenAI } from '@google/genai';
77
import { GoogleGenerativeAI } from '@google/generative-ai';
88
import OpenAI from 'openai';
99

10+
import { GameCharacterV2 } from './fixture/complex-class.tool.dto';
11+
import { MathToolDto } from './fixture/math.tool.dto';
1012
import {
1113
ToolMeta,
1214
ToolProp,
@@ -103,6 +105,61 @@ describe('LLM Tool Call and Structured Output Tests', () => {
103105
expect(data.name).toBeDefined();
104106
});
105107

108+
it('OpenAI (Chat Completions API) - Function calling with direct class conversion, with complex structured tool setup', async () => {
109+
/** a simpler one first */
110+
const messages = ['4+2=?'];
111+
const mathTool = classToOpenAITool(MathToolDto);
112+
const completion1 = await openai.chat.completions.create({
113+
model: 'gpt-4o-mini',
114+
messages: [
115+
{
116+
role: 'user',
117+
content: messages[0],
118+
},
119+
],
120+
tools: [mathTool],
121+
tool_choice: 'required',
122+
});
123+
const jsonResp: MathToolDto = JSON.parse(
124+
completion1.choices[0].message?.tool_calls[0].function.arguments,
125+
);
126+
expect(jsonResp.sum).toBe(6);
127+
128+
/** real complex one */
129+
const userMessage = `You are a helpful AI assistant. Based on the following JSON schema for a game character, please generate a mock response that follows the schema exactly. Make the data realistic and consistent with a game character context.
130+
`;
131+
const expGameCharV2Tool = classToOpenAITool(GameCharacterV2);
132+
133+
const completion = await openai.chat.completions.create({
134+
model: 'gpt-4o-mini',
135+
messages: [
136+
{
137+
role: 'user',
138+
content: userMessage,
139+
},
140+
],
141+
tools: [expGameCharV2Tool],
142+
tool_choice: 'required',
143+
});
144+
145+
const data: GameCharacterV2 = JSON.parse(
146+
completion.choices[0].message?.tool_calls[0].function.arguments,
147+
);
148+
expect(data.location).toBeDefined();
149+
expect(data.location.city).toBeDefined();
150+
expect(data.location.country).toBeDefined();
151+
expect(data.banks[0].account).toBeDefined();
152+
expect(data.banks[0].bankName).toBeDefined();
153+
expect(data.level).toBeDefined();
154+
expect(data.name).toBeDefined();
155+
expect(data.rank).toBeDefined();
156+
expect(data.status).toBeDefined();
157+
expect(data.location).toBeDefined();
158+
expect(data.titles[0]).toBeDefined();
159+
expect(data.scores[0]).toBeGreaterThan(0);
160+
expect(data.availableStatuses[0]).toBeDefined();
161+
});
162+
106163
it('OpenAI (Chat Completions API) - Structured output with response_format', async () => {
107164
const responseFormat = classToOpenAIResponseFormatJsonSchema(CapitalTool, {
108165
// Enable structured output for OpenAI

0 commit comments

Comments
 (0)