参考文章: Claude Code From Scratch Harness 指标评测:Agent Harness 评测 Harness 记忆方案: Agent 记忆方案 Harness 扩展架构对比:Pi 与 DeepSeek Harness 的扩展架构
这是一个最典型的 agent loop 循环:
我来读取当前目录下的 package.json 文件。 //第一次 response
🛠️ 执行工具: read_file({"filePath":"package.json"}) //后续直接输出 tool_use的json 格式,以调用内置写好的工具,并把工具的结果给推到 messages 里面去
//进入下一个 loop,messages 里面已经有了工具返回的结果,它直接读就行,读完后输出:
已读取完成,当前目录下的 `package.json` 内容如下:
//最后不会调用工具,直接结束这个 loop
import dotenv from 'dotenv';
import OpenAI from 'openai';
import type { ChatCompletionMessageParam } from 'openai';
import { toolDefinitions ,executeTool} from './tools';
dotenv.config({ path: 'env.conf' });
const BASE_LOCAL_URL = process.env.BASE_LOCAL_URL
const LOCAL_MODEL = process.env.LOCAL_MODEL ?? 'llama3.2'
const BASE_REMOTE_URL = process.env.BASE_REMOTE_URL
const REMOTE_MODEL = process.env.REMOTE_MODEL ?? 'llama3.2'
const BASE_URL = process.env.USAGE === 'REMOTE' ? BASE_REMOTE_URL : BASE_LOCAL_URL
const MODEL = process.env.USAGE === 'REMOTE' ? REMOTE_MODEL : LOCAL_MODEL
const SYSTEM_PROMPTS = '你是一个可以调用工具的大模型'
const apiKey = process.env.API_KEY
const client = new OpenAI({
baseURL: BASE_URL,
apiKey: apiKey
});
//主函数
export default async function main(){
const messages: ChatCompletionMessageParam[] = [{ role: 'user', content: '你来帮我读一下当前目录下的 package.json' }]
const system = SYSTEM_PROMPTS
while(true){
const response =await client.chat.completions.create({
model:MODEL,
messages,
stream:true,
tools: toolDefinitions,
})
let fullContent = '';
const toolCalls: any[] = [];
for await (const chunk of response) {
const delta = chunk.choices[0]?.delta;
// 1. 打印模型的思考文本输出
if (delta?.content) {
process.stdout.write(delta.content);
fullContent += delta.content;
}
// 2. 收集流式的 Tool Call 碎片
if (delta?.tool_calls) {
for (const tc of delta.tool_calls) {
if (!toolCalls[tc.index]) {
toolCalls[tc.index] = {
id: tc.id,
type: 'function',
function: { name: tc.function?.name || '', arguments: '' }
};
}
if (tc.function?.arguments) {
toolCalls[tc.index].function.arguments += tc.function.arguments;
}
}
}
}
// 3. 如果模型没有发起 Tool Call,说明工作结束,跳出循环
if (toolCalls.length === 0) {
messages.push({ role: 'assistant', content: fullContent });
console.log('\n\n✅ Agent 完成任务!');
break;
}
// 把助手消息(含 tool_calls)完整写回,tool 结果必须紧随其后
const normalizedCalls = toolCalls.map((tc, index) => ({
id: tc.id ?? `call_${index}`,
type: 'function' as const,
function: { name: tc.function.name, arguments: tc.function.arguments || '{}' },
}));
messages.push({
role: 'assistant',
content: fullContent || null,
tool_calls: normalizedCalls,
});
// 4. 执行所有被调用的工具,并将结果写回 messages (role: 'tool')
for (const toolCall of normalizedCalls) {
const functionName = toolCall.function.name;
const functionArgs = JSON.parse(toolCall.function.arguments);
console.log(`\n🛠️ 执行工具: ${functionName}(${JSON.stringify(functionArgs)})`);
const result = await executeTool(functionName, functionArgs);
messages.push({
role: 'tool',
tool_call_id: toolCall.id,
content: result,
});
}
}
}上面只有 read_file这一个工具,我们仅需补充 tools 就能够让 Agent 做到更多事情:
❯ bun run index.ts
◇ injected env (6) from env.conf
🛠️ 执行工具: read_file({"filePath":"test.txt"})
🛠️ 执行工具: list_files({"path":"."})
🛠️ 执行工具: read_file({"filePath":"README.md"})
🛠️ 执行工具: read_file({"filePath":"CLAUDE.md"})
🛠️ 执行工具: edit_file({"filePath":"test.txt","oldString":"1. 这是第一行\n2. ","newString":"1. 这是第一行\n2. 这是第二行"})
🛠️ 执行工具: read_file({"filePath":"test.txt"})
🛠️ 执行工具: run_shell({"command":"xxd test.txt | tail -3"})
🛠️ 执行工具: run_shell({"command":"printf '\\n' >> test.txt && xxd test.txt | tail -2"})
已读取 `test.txt`,它只有一行内容、第二行是空的(`2. ` 后面没有任何文字)。
我按第一行的句式对称补充了第二行:
1. 这是第一行
2. 这是第二行
另外发现原文件结尾缺少换行符(末行没有 `\n`),已顺手补上,现在文件以换行结尾,符合 POSIX 文本文件惯例。
如果你本意不是补一句占位文本,而是想让第二行承载具体内容(比如接续某个列表、写代码片段、或英文版本 `2. This is the second line`),告诉我具体诉求,我可以立刻改写。
✅ Agent 完成任务!