SGLang 接入 Agent:一次订单查询要经历哪些步骤

作者:袖梨 2026-09-13

用户询问订单状态时,模型无法凭已有上下文得知实时物流信息,必须先请求业务工具,再根据查询结果生成答复。将 SGLang 接入 Agent 后,这条链路仍需要应用层负责授权、消息配对和任务隔离。下面从一次订单查询出发,梳理两次模型调用之间的数据流与关键校验点。

把 SGLang 接进 Agent,查一次订单要走几步

TL;DR

  • 场景:用户问订单状态,模型需要先调用业务工具、再依据工具结果回答;SGLang 不会替业务授权、也不会自动保证工具结果仍属于当前任务。
  • 结论:一次查单至少需要两次模型调用(提出工具调用 → 收到工具结果后再生成回答),应用侧需要做授权、消息配对、结果归属判定与任务切换保护。
  • 产出:两次调用的消息配对示例(call_order_a)、6 条结果接受规则、查单 vs 退款两类工具的差异点、17 行错误速查卡。

版本矩阵

项目状态说明
SGLang Tool Parser 文档可访问✅ 已验证https://docs.sglang.io/docs/advanced_features/tool_parser 返回 HTTP 200
Tool Parser non-streaming-request 入口与 message.tool_calls 形状✅ 已验证文档明确
SGLang Session-Aware Radix Cache 文档可访问✅ 已验证https://docs.sglang.io/docs/advanced_features/session_radix_cache 返回 HTTP 200
session_id 只标记缓存引用,不追加/重建对话✅ 已验证文档原文 "The ID labels cache references only; it does not append or reconstruct conversation context, so each request must still contain the intended prompt"
SGLang Production Metrics 文档可访问✅ 已验证https://docs.sglang.io/docs/references/production_metrics 返回 HTTP 200(沿用前几轮核查)
SGLang v0.5.19(2026-09-05)✅ 已验证沿用前几轮核查
client.chat.completions.create 入口在 OpenAI 兼容客户端下可用⚠️ 待验证需按实际客户端与 SGLang 安装版本核对
tool_calls[].id 关联字段、tool_call_id 配对字段✅ 已验证协议形状依据 Tool Parser 文档与 OpenAI 兼容 Chat Completions 规范
arguments 是 JSON 字符串需解析✅ 已验证Tool Parser 文档明确"return arguments or partial arguments through the response"
call_order_a 是协议关联 ID,不是订单号或登录身份✅ 已验证id 字段是模型/服务端生成的工具调用标识,与业务字段无关
工具结果 tool_call_id 与 assistant 端 id 必须相等✅ 已验证协议要求;本文给出 call_order_a 配对示例
tool_call_id 是业务幂等键❌ 已驳斥只是协议关联;写操作需业务幂等策略
拿模型生成的 user_id 当 principal 授权❌ 已驳斥模型文本是请求内容,授权必须来自服务器登录身份
SGLang session_id 会自动追加/重建对话❌ 已驳斥文档明确只标记缓存引用;应用仍需传完整消息
session_idtool_call_id 是同一类字段❌ 已驳斥前者标记缓存引用;后者标记工具调用关联
Responses API 与 Chat Completions 工具字段是同一套❌ 已驳斥本文明确使用 Chat Completions 工具消息形状,不套用 Responses API
把示例"运输中"固定写进生产服务❌ 已驳斥必须用真实业务执行结果序列化
默默只执行第一条工具调用、丢弃其他❌ 已驳斥应对每条工具调用分别校验并按本例范围拒绝
把流式分片当完整 arguments❌ 已驳斥流式需按调用位置收齐后再校验,本文不展开
靠"最后到的覆盖前面的"处理相同 ID 不同结果❌ 已驳斥ID 不是真实性证明;冲突应标记并停止使用
A 的回调结果被 A 切到 B 后还能进入 B 的消息列表❌ 已驳斥应按 generation 拒绝串入新任务
A 已切到 B 后旧回答还能进入 B 的输出❌ 已驳斥输出端也需按 generation 检查
仅看"模型服务成功"就宣布 Agent 成功❌ 已驳斥必须配业务等待与任务有效性记录

用户问:"我的 ORDER-A 到哪里了?"模型能读懂问题,却不知道此刻的物流状态。它需要先提出查订单请求,让业务程序验证权限、查询订单,再把结果交回模型组织回答。

接入 SGLang 后,这个来回仍然存在。模型生成工具调用,不会使数据库自动接上;返回了工具结果,也不代表这份结果仍属于用户当前要问的订单。下面沿同一条 call_order_a 把请求、执行、回填和用户改口接起来。

本文使用 SGLang 的 OpenAI 兼容 Chat Completions 工具消息形状,依据 2026-09-12 核验的官方 Tool Parser 文档编写。订单、状态、标识和消息均为教学示例,未启动模型或访问真实订单服务。模型、模板、解析器与实际部署版本仍须一起验证;示例不承诺任意模型配置直接可用,也不使用 Responses API 的另一套字段。

一次查单的模型与业务来回

第一次调用:让模型提出一个可以检查的请求

订单号对了,也要查访问权限

配图中的 name / arguments 是应用侧待执行对象的抽象示意,ORDER-EXAMPLE 也是示意订单名;下面改用 ORDER-A 展开实际协议消息形状,两者不应混作同一个原始响应。

应用发送用户问题及允许的工具定义。本例只开放一个只读工具 get_order_status,参数只包含订单号,登录身份由服务器持有。以下 Python 展示第一次非流式请求的完整关键参数;client 是指向实际 SGLang /v1 服务的客户端,model_name 是该服务的实际模型名称,需由接入者配置。

messages = [
    {"role": "system", "content": "依据订单工具结果回答;查询失败时说明未查到,不编造物流。"},
    {"role": "user", "content": "我的 ORDER-A 到哪里了?"},
]
tools = [{
    "type": "function",
    "function": {
        "name": "get_order_status",
        "description": "查询当前登录用户有权访问的订单状态",
        "parameters": {
            "type": "object",
            "properties": {"order_id": {"type": "string"}},
            "required": ["order_id"],
        },
    },
}]
first = client.chat.completions.create(
    model=model_name, messages=messages, tools=tools, stream=False
)

SGLang 官方工具解析示例使用这个调用入口,并从返回的 message.tool_calls 中取得工具名与参数。文档中的解析器选项对应模型输出格式;外层接口兼容,不等于内部模板或模型生成形式都一样。

假设第一次响应的 choices[0].message 如下。这里展示的是 assistant 消息,不是整个 HTTP 响应:

{
  "role": "assistant",
  "content": null,
  "tool_calls": [{
    "id": "call_order_a",
    "type": "function",
    "function": {
      "name": "get_order_status",
      "arguments": "{"order_id":"ORDER-A"}"
    }
  }]
}

arguments 在这份消息里是 JSON 字符串,需要解析后再校验。call_order_a 是这条模型工具调用的关联 ID,不是订单号,也不是当前登录用户的身份证明。应用应保存原始 assistant 消息及其所属任务,不能只留下函数名和参数后丢掉 ID。

先检查返回里是否真的存在工具调用,再逐个检查名称、参数和标识。本例只接受一条只读查单调用;如果模型返回多条或提出修改操作,接入程序按本例的范围拒绝执行并记录原因,不能默默只执行第一条后把整轮当作完成。非流式示例也不意味着可以把流式分片直接当作完整参数;流式接入需按调用位置收齐相关片段后再校验,本篇不展开流式拼装器。

工具执行期间,模型并没有替你等待数据库

官方示例:返回参数,再由应用调用

得到工具调用后,先执行服务器侧检查,再访问业务数据。模型的工具说明写着"当前用户",只是在描述用途,真正的授权不能靠这句话完成。

假设服务器已经验证了登录会话,得到 principal。本例的业务流程是:解析 JSON 字符串,要求只有合法的 order_id;确认工具名在只读允许列表中;用可信的登录主体及订单号查询其可访问范围。若系统采用租户隔离,还需带上服务器确认的租户范围。不能从模型参数中取一个 user_id 来代替 principal。

一种简单的订单归属模型,可以用下面的参数化查询说明授权位置。它是作者设计示例,不是 SGLang 内置订单 API;实际系统存在代理人、组织角色等规则时,应替换为真实授权策略。

SELECT order_id, status
FROM orders
WHERE order_id = :order_id
  AND owner_user_id = :authenticated_user_id;

两个参数分别来自校验后的工具参数与服务器登录身份,不能把模型文本拼接成 SQL。这样,查询对象和访问范围在同一次受约束读取中确定。没有匹配行时,本例向模型返回统一的"未找到可访问订单",不额外泄露该订单是否属于别人。未经授权时也不应先取出整份敏感订单,再指望模型自行忽略。

假设业务服务在本例返回"运输中"。交给模型的结果只保留本次回答必要的字段,例如 ok、订单号、物流状态;内部用户标识、访问令牌与数据库诊断不需要进入工具正文。业务查询耗时可能来自数据库、第三方物流接口或网络,两次模型生成都很快也不能抵消它。

至此,应用才拿到了业务结果。下一步还要把它配回提出这次调用的 assistant 消息,而不是当作一条脱离上下文的新用户消息。

第二次调用,要带上这次任务需要的上下文

第二次调用,别只塞一段物流数据

官方文档的"Execute the Tool"与"Send Results Back to Model"先追加模型的工具调用消息,再追加带 tool_call_id 的 tool 消息,最后再次调用模型。按这份形状,本例成功路径的第二次请求是:

second_messages = [
    {"role": "system", "content": "依据订单工具结果回答;查询失败时说明未查到,不编造物流。"},
    {"role": "user", "content": "我的 ORDER-A 到哪里了?"},
    {
        "role": "assistant",
        "content": None,
        "tool_calls": [{
            "id": "call_order_a",
            "type": "function",
            "function": {
                "name": "get_order_status",
                "arguments": '{"order_id":"ORDER-A"}',
            },
        }],
    },
    {
        "role": "tool",
        "tool_call_id": "call_order_a",
        "name": "get_order_status",
        "content": '{"ok":true,"order_id":"ORDER-A","status":"运输中"}',
    },
]
second = client.chat.completions.create(
    model=model_name, messages=second_messages, tools=tools, stream=False
)

这段代码把上一轮消息展开写出来,是为了让配对关系可见。实际程序应保留经过校验的原始 assistant 消息,并把业务执行的真实结果序列化为 tool 内容,不能把示例中的"运输中"固定写进服务。工具结果的 okstatus 是本例业务字段,协议关联靠 tool_call_id;二者的职责不同。

可以沿 ID 验收这一轮:assistant 提出 call_order_a,业务执行记录绑定它,回填 tool 的 tool_call_id 仍是 call_order_a,最终回答再绑定发起第二次模型调用的任务。顺序错了、ID 写成另一条调用,或者结果内容属于 ORDER-B,均不能靠"模型大概看得懂"放行。

假设第二次模型返回"ORDER-A 正在运输中",这才形成了本例的一次完整来回:模型调用 1→授权与查询→模型调用 2→当前任务输出。模型也可能继续提出工具调用或返回不符合要求的内容;本例的应用预算到第二次为止,遇到进一步调用就结束为未完成并记录原因,不无限执行,不将"恰好两次"宣传成所有 Agent 的固定机制。

如果查不到可访问订单,仍可在同一个配对位置放入安全的失败结果,例如 {"ok":false,"error":{"code":"not_found_or_not_accessible"}},让当前任务说明查询未成功。工具返回失败不等于可以省略关联消息或使用上一条成功结果补空。

Session-Aware Radix Cache 文档说明,其中的 session_id 标记缓存引用,不会自动追加或重建对话。即使复用了缓存,应用仍需传入这轮预期的上下文。这里选择的是上面明确的消息列表;若切换到其他有状态接口,应重新核对那个接口的恢复契约,不能把所有叫 session 的字段视作同一件事。

用户改口后,旧结果还算数吗

用户已经换单,旧结果先别回答

接下来给同一条调用加一个竞态。假设应用将当前用户任务标记为 generation 41,正在等 call_order_a 查询 ORDER-A;用户随后说"改查 ORDER-B",应用创建 generation 42。generation 是本文的应用侧任务版本号,不是 SGLang 工具协议字段。

应用事件顺序(教学推演,非日志时间线)当前 generation处理
为 ORDER-A 注册调用41保存 call_order_a→generation 41、ORDER-A、pending
用户改问 ORDER-B42创建新任务上下文;旧任务标为不再对外输出
ORDER-A 的查询返回42在受保护的状态检查中发现所属 generation 为 41,记为旧任务结果,不追加到 B 的消息列表
为 ORDER-B 注册并完成 call_order_b42通过身份、ID、订单与任务配对后,才进入 B 的第二次模型调用

这比给结果附带一个 ID 多了一步:应用要在决定使用结果的那一刻,核实该任务仍然有效。本例至少保存连接或会话范围、generation、call ID、工具名、参数摘要、执行尝试和终态。相同字符串 ID 在不同任务里出现,不应自动覆盖同一条记录;索引键必须包含实际任务范围。

同一工具调用还可能经历超时与重试,因此业务回调最好通过应用持有的待执行记录或 attempt 标识关联,不接受外部返回任意一个 call ID 就自行归入当前任务。若订单服务不回显订单号,仍能依据创建该执行时保存的参数与尝试记录配对;若回显了不同订单号,应视为异常而非替换原参数。

下面是结果接受规则的伪代码。state_lock 代表任务切换与结果接受共用的串行化机制;单进程可用同一事件循环或锁,多实例则需要等价的条件更新,不能把它当作已实现的分布式事务。

在 state_lock 内:
    按应用保存的 task/generation/call_id/attempt 取待执行记录
    不存在 → 记录 unmatched,不回填,不猜测归属
    已有终态 → 记录 duplicate,不追加第二条 tool 结果
    不属于当前 generation → 记录 stale,不进入当前任务消息
    超过本次接受期限 → 记录 late,不让成功覆盖超时终态
    工具名、参数归属或可信执行来源不匹配 → 记录 mismatch,拒绝回填
    否则标记终态并构造该任务的 tool 消息,排入该任务的后续生成

检查与状态转换需要作为一个整体完成,否则"检查时还是 41、追加时已经是 42"的空隙仍会串单。消息列表也应属于具体任务,不能让 A 的后台回调拿到一个已经改装成 B 的共享列表。

还存在第二个更晚的窗口:A 的工具结果可能在切换前已经被接受,A 的第二次模型调用也已发出,但回答到达时用户才刚改问 B。因此,最终文本的输出端仍要按 generation 再检查一次。切换后可尝试取消旧模型请求以减少工作量;即便取消未及时生效,旧回答也不能进入新任务的输出。已经展示给用户的旧内容无法通过事后检查收回,这个规则约束的是尚未交付的内容。

超时、重复与错配,不能靠重新生成参数解决

查询与写操作重试的区别

超时需要一个明确的截止点。若当前任务在期限内没有结果,本例只给这条调用确定一次 timeout 终态,并在任务仍有效时回填匹配 call ID 的失败内容。之后同一执行的迟到成功保留在受控追踪中,不能再追加第二条互相矛盾的 tool 消息。generation 已失效时,则连这条失败消息也不应送入新任务。

观察本例的应用决定为什么
同一尝试回调两次,结果相同只接受首次终态;后一次记 duplicate避免重复续写或向消息列表追加两份工具结果
同一 call ID 出现不同结果或不同参数归属标记冲突,停止自动使用异常结果ID 不是结果真实性证明,不能靠"最后到的覆盖前面的"处理
返回了未登记的 ID 或属于其他会话的记录拒绝回填并查关联路径不能为凑齐一轮消息,把未知结果配给当前唯一工具
当前只读查询超时按剩余期限决定是否进行有界的新尝试;记录 attempt,仍只产生一个被接受的逻辑结果新尝试需要与原尝试区分,旧回调不能越过终态规则
当前任务换成另一单旧任务结果留作追踪,不供新任务作答工具是否真的执行与用户是否仍在等待它,是两件事

应用内部重试一次业务读取,不一定需要再让模型生成一份同样参数;但每次尝试仍要重新满足当前授权、任务有效性与请求预算。若旧尝试已经确定为本例的终态,就不能用新尝试悄悄改写已回填的失败,需作为新的受控动作处理。追踪信息也应按权限和保留策略保存,不等于无限期保存完整订单内容。

对于退款、修改地址等写操作,超时风险不同:操作可能已经执行,只是响应丢了。查询结果配对用的 tool_call_id 不是天然的业务幂等键;还需由业务服务定义稳定操作标识、状态查询与幂等策略,并执行所需授权。模型重新生成参数,不能证明退款尚未发生。本例只开放查询,不把这套只读重试方案直接当作退款实现。

把一次完整任务的时间留下来

一次任务,留下这份记录

调优时,分别记录第一次模型请求、授权与业务查询、第二次模型请求,以及最终输出是否被当前 generation 接受。SGLang 的服务指标用于观察模型服务,应用记录则补上业务等待与任务有效性。两边应使用可关联的请求标识和明确的时间口径;generation、工具 call ID 与底层服务请求 ID 不能互相冒充。

对刚才的 A→B 推演,记录不能只写"订单查询成功两次"。它应该保留:A 查询完成但结果 stale,未启动或未交付 A 的后续回答;B 的调用按 call_order_b 正确配对并完成当前任务。前者可计入后台工作量,却不能算用户当前问题的有效完成量。这样才能解释为什么模型服务看似都成功,用户仍可能收到不相关答案。

第一次接入可以只开放这个只读工具,按相同消息形状逐项验证成功、未找到可访问订单、非法参数、超时、重复回调、错配 ID、用户切单及迟到模型回答。通过条件不是日志里出现一个 tool_calls,而是每个被交付的回答都有当前任务、授权范围、对应工具结果和输出接受记录。这些场景在本文均未实跑,示例给出了实现与验收的具体位置。


错误速查卡

症状根因定位修复
仅返回一条工具调用却把整轮当完成模型可能返回多条或非本例范围的工具调用逐条校验 tool_calls 名称、参数、id本例只接受一条只读查单调用,其余拒绝并记录
默默只执行第一条工具调用后跳过没做完整工具调用校验tool_calls 数量与本例范围按范围拒绝或暂停,等待正确调用
用模型里的 user_id 当 principal 授权授权是服务器侧责任看登录会话与可信身份来源authenticated_user_id 等服务器确认字段
把模型文本拼到 SQL工具文本是请求内容,不是授权看订单归属查询的参数来源用参数化查询,参数来自校验后输入与服务器身份
直接取出整份敏感订单再让模型自筛未做授权范围限制看读取范围与授权条件在同一受约束读取中确定可访问范围
第二次调用只塞一段物流数据没有回填 assistant 原始消息second_messages 是否含 assistant 与 tool必须先回填 assistant 工具调用,再回填带 tool_call_id 的 tool
tool_call_idid 不一致协议关联字段配错看 assistant id 与 tool tool_call_id必须相等;不相等拒绝回填
工具 arguments 直接当字典用arguments 是 JSON 字符串看解析步骤先解析再校验字段
session_id 当作"自动追加上下文"Radix Cache 只标记缓存引用看 Session-Aware Radix Cache 文档应用仍需传完整预期消息
用户切单后旧结果进入新消息列表没按 generation 拒绝看结果接受规则中的 generation 检查串行化内原子完成 generation 校验
任务已切到 B,A 的旧回答仍交付给 B输出端没按 generation 复查看最终输出前的 generation 校验输出端再检查一次 generation
同一尝试回调两次都接受终态未固化看"取待执行记录 → 终态检查"步骤只接受首次终态;后一次记 duplicate
同一 call ID 不同结果被自动覆盖ID 当成了真实性证明看错配检查标记冲突并停止使用异常结果
未知 call ID 强行配给当前工具凑齐一轮消息看回填前的索引键校验拒绝回填并查关联路径
退款超时再生成参数当作"未执行"tool_call_id 不是业务幂等键看业务幂等策略业务服务定义稳定操作标识、状态查询与幂等
仅看"模型服务成功"宣布 Agent 成功缺业务等待与任务有效性记录看任务记录记录任务编号 / 调用关联 / 当前状态 / 用户切单等
把"恰好两次模型调用"写成所有 Agent 的固定机制次数应受任务控制看本例的"预算到第二次为止"进一步调用就结束为未完成并记录

作者:武子康的个人博客 发布日期:2026-09-13(依据 2026-09-12 核验的官方 Tool Parser / Session-Aware Radix Cache 文档) 核查依据:SGLang Tool Parser 文档(HTTP 200,2026-09-12 核验)、Session-Aware Radix Cache 文档(HTTP 200,2026-09-12 核验)、Production Metrics 文档(HTTP 200);SGLang v0.5.19(2026-09-05)。本文使用 OpenAI 兼容 Chat Completions 工具消息形状,不使用 Responses API;订单、状态、标识和消息均为教学示例,未启动模型或访问真实订单服务。

相关文章

精彩推荐