closer to anthropic tool calling...

This commit is contained in:
perf3ct 2025-04-15 18:27:30 +00:00
parent 374975eafc
commit 6df87fc163
No known key found for this signature in database
GPG Key ID: 569C4EEC436F5232
2 changed files with 73 additions and 17 deletions

View File

@ -314,21 +314,39 @@ export class ChatPipeline {
log.info(`Tools enabled in options: ${toolsEnabled}`); log.info(`Tools enabled in options: ${toolsEnabled}`);
log.info(`Response provider: ${currentResponse.provider || 'unknown'}`); log.info(`Response provider: ${currentResponse.provider || 'unknown'}`);
log.info(`Response model: ${currentResponse.model || 'unknown'}`); log.info(`Response model: ${currentResponse.model || 'unknown'}`);
log.info(`Response has tool_calls: ${currentResponse.tool_calls ? 'true' : 'false'}`);
if (currentResponse.tool_calls) {
log.info(`Number of tool calls: ${currentResponse.tool_calls.length}`);
log.info(`Tool calls details: ${JSON.stringify(currentResponse.tool_calls)}`);
// Check if we have a response from Ollama, which might be handled differently // Enhanced tool_calls detection - check both direct property and getter
if (currentResponse.provider === 'Ollama') { let hasToolCalls = false;
log.info(`ATTENTION: Response is from Ollama - checking if tool execution path is correct`);
log.info(`Tool calls type: ${typeof currentResponse.tool_calls}`); // First check the direct property
log.info(`First tool call name: ${currentResponse.tool_calls[0]?.function?.name || 'unknown'}`); if (currentResponse.tool_calls && currentResponse.tool_calls.length > 0) {
hasToolCalls = true;
log.info(`Response has tool_calls property with ${currentResponse.tool_calls.length} tools`);
log.info(`Tool calls details: ${JSON.stringify(currentResponse.tool_calls)}`);
}
// Check if it might be a getter (for dynamic tool_calls collection)
else {
try {
const toolCallsDesc = Object.getOwnPropertyDescriptor(currentResponse, 'tool_calls');
if (toolCallsDesc && typeof toolCallsDesc.get === 'function') {
const dynamicToolCalls = toolCallsDesc.get.call(currentResponse);
if (dynamicToolCalls && dynamicToolCalls.length > 0) {
hasToolCalls = true;
log.info(`Response has dynamic tool_calls with ${dynamicToolCalls.length} tools`);
log.info(`Dynamic tool calls details: ${JSON.stringify(dynamicToolCalls)}`);
// Ensure property is available for subsequent code
currentResponse.tool_calls = dynamicToolCalls;
}
}
} catch (e) {
log.error(`Error checking dynamic tool_calls: ${e}`);
} }
} }
log.info(`Response has tool_calls: ${hasToolCalls ? 'true' : 'false'}`);
// Tool execution loop // Tool execution loop
if (toolsEnabled && currentResponse.tool_calls && currentResponse.tool_calls.length > 0) { if (toolsEnabled && hasToolCalls && currentResponse.tool_calls) {
log.info(`========== STAGE 6: TOOL EXECUTION ==========`); log.info(`========== STAGE 6: TOOL EXECUTION ==========`);
log.info(`Response contains ${currentResponse.tool_calls.length} tool calls, processing...`); log.info(`Response contains ${currentResponse.tool_calls.length} tool calls, processing...`);

View File

@ -186,10 +186,12 @@ export class AnthropicService extends BaseAIService {
opts: ChatCompletionOptions, opts: ChatCompletionOptions,
providerOptions: AnthropicOptions providerOptions: AnthropicOptions
): Promise<ChatResponse> { ): Promise<ChatResponse> {
// Create a list to collect tool calls during streaming
const collectedToolCalls: any[] = [];
// Create a stream handler function that processes the SDK's stream // Create a stream handler function that processes the SDK's stream
const streamHandler = async (callback: (chunk: StreamChunk) => Promise<void> | void): Promise<string> => { const streamHandler = async (callback: (chunk: StreamChunk) => Promise<void> | void): Promise<string> => {
let completeText = ''; let completeText = '';
const toolCalls: any[] = [];
let currentToolCall: any = null; let currentToolCall: any = null;
try { try {
@ -261,7 +263,7 @@ export class AnthropicService extends BaseAIService {
// Process tool use completion // Process tool use completion
else if (chunk.type === 'content_block_stop' && currentToolCall) { else if (chunk.type === 'content_block_stop' && currentToolCall) {
// Add the completed tool call to our list // Add the completed tool call to our list
toolCalls.push(currentToolCall); collectedToolCalls.push({ ...currentToolCall });
// Log the tool completion // Log the tool completion
log.info(`Streaming: Tool use completed: ${currentToolCall.function.name}`); log.info(`Streaming: Tool use completed: ${currentToolCall.function.name}`);
@ -274,6 +276,7 @@ export class AnthropicService extends BaseAIService {
type: 'complete', type: 'complete',
tool: currentToolCall tool: currentToolCall
}, },
tool_calls: collectedToolCalls.length > 0 ? collectedToolCalls : undefined,
raw: chunk raw: chunk
}); });
@ -282,11 +285,16 @@ export class AnthropicService extends BaseAIService {
} }
} }
// Signal completion // Signal completion with all tool calls
log.info(`Streaming complete, collected ${collectedToolCalls.length} tool calls`);
if (collectedToolCalls.length > 0) {
log.info(`Tool calls detected in final response: ${JSON.stringify(collectedToolCalls)}`);
}
await callback({ await callback({
text: '', text: '',
done: true, done: true,
tool_calls: toolCalls.length > 0 ? toolCalls : undefined tool_calls: collectedToolCalls.length > 0 ? collectedToolCalls : undefined
}); });
return completeText; return completeText;
@ -311,13 +319,43 @@ export class AnthropicService extends BaseAIService {
} }
}; };
// Return a response object with the stream handler // Create a custom stream function that captures tool calls
return { const captureToolCallsStream = async (callback: (chunk: StreamChunk) => Promise<void> | void): Promise<string> => {
// Use the original stream handler but wrap it to capture tool calls
return streamHandler(async (chunk: StreamChunk) => {
// If the chunk has tool calls, update our collection
if (chunk.tool_calls && chunk.tool_calls.length > 0) {
// Update our collection with new tool calls
chunk.tool_calls.forEach(toolCall => {
// Only add if it's not already in the collection
if (!collectedToolCalls.some(tc => tc.id === toolCall.id)) {
collectedToolCalls.push(toolCall);
}
});
}
// Call the original callback
return callback(chunk);
});
};
// Return a response object with the stream handler and tool_calls property
const response: ChatResponse = {
text: '', // Initial text is empty, will be populated during streaming text: '', // Initial text is empty, will be populated during streaming
model: providerOptions.model, model: providerOptions.model,
provider: this.getName(), provider: this.getName(),
stream: streamHandler stream: captureToolCallsStream
}; };
// Define a getter for tool_calls that will return the collected tool calls
Object.defineProperty(response, 'tool_calls', {
get: function() {
return collectedToolCalls.length > 0 ? collectedToolCalls : undefined;
},
enumerable: true
});
return response;
} }
/** /**