Ai Agent实践

大模型是什么

大模型(LLM,Large Language Model)本质上是一个接受文本输入、返回文本输出的函数。从工程角度来说,你不需要理解它内部的神经网络结构,只需要知道:

  • 给它一段文字(Prompt),它会返回一段文字(Completion)
  • 它的能力来自在海量文本上的预训练,具备语言理解、推理、生成能力
  • 通过 HTTP API 调用,和调用任何后端接口没有本质区别
    大模型就像一个能理解自然语言的超级函数,你传入参数(问题、指令),它返回结果(回答、代码、分析)。

核心概念

Token

Token 是大模型处理文本的基本单位,既不是字符,也不是单词,而是介于两者之间的”词片”。

  • API 按 Token 计费,输入和输出分别计价
  • 模型有最大上下文长度限制,DeepSeek-chat 支持 64K tokens
  • Token 数量影响响应速度和成本

快速估算规则(不用装 tiktoken,这个精度够用):
● 英文:1 token ≈ 4 个字符
● 中文:1 个汉字 ≈ 1.5~2 tokens,按 0.6 token/字估算
● 代码:比文字消耗更多 token

上下文窗口

大模型每次调用都是无状态的——它不记得上一次对话。要实现多轮对话,需要每次都把完整的历史记录作为输入传给模型:

1
2
3
第一轮:[用户:"你好"] → 模型回复
第二轮:[用户:"你好", 助手:"你好!", 用户:"我叫张三"] → 模型回复
第三轮:[用户:"你好", 助手:"...", 用户:"我叫张三", 助手:"...", 用户:"你还记得我叫什么吗?"] → 模型回复

上下文窗口的限制带来了一个工程问题:对话太长超过限制时怎么处理?

温度参数

Temperature 控制模型输出的随机性,取值范围 0~2:

Temperature 特点 适用场景
0 确定性输出,每次结果几乎相同 代码生成、数据提取、分类
0.2~0.5 低随机,结果稳定但有轻微变化 客服问答、摘要
0.7 中等随机,有创造性但不失控 通用对话(默认值)
1.0~1.5 高随机,创意性强但可能跑偏 写作、头脑风暴

消息角色

OpenAI 兼容 API(DeepSeek、Qwen、Claude 等都支持)使用三种角色:

1
2
3
4
5
6
messages: [
{ role: 'system', content: '你是一位前端开发导师' }, // 系统提示,定义角色和行为
{ role: 'user', content: '什么是 Vue3 的响应式?' }, // 用户消息
{ role: 'assistant', content: '...' }, // 模型的上一条回复
{ role: 'user', content: '能举个例子吗?' }, // 继续对话
]

API调用

申请 API Key

以 DeepSeek 为例:

  1. 访问 platform.deepseek.com
  2. 注册登录后,进入「API Keys」页面
  3. 点击「创建 API Key」,复制保存(只显示一次)
    Key 格式类似:sk-xxxxxxxxxxxxxxxxxxxxxxxx
    安全注意事项:
  • 不要把 API Key 提交到 Git
  • 项目根目录创建 .env 文件存储
  • .gitignore 里加上 .env

Fetch调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import 'dotenv/config'

const API_URL = 'https://api.deepseek.com/v1/chat/completions'

async function chat(userMessage) {
const res = await fetch(API_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.DEEPSEEK_API_KEY}`,
},
body: JSON.stringify({
model: 'deepseek-chat',
messages: [{ role: 'user', content: userMessage }],
temperature: 0.7,
max_tokens: 1024,
}),
})

if (!res.ok) {
const err = await res.json()
throw new Error(`API 错误 ${res.status}: ${err.error?.message}`)
}

const data = await res.json()
const reply = data.choices[0].message.content
const usage = data.usage // { prompt_tokens, completion_tokens, total_tokens }

console.log('回复:', reply)
console.log('Token 用量:', usage)

return { reply, usage }
}

chat('用一句话解释什么是大模型').catch(console.error)

LangChain.js调用

实际项目里推荐用 LangChain.js,好处是屏蔽底层 HTTP 细节、统一接口、换模型只改配置不改代码,有链式调用、记忆、工具等生态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// langchain-basic.js
import 'dotenv/config'
import { ChatOpenAI } from '@langchain/openai'
import { HumanMessage, SystemMessage } from '@langchain/core/messages'

const model = new ChatOpenAI({
model: 'deepseek-chat',
apiKey: process.env.DEEPSEEK_API_KEY?.trim(),
configuration: { baseURL: 'https://api.deepseek.com/v1' },
temperature: 0.7,
})

const res = await model.invoke([
new SystemMessage('你是一位耐心的前端开发导师,擅长用类比解释技术概念。'),
new HumanMessage('用"盖房子"来类比解释什么是大模型应用开发'),
])

console.log(res.content)

流式输出(Streaming)

非流式调用要等模型生成完整响应后才返回,长文本场景下用户体验差。流式输出让内容像打字机一样逐字出现

SSE(text/event-stream)

chunked + plain text

● res.body.getReader() 获取 ReadableStream 的读取器
● TextDecoder 把 Uint8Array 转成字符串
● 用 buffer 拼接跨 chunk 的数据,防止 SSE 消息被截断
● 每条 SSE 消息格式:event: xxx\ndata: xxx\n\n

总结

● 大模型本质是一个 HTTP 接口,调用方式和普通后端接口没有本质区别
● Token 是计量单位,成本要从第一天就重视,不要等上线了才发现账单爆了
● 每次调用都是无状态的,多轮对话靠手动把历史记录拼进请求里
● Temperature 控制随机性,生产环境用低值保稳定,创意场景再调高
● 用 LangChain.js 封装调用,换模型只改 .env 里的配置

提示词工程

含糊的指令会得到含糊的结果。

Few-shot 提示

Few-shot 是在提示词里加几个”输入→输出”的配对例子,教会模型你想要的格式和风格。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import { HumanMessage, AIMessage, SystemMessage } from '@langchain/core/messages'

// 场景:从需求描述中提取组件规格说明,保证格式一致
async function extractComponentSpec(requirement) {
const messages = [
new SystemMessage('你是前端架构师,将需求转换为组件规格,只输出 JSON,不加解释。'),

// 示例 1
new HumanMessage('做一个带 loading 的搜索框,支持防抖'),
new AIMessage(JSON.stringify({
name: 'SearchInput',
props: [
{ name: 'placeholder', type: 'string', default: '请输入' },
{ name: 'debounce', type: 'number', default: 300 },
],
emits: ['search', 'clear'],
features: ['防抖处理', 'loading 状态', '清空按钮'],
}, null, 2)),

// 示例 2
new HumanMessage('做一个支持跳页的分页组件'),
new AIMessage(JSON.stringify({
name: 'Pagination',
props: [
{ name: 'total', type: 'number', required: true },
{ name: 'pageSize', type: 'number', default: 10 },
],
emits: ['change'],
features: ['页码跳转', '边界禁用'],
}, null, 2)),

// 真正的请求
new HumanMessage(requirement),
]

const res = await model.invoke(messages)
return JSON.parse(res.content)
}

const spec = await extractComponentSpec('做一个日期范围选择器,支持快捷选项:近7天、近30天、本月')
console.log(spec)

思维链(Chain of Thought)

结构化输出

1. Prompt 约束 + 容错解析

2. withStructuredOutput

LangChain.js & LangGraph

为什么需要LangChain.js & LangGraph

  • 每次都要手写消息格式、处理流式解析、管理对话历史
  • 多步骤的 AI 流程(先分析、再检索、再生成)要自己串联
  • 换个模型要改好几处代码
  • 复杂的条件分支逻辑写起来乱

LangChain.js 解决前两个问题,LangGraph 解决后两个。

1
2
3
4
5
原始写法:fetch → 解析 → 手动拼接历史 → 再 fetch → ...

LangChain.js:model.invoke() → 链式组合 → 自动管理历史

LangGraph:节点 + 边 + 条件路由 → 状态机驱动的 AI 流程

LangChain.js核心用法