diff --git a/package.json b/package.json index a8c9493..553ad54 100644 --- a/package.json +++ b/package.json @@ -4,18 +4,20 @@ "type": "module", "private": true, "devDependencies": { + "@kevisual/js-filter": "^0.0.5", + "@kevisual/router": "^0.1.0", "@types/bun": "latest", - "dotenv": "^17.2.3", + "dotenv": "^17.3.1", "zod": "^4.3.6" }, "peerDependencies": { "typescript": "^5" }, "dependencies": { - "@ai-sdk/anthropic": "^3.0.35", - "@ai-sdk/openai": "^3.0.25", - "@ai-sdk/openai-compatible": "^2.0.26", - "@kevisual/ai": "^0.0.24", - "ai": "^6.0.67" + "@ai-sdk/anthropic": "^3.0.58", + "@ai-sdk/openai": "^3.0.41", + "@ai-sdk/openai-compatible": "^2.0.35", + "@kevisual/ai": "^0.0.26", + "ai": "^6.0.116" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4b4a89e..83bb0c6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,81 +9,93 @@ importers: .: dependencies: '@ai-sdk/anthropic': - specifier: ^3.0.35 - version: 3.0.35(zod@4.3.6) + specifier: ^3.0.58 + version: 3.0.58(zod@4.3.6) '@ai-sdk/openai': - specifier: ^3.0.25 - version: 3.0.25(zod@4.3.6) + specifier: ^3.0.41 + version: 3.0.41(zod@4.3.6) '@ai-sdk/openai-compatible': - specifier: ^2.0.26 - version: 2.0.26(zod@4.3.6) + specifier: ^2.0.35 + version: 2.0.35(zod@4.3.6) '@kevisual/ai': - specifier: ^0.0.24 - version: 0.0.24 + specifier: ^0.0.26 + version: 0.0.26 ai: - specifier: ^6.0.67 - version: 6.0.67(zod@4.3.6) + specifier: ^6.0.116 + version: 6.0.116(zod@4.3.6) typescript: specifier: ^5 version: 5.9.3 devDependencies: + '@kevisual/js-filter': + specifier: ^0.0.5 + version: 0.0.5 + '@kevisual/router': + specifier: ^0.1.0 + version: 0.1.0 '@types/bun': specifier: latest version: 1.3.8 dotenv: - specifier: ^17.2.3 - version: 17.2.3 + specifier: ^17.3.1 + version: 17.3.1 zod: specifier: ^4.3.6 version: 4.3.6 packages: - '@ai-sdk/anthropic@3.0.35': - resolution: {integrity: sha512-Y3g/5uVj621XSB9lGF7WrD7qR+orhV5xpaYkRF8kfj2j4W7e7BBGIvxcdsCf85FjJbc6tKQdNTZ84ZEqT3Y5TQ==} + '@ai-sdk/anthropic@3.0.58': + resolution: {integrity: sha512-/53SACgmVukO4bkms4dpxpRlYhW8Ct6QZRe6sj1Pi5H00hYhxIrqfiLbZBGxkdRvjsBQeP/4TVGsXgH5rQeb8Q==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 - '@ai-sdk/gateway@3.0.32': - resolution: {integrity: sha512-7clZRr07P9rpur39t1RrbIe7x8jmwnwUWI8tZs+BvAfX3NFgdSVGGIaT7bTz2pb08jmLXzTSDbrOTqAQ7uBkBQ==} + '@ai-sdk/gateway@3.0.66': + resolution: {integrity: sha512-SIQ0YY0iMuv+07HLsZ+bB990zUJ6S4ujORAh+Jv1V2KGNn73qQKnGO0JBk+w+Res8YqOFSycwDoWcFlQrVxS4A==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 - '@ai-sdk/openai-compatible@2.0.26': - resolution: {integrity: sha512-l6jdFjI1C2eDAEm7oo+dnRn0oG1EkcyqfbEZ7ozT0TnYrah6amX2JkftYMP1GRzNtAeCB3WNN8XspXdmi6ZNlQ==} + '@ai-sdk/openai-compatible@2.0.35': + resolution: {integrity: sha512-g3wA57IAQFb+3j4YuFndgkUdXyRETZVvbfAWM+UX7bZSxA3xjes0v3XKgIdKdekPtDGsh4ZX2byHD0gJIMPfiA==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 - '@ai-sdk/openai@3.0.25': - resolution: {integrity: sha512-DsaN46R98+D1W3lU3fKuPU3ofacboLaHlkAwxJPgJ8eup1AJHmPK1N1y10eJJbJcF6iby8Tf/vanoZxc9JPUfw==} + '@ai-sdk/openai@3.0.41': + resolution: {integrity: sha512-IZ42A+FO+vuEQCVNqlnAPYQnnUpUfdJIwn1BEDOBywiEHa23fw7PahxVtlX9zm3/zMvTW4JKPzWyvAgDu+SQ2A==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 - '@ai-sdk/provider-utils@4.0.13': - resolution: {integrity: sha512-HHG72BN4d+OWTcq2NwTxOm/2qvk1duYsnhCDtsbYwn/h/4zeqURu1S0+Cn0nY2Ysq9a9HGKvrYuMn9bgFhR2Og==} + '@ai-sdk/provider-utils@4.0.19': + resolution: {integrity: sha512-3eG55CrSWCu2SXlqq2QCsFjo3+E7+Gmg7i/oRVoSZzIodTuDSfLb3MRje67xE9RFea73Zao7Lm4mADIfUETKGg==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 - '@ai-sdk/provider@3.0.7': - resolution: {integrity: sha512-VkPLrutM6VdA924/mG8OS+5frbVTcu6e046D2bgDo00tehBANR1QBJ/mPcZ9tXMFOsVcm6SQArOregxePzTFPw==} + '@ai-sdk/provider@3.0.8': + resolution: {integrity: sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ==} engines: {node: '>=18'} - '@kevisual/ai@0.0.24': - resolution: {integrity: sha512-7jvZk1/L//VIClK7usuNgN4ZA9Etgbooka1Sj5quE/0UywR+NNnwqXVZ89Y1fBhI1TkhauDsdJBAtcQ7r/vbVw==} + '@kevisual/ai@0.0.26': + resolution: {integrity: sha512-lhaMpxi+vgqPdyBKiuNbSil4hy13tNLbDiqCtG0qUXKtvoowK6xMx269pSSYkYBivczM8g8I0XEouuJceUpJPg==} + + '@kevisual/js-filter@0.0.5': + resolution: {integrity: sha512-+S+Sf3K/aP6XtZI2s7TgKOr35UuvUvtpJ9YDW30a+mY0/N8gRuzyKhieBzQN7Ykayzz70uoMavBXut2rUlLgzw==} '@kevisual/logger@0.0.4': resolution: {integrity: sha512-+fpr92eokSxoGOW1SIRl/27lPuO+zyY+feR5o2Q4YCNlAdt2x64NwC/w8r/3NEC5QenLgd4K0azyKTI2mHbARw==} - '@kevisual/permission@0.0.3': - resolution: {integrity: sha512-8JsA/5O5Ax/z+M+MYpFYdlioHE6jNmWMuFSokBWYs9CCAHNiSKMR01YLkoVDoPvncfH/Y8F5K/IEXRCbptuMNA==} + '@kevisual/permission@0.0.4': + resolution: {integrity: sha512-zwBYPnT/z21W4q2wkklJrxvoYBYWG/+a3iXFDKqXQAnDOcxm/SU1f1N6FQb9KxGKl36/fclVlhxlxqszvKCenQ==} - '@kevisual/query@0.0.38': - resolution: {integrity: sha512-bfvbSodsZyMfwY+1T2SvDeOCKsT/AaIxlVe0+B1R/fNhlg2MDq2CP0L9HKiFkEm+OXrvXcYDMKPUituVUM5J6Q==} + '@kevisual/query@0.0.52': + resolution: {integrity: sha512-m1UbyDTIxtfAQXM+EqhXA4ytE2V8rV8mXTZVBwzfW9O6+gtvAcRY7K1YYxfewTSXLVh9nwvfHe0KQ8MDL5ukyw==} + + '@kevisual/router@0.1.0': + resolution: {integrity: sha512-7NHoKP36uWkTDp/hxeUBMtawma91BrOAwG/caOrVsO8tM3wjqhlmCt0sIvLBM+snVJkmylROQR0WGaygd3JqYw==} '@opentelemetry/api@1.9.0': resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} @@ -102,8 +114,8 @@ packages: resolution: {integrity: sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w==} engines: {node: '>= 20'} - ai@6.0.67: - resolution: {integrity: sha512-xBnTcByHCj3OcG6V8G1s6zvSEqK0Bdiu+IEXYcpGrve1iGFFRgcrKeZtr/WAW/7gupnSvBbDF24BEv1OOfqi1g==} + ai@6.0.116: + resolution: {integrity: sha512-7yM+cTmyRLeNIXwt4Vj+mrrJgVQ9RMIW5WO0ydoLoYkewIvsMcvUmqS4j2RJTUXaF1HphwmSKUMQ/HypNRGOmA==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 @@ -111,10 +123,13 @@ packages: bun-types@1.3.8: resolution: {integrity: sha512-fL99nxdOWvV4LqjmC+8Q9kW3M4QTtTR1eePs94v5ctGqU8OeceWrSUaRw3JYb7tU3FkMIAjkueehrHPPPGKi5Q==} - dotenv@17.2.3: - resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==} + dotenv@17.3.1: + resolution: {integrity: sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==} engines: {node: '>=12'} + es-toolkit@1.45.1: + resolution: {integrity: sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw==} + eventsource-parser@3.0.6: resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==} engines: {node: '>=18.0.0'} @@ -122,9 +137,6 @@ packages: json-schema@0.4.0: resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} - tslib@2.8.1: - resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} @@ -138,55 +150,59 @@ packages: snapshots: - '@ai-sdk/anthropic@3.0.35(zod@4.3.6)': + '@ai-sdk/anthropic@3.0.58(zod@4.3.6)': dependencies: - '@ai-sdk/provider': 3.0.7 - '@ai-sdk/provider-utils': 4.0.13(zod@4.3.6) + '@ai-sdk/provider': 3.0.8 + '@ai-sdk/provider-utils': 4.0.19(zod@4.3.6) zod: 4.3.6 - '@ai-sdk/gateway@3.0.32(zod@4.3.6)': + '@ai-sdk/gateway@3.0.66(zod@4.3.6)': dependencies: - '@ai-sdk/provider': 3.0.7 - '@ai-sdk/provider-utils': 4.0.13(zod@4.3.6) + '@ai-sdk/provider': 3.0.8 + '@ai-sdk/provider-utils': 4.0.19(zod@4.3.6) '@vercel/oidc': 3.1.0 zod: 4.3.6 - '@ai-sdk/openai-compatible@2.0.26(zod@4.3.6)': + '@ai-sdk/openai-compatible@2.0.35(zod@4.3.6)': dependencies: - '@ai-sdk/provider': 3.0.7 - '@ai-sdk/provider-utils': 4.0.13(zod@4.3.6) + '@ai-sdk/provider': 3.0.8 + '@ai-sdk/provider-utils': 4.0.19(zod@4.3.6) zod: 4.3.6 - '@ai-sdk/openai@3.0.25(zod@4.3.6)': + '@ai-sdk/openai@3.0.41(zod@4.3.6)': dependencies: - '@ai-sdk/provider': 3.0.7 - '@ai-sdk/provider-utils': 4.0.13(zod@4.3.6) + '@ai-sdk/provider': 3.0.8 + '@ai-sdk/provider-utils': 4.0.19(zod@4.3.6) zod: 4.3.6 - '@ai-sdk/provider-utils@4.0.13(zod@4.3.6)': + '@ai-sdk/provider-utils@4.0.19(zod@4.3.6)': dependencies: - '@ai-sdk/provider': 3.0.7 + '@ai-sdk/provider': 3.0.8 '@standard-schema/spec': 1.1.0 eventsource-parser: 3.0.6 zod: 4.3.6 - '@ai-sdk/provider@3.0.7': + '@ai-sdk/provider@3.0.8': dependencies: json-schema: 0.4.0 - '@kevisual/ai@0.0.24': + '@kevisual/ai@0.0.26': dependencies: '@kevisual/logger': 0.0.4 - '@kevisual/permission': 0.0.3 - '@kevisual/query': 0.0.38 + '@kevisual/permission': 0.0.4 + '@kevisual/query': 0.0.52 + + '@kevisual/js-filter@0.0.5': {} '@kevisual/logger@0.0.4': {} - '@kevisual/permission@0.0.3': {} + '@kevisual/permission@0.0.4': {} - '@kevisual/query@0.0.38': + '@kevisual/query@0.0.52': {} + + '@kevisual/router@0.1.0': dependencies: - tslib: 2.8.1 + es-toolkit: 1.45.1 '@opentelemetry/api@1.9.0': {} @@ -202,11 +218,11 @@ snapshots: '@vercel/oidc@3.1.0': {} - ai@6.0.67(zod@4.3.6): + ai@6.0.116(zod@4.3.6): dependencies: - '@ai-sdk/gateway': 3.0.32(zod@4.3.6) - '@ai-sdk/provider': 3.0.7 - '@ai-sdk/provider-utils': 4.0.13(zod@4.3.6) + '@ai-sdk/gateway': 3.0.66(zod@4.3.6) + '@ai-sdk/provider': 3.0.8 + '@ai-sdk/provider-utils': 4.0.19(zod@4.3.6) '@opentelemetry/api': 1.9.0 zod: 4.3.6 @@ -214,14 +230,14 @@ snapshots: dependencies: '@types/node': 25.2.0 - dotenv@17.2.3: {} + dotenv@17.3.1: {} + + es-toolkit@1.45.1: {} eventsource-parser@3.0.6: {} json-schema@0.4.0: {} - tslib@2.8.1: {} - typescript@5.9.3: {} undici-types@7.16.0: {} diff --git a/src/common.ts b/src/common.ts index 5a6fc6f..9304f12 100644 --- a/src/common.ts +++ b/src/common.ts @@ -2,6 +2,24 @@ import { createOpenAICompatible } from '@ai-sdk/openai-compatible'; import { createAnthropic } from '@ai-sdk/anthropic'; import { generateText } from 'ai'; import 'dotenv/config'; +import util from 'node:util'; +// 保存原始 fetch 引用 +const originalFetch = fetch; + +export const defaultFetch = { + fetch: async (input: string | URL | Request, init?: BunFetchRequestInit) => { + console.log('请求 URL:', input); + console.log('请求选项:', init); + + const response = await originalFetch(input, init); // 调用原始 fetch + + console.log('响应状态:', response.status); + const responseBody = await response.clone().text(); // 使用 clone 避免消耗原始 response + console.log('响应体:', responseBody); + + return response; + } +} export function resolveEnvVars(value: string): string { return value.replace(/{env:([^}]+)}/g, (_, varName) => { @@ -19,6 +37,8 @@ export const models = { 'MiniMax-M2.1': 'MiniMax-M2.1', 'qwen3-coder-plus': 'qwen3-coder-plus', 'hunyuan-a13b': 'hunyuan-a13b', + 'qwen-plus': 'qwen-plus', + 'auto': 'AUTO_Models', } export const bailian = createOpenAICompatible({ baseURL: 'https://coding.dashscope.aliyuncs.com/v1', @@ -36,6 +56,7 @@ export const minimax = createAnthropic({ baseURL: 'https://api.minimaxi.com/anthropic/v1', name: 'custom-minimax', apiKey: process.env.MINIMAX_API_KEY!, + fetch: defaultFetch.fetch as any }); export const doubao = createOpenAICompatible({ @@ -44,8 +65,20 @@ export const doubao = createOpenAICompatible({ apiKey: process.env.DOUBAO_API_KEY!, }); + export const cnb = createOpenAICompatible({ - baseURL: resolveEnvVars('https://api.cnb.cool/{env:CNB_REPO_SLUG}/-/ai/'), + baseURL: 'https://api.cnb.cool/kevisual/kevisual/-/ai/', + // baseURL: resolveEnvVars('https://api.cnb.cool/{env:CNB_REPO_SLUG}/-/ai/'), name: 'custom-cnb', apiKey: process.env.CNB_API_KEY!, + fetch: defaultFetch.fetch as any }); +export const proxyCnb = createOpenAICompatible({ + baseURL: 'http://localhost:4005/api', + name: 'proxy-cnb', + apiKey: process.env.CNB_API_KEY!, +}); + +export const showMore = (obj: any) => { + return util.inspect(obj, { depth: null, colors: true }); +} \ No newline at end of file diff --git a/src/routes/ai.ts b/src/routes/ai.ts new file mode 100644 index 0000000..cb72044 --- /dev/null +++ b/src/routes/ai.ts @@ -0,0 +1,83 @@ +import { generateText, tool, type ModelMessage, type ToolApprovalResponse } from 'ai'; +import { z } from 'zod'; +import { cnb, proxyCnb, models, showMore } from '../common.ts'; + +import { app } from './app.ts'; +import type { App } from '@kevisual/router'; + +const createTool = async (app: App, message: { path: string, key: string }) => { + const route = app.findRoute({ path: message.path, key: message.key }); + if (!route) { + console.error(`未找到路径 ${message.path} 和 key ${message.key} 的路由`); + return null; + } + const _tool = tool({ + description: route.description || '无描述', + inputSchema: z.object({ + ...route.metadata?.args + }), // 这里可以根据实际需要定义输入参数的 schema + execute: async (args: any) => { + console.log(`执行工具 ${message.path} ${message.key},输入参数:`, args); + // 这里可以根据实际需要调用对应的路由处理函数 + const res = await app.run({ path: message.path, key: message.key, payload: args }); + // 假设路由处理函数返回一个字符串结果 + console.log(`工具 ${message.path} ${message.key} 执行结果:`, res); + // return '任务列表:' + JSON.stringify(res); + return res; + } + }); + return _tool; +} + +const createTools = async (app: App) => { + const tools: Record = {}; + for (const route of app.routes) { + const id = route.id!; + const _tool = await createTool(app, { path: route.path!, key: route.key! }); + if (_tool && id) { + tools[id] = _tool; + } + } + return tools; +} + +const tools = await createTools(app); + +let messages: ModelMessage[] = [ + { + role: 'user', + content: '任务步骤:第一步获取今天的任务列表,第二步完成id为1的任务。' + } +] +let result = await generateText({ + model: proxyCnb(models['auto']), + tools, + messages + // prompt: '今天的任务是什么? 如果有id为1的任务,请完成他。' + // prompt: '完成今天的任务1。' +}); +console.log('生成结果:', result.text); +console.log('=== 生成内容详情 ==='); +console.log(showMore(result)); + +messages.push(...result.response.messages); + +const result2 = await generateText({ + model: cnb(models['auto']), + tools, + messages +}); +console.log('生成结果2:', result2.text); +console.log('=== 生成内容详情 ==='); +console.log(showMore(result2)); + +messages.push(...result2.response.messages); + +const result3 = await generateText({ + model: cnb(models['auto']), + tools, + messages +}); +console.log('生成结果3:', result3.text); +console.log('=== 生成内容详情 ==='); +console.log(showMore(result3)); diff --git a/src/routes/ai2.ts b/src/routes/ai2.ts new file mode 100644 index 0000000..de08d43 --- /dev/null +++ b/src/routes/ai2.ts @@ -0,0 +1,20 @@ +import { app } from './app.ts'; +import { cnb, proxyCnb, models, showMore } from '../common.ts'; +import { runAgent } from './lib.ts'; + +const result = await runAgent({ + app, + query:"WHERE path='task' AND key='today'", + languageModel: proxyCnb(models['auto']), + messages: [ + { + role: 'user', + content: '任务步骤:第一步获取今天的任务列表,第二步完成id为1的任务。' + } + ] +}) + +console.log('最终结果:', result.text); + +console.log('=== 生成内容详情 ==='); +console.log(showMore(result.steps)); \ No newline at end of file diff --git a/src/routes/app.ts b/src/routes/app.ts new file mode 100644 index 0000000..eb06536 --- /dev/null +++ b/src/routes/app.ts @@ -0,0 +1,67 @@ +import { App } from '@kevisual/router' +import z from 'zod'; + +export const app = new App(); + +const tasks = [ + { + id: 1, + title: 'Task 1', + description: 'This is the first task.', + completed: false, + }, + { + id: 2, + title: 'Task 2', + description: 'This is the second task.', + completed: true, + }, +] +app.route({ + path: 'task', + key: 'today', + description: '获取今日待办任务列表', +}).define(async (ctx) => { + + const list = tasks.map(task => ({ + id: task.id, + title: task.title, + description: task.description, + completed: task.completed, + })) + ctx.body = { list } +}).addTo(app) + +app.route({ + path: 'task', + key: 'done', + description: '完成任务', + metadata: { + args: { + id: z.number().describe('任务ID'), + } + } +}).define(async (ctx) => { + const { id } = ctx.args as { id: number }; + const task = tasks.find(t => t.id === id); + if (task) { + task.completed = true; + ctx.body = { message: `任务 ${id} 已完成` }; + } else { + ctx.body = { message: `未找到任务 ${id}` }; + } +}).addTo(app) + +app.route({ + path: 'task', + key: 'list', + description: '获取所有任务列表', +}).define(async (ctx) => { + const list = tasks.map(task => ({ + id: task.id, + title: task.title, + description: task.description, + completed: task.completed, + })) + ctx.body = { list } +}).addTo(app) \ No newline at end of file diff --git a/src/routes/lib.ts b/src/routes/lib.ts new file mode 100644 index 0000000..afa727c --- /dev/null +++ b/src/routes/lib.ts @@ -0,0 +1,66 @@ +import { App, type RouteInfo } from '@kevisual/router' +import { generateText, tool, type ModelMessage, type LanguageModel, type GenerateTextResult } from 'ai'; +import z from 'zod'; +import { filter } from '@kevisual/js-filter' +const createTool = async (app: App, message: { path: string, key: string }) => { + const route = app.findRoute({ path: message.path, key: message.key }); + if (!route) { + console.error(`未找到路径 ${message.path} 和 key ${message.key} 的路由`); + return null; + } + const _tool = tool({ + description: route.description || '无描述', + inputSchema: z.object({ + ...route.metadata?.args + }), // 这里可以根据实际需要定义输入参数的 schema + execute: async (args: any) => { + console.log(`执行工具 ${message.path} ${message.key},输入参数:`, args); + // 这里可以根据实际需要调用对应的路由处理函数 + const res = await app.run({ path: message.path, key: message.key, payload: args }); + // 假设路由处理函数返回一个字符串结果 + console.log(`工具 ${message.path} ${message.key} 执行结果:`, res); + // return '任务列表:' + JSON.stringify(res); + return res; + } + }); + return _tool; +} + +const createTools = async (app: App) => { + const tools: Record = {}; + for (const route of app.routes) { + const id = route.id!; + const _tool = await createTool(app, { path: route.path!, key: route.key! }); + if (_tool && id) { + tools[id] = _tool; + } + } + return tools; +} +type Route = Partial + +export const reCallAgent = async (opts: { messages?: ModelMessage[], tools?: Record, languageModel: LanguageModel }): Promise, any>> => { + const { messages = [], tools = {}, languageModel } = opts; + const result = await generateText({ + model: languageModel, + messages, + tools, + }); + const step = result.steps[0]!; + if (step.finishReason === 'tool-calls') { + messages.push(...result.response.messages); + return reCallAgent({ messages, tools, languageModel }); + } + return result +} +export const runAgent = async (opts: { app: App, messages?: ModelMessage[], routes?: Route[], query?: string, languageModel: LanguageModel }) => { + const { app, languageModel } = opts; + let messages = opts.messages || []; + + let routes = app.routes; + if (opts.query) { + routes = filter(routes, opts.query); + }; + const tools = await createTools(app); + return await reCallAgent({ messages, tools, languageModel }); +} diff --git a/src/test-cnb.ts b/src/test-cnb.ts index 03269ba..768eeb6 100644 --- a/src/test-cnb.ts +++ b/src/test-cnb.ts @@ -17,7 +17,7 @@ function resolveEnvVars(value: string): string { const cnb = createOpenAICompatible({ baseURL: resolveEnvVars('{env:CNB_API_ENDPOINT}/{env:CNB_REPO_SLUG}/-/ai/'), name: 'custom-cnb', - apiKey: process.env.CNB_API_KEY!, + // apiKey: process.env.CNB_API_KEY!, }); const { text } = await generateText({ diff --git a/src/test-minimax.ts b/src/test-minimax.ts new file mode 100644 index 0000000..0973395 --- /dev/null +++ b/src/test-minimax.ts @@ -0,0 +1,9 @@ +import { minimax } from "./common" +import { generateText } from 'ai'; + +const { text } = await generateText({ + model: minimax('MiniMax-M2.1'), + prompt: 'What is an agent?', +}); + +console.log('Response:', text); \ No newline at end of file