Skip to content

预计时间

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 ModeFunction 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,鼓励谈新话题
});
参数低值高值什么时候设低/高?
temperature0(很确定、重复)2(随机、创意)代码/数据提取 → 0;创意写作 → 0.8+
top_p0.1(只选最好的)1(考虑所有)精确回答 → 0.1;头脑风暴 → 0.9
max_tokens--按需要设置,防止意外长回复

实践

  1. 用 tiktoken 算一下你最喜欢的 10 条推文各有多少 Token
  2. 写一个 Function Calling demo:定义一个 get_weather(city) → LLM 自动调
  3. 用 JSON Mode 让 LLM 从一段文本中提取姓名、电话、地址