Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -673,18 +673,32 @@ pnpm dev
# Type checking
pnpm typecheck

# Run tests
pnpm test

# Run tests for a specific package
cd packages/ai && pnpm test

# Clean build artifacts
pnpm clean
```

## Documentation

### User Guides
- 📖 [Tool Execution Loop](docs/TOOL_EXECUTION_LOOP.md) - How automatic tool execution works
- 📖 [Unified Chat API](docs/UNIFIED_CHAT_API.md) - `chat()` vs `chatCompletion()` methods
- 📖 [Quick Reference](docs/UNIFIED_CHAT_QUICK_REFERENCE.md) - Quick API reference
- 📖 [Tool Registry](docs/TOOL_REGISTRY.md) - Define tools once, use everywhere
- 📖 [Type Safety](docs/TYPE_SAFETY.md) - Type-safe multi-adapter usage
- 📖 [CLI Example](examples/cli/README.md) - Interactive command-line interface

### Examples
- 📖 [CLI Example](examples/cli/README.md) - Interactive command-line interface with tool calling

### Implementation Details
- 📖 [Implementation Summary](docs/IMPLEMENTATION_SUMMARY.md) - Architecture overview
- 📖 [Unified Chat Implementation](docs/UNIFIED_CHAT_IMPLEMENTATION.md) - `chat()` and `chatCompletion()` implementation
- 📖 [Migration Guide](docs/MIGRATION_UNIFIED_CHAT.md) - Migrating from `as` option API

## Contributing

Expand Down
61 changes: 59 additions & 2 deletions docs/IMPLEMENTATION_SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,40 @@ This implementation adds two major features to the AI SDK:
- Detailed error reporting from all failed adapters
- Works with all methods (chat, stream, generate, summarize, embed)

## Tool Execution Architecture

The `chat()` method includes an automatic tool execution loop implemented via the `ToolCallManager` class:

```typescript
// In AI.chat() method
const toolCallManager = new ToolCallManager(tools || []);

while (iterationCount < maxIterations) {
// Stream chunks and accumulate tool calls
for await (const chunk of adapter.chatStream()) {
if (chunk.type === "tool_call") {
toolCallManager.addToolCallChunk(chunk);
}
}

// Execute tools if model requested them
if (shouldExecuteTools && toolCallManager.hasToolCalls()) {
const toolResults = yield* toolCallManager.executeTools(doneChunk);
messages = [...messages, ...toolResults];
continue; // Next iteration
}

break; // No tools to execute, done
}
```

**ToolCallManager handles:**
- ✅ Accumulating streaming tool call chunks
- ✅ Validating tool calls (ID and name present)
- ✅ Executing tool `execute` functions
- ✅ Yielding `tool_result` chunks
- ✅ Creating tool result messages

## Architecture

### Type System
Expand Down Expand Up @@ -52,8 +86,9 @@ type ChatOptionsWithFallback<TAdapters> = {

1. **BaseAdapter** - Abstract class with generic model list
2. **AIAdapter Interface** - Includes `models` property with generic type
3. **AI Class** - Main class with fallback logic
4. **Adapter Implementations** - OpenAI, Anthropic, Gemini, Ollama with model lists
3. **AI Class** - Main class with fallback logic and tool execution loop
4. **ToolCallManager** - Handles tool call accumulation, validation, and execution
5. **Adapter Implementations** - OpenAI, Anthropic, Gemini, Ollama with model lists

### Fallback Logic

Expand Down Expand Up @@ -309,6 +344,28 @@ expect(mockAdapter1.chatCompletion).toHaveBeenCalled();
expect(mockAdapter2.chatCompletion).toHaveBeenCalled();
```

### Test ToolCallManager

The `ToolCallManager` class has comprehensive unit tests:

```bash
cd packages/ai
pnpm test
```

Test coverage includes:
- ✅ Accumulating streaming tool call chunks (name, arguments)
- ✅ Filtering incomplete tool calls (missing ID or name)
- ✅ Executing tools with parsed arguments
- ✅ Handling tool execution errors gracefully
- ✅ Handling tools without execute functions
- ✅ Multiple tool calls in one iteration
- ✅ Clearing state between iterations
- ✅ Emitting tool_result chunks
- ✅ Creating tool result messages

See `packages/ai/src/tool-call-manager.test.ts` for implementation.

## Performance Considerations

### Single Adapter Mode
Expand Down
90 changes: 90 additions & 0 deletions docs/TOOL_EXECUTION_LOOP.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@ The `chat()` method in TanStack AI includes an **automatic tool execution loop**

**You don't need to manually execute tools or manage conversation state** - the SDK handles everything!

## Architecture

The tool execution loop is implemented using the `ToolCallManager` class, which:
- **Accumulates tool calls** from streaming chunks
- **Validates tool calls** (ensures IDs and names are present)
- **Executes tools** and emits `tool_result` chunks
- **Returns tool result messages** for adding to conversation

This separation makes the code maintainable and testable.

## How It Works

### Step-by-Step Flow
Expand Down Expand Up @@ -352,6 +362,86 @@ if (result.toolCalls) {

**For most use cases, use `chat()` with automatic tool execution!**

## ToolCallManager Class

The tool execution logic is implemented in the `ToolCallManager` class for better maintainability and testability.

### Public API

```typescript
class ToolCallManager {
constructor(tools: ReadonlyArray<Tool>);

// Add a streaming tool call chunk
addToolCallChunk(chunk: ToolCallChunk): void;

// Check if there are complete tool calls
hasToolCalls(): boolean;

// Get all validated tool calls
getToolCalls(): ToolCall[];

// Execute tools and yield tool_result chunks
async *executeTools(doneChunk): AsyncGenerator<ToolResultStreamChunk, Message[]>;

// Clear for next iteration
clear(): void;
}
```

### Usage in chat() method

```typescript
async *chat(options) {
const toolCallManager = new ToolCallManager(options.tools || []);

while (iterationCount < maxIterations) {
// Stream chunks
for await (const chunk of adapter.chatStream()) {
yield chunk;

if (chunk.type === "tool_call") {
toolCallManager.addToolCallChunk(chunk); // Accumulate
}
}

// Execute if needed
if (toolCallManager.hasToolCalls()) {
const toolResults = yield* toolCallManager.executeTools(doneChunk);
messages = [...messages, ...toolResults];
toolCallManager.clear(); // Clear for next iteration
continue;
}

break;
}
}
```

### Benefits

- ✅ **Testable** - Unit tests for tool execution logic
- ✅ **Maintainable** - Tool logic separate from chat logic
- ✅ **Reusable** - Can be used in other contexts
- ✅ **Clean** - Single responsibility principle

### Unit Tests

The `ToolCallManager` has comprehensive unit tests. Run them with:

```bash
cd packages/ai
pnpm test
```

See `packages/ai/src/tool-call-manager.test.ts` for test scenarios:
- Accumulating streaming chunks
- Filtering incomplete tool calls
- Executing tools
- Error handling
- Multiple tool calls
- Clearing state

## License

MIT
Expand Down
111 changes: 100 additions & 11 deletions docs/UNIFIED_CHAT_IMPLEMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,69 @@ The chat API was previously unified using an `as` configuration option. The curr

```typescript
class AI<TAdapter> {
// Streaming method
chat(options): AsyncIterable<StreamChunk> {
return this.adapter.chatStream(options);
// Streaming method with automatic tool execution loop
async *chat(options): AsyncIterable<StreamChunk> {
// Manages tool execution internally using ToolCallManager
const toolCallManager = new ToolCallManager(options.tools || []);

while (iterationCount < maxIterations) {
// Stream from adapter
for await (const chunk of this.adapter.chatStream(options)) {
yield chunk;

// Track tool calls
if (chunk.type === "tool_call") {
toolCallManager.addToolCallChunk(chunk);
}
}

// Execute tools if needed
if (shouldExecuteTools && toolCallManager.hasToolCalls()) {
const toolResults = yield* toolCallManager.executeTools(doneChunk);
messages = [...messages, ...toolResults];
continue; // Next iteration
}

break; // Done
}
}

// Promise-based method
// Promise-based method (no tool execution loop)
async chatCompletion(options): Promise<ChatCompletionResult> {
return this.adapter.chatCompletion(options);
}
}
```

### ToolCallManager Class

The tool execution logic is extracted into a dedicated `ToolCallManager` class:

```typescript
class ToolCallManager {
// Accumulate tool calls from streaming chunks
addToolCallChunk(chunk): void;

// Check if there are tool calls to execute
hasToolCalls(): boolean;

// Get all complete tool calls
getToolCalls(): ToolCall[];

// Execute tools and yield tool_result chunks
async *executeTools(doneChunk): AsyncGenerator<ToolResultStreamChunk, Message[]>;

// Clear for next iteration
clear(): void;
}
```

**Benefits:**
- ✅ **Separation of concerns** - tool logic isolated from chat logic
- ✅ **Testable** - ToolCallManager can be unit tested independently
- ✅ **Maintainable** - changes to tool execution don't affect chat method
- ✅ **Reusable** - can be used in other contexts if needed

### Benefits of Separate Methods

✅ **Clearer API**: Method names indicate return type
Expand Down Expand Up @@ -87,28 +138,46 @@ See `docs/MIGRATION_UNIFIED_CHAT.md` for migration guide from the `as` option AP
✅ **All features still supported**:
- Discriminated union types for adapter-model pairs
- Fallback mechanism (single-with-fallbacks or fallbacks-only)
- Tool execution with auto-execution
- **Automatic tool execution loop** (via `ToolCallManager`)
- Error chunk detection for streaming
- Type-safe model selection

✅ **No breaking changes** to core functionality:
- Streaming behavior matches old `streamChat()` method
- Promise behavior matches old `chat()` method
- Error handling and fallbacks work identically
- **Tool execution now handled by `ToolCallManager` class**

## Files Changed

### Core Implementation
- ✅ `packages/ai/src/ai.ts`
- Removed `as` option from `chat()` method
- Made `chat()` streaming-only
- Made `chat()` streaming-only with automatic tool execution loop
- Added `chatCompletion()` method for promise-based calls
- Removed `streamToResponse()` private method (use `toStreamResponse()` from `stream-to-response.ts`)
- Refactored to use `ToolCallManager` for tool execution

- ✅ `packages/ai/src/tool-call-manager.ts` (NEW)
- Encapsulates tool call accumulation, validation, and execution
- Independently testable
- Yields `tool_result` chunks during execution
- Returns tool result messages for conversation history

- ✅ `packages/ai/src/types.ts`
- Added `ToolResultStreamChunk` type
- Added `"tool_result"` to `StreamChunkType` union
- Updated `StreamChunk` union to include `ToolResultStreamChunk`

### Documentation
- ✅ `docs/UNIFIED_CHAT_API.md` - Updated API documentation
- ✅ `docs/UNIFIED_CHAT_API.md` - Updated API documentation with tool execution details
- ✅ `docs/MIGRATION_UNIFIED_CHAT.md` - Migration guide
- ✅ `docs/UNIFIED_CHAT_QUICK_REFERENCE.md` - Quick reference updated
- ✅ `docs/TOOL_EXECUTION_LOOP.md` (NEW) - Comprehensive tool execution guide
- ✅ `README.md` - Updated with tool execution loop documentation
- ✅ `examples/cli/README.md` - Updated with automatic tool execution details
- ✅ `packages/ai-react/README.md` - Updated backend examples with tool execution
- ✅ `packages/ai-client/README.md` - Added backend example with tool execution

## Benefits of Current Approach

Expand All @@ -119,6 +188,9 @@ See `docs/MIGRATION_UNIFIED_CHAT.md` for migration guide from the `as` option AP
5. **Type Safety Maintained** - All discriminated unions still work
6. **Backward Compatible Migration** - Easy to migrate from old API
7. **Fallbacks Everywhere** - Both methods support same fallback mechanism
8. **Automatic Tool Execution** - `chat()` handles tool calling in a loop via `ToolCallManager`
9. **Testable Architecture** - Tool execution logic isolated in separate class
10. **Clean Separation** - `chat()` for streaming+tools, `chatCompletion()` for promises+structured output

## Testing Recommendations

Expand All @@ -129,10 +201,14 @@ Test scenarios:
4. ✅ Stream mode with fallbacks
5. ✅ HTTP response mode with primary adapter
6. ✅ HTTP response mode with fallbacks
7. ✅ Tool execution in both modes
8. ✅ Error chunk detection triggers fallbacks
9. ✅ Type inference for both methods
10. ✅ Fallback-only mode (no primary adapter)
7. ✅ Automatic tool execution in `chat()` (via `ToolCallManager`)
8. ✅ Manual tool handling in `chatCompletion()`
9. ✅ Error chunk detection triggers fallbacks
10. ✅ Type inference for both methods
11. ✅ Fallback-only mode (no primary adapter)
12. ✅ `ToolCallManager` unit tests (accumulation, validation, execution)
13. ✅ Multi-round tool execution (up to `maxIterations`)
14. ✅ Tool execution error handling

## Next Steps

Expand All @@ -144,6 +220,19 @@ Test scenarios:
2. **Update imports**: Add `toStreamResponse` import if needed
3. **Test fallback behavior**: Verify seamless failover in all modes

### Testing ToolCallManager

The `ToolCallManager` class is independently testable. See `packages/ai/src/tool-call-manager.test.ts` for unit tests.

Test scenarios:
- ✅ Accumulating streaming tool call chunks
- ✅ Filtering incomplete tool calls
- ✅ Executing tools with valid arguments
- ✅ Handling tool execution errors
- ✅ Handling tools without execute functions
- ✅ Multiple tool calls in one iteration
- ✅ Clearing tool calls between iterations

### Future Enhancements
- Consider adding structured output support to streaming
- Add streaming response mode to embeddings
Expand Down
Loading