diff --git a/bin/kimi-agent.js b/bin/kimi-agent.js new file mode 100644 index 0000000..8d9ed95 --- /dev/null +++ b/bin/kimi-agent.js @@ -0,0 +1,71 @@ +import Agent from '../lib/agent.js'; +import Kimi from '../lib/kimi.js'; +import { getAPIKey } from '../lib/apikey.js'; + +const apiKey = await getAPIKey(); + +const kimi = new Kimi({ + apiKey +}); + +const agent = new Agent(kimi, 'moonshot-v1-8k'); + +agent.registerTool('call', { + description: '根据人名判断性别,如果是男孩子,会返回男,如果是女孩子,会返回女', + parameters: { + name: { + type: 'string', + description: '姓名,人的名字' + } + }, + method: async function (args) { + return Math.random() > 0.5 ? '男' : '女'; + } +}); +agent.registerTool('searchAPI', { + description: '搜索 API,根据需求、功能描述,找到可以实现该功能的接口', + parameters: { + query: { + type: 'string', + description: '问题描述,比如发送短信该用哪个 API' + } + }, + method: async function (args) { + return 'CreateInstance'; + } +}); + +agent.registerTool('searchProduct', { + description: '根据意图确定产品是什么', + parameters: { + query: { + type: 'string', + description: '用户的意图,可能是在找一个功能,也可能是在找一个接口' + } + }, + method: async function (args) { + return `ecs`; + } +}); + +agent.registerTool('clicommand', { + description: '参数,生成 aliyun 命令行语句,产品名依赖其它工具来进行判断', + parameters: { + product: { + type: 'string', + description: '产品名,产品名需要通过其它工具来进行确认' + }, + api: { + type: 'string', + description: 'API 名字' + } + }, + method: async function (args) { + return `aliyun ${args.product} ${args.api}`; + } +}); + +const [prompt] = process.argv.slice(2); +await agent.chat(prompt); + +process.exit(0); \ No newline at end of file diff --git a/lib/agent.js b/lib/agent.js new file mode 100644 index 0000000..db4a0de --- /dev/null +++ b/lib/agent.js @@ -0,0 +1,127 @@ +import { readAsSSE } from 'httpx'; + +function getTools(toolMap) { + const tools = []; + for (const [key, tool] of toolMap) { + const properties = {}; + for (const [paramName, parameter] of Object.entries(tool.parameters)) { + properties[paramName] = parameter; + } + + tools.push({ + 'type': 'function', + function: { + name: key, + description: tool.description, + parameters: { + 'properties': properties, + 'type': 'object' + } + } + }); + } + + return tools; +} + +export default class Agent { + constructor(kimi, model) { + this.kimi = kimi; + this.model = model; + this.tools = new Map(); + } + + registerTool(define, method) { + this.tools.set(define, method); + } + + async chatWithTool(message) { + const response = await this.kimi.chat([ + { + role: 'user', + content: message + } + ], { + model: this.model, + tools: getTools(this.tools) + }); + + let toolCalls = []; + let content = ''; + for await (const event of readAsSSE(response)) { + if (event.data !== '[DONE]') { + const data = JSON.parse(event.data); + + const choice = data.choices[0]; + if (choice.finish_reason !== 'tool_calls') { + if (choice.delta.content) { + content += choice.delta.content; + } + if (choice.delta.tool_calls) { + const toolCall = choice.delta.tool_calls[0]; + const index = toolCall.index; + + if (!toolCalls[index]) { + toolCalls[index] = toolCall; + } else { + toolCalls[index].function.arguments += toolCall.function.arguments; + } + } + } + } + } + + return {content, toolCalls}; + } + + async chat(promt) { + const {content, toolCalls} = await this.chatWithTool(promt); + const toolResults = []; + for (const toolCall of toolCalls) { + const result = await this.runTool(toolCall); + toolResults.push({ + role: 'tool', + tool_call_id: toolCall.id, + content: result + }); + } + + const messages = [ + {role: 'user', content: promt}, + {role: 'assistant', content, tool_calls: toolCalls}, + ...toolResults + ]; + console.log(messages); + const response = await this.kimi.chat(messages, { + model: this.model, + }); + let lastEvent; + let message = ''; + for await (const event of readAsSSE(response)) { + if (event.data !== '[DONE]') { + const data = JSON.parse(event.data); + const choice = data.choices[0]; + if (choice.finish_reason === 'content_filter') { + console.log(event); + } else if (choice.finish_reason === 'stop') { + lastEvent = event; + } else if (!choice.finish_reason) { + const content = choice.delta.content; + if (content) { + process.stdout.write(content); + message += content; + } + } + } else { + console.log(); + } + } + } + + async runTool(call) { + const tool = this.tools.get(call.function.name); + const args = JSON.parse(call.function.arguments); + const result = await tool.method(args); + return result; + } +} \ No newline at end of file