Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ All notable changes to Schema Forge will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.0.3] - 2025-03-28

1. update README.md

## [1.0.3] - 2025-03-27

1. Add reflect-metadata v0.1.14 backward-compatibility.
Expand Down
58 changes: 58 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,14 @@ Make sure to enable experimental decorators in your `tsconfig.json`:

## Quick Start

> **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.

> **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:
> - **Arrays**: Use `items: { type: 'string' }` or `items: { type: CustomClass }` for arrays
> - **Enums**: Use `enum: ['value1', 'value2']` or `enum: EnumType` for enumerated values
>
> See examples below for details.

### Define a Class with Decorators

```typescript
Expand Down Expand Up @@ -438,6 +446,56 @@ class User {
}
```

### When to Specify Types Explicitly

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:

1. **Arrays**: TypeScript's type reflection can determine that a property is an array, but it cannot identify the element type
```typescript
// INCORRECT - will not properly identify element type
@ToolProp({ description: 'List of tags' })
tags: string[]; // TypeScript knows this is Array, but not that elements are strings

// CORRECT - explicitly specify element type
@ToolProp({
description: 'List of tags',
items: { type: 'string' } // Required for primitive arrays
})
tags: string[];

// CORRECT - for arrays of custom classes
@ToolProp({
description: 'Previous addresses',
items: { type: Address } // Pass the class directly
})
previousAddresses: Address[];
```

2. **Enums**: TypeScript enums need explicit handling to generate proper schema enumeration values
```typescript
enum UserRole { Admin = 'admin', User = 'user', Guest = 'guest' }

// INCORRECT - will not include enum values in schema
@ToolProp({ description: 'User role' })
role: UserRole;

// CORRECT - explicitly specify enum
@ToolProp({
description: 'User role',
enum: UserRole // Pass the enum directly
})
role: UserRole;

// CORRECT - alternatively, provide values directly
@ToolProp({
description: 'User role',
enum: ['admin', 'user', 'guest']
})
role: string;
```

All other primitive types (string, number, boolean) and custom classes are automatically inferred without additional type specification.

### Using TypeScript Enums

Schema Forge works well with native TypeScript enums:
Expand Down
12 changes: 6 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@firefliesai/schema-forge",
"version": "1.0.3",
"version": "1.0.4",
"main": "dist/lib/index.js",
"types": "dist/lib/index.d.ts",
"exports": {
Expand Down
57 changes: 57 additions & 0 deletions src/llm-tool.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { GoogleGenAI } from '@google/genai';
import { GoogleGenerativeAI } from '@google/generative-ai';
import OpenAI from 'openai';

import { GameCharacterV2 } from './fixture/complex-class.tool.dto';
import { MathToolDto } from './fixture/math.tool.dto';
import {
ToolMeta,
ToolProp,
Expand Down Expand Up @@ -103,6 +105,61 @@ describe('LLM Tool Call and Structured Output Tests', () => {
expect(data.name).toBeDefined();
});

it('OpenAI (Chat Completions API) - Function calling with direct class conversion, with complex structured tool setup', async () => {
/** a simpler one first */
const messages = ['4+2=?'];
const mathTool = classToOpenAITool(MathToolDto);
const completion1 = await openai.chat.completions.create({
model: 'gpt-4o-mini',
messages: [
{
role: 'user',
content: messages[0],
},
],
tools: [mathTool],
tool_choice: 'required',
});
const jsonResp: MathToolDto = JSON.parse(
completion1.choices[0].message?.tool_calls[0].function.arguments,
);
expect(jsonResp.sum).toBe(6);

/** real complex one */
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.
`;
const expGameCharV2Tool = classToOpenAITool(GameCharacterV2);

const completion = await openai.chat.completions.create({
model: 'gpt-4o-mini',
messages: [
{
role: 'user',
content: userMessage,
},
],
tools: [expGameCharV2Tool],
tool_choice: 'required',
});

const data: GameCharacterV2 = JSON.parse(
completion.choices[0].message?.tool_calls[0].function.arguments,
);
expect(data.location).toBeDefined();
expect(data.location.city).toBeDefined();
expect(data.location.country).toBeDefined();
expect(data.banks[0].account).toBeDefined();
expect(data.banks[0].bankName).toBeDefined();
expect(data.level).toBeDefined();
expect(data.name).toBeDefined();
expect(data.rank).toBeDefined();
expect(data.status).toBeDefined();
expect(data.location).toBeDefined();
expect(data.titles[0]).toBeDefined();
expect(data.scores[0]).toBeGreaterThan(0);
expect(data.availableStatuses[0]).toBeDefined();
});

it('OpenAI (Chat Completions API) - Structured output with response_format', async () => {
const responseFormat = classToOpenAIResponseFormatJsonSchema(CapitalTool, {
// Enable structured output for OpenAI
Expand Down