预计时间
1 周
学习目标
- 理解 Token 和 Context Window
- 掌握 Function Calling
- 学会 Structured Output
- 会用关键参数调优
一、Token
1.1 什么是 Token?
LLM 不认"字",只认 Token。
text
"你好世界" → ["你", "好", "世界"] (3 tokens)
"Hello world" → ["Hello", " world"] (2 tokens)
"人工智能" → ["人工智能"] (1 token,常见词)
"tiktoken" → ["tik", "token"] (2 tokens,罕见词)Token 很重要因为:
text
1. 按 Token 计费(不是按字数)
中文 1 字 ≈ 1-2 Token 英文 1 词 ≈ 1-2 Token
2. Context Window 按 Token 计算
一个模型支持 128K Token → 可以一次处理约 10 万字中文
3. API 速率限制按 Token
每分钟 Token 限额 (TPM) 决定了你的并发能力1.2 计算 Token
javascript
// npm install tiktoken
import { encoding_for_model } from 'tiktoken';
const enc = encoding_for_model('gpt-4o');
const tokens = enc.encode('你好世界');
console.log(tokens.length); // → token 数量
console.log(tokens); // → [57668, 53901, 2354...]
// 估算 Token(不需要调 API 就提前知道)
// 规则:中文 1 字 ≈ 1.5 Token,英文 1 词 ≈ 1.3 Token
function estimateTokens(text) {
const chineseChars = (text.match(/[\u4e00-\u9fff]/g) || []).length;
const englishWords = (text.match(/[a-zA-Z]+/g) || []).length;
return Math.ceil(chineseChars * 1.5 + englishWords * 1.3);
}二、Context Window
2.1 模型能"看到"多少?
text
Context Window = 模型一次能处理的 Token 总数
GPT-4o: 128K Token (~10 万汉字)
Claude 3.5: 200K Token (~15 万汉字)
Gemini 1.5: 1M Token (~75 万汉字)
如果对话历史 + 用户输入 > Context Window:
→ 模型"忘记"最早的内容
→ 回答质量断崖下降2.2 上下文管理策略
text
策略 1:滑动窗口
保留最近 N 条消息 + 当前问题
简单粗暴,丢失早期上下文
策略 2:摘要压缩
对较旧的对话自动生成摘要
System: "之前你们讨论了 X,结论是 Y"
保留关键信息,丢弃细节
策略 3:向量检索(RAG 的做法)
每次对话都存 Vector DB
新问题来了 → 先检索相关历史 → 拼到 context
最精确,但多一步检索2.3 实际计算
javascript
// 在发送 API 请求前检查 Token 数
function buildMessages(history, newQuery, maxTokens = 100000) {
let messages = [{ role: 'system', content: systemPrompt }];
let totalTokens = countTokens(systemPrompt);
// 从最近的消息往前加
for (let i = history.length - 1; i >= 0; i--) {
const msgTokens = countTokens(history[i].content);
if (totalTokens + msgTokens > maxTokens) break;
messages.unshift(history[i]);
totalTokens += msgTokens;
}
messages.push({ role: 'user', content: newQuery });
return messages;
}三、Function Calling
这是 LLM 从"聊天"到"干活"的关键能力。
3.1 流程
text
1. 你定义 function
↓
2. 用户提问 → 附带所有 function 定义给 LLM
↓
3. LLM 决定:直接回答 还是 调用某个 function
↓
4. 如果选择 function → 返回 { name, arguments }
↓
5. 你执行 function → 拿到结果
↓
6. 把结果返回 LLM → LLM 生成最终回答3.2 代码示例
javascript
// 定义可调用的工具
const tools = [{
type: 'function',
function: {
name: 'search_knowledge_base',
description: '搜索企业知识库,返回相关文档片段',
parameters: {
type: 'object',
properties: {
query: { type: 'string', description: '搜索关键词' },
top_k: { type: 'number', description: '返回数量,默认 5' },
},
required: ['query'],
},
},
}, {
type: 'function',
function: {
name: 'get_document_content',
description: '获取指定文档的完整内容',
parameters: {
type: 'object',
properties: {
doc_id: { type: 'string', description: '文档 ID' },
},
required: ['doc_id'],
},
},
}];
// 调用 LLM
const response = await openai.chat.completions.create({
model: 'gpt-4o',
messages: [{ role: 'user', content: '公司请假流程是什么?' }],
tools,
});
const msg = response.choices[0].message;
if (msg.tool_calls) {
// LLM 想调用工具
for (const toolCall of msg.tool_calls) {
const fn = toolCall.function;
console.log(`LLM 想调: ${fn.name}(${fn.arguments})`);
// 执行
const result = await execute(fn.name, JSON.parse(fn.arguments));
// 把结果追加到对话历史
messages.push({
role: 'tool',
tool_call_id: toolCall.id,
content: JSON.stringify(result),
});
}
// 再次调 LLM,让它在工具结果基础上生成回答
const finalResponse = await openai.chat.completions.create({
model: 'gpt-4o',
messages,
});
console.log(finalResponse.choices[0].message.content);
}javascript
// 执行工具的实际逻辑
async function execute(name, args) {
switch (name) {
case 'search_knowledge_base':
return await vectorDB.search(args.query, args.top_k || 5);
case 'get_document_content':
return await db.documents.findById(args.doc_id);
}
}四、Structured Output
4.1 为什么要结构化?
text
场景:分析用户反馈
❌ 自然语言输出:
"这个用户情绪比较积极,总的来说挺满意的,不需要做什么"
→ 你没法写代码判断是 positive 还是 negative
✅ Structured Output:
```json
{
"sentiment": "positive",
"confidence": 0.92,
"summary": "用户对产品很满意",
"action_items": []
}
→ 代码可以直接 `if (result.action_items.length > 0) { ... }`4.2 JSON Mode
javascript
const response = await openai.chat.completions.create({
model: 'gpt-4o',
messages: [
{ role: 'system', content: '你是一个情感分析助手。返回 JSON 格式。' },
{ role: 'user', content: '分析这条反馈:产品很好用,我很满意!' },
],
response_format: { type: 'json_object' }, // 👈 强制 JSON
});4.3 JSON Mode vs Function Calling
| JSON Mode | Function Calling | |
|---|---|---|
| 用途 | 让 LLM 返回 JSON | 让 LLM 选择 + 调用工具 |
| Schema | 靠 prompt 描述 | 靠 parameters JSON Schema |
| 确定性 | 较低(可能不严格符合 schema) | 较高(有 schema 约束) |
| 适用 | 简单的结构化提取 | 复杂的工具调用流程 |
五、关键参数
javascript
const response = await openai.chat.completions.create({
model: 'gpt-4o',
messages,
temperature: 0.7, // 0-2,越高越"有创意"
top_p: 0.9, // nucleus sampling,累积概率阈值
max_tokens: 2000, // 最多返回多少 Token
frequency_penalty: 0, // -2 到 2,避免重复
presence_penalty: 0, // -2 到 2,鼓励谈新话题
});| 参数 | 低值 | 高值 | 什么时候设低/高? |
|---|---|---|---|
| temperature | 0(很确定、重复) | 2(随机、创意) | 代码/数据提取 → 0;创意写作 → 0.8+ |
| top_p | 0.1(只选最好的) | 1(考虑所有) | 精确回答 → 0.1;头脑风暴 → 0.9 |
| max_tokens | - | - | 按需要设置,防止意外长回复 |
实践
- 用 tiktoken 算一下你最喜欢的 10 条推文各有多少 Token
- 写一个 Function Calling demo:定义一个
get_weather(city)→ LLM 自动调 - 用 JSON Mode 让 LLM 从一段文本中提取姓名、电话、地址