AI 自动编程实践感想
近期我意识到几个范式的变化,决定将大部分的实践精力都放在 AI 自动编程方面,一方面几个 AI 编程工具都实现了AI 代理模式,对 AI 代理和大模型的能力了解有帮助;另一个方面可以实践编程语言和软件工程。
在使用 AI 自动编程工具时,一个重要的技能是如何构造提示词,如果把握提示词的细节和精度。
我在实现一个 MCP Client 时,有了一些粗浅的心得。项目的背景是我用 typescript 语言调用 MCP 的 SDK 来实现一个 MCP 客户端,MCP Server 我选择了 sqlite Server,提供了多种工具,包括:
- read_query
- write_query
- create_table
- list_tables
- describe_table
- append_insight
在实现一些任务时,比如要查询某个表的数据,需要依次调用list_tables
describe_table
和read_query
,和聊天工具的一问一答式的的交互不一样,模型端收到任务后,需要一直迭代,直到任务完成。类似Claude
客户端:
为了实现这个功能,我经历了三个版本。在这里,我的测试任务是:“先查询有哪些表,看下表的结构,然后查询表的前三条数据”
程序的第一个版本,只调用了两次模型,第一次是工具调用;第二次是普通调用。按理来说,应该不满足需求,但是客户端有模有样的输出所有信息,我很疑惑,把控制台的输出和代码提交给 Cursor
,让它诊断下问题,结果好几轮都没有定位到问题,最后我比照了下数据库里的真实数据,才知道原来是模型的幻觉输出。
程序的第二个版本,我直接输出我的需求,让 Cursor
帮我构建程序,它先是将字符串是否包含“任务完成”的字样来判断,任务是否结束,但模型的输出不会那么严格遵循“任务完成”这样的输出,我让 Cursor
改为结构性的输出,经过几轮的修改,如下:
const openaiResponse = await this.openai.chat.completions.create({
model: 'anthropic/claude-3-sonnet',
messages: [
{
role: 'system',
content: `你是一个数据库助手。请基于用户的要求和之前的结果,决定下一步需要执行什么操作。
如果需要执行工具,请先用一句话说明你要做什么,然后再使用工具。
如果不需要执行工具,每次响应都必须包含 JSON 格式的状态信息:
{
"status": "continue" | "complete",
"reason": "说明原因..."
"result": "返回结果..."
}
示例:
1. 执行工具:先说明意图,再使用工具
2. 不执行工具:返回 {"status": "complete", "reason": "所有查询已完成","result": "结果为..."}`
},
...messages
],
temperature: 0.7,
tools: availableTools.map(tool => ({
type: 'function',
function: {
name: tool.name,
description: tool.description,
parameters: tool.input_schema
}
}))
});
第二个版本的输出还是有点问题,因为大模型并不是每次都遵循输出 JSON 格式的状态信息,需要对无法解析出 json 的情况做异常处理,也需要防止无限制的迭代下去,有个兜底的代码。当模型没有按照规范输出 JSON 格式,就会导致模型多交互一次。
// 处理状态响应
else if (assistantMessage.content) {
try {
// 尝试解析状态 JSON
const statusJson = JSON.parse(assistantMessage.content);
if (statusJson.status === 'complete') {
console.log('\n✅ 完成:', statusJson.result);
console.log('您还有哪些需求?');
lastResult = statusJson.result;
isTaskComplete = true;
statusChecked = true;
// 如果完成了,直接返回结果
return lastResult;
} else if (statusJson.status === 'continue') {
console.log('\n⏳ 继续:', statusJson.reason);
statusChecked = true;
}
} catch (e) {
// 如果不是 JSON,当作普通响应处理
if (assistantMessage.content.trim()) {
console.log('💭 AI 说:', assistantMessage.content);
lastResult = assistantMessage.content;
}
}
if (assistantMessage.content.trim()) {
messages.push({
role: "assistant",
content: assistantMessage.content
});
}
}
// 如果没有检查到状态信息,可能需要提醒模型
if (!statusChecked) {
messages.push({
role: "user",
content: "请明确指出当前任务的状态(complete/continue)"
});
}
// 防止无限循环
if (stepCount > 10) {
console.log('\n⚠️ 步骤数超过限制,强制结束');
lastResult = '由于步骤数超过限制,执行被强制结束';
isTaskComplete = true;
return lastResult;
}
第三个版本,在第二个版本迭代了多轮之后,我意识到可能走到死胡同了,突然灵机一动,大模型输出内容如果不包括工具调用,不就是意味着可以结束循环了吗?所以我让Cursor
将代码的逻辑改为:判断模型的输出是否包含工具调用,作为继续迭代的判断条件。如下:
const openaiResponse = await this.openai.chat.completions.create({
model: 'deepseek/deepseek-chat',
messages: [
{
role: 'system',
content: `你是一个数据库助手。请基于用户的要求和之前的结果,决定下一步需要执行什么操作。
如果需要执行工具,请先用一句话说明你要做什么,然后再使用工具。
如果不需要执行工具,直接返回最终结果。`
},
...messages
],
temperature: 0.7,
tools: availableTools.map(tool => ({
type: 'function',
function: {
name: tool.name,
description: tool.description,
parameters: tool.input_schema
}
}))
});
<代码省略...>
// 如果是普通文本响应,直接返回结果
else if (assistantMessage.content) {
console.log('\n✅ AI 助手:', assistantMessage.content);
return assistantMessage.content;
}
由于我的编程水平不高,第三个版本,可能不是最好的实现;第二种实现,我发现如果有专门的提取 JSON 字符串的程序来辅助也应该有不错的效果。
这里主要想表达的观点是,在使用自动编程中,当前还是需要有逻辑思维,基本的编程概念、产品设计能力和架构能力,可以控制模型生成代码的方向和思路;如果在一个错误的方法中让大模型反复迭代,会一直增加代码的复杂程度,同时也无法做到预期的效果。
接下来,我可能在实践过程中要一点点积累前端编程的名词概念、思维框架还有后端的模式设计之类,不断积累构造提示词的能力。
留下评论