Back to blog

LLM 是怎么被程序调用的

从最小 API 调用、系统提示词、上下文、参数、结构化输出和工具调用,把聊天框背后的程序链路拆开。
2026年5月7日
9 分钟阅读

第一篇文章里,我们已经把 LLM 的基本概念摆了一遍:token、prompt、上下文窗口、工具调用,大概都知道是什么了。

但那些概念放到真实产品里,其实还差一层。

你在聊天框里打一句话:

帮我解释一下什么是 LLM API。

屏幕上很快冒出一段回答。这个体验太顺了,顺到容易让人以为:用户的话就是直接丢给模型,模型再直接把答案吐回来。

程序里不是这么看的。

程序里看到的是一次请求。它要决定:这句话要不要加规则?要不要带上前面的聊天记录?要不要限制输出长度?模型返回的是直接展示,还是先转成 JSON?如果模型说要查订单,谁真的去查?

这篇不讲 GPU,也不讲张量计算。

我只想把聊天框背后那条使用链路拆开:一个用户问题,是怎么一步步变成一次 LLM API 调用,又怎么回到用户面前的。


01 |最小 API 调用

先从最小的形态看。

这里只有一个用户提示词,没有系统规则,没有历史对话,也没有工具。

import OpenAI from "openai";

const client = new OpenAI();

const response = await client.responses.create({
  model: "gpt-5.2",
  input: "用一句话解释什么是 LLM API。",
});

console.log(response.output_text);

这段代码做了三件事。

创建一个 client,指定一个 model,把 input 发出去。

response.output_text 是 SDK 帮你取出来的文本结果。它适合最简单的场景:问一句,回一句,直接展示。

这里的 SDK 可以先理解成官方给开发者准备好的调用工具。它不是模型,只是把 HTTP 请求、鉴权、参数格式、返回解析这些杂活包起来。

如果把聊天框剥掉,最小的 LLM 使用方式就是这样:

一个用户提示词,经过应用代码,被发到模型服务,再拿回一段文本。

这已经不是“和一个网页聊天”了。

这是程序在调用一个远程模型。

[配图位 | 图表结构稿] 主题:最小 API 调用链路 目的:让读者看懂聊天框背后至少有一层应用代码和 API 调用 内容:用户提示词 → 应用代码 → OpenAI SDK → LLM API → 文本结果 形式:横向流程图,中文标签:用户提示词、应用代码、SDK、LLM API、文本结果


02 |系统提示词

最小调用能跑,但还不像一个产品。

假设你在做一个电商客服助手,用户输入:

帮我查一下订单 12345 到哪了?

如果只把这句话发给模型,模型可能会很努力地“扮演客服”,甚至编出一个听起来合理的物流状态。

这时候程序要加一层规则。

const response = await client.responses.create({
  model: "gpt-5.2",
  instructions:
    "你是一个电商客服助手。回答必须简短。没有订单数据时,不要编造物流状态。",
  input: "帮我查一下订单 12345 到哪了?",
});

input 是用户这次说的话。

instructions 更像应用提前写好的工作守则:你是谁,要怎么回答,哪些事情不能做。

这个地方很像一个客服系统给新人递了一张纸:

回答要短。没有查到订单数据,就说查不到。别现场编。

模型不一定真的“懂规矩”,但它会被这段规则影响。

这也解释了为什么同一个模型可以出现在很多产品里。放进客服规则里,它像客服;放进代码审查规则里,它像代码助手;放进写作规则里,它像写作搭档。

不过这里别想得太神。

系统提示词只是约束模型回答的文字,不是门禁系统。订单权限、敏感信息、付款确认,这些都不能只靠一句 prompt 兜底。


03 |历史上下文

聊天产品最容易制造一个错觉:模型记住你了。

你说:

我叫 mevin。

它回:

好的,我记下了。

你再问:

我叫什么?

它答得出来,看起来像记忆。

工程里通常没这么玄。

程序会把前面的几轮对话重新放进这次请求里。

const response = await client.responses.create({
  model: "gpt-5.2",
  input: [
    { role: "user", content: "我叫 mevin。" },
    { role: "assistant", content: "好的,我记下了。" },
    { role: "user", content: "我叫什么?" },
  ],
});

这段数组才是重点。

模型这次能答出来,不是因为它在某个地方永久记住了 mevin,而是因为“我叫 mevin”这句话又被放回了请求里。

第一篇讲过 context window,上下文窗口。

它不是记忆容量,更像一次考试时桌面能摊开的参考资料。桌面上有名字,它就能参考;桌面上没有,它就只能猜。

真实产品里,历史不会无限塞。

最近几轮可以直接带上,更早的内容可能被压缩成摘要,也可能存在数据库里,需要时再检索回来。很多所谓“长期记忆”,其实是应用把信息存起来,再挑合适的时候塞回上下文。

这件事没有“模型记住我”那么浪漫。

但它更接近你真正要理解的使用链路。

[配图位 | 图表结构稿] 主题:历史上下文如何进入请求 目的:解释“记忆”和“上下文”的区别 内容:系统提示词 + 历史消息 + 当前问题 → 本次 API 请求;旁边标注“没放进去的内容,模型看不到” 形式:分层打包图,中文标签:系统提示词、历史消息、当前问题、本次请求、上下文窗口


04 |请求参数

到这里,请求里已经有三样东西:用户提示词、系统提示词、历史上下文。

但一次 API 调用通常还会带参数。

这些参数不是主角,但它们决定这次模型怎么工作。

model 决定调用哪个模型。

temperature 决定回答更稳,还是更发散。抽取订单号、写 SQL 这类任务通常希望稳一点;想标题、想角度时可以放开一点。

max_output_tokens 限制输出长度。

stream 决定要不要边生成边返回。你在聊天框里看到文字一段段冒出来,背后通常就有流式输出。

还有 toolstext.format / schema,后面会讲。

这些字段放在一起,其实是在给模型划工作范围:

  • 找谁回答
  • 看哪些内容
  • 回答多长
  • 能不能用工具
  • 结果要不要按固定结构回来

聊天框把这些决定藏起来了。

API 调用把它们摊在开发者面前。


05 |模型返回值

请求发出去,模型会返回结果。

最简单的时候,应用直接取 response.output_text,把文本放回聊天框。

但只要应用稍微复杂一点,返回值就不只是“一段可以展示的话”。

它可能是一段流式文本。也可能是一个符合 schema 的 JSON。也可能是一个工具调用请求。还有可能是错误信息。

这会影响程序下一步做什么。

普通聊天,展示就行。

客服系统,要保存进会话记录。

信息抽取,要解析字段再写入数据库。

工具调用,要先执行工具,再把结果交回模型。

失败了,还要重试、降级,或者告诉用户稍后再试。

这里有一个分叉:

如果返回结果是给人看的,文本就够了。

如果返回结果要继续交给程序,文本就开始不够用了。

这就引出结构化输出。


06 |结构化输出

用户说:

明天下午三点提醒我开会。

模型回:

好的,我会提醒你明天下午三点开会。

人能看懂。

程序不好用。

提醒系统真正需要的是更明确的字段:任务是什么,日期是哪天,时间几点。

{
  "task": "开会",
  "date": "明天",
  "time": "15:00"
}

结构化输出解决的就是这个接力问题。

它不是为了让答案看起来更技术,而是为了让下一段程序能接得住。

日历应用要日期和时间。工单系统要分类和优先级。订单系统要订单号和查询类型。

自然语言适合给人读。

JSON / schema 适合给程序继续跑。

但结构正确,不代表内容可信。

模型可能输出一个格式完全正确、但日期解析错了的 JSON。程序还是要校验日期、权限、订单号、业务状态。

结构化输出解决的是“怎么接住结果”,不是“结果一定对”。

[配图位 | 图表结构稿] 主题:自然语言到结构化输出 目的:解释为什么程序更喜欢 JSON / schema 内容:左侧用户输入“明天下午三点提醒我开会”,右侧拆成 task/date/time 三个字段;底部标注“给人读”和“给程序处理”的区别 形式:左右对照图,中文标签:自然语言、结构化输出、任务、日期、时间


07 |工具调用

结构化输出解决了“结果怎么交给程序”。

工具调用解决的是另一个问题:模型什么时候需要外部系统帮忙。

用户问:

帮我查一下订单 12345 到哪了。

这不是一个纯文本问题。

模型就算文笔再好,也不能凭空知道订单状态。它需要订单系统。

更真实的链路是:

用户:帮我查一下订单 12345 到哪了
模型:需要调用 get_order_status(order_id: "12345")
程序:执行订单查询
程序:把查询结果交回模型
模型:把结果组织成用户能读懂的话

工具调用里,模型负责提出动作:要用哪个工具,参数是什么。

程序负责执行动作:真的去查数据库、调 API、跑代码,或者拒绝执行。

这个边界一定要分清。

模型不是自己拥有外部世界的遥控器。开发者只是允许它在某些条件下“请求”工具。

查天气、查订单这类动作可以比较自动。

发邮件、付款、删文件,就不能这么随便。程序应该加权限、确认、日志,必要时让人点头。

所以工具调用不是魔法。

它只是把模型的语言判断,接到程序能执行的动作上。

[配图位 | 图表结构稿] 主题:工具调用链路 目的:纠正“模型自己调用外部 API”的误解 内容:用户请求 → 模型提出工具调用 → 程序执行工具 → 工具结果回填 → 模型组织回答 形式:循环箭头图,中文标签:用户请求、工具调用请求、应用代码、外部工具、工具结果、最终回答


08 |Agent 循环

工具调用是一轮。

Agent 是多轮。

普通调用像这样:问一句,答一句。

工具调用多了一步:模型判断要不要用工具,程序执行工具,再把结果交回模型。

Agent 会把这个过程反复跑。

比如你让它“整理这份资料,并生成一个表格”。它可能会:

读取文件
→ 摘要内容
→ 发现缺字段
→ 再读取另一份文件
→ 生成表格
→ 检查格式
→ 返回结果

看起来像它自己在做事。

拆开看,还是模型调用、工具执行、状态更新、结束判断这几件事。

麻烦也从这里开始。

跑一轮,有一轮成本。多一个工具,多一层权限风险。任务什么时候停,中途出错怎么追踪,哪一步需要人确认,都要设计。

Agent 不该被理解成“更聪明的聊天机器人”。

它更像一套循环程序:模型负责判断下一步,工具负责执行动作,应用负责记录状态和决定什么时候停。

MCP 可以放在旁边理解。

它不是模型,也不是 Agent 本身。它更像一套连接标准,让工具和数据源更容易接进应用。

[配图位 | 图表结构稿] 主题:Agent loop 目的:解释 Agent 是多轮模型判断和工具执行组成的循环 内容:目标 → 模型判断 → 工具 / 检索 / 子任务 → 状态更新 → 是否结束;旁边标注成本、权限、停止条件 形式:循环流程图,中文标签:目标、模型判断、工具执行、状态更新、结束判断、成本、权限、停止条件


09 |程序责任

现在可以把这条链路压成一句话:

LLM 应用不是“用户把话发给模型”,而是应用把用户输入包装成一次可控请求,再决定怎么处理模型返回的东西。

最小调用里,只有用户提示词。

加上系统提示词,应用开始给模型设规则。

加上历史上下文,多轮对话才成立。

加上请求参数,模型这次怎么回答开始有边界。

拿到返回值之后,应用要判断它是直接展示、保存、解析,还是继续处理。

结构化输出让结果能进入程序逻辑。

工具调用让模型可以请求外部动作。

Agent 循环把这些动作串成多轮任务。

这里面最容易被忽略的是责任边界。

模型可以生成、判断、组织语言。

但保存上下文、校验输出、执行工具、控制权限、记录日志、处理失败,这些都是应用的事。

用户看到的是聊天框。

开发者真正要设计的,是聊天框背后的这条程序链路。