Skip to content

实操 01 LLM API 实操入门:从第一条请求到生产级调用

所属:AI 知识图谱实操系列(practice/)。实操系列与正文分离:正文(00~11)写不变的原理与判断,实操篇给可运行代码,可随时随版本重写。

速览卡

内容
一句话定位把"知道大模型是什么"变成"能在代码里稳定调用它",补上正文与动手之间的最后一公里
核心内容首次调用、核心参数、流式输出、多轮会话、结构化输出、Function Calling、Token 计量与成本估算、错误重试
前置知识00 基础概念(Token/温度/上下文窗口)、任意一门编程语言基础
读完能做到独立写一个:流式输出的多轮对话工具,带 JSON 结构化输出与工具调用,并能估算月成本
配套正文参数选择原理见 00;提示词写法见 02;工具调用设计原则见 03;成本监控体系见 05

版本约定:本篇以 OpenAI Python SDK 为载体(国内外绝大多数服务商都兼容其 API 格式,换厂商通常只改 base_urlapi_key)。具体模型名与价格一律标 [易变],以各厂商官方文档为准。


一、环境准备与首次调用

1.1 安装与鉴权

bash
# Python 3.10+ 建议用虚拟环境
python -m venv .venv
source .venv/bin/activate        # Windows: .venv\Scripts\activate
pip install openai

密钥永远不进代码库,用环境变量:

bash
export LLM_API_KEY="sk-..."          # 你的密钥
export LLM_BASE_URL="https://api.openai.com/v1"   # 换厂商只改这一行
python
# hello_llm.py —— 你的第一个大模型调用
import os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["LLM_API_KEY"],
    base_url=os.environ.get("LLM_BASE_URL"),  # None 时用 SDK 默认值
)

resp = client.chat.completions.create(
    model="gpt-4o-mini",           # [易变] 以厂商当前文档为准
    messages=[
        {"role": "system", "content": "你是一个严谨的技术助手。"},
        {"role": "user", "content": "用一句话解释什么是 Token。"},
    ],
)
print(resp.choices[0].message.content)

三种角色,一次说清(这是所有对话 API 的通用结构,三五年内不会变):

角色谁在说作用
system应用开发者设定身份、规则、边界。用户看不到,权重最高
user终端用户用户输入
assistant模型模型回复。多轮对话时要把历史轮次传回去(见第三节)

1.2 一次调用发生了什么

你的代码 ──HTTP POST /chat/completions──▶ 服务商服务器
           {model, messages, temperature...}
                                              │ 模型逐 Token 生成
你的代码 ◀──完整 JSON 响应────────────────── ┘

响应里除了正文,还有 usage(本次消耗的 Token 数)——每次都看一眼,它是成本核算的原始数据(第五节)。


二、核心参数:只需要真正掌握这四个

参数作用什么时候调不调的默认行为
temperature随机性。0≈每次都一样,1+≈更发散抽取/分类/代码 → 0~0.3;文案/头脑风暴 → 0.7~1.0多数服务默认 1.0,做工程必须显式设置
max_tokens生成上限(防止失控账单)生产环境永远设置不设可能生成到上限
stop停止序列结构化场景防越界
top_p核采样的截断与 temperature 二选一调,不要同时调1.0

原理与更多采样细节见 00 篇 · 温度与 CoT


三、流式输出与多轮会话

3.1 流式(打字机效果 + 首字延迟优化)

python
stream = client.chat.completions.create(
    model="gpt-4o-mini",                      # [易变]
    messages=[{"role": "user", "content": "解释一下 RAG 的九个环节"}],
    stream=True,                              # 关键开关
)
for chunk in stream:
    delta = chunk.choices[0].delta.content
    if delta:                                  # 末尾可能为 None
        print(delta, end="", flush=True)

流式不减少总 Token、不省成本,但把首字延迟从"整段生成完"降到"第一个 Token 生成完"——对交互体验是质变。延迟优化全景见 05 篇

3.2 多轮会话:模型没有记忆,记忆就是你把历史传回去

最常见的初学者误区是以为服务端记住了上下文。每一轮你都要把完整历史传过去

python
class Conversation:
    def __init__(self, system_prompt: str):
        self.messages = [{"role": "system", "content": system_prompt}]

    def ask(self, client, user_input: str) -> str:
        self.messages.append({"role": "user", "content": user_input})
        resp = client.chat.completions.create(
            model="gpt-4o-mini",              # [易变]
            messages=self.messages,
            temperature=0.3,
        )
        answer = resp.choices[0].message.content
        self.messages.append({"role": "assistant", "content": answer})
        return answer

conv = Conversation("你是一个严谨的技术助手。")
conv.ask(client, "Flutter 和 React Native 的渲染差异?")
conv.ask(client, "那性能问题一般出在哪?")   # 模型能理解"那"指什么,因为历史在 messages 里

上下文会越来越长,成本线性上涨。工程上必须做截断/压缩策略:

  • 滑动窗口:只保留最近 N 轮 + system
  • 摘要压缩:把更早的历史让模型自己总结成一段
  • 硬上限:超过 max context 直接报错,不要等到服务端报

上下文工程的方法论见 02 篇


四、结构化输出:让模型输出能被程序解析

对话式输出是给人看的;程序需要 JSON。三档方案,可靠性递增:

4.1 档位一:提示词约束 + 代码校验(最通用,任何模型可用)

python
import json
from pydantic import BaseModel, ValidationError

class BookReview(BaseModel):
    title: str
    rating: int        # 1~5
    tags: list[str]

SYS = """从书评中抽取信息,严格输出 JSON,schema:
{"title": string, "rating": 1-5的整数, "tags": string[]}
不要输出任何 JSON 以外的内容。"""

def extract(review_text: str, client) -> BookReview:
    resp = client.chat.completions.create(
        model="gpt-4o-mini",                      # [易变]
        messages=[
            {"role": "system", "content": SYS},
            {"role": "user", "content": review_text},
        ],
        temperature=0,                            # 抽取任务要确定性
    )
    for _ in range(3):                            # 校验失败自动重试
        try:
            return BookReview.model_validate_json(resp.choices[0].message.content)
        except ValidationError:
            resp = client.chat.completions.create(
                model="gpt-4o-mini",
                messages=[
                    {"role": "system", "content": SYS},
                    {"role": "user", "content": review_text},
                    {"role": "assistant", "content": resp.choices[0].message.content},
                    {"role": "user", "content": "上面的输出不符合 schema,重新严格按 schema 输出 JSON。"},
                ],
                temperature=0,
            )
    raise RuntimeError("连续 3 次输出不合法,转人工处理")

要点temperature=0 + 显式 schema + "不要输出多余内容" + pydantic 校验 + 失败带错误上下文重试。这套骨架可以直接搬去生产。

4.2 档位二:API 级 JSON Mode / Schema 约束

主流服务普遍提供开关(OpenAI 系为 response_format),让服务端在解码层约束 JSON 合法性,比纯提示词可靠得多。注意两点:

  • json_object 模式仍需要你在提示词里给出字段说明
  • json_schema(严格模式)最可靠,但支持的字段类型有限、部分模型不支持 → 查当期文档 [易变]

4.3 怎么选

场景方案
内部工具、允许偶发重试档位一(最通用)
面向用户、失败影响体验档位二 + 档位一兜底重试
抽取结果直接入库档位二严格模式 + pydantic 双保险

五、Function Calling:让模型能"动手"

模型不会执行任何东西——它只会输出"我想调用 X 工具、参数是 Y",执行永远在你的代码里。这就是 03 篇工具调用 5 原则的代码化:

python
import json

tools = [{
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "查询指定城市当前天气",   # description 写清楚,模型靠它决定何时调用
        "parameters": {
            "type": "object",
            "properties": {
                "city":  {"type": "string", "description": "城市名,如 北京"},
                "unit":  {"type": "string", "enum": ["celsius", "fahrenheit"]},
            },
            "required": ["city"],
        },
    },
}]

def get_weather(city: str, unit: str = "celsius") -> dict:
    # 你的真实实现:查数据库 / 调第三方 API……
    return {"city": city, "temp_c": 26, "condition": "多云"}

messages = [{"role": "user", "content": "北京今天适合跑步吗?"}]
resp = client.chat.completions.create(
    model="gpt-4o-mini", messages=messages, tools=tools, temperature=0,
)

# 第一步:模型决定调用工具(也可能不调,要判空)
msg = resp.choices[0].message
if msg.tool_calls:
    messages.append(msg)                       # 助手的"调用意图"也要进历史
    for tc in msg.tool_calls:
        args = json.loads(tc.function.arguments)
        result = get_weather(**args)
        messages.append({
            "role": "tool",
            "tool_call_id": tc.id,
            "content": json.dumps(result, ensure_ascii=False),
        })
    # 第二步:把工具结果交回模型,生成最终自然语言回答
    final = client.chat.completions.create(
        model="gpt-4o-mini", messages=messages, tools=tools, temperature=0,
    )
    print(final.choices[0].message.content)
else:
    print(msg.content)                         # 模型认为不需要工具,直接回答

两个必然踩的坑

  1. 工具描述写得太含糊 → 模型该调不调、不该调乱调。description 要说清"什么时候用我、参数什么格式"。
  2. 忘记把 tool 角色的结果传回第二轮 → 模型瞎编工具结果。工具执行结果必须回填 messages

多工具、循环执行、步数上限的完整 Agent 循环,见实操系列后续 Agent 篇(规划中)。


六、Token 计量与成本估算

6.1 先会数 Token

bash
pip install tiktoken
python
import tiktoken
enc = tiktoken.get_encoding("cl100k_base")     # GPT 系;国产模型用厂商自带计数器更准
n = len(enc.encode("解释一下 RAG 的九个环节"))
print(n)                                        # 输入 Token 数(估算)

粗算规则(中文):1 个汉字 ≈ 1~2 个 Token。精确值以响应里的 usage 为准。

6.2 会算钱(方法不变,单价查官方 [易变]

单次成本 = 输入 Token 数 × 输入单价 + 输出 Token 数 × 输出单价

关键事实:输出单价通常是输入的 3~4 倍 → 控制生成长度比精简提示词更省钱。

月成本估算模板(把数填进去就能用):

日请求量 R × 平均输入 Token I × 输入单价 Pi
+ 日请求量 R × 平均输出 Token O × 输出单价 Po
= 日成本;× 30 = 月成本

示例:R=10,000,I=2,000,O=500,输入 $0.15/百万,输出 $0.60/百万([易变] 仅示意量级):

10,000 × 2,000 × 0.15/10⁶ + 10,000 × 500 × 0.60/10⁶
= 3.0 + 3.0 = $6/天 ≈ $180/月

6.3 生产环境必须做的三件事

  1. 每次调用记录 usage(时间、模型、输入/输出 Token、业务场景标签)→ 这是 05 篇成本监控的数据底座
  2. 设预算熔断:按天/按用户累计,超阈值降级或拒绝
  3. 缓存重复请求:相同输入直接返回缓存结果,重复率高的场景能省 30%+

降本手段全景见 05 篇(按性价比排序)。


七、错误处理与重试:生产与 Demo 的分水岭

四类必然遇到的错误与对策:

错误典型原因对策
401 / 403密钥错、欠费、无权限不重试,立即告警
429限流指数退避重试(见下)
5xx / 超时服务端抖动重试 1~2 次,加超时上限
400 上下文超限历史太长截断/压缩历史后重试一次
python
import time

def call_with_retry(fn, max_retries=3, base_delay=1.0):
    """指数退避重试:1s → 2s → 4s。只对可重试错误生效。"""
    for attempt in range(max_retries):
        try:
            return fn()
        except Exception as e:
            status = getattr(e, "status_code", None)
            if status in (401, 403):
                raise                              # 认证类错误不重试
            if attempt == max_retries - 1:
                raise
            delay = base_delay * (2 ** attempt)
            time.sleep(delay)

生产环境直接用 SDK 自带的重试参数 + 超时:

python
client = OpenAI(api_key=..., max_retries=3, timeout=30.0)

更完整的高可用设计(多模型降级、网关、熔断)见 05 篇网关与稳定性


八、动手任务

10 篇路线 B 阶段一的要求,本篇读完后完成:

  • [ ] 任务 1:跑通 hello_llm.py,打印 usage,手动算一次这次调用花了多少钱
  • [ ] 任务 2:把 Conversation 类加上"最多保留最近 6 轮"的滑动窗口,并打印每轮结束后的累计 Token 数
  • [ ] 任务 3:用档位一的结构化输出,写一个"把任意商品描述抽成 {名称, 价格区间, 三个卖点}"的函数,故意喂 10 条脏数据,统计校验重试的触发率
  • [ ] 任务 4(进阶):给天气工具再加一个 get_time 工具,构造一个"需要连续调用两个工具"的问题(如"现在适合跑步吗"需要时间+天气),体验多轮工具调用
  • [ ] 自检:能不看代码说出——三种角色分别是什么?为什么多轮要传完整历史?工具调用的结果怎么回到模型手里?

延伸方向