
为什么会有这篇文章
在小红书上经常会刷到"有没有这样的工具"的帖子,其实大部分都是相对比较简单的,于是我在想与其实现这些小工具,不如打造一个小工具的工厂,用户可以根据自己的想法生成各种小工具,岂不美哉。于是就有了百变AI助手 ,之所以叫百变AI助手是因为它可以变成任何你想要的样子,为了推广自己的APP也为了分享我在做这款APP的时候学到的一点点东西,所以有了这篇文章。
你能从这篇文章中得到什么
通过这篇文章,你将学会:
- AI助手核心功能:聊天对话、联网搜索、工具调用
- 大模型基础使用:API调用、提示词工程、流式输出
- Agent开发技能:工具调用机制、多轮对话管理
- MCP协议集成:标准化工具连接,快速扩展AI能力
最终目标:构建一个能够实时联网搜索、调用各种工具的智能AI助手。
前置知识
大语言模型(LLM)
大语言模型(Large Language Model,简称LLM)是参数量巨大的深度学习模型,特别是在自然语言处理领域表现突出的神经网络模型。这些模型通过在海量文本数据上进行训练,学会了理解和生成人类语言。
简单来说,它是一个听得懂人话,可以按照你的要求输出内容的服务。
提示词(Prompt)
是你和大模型对话时的"指令语言",简单来说就是你告诉AI要做什么以及怎么做的话。
"今天天气怎么样?" ← 这是提示词
"帮我写个邮件" ← 这也是提示词
"你好" ← 这同样是提示词
提示词分为系统提示词和用户的提示词。
系统提示词
作为我们的助手我们可以内置一部分提示词,在每一次请求大模型API接口的时候传递,这样它在回答的时候就会按照我们的要求实现。
假设你在打造一个翻译软件,我们明确AI负责翻译即可,而不是随意回答用户的问题,那么我们可以在请求时候要求AI只输出结果:
arduino
// ✅ 有系统提示词
system: "你是专业翻译助手,只输出翻译结果..."
messages: [
{ role: "user", content: "翻译成中文:Hello World" }
]
// 结果:你好世界
我们不仅可以在系统提示词中要求AI按照我们的需求输出,同时也可以告诉AI一些额外的信息,这样AI在回复信息的时候可以有更多额外的信息:
swift
// ✅ 有系统提示词
system: "你是百变AI助手 (Morphix AI),一款智能AI助手应用\n当前日期:2025-08-12"
用户提问:你好,今天的日期
// 结果:
2025-08-12
用户提示词
用户提问的问题。
实战:构建基础AI助手
到这里我们已经可以开始一个简单的案例了。
我们使用 openrouter 为案例,openrouter 提供了免费的模型供我们直接使用,openrouter 提供各种收费模型、免费模型可以直接使用,支持的大模型非常全,国内的国外的应有尽有。
为了我们能够快速开始,我这里提供了api key,可以直接使用无需注册:
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
大家别心疼,放心使用因为我已经限制了用量,只能使用免费的模型。
如果是线上的项目的apiKey,建议放采用密钥管理工具,避免密钥的泄露,在开发的过程中发现了一个非常好用的密钥管理工具,可以统一管理密钥,不需要在各种项目中复制来复制去,Infisical。
话不多说我们直接开始。
掌握基础的调用
php
import OpenAI from 'openai';
// 初始化openai 客户端
const openai = new OpenAI({
baseURL: 'https://openrouter.ai/api/v1',
apiKey: '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',
});
async function main() {
const completion = await openai.chat.completions.create({
model: 'https://openrouter.ai/api/v1',
// 用户发送的信息,是一个数组
messages: [
{
role: 'system',
// 系统提示词
content: '你是一个AI助手',
},
{
role: 'user',
content: 'who are you',
},
],
});
console.log(completion.choices[0].message);
}
main();
多轮对话
php
import OpenAI from 'openai';
// 初始化openai 客户端
const openai = new OpenAI({
baseURL: 'https://openrouter.ai/api/v1',
apiKey: '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',
});
async function main() {
const completion = await openai.chat.completions.create({
model: 'https://openrouter.ai/api/v1',
// 用户发送的信息,是一个数组
messages: [
{
role: 'system',
// 系统提示词
content: '你是一个AI助手',
},
{
role: 'user',
content: 'who are you',
},
{
role: 'assistant',
content: 'AI 的回答',
},
{
role: 'user',
content: '用户再一次的提问',
},
],
});
console.log(completion.choices[0].message);
}
main();
看到这里你会发现 messages 随着用户的对话会越来越长,大模型按照输入和输出计费的,越到后面成本越高,但是好在大模型支持输入缓存,claude 输入缓存的价格是正常输入的 1/10,大部分模型会默认开启缓存,无需额外控制,但是 claude 的模型需要注意,需要主动声明开启缓存。
流式输出
AI的回答如果等到全部结束后再显示结果通常情况下会非常慢,如果说是深度思考的模型就更慢了。
所以大部分都是采用的流式输出,开启流式输出也非常简单,只需要增加参数stream: true
即可,不过我们需要做一些额外的处理。
javascript
import OpenAI from 'openai';
// 初始化openai 客户端
const openai = new OpenAI({
baseURL: 'https://openrouter.ai/api/v1',
apiKey: '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',
});
const stream = await openai.chat.completions.create({
model: request.model || this.config.model,
messages: [
// 消息数组
],
// 开启流式输出
stream: true,
});
let fullContent = '';
for await (const chunk of stream) {
const content = chunk.choices[0]?.delta?.content || '';
if (content) {
fullContent += content;
console.log(content);
}
}
console.log(fullContent);
利用上述知识我们已经能够构建以常规的AI助手应用了。
GitHub - paul-leo/ai-assistant-demo
这是我使用上述几个简单的api开发的一个AI助手demo,非常适合新手学习。
直接访问地址:ai-assistant-demo-theta.vercel.app/
恭喜你!看到这里你已经掌握了如何开发一个简单的AI助手,但是我知道你的求知欲不容许你只停留在这里。
我们已经可以创建一个简单的AI助手了,但是到目前为止也只能问答,但是距离Agent还差一个工具调用,有了工具调用AI就可以自动搜索,或者在需要时自动操作一些内容,例如 vibeCode 中的读写文件。
工具调用
为了让AI更加强大,我们可以告诉AI我们有哪些工具,这些工具是干什么的,工具需要哪些参数。
AI在返回结果的时候会自动识别可用的工具,然后告诉我们调用哪个工具,调用的参数是什么。
大部分的AI都提供了专门声明工具调用的字段。
工具调用原理
- 定义工具:告诉AI有哪些工具可用
- AI识别:AI判断是否需要使用工具
- 执行工具:我们代码执行具体工具功能
- 结果注入:将执行结果告诉AI
- 继续对话:AI基于工具结果回答用户
实战:添加搜索功能
基于上面的案例,我们拿 manus 中的一个工具声明为例: system-prompts-and-models-of-ai-tools/Manus Agent Tools & Prompt/tools.json

定义工具
json
{
"type": "function",
"function": {
"name": "info_search_web",
"description": "使用搜索引擎搜索网页内容,用于获取最新信息或查找参考资料。",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "搜索关键词,使用Google搜索风格,建议3-5个关键词。"
},
"date_range": {
"type": "string",
"enum": ["all", "past_hour", "past_day", "past_week", "past_month", "past_year"],
"description": "(可选)搜索结果的时间范围筛选。可选值:all-全部,past_hour-过去1小时,past_day-过去1天,past_week-过去1周,past_month-过去1月,past_year-过去1年。"
}
},
"required": ["query"]
}
}
}
用户提问
最近有什么重要的AI技术突破吗?特别是大模型方面的。
工具识别
AI在收到用户的请求和工具的定义后,会自动识别需要调用哪些工具,AI是否能够准确的识别到应该调用的工具,对于AI的能力是一种考验,有时候AI也不知道什么时候应该调用工具,什么时候不应该,我们可以在系统提示词中告诉AI应该如何处理,工具之间的关联性,同时工具的说明也能起到告知AI如何使用工具的作用。
json
{
"type": "function",
"function": {
"name": "info_search_web",
"parameters": {
"query": "AI技术突破 大模型 2024 最新发展",
"date_range": "past_month"
}
}
}
工具执行
我们通过AI返回的信息,我们知道应该我们的工具调用的时候参数是什么,工具的执行过程AI不会帮我完成,还是需要通过我们在代码中预定义,在识别到AI返回应该调用的结果调用工具并返回结果,执行的结果我们后续会用到。
工具执行结果注入上下文
在工具执行完成后我们还是需要告诉AI工具的结果是什么,所以我们还是需要注入上下文然后再次向AI发起请求,AI就会知道更多额外的信息,同时知道下一步应该做什么。
结果注入上下文后的消息如下:
python
{
"model": "qwen-coder",
"messages": [
// 用户发起提问
{
"role": "user",
"content": "2024年AI发展趋势如何?"
},
{
"role": "assistant",
// content
"content": null,
// AI 识别的调用
"tool_calls": [
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "info_search_web",
"arguments": "{"query": "2024 AI发展趋势 人工智能", "date_range": "past_year"}"
}
}
]
},
// 工具执行的结果,注入上下文
{
"role": "tool",
"tool_call_id": "call_abc123",
"content": "{"success": true, "results": ["2024年生成式AI技术迎来重大突破,多模态大语言模型成为主流...", "AI在医疗、教育、金融等领域的应用加速普及..."], "sources": ["techcrunch.com", "arxiv.org", "openai.com"]}"
}
],
// 工具的定义,在每一次请求都应该传入
"tools": [
{
"type": "function",
"function": {
"name": "info_search_web",
"description": "使用搜索引擎搜索网页内容,用于获取最新信息或查找参考资料。",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "搜索关键词"},
"date_range": {"type": "string", "enum": ["all", "past_hour", "past_day", "past_week", "past_month", "past_year"]}
},
"required": ["query"]
}
}
}
]
}
我们回顾一下整个过程:
css
graph TD
A[用户提问] --> B[AI分析是否需要工具]
B -->|需要| C[AI返回工具调用指令]
B -->|不需要| F[直接回答]
C --> D[代码执行具体工具]
D --> E[将结果注入上下文]
E --> G[AI基于工具结果回答]
G --> H[返回最终答案]
我们利用上述能力给我们的AI增加一个搜索工具。
具体实现可查看:github.com/paul-leo/ai...
MCP (Model Context Protocol)
我们现在只有一个工具,但是随着系统的复杂性,工具越来越多,假设我们今天要让AI具备搜索的功能,明天我们又想给AI增加一个可以查询地图的功能,这些功能以前可能已经有了,但是它们是以API的形式提供服务,如果这样的话只能由开发者将这些功能添加到AI助手中,这个世界上有那么多的api,怎么可能集成得完呢。
于是聪明的工程师想到了一个办法,提供的服务的人将服务封装成一个工具包,给这些工具包增加一些描述,AI助手能够很方便的拿到一份工具包的说明书,里面有什么工具,如何使用等信息。
当用户在发起请求的时,AI助手就可以通过工具调用完成各种各样的任务。
MCP是什么
MCP 是由 Anthropic 于 2024年11月25日开源发布的一个开放标准协议,全称为"模型上下文协议"(Model Context Protocol)。它是一个用于连接AI助手与数据存储系统的统一标准,包括内容存储库、业务工具和开发环境等。 Introducing the Model Context Protocol
简单来说,MCP 就像是AI应用的"USB-C端口"。就像USB-C为设备连接各种外设提供了标准化方式一样,MCP为AI模型连接不同数据源和工具提供了标准化方式。
MCP 是一个协议,一头连接的是MCP Client(AI助手,Agent),一头连接的是服务(例如:AI Search MCP、Figma MCP)。
有这样一个标准协议,提供服务的人知道如何将其封装,MCP Client 知道能够去使用,对于用户来说大大的简化了扩展AI助手的方法,简直是一举三得。
对于我们开发AI助手来说我们可以内置一部分MCP工具,例如上面的搜索功能已经有了现成的mcp server。
为了演示MCP和工具调用的区别,我们增加一个AliMap的MCP,这样当我们询问地理位置或者导航时能够给我们实时的信息。
MCP如何使用
1. 连接MCP服务
这里需要用到 @modelcontextprotocol/sdk
,是mcp的官方sdk,用于客户端连接和编写mcp服务端。
javascript
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
async function getAMapMcpClient() {
const client = new Client({
name: 'name',
version: '1.0.0',
});
const sseTransport = new SSEClientTransport(
new URL('https://mcp.amap.com/sse?key=YOUR_AMAP_KEY_HERE')
);
await client.connect(sseTransport);
return client;
}
注意:由于高德地图域名跨域的限制,在浏览器端使用MCP会失败,我们可以将MCP的调用放在我们自己的服务器,或者使用Next.js这样的全栈开发框架来解决跨域问题。
通过获取工具声明我们就能知道这个工具包中有哪些工具,这些工具如何使用,随后我们要将这个信息告诉LLM。
2. 获取并转换工具格式
OpenAI 和 Claude 支持的格式有所区别,mcp 默认的工具声明的格式是 Claude 的,我们用的是 OpenAI 的sdk,所以我们需要先将工具的格式转换成 OpenAI sdk 工具的格式。
javascript
export async function getAMapTools() {
const client = await getAMapMcpClient();
const tools = await client.listTools();
if (!tools || !Array.isArray(tools)) {
return [];
}
const openaiTools = tools.map((tool: IMcpTool) =>
convertToolFormat(tool, 'amap')
);
console.log(openaiTools);
return openaiTools;
}
// 格式转换工具
function convertToolFormat(
tool: IMcpTool,
mcpName: string
): OpenAI.Chat.Completions.ChatCompletionTool {
return {
type: 'function',
function: {
// 这里我们在工具前面增加了mcp的名字,这样可以避免名字冲突,也可以用于执行阶段确认哪个mcp
name: `${mcpName}__${tool.name}`,
description: tool.description,
parameters: {
type: 'object',
properties: tool.inputSchema.properties,
required: tool.inputSchema.required,
},
},
};
}
3. 请求LLM
MCP通常会自己处理具体的调用,我们只需要知道要执行这个工具,执行完成后告诉我们结果即可。
4. 执行工具
当我们识别到LLM返回的结果中包含对应的mcp工具时我们需要处理工具调用。
typescript
export async function execMcpTool(toolName: string, arguments: any) {
const client = await getAMapMcpClient();
const result = await client.callTool({
name: toolName,
arguments: arguments,
});
return result;
}
5. 再次发起请求
将执行结果添加到上下文中然后自动发起下一次请求。
到这里我们已经可以学会了如何给助手添加 MCP Server,如果我们想扩展我们AI助手也可以自己实现一些mcp,不仅能够很方便将AI助手的功能拆分成不同的模块,在百变AI助手中产品经理和程序员角色就是通过不同的提示词和不同的mcp工具集来实现的。
最后看看我们的成果: 现在AI助手还不能获取地理位置,后续我们可以编写一个获取用户位置的mcp让AI自动返回和用户相关的信息。
如果你也对AI感兴趣可以关注我,我会后续继续更新,我们一起将上述的AI助手打造成一个更强大的工具,在后续打算集成自动化能够帮助我们自动完成一些繁杂的任务。
如果你对百变AI助手好奇不妨下载一个试试:
安卓 :Morphix AI - Shape the Infinite, Define Your Reality
iOS :百变AI助手-创建你的专属app
如果你对这个项目感兴趣,希望能够一起开发,随时欢迎!