模型的异步工具调用

一、 核心改变:API 层的 async: true 声明

在过去,你定义工具(Functions)传给大模型时,它默认为阻塞式的。

也就是说,你不给 tool result 而是直接发下一条请求,确实也是可以的。你作为客户端,完全可以把这个 tool_calls 晾在一边,不理它。模型不会死锁或崩溃,但是你的 Agent(智能体)业务逻辑大概率会“乱掉”或者被模型拒绝

从模型“大脑”(对话状态)来看:它是严重阻塞的:在旧的 API 规范中,大模型的“大脑”被硬编码成了一套严格的 ReAct 状态机 如果你不给 tool result,而是直接塞一个新问题发下一条请求,对模型来说相当于“记忆出现了严重的逻辑断层” 由于打破了它预设的逻辑闭环,旧模型经常会犯迷糊,继续复读一遍“请帮我运行工具”,或者直接返回错误。如果你想强行跳过这个工具,你必须在客户端的代码里,把模型刚才发出的 tool_calls 从对话历史中手动删掉(Pop 掉)

现在 OpenAI Astra 允许你在声明工具的 JSON Schema 中直接加入一个关键字 async: true

1
2
3
4
5
6
7
8
9
{
"type": "function",
"function": {
"name": "run_long_data_analysis",
"description": "执行需要花费几分钟的复杂数据分析工作",
"async": true, // 关键:声明这是一个异步工具,不需要模型原地死等
"parameters": { ... }
}
}

在过去:你不给结果,模型就认为你的对话历史“残缺了”或者“格式坏了”,它的注意力机制在处理这段断层上下文时会严重失焦。在现在(Astra 异步):你告诉模型 async: true。模型在输出 tool_calls 的那一刻,它的状态机就主动进入了“后台挂起”模式。它知道这个工具去后台跑了,此时你不需要删记忆,直接发下一条新请求,模型能够非常顺畅地在上下文里跟你聊别的话题,直到几分钟后工具结果回来。


二、 运行时的状态机实现(稳定 Call ID 映射)

这是技术实现里最巧妙的部分。异步调用最怕的是“结果返回了,但模型把上下文忘光了”(这里指的是当 5 分钟后 call_1 的结果返回时,对话历史里已经塞满了这 5 分钟内产生的大量新对话、新推理逻辑。虽然你用 ID 把结果强行贴在了最新的对话末尾,但模型由于上下文太长(尤其是信息被埋在了最中间),注意力机制对 5 分钟前那个 call_1 的感知力会急剧下降,这就导致了技术上常说的 “Lost in the Middle”(迷失在中间)现象。它看到结果了,但它的注意力已经很难和 5 分钟前它发出调用时的那对“起因”产生强烈的权重关联了)

  1. 生成全局唯一 ID(call_id*:当模型决定调用这个异步工具时,它会输出一个 call_id: "call_abc123",然后模型*立即结束这一轮思考,转去执行其他任务(比如先回答用户的另一个小问题,或者继续生成不需要该数据的文本)。 [2, 3, 4]
  2. 长耗时工具离线运行:你的本地客户端或服务器收到这个 call_id,开始在后台开一个独立进程(如 Python 的 asyncio 任务或 Celery 队列)去跑代码。 [5]
  3. 回传结果关联:当工具在几分钟后跑完时,客户端向大模型服务器发送一个包含原有 call_id 的事件: [3, 4]
    1
    2
    {"role":"tool","call_id":"call_abc123",// 严格对应,大模型靠它把数据塞回正确的记忆片段中"content":"分析完成,结果为:..."
    }

三、 通信层:基于 WebSocket 的全双工流式会话(Mid-turn Steering)

  1. 建立 WebSocket 持续长连接:客户端与大模型服务器之间保持一条“双向同时说话”的通道。
  2. 推理与输入交错(Interleaved)
  • 模型侧:正在利用其内部的 CoT(思维链) 疯狂进行逻辑推理和输出。
  • 用户侧:如果发现模型跑偏了,用户直接发送一句新的 Prompt 文本(无需等待模型停下)。
  • 服务器侧实现:服务器在接收到用户新输入的瞬间,不中断当前的上下文会话,而是将新输入作为“实时动态观测(Observation)”直接插播进模型的 Attention(注意力机制)缓存中。模型会瞬间感知到新指令,并在线调整后面的推理路线(这在技术上被称为 Mid-turn Steering / 中途转向)。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    【WebSocket 接收层】                  【服务器核心:动态 KV Cache 管理器】                     【Transformer 推理引擎】
    │ │ │
    │ ┌───────────────────────┐ │
    │ │ [ 原始历史上下文 KV ] │ │
    │ └───────────┬───────────┘ │
    │ │ │
    │ ▼ │
    │ ┌───────────────────────┐ │
    │ │ [ 正在生成的 CoT KV ] │ ───(当前正基于此进行 Attention 计算)──>│
    │ └───────────────────────┘ │
    │ ▲ │
    │ │ (突发插播!) │
    用户发送: "不要用Python,改用Go" │ │
    │ │ │
    ▼ │ │
    [ 瞬间 Tokenize 并计算 KV ] ───────────────────────────┘ │
    [ 新输入 Observation KV ] │
    │ │
    ▼ │
    ┌───────────────────────┐ │
    │ [ 融合后的新 KV 缓存 ] │ ───(下一标点/Token 立即感知新权重)──>│
    └───────────────────────┘ │

    【 效果:Mid-turn Steering (中途转向) 】 │
    模型在下一发色点(Token)的注意力矩阵被突变改变, │
    直接放弃原有的 Python 思路,流畅转向输出 Go 代码。 ▼

它是怎么操作 KV Cache 的?
1. 并行多路注意力(Parallel Attention / Cross-Attention):服务器不需要重写过去已经生成的 KV Cache(那是只读的),而是把用户新输入的词转成 KV 矩阵,作为一组新的虚拟 Token,利用交叉注意力机制(Cross-Attention)动态地引入到当前 Transformer 层的注意力计算中。
2. 动态因果掩码(Dynamic Causal Masking):在传统的 GPT 中,前面的词不能看到后面的词(通过 Mask 控制)。在 Transformer 的实际序列中,新指令确实必须作为新的 Token 拼在正在生成的 Token 后面。而全双工服务器修改了 Mask 矩阵,使得模型正在生成的第 N 个 Token,能够突破时序限制,强行关注(Attend to)刚刚通过 WebSocket 飞进来的用户新指令。
3. 分叉与回滚(KV Cache Forking):如果用户说的是“别想了,听我的”,服务器会直接在发出工具调用或思考的那个节点上斩断(Truncate)后面的 KV Cache,让模型瞬间“回滚”并基于新的输入继续往下长出新的 KV 节点。

0%