feat: 添加 zod 依赖,重构工具和审批示例,删除不再使用的调试文件
This commit is contained in:
@@ -5,7 +5,8 @@
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest",
|
||||
"dotenv": "^17.2.3"
|
||||
"dotenv": "^17.2.3",
|
||||
"zod": "^4.3.6"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5"
|
||||
|
||||
13
pnpm-lock.yaml
generated
13
pnpm-lock.yaml
generated
@@ -23,6 +23,9 @@ importers:
|
||||
ai:
|
||||
specifier: ^6.0.67
|
||||
version: 6.0.67(zod@4.3.6)
|
||||
typescript:
|
||||
specifier: ^5
|
||||
version: 5.9.3
|
||||
devDependencies:
|
||||
'@types/bun':
|
||||
specifier: latest
|
||||
@@ -30,6 +33,9 @@ importers:
|
||||
dotenv:
|
||||
specifier: ^17.2.3
|
||||
version: 17.2.3
|
||||
zod:
|
||||
specifier: ^4.3.6
|
||||
version: 4.3.6
|
||||
|
||||
packages:
|
||||
|
||||
@@ -119,6 +125,11 @@ packages:
|
||||
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'}
|
||||
hasBin: true
|
||||
|
||||
undici-types@7.16.0:
|
||||
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
|
||||
|
||||
@@ -211,6 +222,8 @@ snapshots:
|
||||
|
||||
tslib@2.8.1: {}
|
||||
|
||||
typescript@5.9.3: {}
|
||||
|
||||
undici-types@7.16.0: {}
|
||||
|
||||
zod@4.3.6: {}
|
||||
|
||||
51
src/common.ts
Normal file
51
src/common.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
|
||||
import { createAnthropic } from '@ai-sdk/anthropic';
|
||||
import { generateText } from 'ai';
|
||||
import 'dotenv/config';
|
||||
|
||||
export function resolveEnvVars(value: string): string {
|
||||
return value.replace(/{env:([^}]+)}/g, (_, varName) => {
|
||||
const envValue = process.env[varName];
|
||||
if (!envValue) {
|
||||
throw new Error(`Environment variable ${varName} is not set`);
|
||||
}
|
||||
return envValue;
|
||||
});
|
||||
}
|
||||
|
||||
export const models = {
|
||||
'doubao-ark-code-latest': 'doubao-ark-code-latest',
|
||||
'GLM-4.7': 'GLM-4.7',
|
||||
'MiniMax-M2.1': 'MiniMax-M2.1',
|
||||
'qwen3-coder-plus': 'qwen3-coder-plus',
|
||||
'hunyuan-a13b': 'hunyuan-a13b',
|
||||
}
|
||||
export const bailian = createOpenAICompatible({
|
||||
baseURL: 'https://coding.dashscope.aliyuncs.com/v1',
|
||||
name: 'custom-bailian',
|
||||
apiKey: process.env.BAILIAN_CODE_API_KEY!,
|
||||
});
|
||||
|
||||
export const zhipu = createOpenAICompatible({
|
||||
baseURL: 'https://open.bigmodel.cn/api/coding/paas/v4',
|
||||
name: 'custom-zhipu',
|
||||
apiKey: process.env.ZHIPU_API_KEY!,
|
||||
});
|
||||
|
||||
export const minimax = createAnthropic({
|
||||
baseURL: 'https://api.minimaxi.com/anthropic/v1',
|
||||
name: 'custom-minimax',
|
||||
apiKey: process.env.MINIMAX_API_KEY!,
|
||||
});
|
||||
|
||||
export const doubao = createOpenAICompatible({
|
||||
baseURL: 'https://ark.cn-beijing.volces.com/api/coding/v3',
|
||||
name: 'custom-doubao',
|
||||
apiKey: process.env.DOUBAO_API_KEY!,
|
||||
});
|
||||
|
||||
export const cnb = createOpenAICompatible({
|
||||
baseURL: resolveEnvVars('https://api.cnb.cool/{env:CNB_REPO_SLUG}/-/ai/'),
|
||||
name: 'custom-cnb',
|
||||
apiKey: process.env.CNB_API_KEY!,
|
||||
});
|
||||
@@ -1,33 +0,0 @@
|
||||
import 'dotenv/config';
|
||||
|
||||
const endpoint = 'https://coding.dashscope.aliyuncs.com/apps/anthropic/messages';
|
||||
|
||||
// 测试不同的认证方式
|
||||
const authMethods = [
|
||||
{ name: 'x-api-key', headers: { 'x-api-key': process.env.BAILIAN_CODE_API_KEY! } },
|
||||
{ name: 'Authorization Bearer', headers: { 'Authorization': `Bearer ${process.env.BAILIAN_CODE_API_KEY!}` } },
|
||||
{ name: 'Authorization Bearer sk-', headers: { 'Authorization': `Bearer sk-${process.env.BAILIAN_CODE_API_KEY!}` } },
|
||||
{ name: 'Authorization sk-', headers: { 'Authorization': `sk-${process.env.BAILIAN_CODE_API_KEY!}` } },
|
||||
];
|
||||
|
||||
for (const { name, headers } of authMethods) {
|
||||
console.log(`\nTesting: ${name}`);
|
||||
const response = await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...headers,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: 'qwen3-coder-plus',
|
||||
max_tokens: 100,
|
||||
messages: [{ role: 'user', content: 'Hello' }],
|
||||
}),
|
||||
});
|
||||
|
||||
console.log(`Status: ${response.status}`);
|
||||
const text = await response.text();
|
||||
if (text) {
|
||||
console.log(`Response: ${text.substring(0, 300)}`);
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
import 'dotenv/config';
|
||||
|
||||
function resolveEnvVars(value: string): string {
|
||||
return value.replace(/{env:([^}]+)}/g, (_, varName) => {
|
||||
const envValue = process.env[varName];
|
||||
if (!envValue) {
|
||||
throw new Error(`Environment variable ${varName} is not set`);
|
||||
}
|
||||
return envValue;
|
||||
});
|
||||
}
|
||||
|
||||
const endpoint = 'https://cnb.cool/kevisual/cnb/-/ai/chat/completions';
|
||||
const apiKey = process.env.CNB_API_KEY!;
|
||||
|
||||
console.log('Endpoint:', endpoint);
|
||||
console.log('API Key:', apiKey ? 'Set' : 'Not set');
|
||||
console.log('API Key length:', apiKey.length);
|
||||
console.log('API Key prefix:', apiKey.substring(0, 10) + '...');
|
||||
|
||||
// 尝试不同的认证头
|
||||
const authHeaders = [
|
||||
{ name: 'Authorization Bearer', headers: { 'Authorization': `Bearer ${apiKey}` } },
|
||||
{ name: 'Authorization (no Bearer)', headers: { 'Authorization': apiKey } },
|
||||
{ name: 'x-api-key', headers: { 'x-api-key': apiKey } },
|
||||
{ name: 'api-key', headers: { 'api-key': apiKey } },
|
||||
];
|
||||
|
||||
for (const { name, headers } of authHeaders) {
|
||||
console.log(`\n=== Testing: ${name} ===`);
|
||||
const response = await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...headers,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: 'hunyuan-a13b',
|
||||
messages: [{ role: 'user', content: '你好' }],
|
||||
}),
|
||||
});
|
||||
|
||||
console.log(`Status: ${response.status}`);
|
||||
const text = await response.text();
|
||||
console.log('Response:', text);
|
||||
}
|
||||
60
src/tools/a.ts
Normal file
60
src/tools/a.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { anthropic } from '@ai-sdk/anthropic';
|
||||
import { generateText, stepCountIs, tool, } from 'ai';
|
||||
import { zhipu, models, minimax } from '../common.ts';
|
||||
import { z } from 'zod'
|
||||
const runCommand = async (command: string): Promise<string> => {
|
||||
// Implementation to execute the bash command and return the output
|
||||
// return `Executed command: ${command}`; // Placeholder implementation
|
||||
return new Promise((resolve, reject) => {
|
||||
// const { exec } = require('child_process');
|
||||
// exec(command, (error: any, stdout: string, stderr: string) => {
|
||||
// if (error) {
|
||||
// reject(`Error: ${stderr}`);
|
||||
// } else {
|
||||
// resolve(stdout);
|
||||
// }
|
||||
// });
|
||||
resolve(`Executed command: ${command}`); // Placeholder implementation
|
||||
});
|
||||
}
|
||||
const result = await generateText({
|
||||
// model: minimax(models['MiniMax-M2.1']),
|
||||
model: zhipu(models['GLM-4.7']),
|
||||
tools: {
|
||||
// bash: minimax.tools
|
||||
time: tool({
|
||||
description: '获取当前系统时间。',
|
||||
inputSchema: z.object({}),
|
||||
execute: async (_args: {}) => {
|
||||
console.log('获取当前系统时间');
|
||||
return new Date().toString();
|
||||
},
|
||||
}),
|
||||
timeZone: tool({
|
||||
description: '将指定时间字符串转换为目标时区格式。必须先使用 time 工具获取时间字符串作为输入。',
|
||||
inputSchema: z.object({
|
||||
zone: z.string().describe('IANA 时区名称,例如 "America/New_York"。'),
|
||||
date: z.string().describe('从 time 工具获取的日期字符串。'),
|
||||
}),
|
||||
// needsApproval: true,
|
||||
execute: async (args: { zone: string, date: string }) => {
|
||||
const date = new Date(args.date);
|
||||
console.log('格式化日期:', args.date, '到时区:', args.zone);
|
||||
const options: Intl.DateTimeFormatOptions = {
|
||||
timeZone: args.zone,
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
};
|
||||
return new Intl.DateTimeFormat('zh-CN', options).format(date);
|
||||
},
|
||||
}),
|
||||
},
|
||||
stopWhen: stepCountIs(5),
|
||||
prompt: '我的时区是 America/New_York,请显示我当前系统在该时区的时间。',
|
||||
});
|
||||
|
||||
console.log('Response:', result.text);
|
||||
134
src/tools/approval-example.ts
Normal file
134
src/tools/approval-example.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
import { generateText, tool, type ModelMessage, type ToolApprovalResponse } from 'ai';
|
||||
import { z } from 'zod';
|
||||
import { zhipu, models } from '../common.ts';
|
||||
|
||||
// 定义一个需要审批的工具
|
||||
const timeZone = tool({
|
||||
description: '将指定时间字符串转换为目标时区格式。必须先使用 time 工具获取时间字符串作为输入。',
|
||||
inputSchema: z.object({
|
||||
zone: z.string().describe('IANA 时区名称,例如 "America/New_York"。'),
|
||||
date: z.string().describe('从 time 工具获取的日期字符串。'),
|
||||
}),
|
||||
// needsApproval: false, // 设置需要审批
|
||||
execute: async ({ zone, date }) => {
|
||||
const dateObj = new Date(date);
|
||||
console.log('格式化日期:', date, '到时区:', zone);
|
||||
const options: Intl.DateTimeFormatOptions = {
|
||||
timeZone: zone,
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
};
|
||||
return new Intl.DateTimeFormat('zh-CN', options).format(dateObj);
|
||||
},
|
||||
});
|
||||
|
||||
const time = tool({
|
||||
description: '获取当前系统时间。',
|
||||
inputSchema: z.object({}),
|
||||
// needsApproval: true,
|
||||
execute: async () => {
|
||||
console.log('获取当前系统时间');
|
||||
return new Date().toString();
|
||||
},
|
||||
});
|
||||
|
||||
// 模拟审批函数 - 在实际应用中,这可能是用户通过 UI 确认
|
||||
function mockAskUserApproval(toolCall: { toolName: string; input: any }): boolean {
|
||||
console.log('=== 待审批的工具调用 ===');
|
||||
console.log(`工具: ${toolCall.toolName}`);
|
||||
console.log(`输入: ${JSON.stringify(toolCall.input)}`);
|
||||
console.log('==========================');
|
||||
|
||||
// 在实际应用中,这里会等待用户输入
|
||||
// 这里模拟用户总是批准
|
||||
console.log('模拟用户审批: 批准\n');
|
||||
return true;
|
||||
}
|
||||
|
||||
// 模拟获取拒绝原因
|
||||
function mockAskDenialReason(): string {
|
||||
// 在实际应用中,这里会等待用户输入拒绝原因
|
||||
return '用户取消了操作';
|
||||
}
|
||||
|
||||
async function runApprovalExample() {
|
||||
// 初始消息
|
||||
const messages: ModelMessage[] = [
|
||||
{ role: 'user', content: '我的时区是 America/New_York,请显示我当前系统在该时区的时间。' },
|
||||
];
|
||||
|
||||
// 第一步: 发起请求
|
||||
console.log('=== 第一步: 发起请求 ===\n');
|
||||
let result = await generateText({
|
||||
model: zhipu(models['GLM-4.7']),
|
||||
tools: { time, timeZone },
|
||||
prompt: '我的时区是 America/New_York,请显示我当前系统在该时区的时间。',
|
||||
});
|
||||
console.log('=== 第一步结果 ===\n', result.text, '\n');
|
||||
|
||||
// 添加响应消息到历史
|
||||
messages.push(...result.response.messages);
|
||||
|
||||
// 检查是否有需要审批的工具调用
|
||||
let approvalRequests: Array<{ type: 'tool-approval-request', approvalId: string, toolCall: any }> = [];
|
||||
|
||||
for (const part of result.content) {
|
||||
if (part.type === 'tool-approval-request') {
|
||||
approvalRequests.push(part);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果有审批请求,处理它们
|
||||
while (approvalRequests.length > 0) {
|
||||
console.log('=== 处理审批请求 ===\n');
|
||||
|
||||
// 收集审批响应
|
||||
const approvalResponses: ToolApprovalResponse[] = [];
|
||||
|
||||
for (const request of approvalRequests) {
|
||||
const approved = mockAskUserApproval({
|
||||
toolName: request.toolCall.toolName,
|
||||
input: request.toolCall.input,
|
||||
});
|
||||
|
||||
approvalResponses.push({
|
||||
type: 'tool-approval-response',
|
||||
approvalId: request.approvalId,
|
||||
approved,
|
||||
reason: approved ? '用户批准了操作' : mockAskDenialReason(),
|
||||
});
|
||||
}
|
||||
|
||||
// 将审批响应添加到消息
|
||||
messages.push({ role: 'tool', content: approvalResponses });
|
||||
|
||||
// 第二步: 使用审批响应继续执行
|
||||
console.log('=== 第二步: 使用审批响应继续 ===\n');
|
||||
result = await generateText({
|
||||
model: zhipu(models['GLM-4.7']),
|
||||
tools: { time, timeZone },
|
||||
messages,
|
||||
});
|
||||
|
||||
// 添加响应消息到历史
|
||||
messages.push(...result.response.messages);
|
||||
|
||||
// 检查是否还有新的审批请求(可能有多轮审批)
|
||||
approvalRequests = [];
|
||||
for (const part of result.content) {
|
||||
if (part.type === 'tool-approval-request') {
|
||||
approvalRequests.push(part);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('=== 最终结果 ===');
|
||||
console.log('响应:', result.text);
|
||||
console.log('步骤数:', result.steps.length);
|
||||
}
|
||||
|
||||
runApprovalExample().catch(console.error);
|
||||
Reference in New Issue
Block a user