Enable tool-based message generation in chat-stream-provider and add BasicValueTool and WeatherTool.

Updated dependencies to latest versions in `bun.lock`. Modified development script in `package.json` to include watch mode.
This commit is contained in:
geoffsee
2025-07-04 08:56:11 -04:00
committed by Geoff Seemueller
parent de968bcfbd
commit 06b6a68b9b
5 changed files with 320 additions and 11 deletions

View File

@@ -66,7 +66,7 @@
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-icons": "^5.4.0",
"react-streaming": "^0.3.44",
"react-streaming": "^0.4.2",
"react-textarea-autosize": "^8.5.5",
"shiki": "^1.24.0",
"typescript": "^5.7.2",
@@ -148,7 +148,7 @@
"bun": "^1.2.17",
"bun-sqlite-key-value": "^1.13.1",
"chokidar": "^4.0.1",
"dotenv": "^16.5.0",
"dotenv": "^17.0.0",
"itty-router": "^5.0.18",
"jsdom": "^24.0.0",
"mobx": "^6.13.5",
@@ -177,7 +177,7 @@
"@vitest/ui": "^3.1.4",
"bun-sqlite-key-value": "^1.13.1",
"chokidar": "^4.0.1",
"dotenv": "^16.5.0",
"dotenv": "^17.0.0",
"itty-router": "^5.0.18",
"jsdom": "^24.0.0",
"mobx": "^6.13.5",
@@ -994,7 +994,7 @@
"dompurify": ["dompurify@3.2.5", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-mLPd29uoRe9HpvwP2TxClGQBzGXeEC/we/q+bFlmPPmj2p2Ugl3r6ATu/UU1v77DXNcehiBg9zsr1dREyA/dJQ=="],
"dotenv": ["dotenv@16.5.0", "", {}, "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg=="],
"dotenv": ["dotenv@17.0.1", "", {}, "sha512-GLjkduuAL7IMJg/ZnOPm9AnWKJ82mSE2tzXLaJ/6hD6DhwGfZaXG77oB8qbReyiczNxnbxQKyh0OE5mXq0bAHA=="],
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
@@ -1566,7 +1566,7 @@
"react-remove-scroll-bar": ["react-remove-scroll-bar@2.3.8", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="],
"react-streaming": ["react-streaming@0.3.50", "", { "dependencies": { "@brillout/import": "^0.2.3", "@brillout/json-serializer": "^0.5.1", "@brillout/picocolors": "^1.0.11", "isbot-fast": "1.2.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" } }, "sha512-BRjWzY83dtnjgFUCYM0H2J3iRuYruE++/AidXmjxRclTsz1O/zBdUVgjAO2uKb05y/zybskcKjmK/5xyMc1MSg=="],
"react-streaming": ["react-streaming@0.4.2", "", { "dependencies": { "@brillout/import": "^0.2.3", "@brillout/json-serializer": "^0.5.1", "@brillout/picocolors": "^1.0.11", "isbot-fast": "1.2.0" }, "peerDependencies": { "react": ">=19", "react-dom": ">=19" } }, "sha512-b192E9E0TnE9wWdc8uZg00MMY36btQmFV25cDOGcWQwl5qF8vzNcQPVJwWkljLOec5qIJWumH9cvEvAL1JlAGg=="],
"react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="],
@@ -2146,6 +2146,10 @@
"@open-gsio/client/vite": ["vite@7.0.0", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.6", "picomatch": "^4.0.2", "postcss": "^8.5.6", "rollup": "^4.40.0", "tinyglobby": "^0.2.14" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-ixXJB1YRgDIw2OszKQS9WxGHKwLdCsbQNkpJN171udl6szi/rIySHL6/Os3s2+oE4P/FLD4dxg4mD7Wust+u5g=="],
"@open-gsio/coordinators/@types/bun": ["@types/bun@1.2.18", "", { "dependencies": { "bun-types": "1.2.18" } }, "sha512-Xf6RaWVheyemaThV0kUfaAUvCNokFr+bH8Jxp+tTZfx7dAPA8z9ePnP9S9+Vspzuxxx9JRAXhnyccRj3GyCMdQ=="],
"@open-gsio/scripts/@types/bun": ["@types/bun@1.2.18", "", { "dependencies": { "bun-types": "1.2.18" } }, "sha512-Xf6RaWVheyemaThV0kUfaAUvCNokFr+bH8Jxp+tTZfx7dAPA8z9ePnP9S9+Vspzuxxx9JRAXhnyccRj3GyCMdQ=="],
"@open-gsio/server/vite": ["vite@7.0.0", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.6", "picomatch": "^4.0.2", "postcss": "^8.5.6", "rollup": "^4.40.0", "tinyglobby": "^0.2.14" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-ixXJB1YRgDIw2OszKQS9WxGHKwLdCsbQNkpJN171udl6szi/rIySHL6/Os3s2+oE4P/FLD4dxg4mD7Wust+u5g=="],
"@open-gsio/services/vite": ["vite@7.0.0", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.6", "picomatch": "^4.0.2", "postcss": "^8.5.6", "rollup": "^4.40.0", "tinyglobby": "^0.2.14" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-ixXJB1YRgDIw2OszKQS9WxGHKwLdCsbQNkpJN171udl6szi/rIySHL6/Os3s2+oE4P/FLD4dxg4mD7Wust+u5g=="],
@@ -2450,6 +2454,10 @@
"@open-gsio/client/vite/rollup": ["rollup@4.44.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.44.0", "@rollup/rollup-android-arm64": "4.44.0", "@rollup/rollup-darwin-arm64": "4.44.0", "@rollup/rollup-darwin-x64": "4.44.0", "@rollup/rollup-freebsd-arm64": "4.44.0", "@rollup/rollup-freebsd-x64": "4.44.0", "@rollup/rollup-linux-arm-gnueabihf": "4.44.0", "@rollup/rollup-linux-arm-musleabihf": "4.44.0", "@rollup/rollup-linux-arm64-gnu": "4.44.0", "@rollup/rollup-linux-arm64-musl": "4.44.0", "@rollup/rollup-linux-loongarch64-gnu": "4.44.0", "@rollup/rollup-linux-powerpc64le-gnu": "4.44.0", "@rollup/rollup-linux-riscv64-gnu": "4.44.0", "@rollup/rollup-linux-riscv64-musl": "4.44.0", "@rollup/rollup-linux-s390x-gnu": "4.44.0", "@rollup/rollup-linux-x64-gnu": "4.44.0", "@rollup/rollup-linux-x64-musl": "4.44.0", "@rollup/rollup-win32-arm64-msvc": "4.44.0", "@rollup/rollup-win32-ia32-msvc": "4.44.0", "@rollup/rollup-win32-x64-msvc": "4.44.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-qHcdEzLCiktQIfwBq420pn2dP+30uzqYxv9ETm91wdt2R9AFcWfjNAmje4NWlnCIQ5RMTzVf0ZyisOKqHR6RwA=="],
"@open-gsio/coordinators/@types/bun/bun-types": ["bun-types@1.2.18", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-04+Eha5NP7Z0A9YgDAzMk5PHR16ZuLVa83b26kH5+cp1qZW4F6FmAURngE7INf4tKOvCE69vYvDEwoNl1tGiWw=="],
"@open-gsio/scripts/@types/bun/bun-types": ["bun-types@1.2.18", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-04+Eha5NP7Z0A9YgDAzMk5PHR16ZuLVa83b26kH5+cp1qZW4F6FmAURngE7INf4tKOvCE69vYvDEwoNl1tGiWw=="],
"@open-gsio/server/vite/fdir": ["fdir@6.4.6", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w=="],
"@open-gsio/server/vite/postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],

View File

@@ -2,6 +2,7 @@ import { Schema } from '@open-gsio/schema';
import type { Instance } from 'mobx-state-tree';
import { OpenAI } from 'openai';
import type Message from '../../../schema/src/models/Message.ts';
import { AssistantSdk } from '../assistant-sdk';
import { ProviderRepository } from '../providers/_ProviderRepository.ts';
import type {

View File

@@ -1,6 +1,7 @@
import { OpenAI } from 'openai';
import ChatSdk from '../chat-sdk/chat-sdk.ts';
import { BasicValueTool, WeatherTool } from '../tools/basic.ts';
import type { GenericEnv } from '../types';
export interface CommonProviderParams {
@@ -35,12 +36,270 @@ export abstract class BaseChatProvider implements ChatStreamProvider {
});
const client = this.getOpenAIClient(param);
const streamParams = this.getStreamParams(param, safeMessages);
const stream = await client.chat.completions.create(streamParams);
for await (const chunk of stream as unknown as AsyncIterable<any>) {
const shouldBreak = await this.processChunk(chunk, dataCallback);
if (shouldBreak) break;
// const tools = [WeatherTool];
const tools = [
{
type: 'function',
function: {
name: 'getCurrentTemperature',
description: 'Get the current temperature for a specific location',
parameters: {
type: 'object',
properties: {
location: {
type: 'string',
description: 'The city and state, e.g., San Francisco, CA',
},
unit: {
type: 'string',
enum: ['Celsius', 'Fahrenheit'],
description: "The temperature unit to use. Infer this from the user's location.",
},
},
required: ['location', 'unit'],
},
},
},
];
const getCurrentTemp = (location: string) => {
return '20C';
};
const callFunction = async (name, args) => {
if (name === 'getCurrentTemperature') {
return getCurrentTemp(args.location);
}
};
// Main conversation loop - handle tool calls properly
let conversationComplete = false;
let toolCallIterations = 0;
const maxToolCallIterations = 5; // Prevent infinite loops
let toolsExecuted = false; // Track if we've executed tools
while (!conversationComplete && toolCallIterations < maxToolCallIterations) {
const streamParams = this.getStreamParams(param, safeMessages);
// Only provide tools on the first call, after that force text response
const currentTools = toolsExecuted ? undefined : tools;
const stream = await client.chat.completions.create({ ...streamParams, tools: currentTools });
let assistantMessage = '';
const toolCalls: any[] = [];
for await (const chunk of stream as unknown as AsyncIterable<any>) {
console.log('chunk', chunk);
// Handle tool calls
if (chunk.choices[0]?.delta?.tool_calls) {
const deltaToolCalls = chunk.choices[0].delta.tool_calls;
for (const deltaToolCall of deltaToolCalls) {
if (deltaToolCall.index !== undefined) {
// Initialize or get existing tool call
if (!toolCalls[deltaToolCall.index]) {
toolCalls[deltaToolCall.index] = {
id: deltaToolCall.id || '',
type: deltaToolCall.type || 'function',
function: {
name: deltaToolCall.function?.name || '',
arguments: deltaToolCall.function?.arguments || '',
},
};
} else {
// Append to existing tool call
if (deltaToolCall.function?.arguments) {
toolCalls[deltaToolCall.index].function.arguments +=
deltaToolCall.function.arguments;
}
if (deltaToolCall.function?.name) {
toolCalls[deltaToolCall.index].function.name += deltaToolCall.function.name;
}
if (deltaToolCall.id) {
toolCalls[deltaToolCall.index].id += deltaToolCall.id;
}
}
}
}
}
// Handle regular content
if (chunk.choices[0]?.delta?.content) {
assistantMessage += chunk.choices[0].delta.content;
}
// Check if stream is finished
if (chunk.choices[0]?.finish_reason) {
if (chunk.choices[0].finish_reason === 'tool_calls' && toolCalls.length > 0) {
// Increment tool call iterations counter
toolCallIterations++;
console.log(`Tool call iteration ${toolCallIterations}/${maxToolCallIterations}`);
// Execute tool calls and add results to conversation
console.log('Executing tool calls:', toolCalls);
// Send feedback to user about tool invocation
dataCallback({
type: 'chat',
data: {
choices: [
{
delta: {
content: `\n\n🔧 Invoking ${toolCalls.length} tool${toolCalls.length > 1 ? 's' : ''}...\n`,
},
},
],
},
});
// Add assistant message with tool calls to conversation
safeMessages.push({
role: 'assistant',
content: assistantMessage || null,
tool_calls: toolCalls,
});
// Execute each tool call and add results
for (const toolCall of toolCalls) {
if (toolCall.type === 'function') {
const name = toolCall.function.name;
console.log(`Calling function: ${name}`);
// Send feedback about specific tool being called
dataCallback({
type: 'chat',
data: {
choices: [
{
delta: {
content: `📞 Calling ${name}...`,
},
},
],
},
});
try {
const args = JSON.parse(toolCall.function.arguments);
console.log(`Function arguments:`, args);
const result = await callFunction(name, args);
console.log(`Function result:`, result);
// Send feedback about tool completion
dataCallback({
type: 'chat',
data: {
choices: [
{
delta: {
content: `\n`,
},
},
],
},
});
// Add tool result to conversation
safeMessages.push({
role: 'tool',
tool_call_id: toolCall.id,
content: result?.toString() || '',
});
} catch (error) {
console.error(`Error executing tool ${name}:`, error);
// Send feedback about tool error
dataCallback({
type: 'chat',
data: {
choices: [
{
delta: {
content: ` ❌ Error\n`,
},
},
],
},
});
safeMessages.push({
role: 'tool',
tool_call_id: toolCall.id,
content: `Error: ${error.message}`,
});
}
}
}
// Mark that tools have been executed to prevent repeated calls
toolsExecuted = true;
// Send feedback that tool execution is complete
dataCallback({
type: 'chat',
data: {
choices: [
{
delta: {
content: `\n🎯 Tool execution complete. Generating response...\n\n`,
},
},
],
},
});
// Continue conversation with tool results
break;
} else {
// Regular completion - send final response
conversationComplete = true;
}
}
// Process chunk normally for non-tool-call responses
if (!chunk.choices[0]?.delta?.tool_calls) {
console.log('after-tool-call-chunk', chunk);
const shouldBreak = await this.processChunk(chunk, dataCallback);
if (shouldBreak) {
conversationComplete = true;
break;
}
}
}
}
// Handle case where we hit maximum tool call iterations
if (toolCallIterations >= maxToolCallIterations && !conversationComplete) {
console.log('Maximum tool call iterations reached, forcing completion');
// Send a message indicating we've hit the limit and provide available information
dataCallback({
type: 'chat',
data: {
choices: [
{
delta: {
content:
'\n\n⚠ Maximum tool execution limit reached. Based on the available information, I can provide the following response:\n\n',
},
},
],
},
});
// Make one final call without tools to get a response based on the tool results
const finalStreamParams = this.getStreamParams(param, safeMessages);
const finalStream = await client.chat.completions.create({
...finalStreamParams,
tools: undefined, // Remove tools to force a text response
});
for await (const chunk of finalStream as unknown as AsyncIterable<any>) {
const shouldBreak = await this.processChunk(chunk, dataCallback);
if (shouldBreak) break;
}
}
}
}

View File

@@ -0,0 +1,41 @@
// tools/basicValue.ts
export interface BasicValueResult {
value: string;
}
export const BasicValueTool = {
name: 'basicValue',
type: 'function',
description: 'Returns a basic value (timestamp-based) for testing',
parameters: {
type: 'object',
properties: {},
required: [],
},
function: async (): Promise<BasicValueResult> => {
// generate something obviously basic
const basic = `tool-called-${Date.now()}`;
console.log('[BasicValueTool] returning:', basic);
return { value: basic };
},
};
export const WeatherTool = {
name: 'get_weather',
type: 'function',
description: 'Get current temperature for a given location.',
parameters: {
type: 'object',
properties: {
location: {
type: 'string',
description: 'City and country e.g. Bogotá, Colombia',
},
},
required: ['location'],
additionalProperties: false,
},
function: async (params: { location: string }) => {
console.log('[WeatherTool] Getting weather for:', params.location);
return { temperature: '25°C' };
},
};

View File

@@ -2,7 +2,7 @@
"name": "@open-gsio/server",
"type": "module",
"scripts": {
"dev": "bun src/server/server.ts",
"dev": "bun --watch src/server/server.ts",
"build": "bun ./src/server/build.ts"
},
"devDependencies": {