[对比学习LangChain和MAF-10]采用后台响应以适应长耗时推理
MAF支持后台响应,用于处理可能需要较长时间才能完成的长时间运行操作。此功能使Agent能够开始处理请求并返回一个ContinationToken,该ContinationToken可用于轮询结果或恢复中断的数据流。后台响应特别适合:
- 需要大量处理时间的复杂推理任务;
- 可能因网络问题或客户端超时而中断的操作;
- 以及需要启动长时间运行的任务并在稍后查看结果的场景;
虽然LangChain针对Agent的调用不支持类似的后台响应,但是LangGraph SDK提供的客户端允许我们以后台任务的方式调用Agent,并返回一个Run对象来跟踪任务的状态和结果,也起到了类似的作用。下面我们来看看MAF和LangChain是如何支持Agent的后台响应的。
1. MAF
后台响应使用ContinationToken机制来处理长时间运行的操作。当您向启用了后台响应的Agent发送请求时,会发生以下两种情况之一:
- 立即完成:Agent快速完成任务并返回最终响应,无需
ContinationToken; - 后台处理:Agent在后台开始处理,并返回一个
ContinationToken,而不是最终结果;
ContinationToken包含所有必要信息,可用于使用非流式Agent API轮询完成状态,或使用流式Agent API恢复中断的流。当ContinationToken为空时,操作完成;这种情况发生在后台响应已完成、失败或无法继续进行时。
1.1 非流式轮询
如果后台响应被应用到AIAgent的非流式RunAsync方法中,如果Agent在处理请求时返回了一个ContinationToken,意味着Agent正在后台处理这个请求,我们可以采用如下的方式利用这个ContinationToken来轮询结果,直到操作完成。
using Azure.AI.Projects;
using dotenv.net;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using OpenAI;
using OpenAI.Responses;
using System.ClientModel;
DotEnv.Load();
var model = Environment.GetEnvironmentVariable("MODEL")!;
var apiKey = Environment.GetEnvironmentVariable("API_KEY")!;
var endpoint = Environment.GetEnvironmentVariable("OPENAI_URL")!;
var agent = new OpenAIClient(
credential: new ApiKeyCredential(key: apiKey),
options: new OpenAIClientOptions { Endpoint = new Uri(endpoint) })
.GetResponsesClient()
.AsAIAgent(model: model);
var session = await agent.CreateSessionAsync();
var options = new ChatClientAgentRunOptions
{
AllowBackgroundResponses = true,
};
var index = 1;
var response =await agent.RunAsync(
message: "制定一份赴日旅游攻略,行程计划5天。",
options: options,
session: session);
PrintResponse(response);
var token = response.ContinuationToken;
while (token is not null)
{
await Task.Delay(2000);
options.ContinuationToken = token;
response = await agent.RunAsync(session: session,options: options);
token = response.ContinuationToken;
PrintResponse(response);
}
void PrintResponse(AgentResponse response)
{
Console.WriteLine($"----------------------------------------{index ++}----------------------------------------");
var hasContinuationToken = response.ContinuationToken is null ? "No" : "Yes";
var text = response.Text ?? "No content.";
Console.WriteLine($"""
HasContinuationToken: {hasContinuationToken}
ResponseID: {response.ResponseId}
Text: {text}
""");
}
在上面的演示程序中,我们首先创建了一个AIAgent对象,并调用RunAsync方法来请求制定一份赴日旅游攻略的行程计划。我们创建了一个ChatClientAgentRunOptions配置选项作为调用RunAsync方法的参数,此对象的AllowBackgroundResponses属性被设置为True,进而开启的后台响应功能。在得到代表响应结果的AgentResponse对象之后,我们调用PrintResponse方法输出相关信息(是否具有ContinuationToken、当前响应ID和文本内容)。
所有我们开启了一个循环来轮询结果,在每次迭代中,我们首先检查当前的AgentResponse对象是否包含ContinuationToken,如果没有,说明操作已经完成,我们就退出循环;如果包含,就将这个ContinuationToken赋值给ChatClientAgentRunOptions对象的ContinuationToken属性,并再次调用RunAsync方法来获取最新的响应结果。我们在每次得到新的响应结果之后都调用PrintResponse方法来输出相关信息。程序执行后的输出如下所示:
----------------------------------------1----------------------------------------
HasContinuationToken: Yes
ResponseID: resp_0e602ed1ac4f8533006a06d836cea08194aebd3332cbbbe92f
Text:
----------------------------------------2----------------------------------------
HasContinuationToken: Yes
ResponseID: resp_0e602ed1ac4f8533006a06d836cea08194aebd3332cbbbe92f
Text:
----------------------------------------3----------------------------------------
HasContinuationToken: Yes
ResponseID: resp_0e602ed1ac4f8533006a06d836cea08194aebd3332cbbbe92f
Text:
----------------------------------------4----------------------------------------
HasContinuationToken: Yes
ResponseID: resp_0e602ed1ac4f8533006a06d836cea08194aebd3332cbbbe92f
Text:
----------------------------------------5----------------------------------------
HasContinuationToken: Yes
ResponseID: resp_0e602ed1ac4f8533006a06d836cea08194aebd3332cbbbe92f
Text:
----------------------------------------6----------------------------------------
HasContinuationToken: Yes
ResponseID: resp_0e602ed1ac4f8533006a06d836cea08194aebd3332cbbbe92f
Text:
----------------------------------------7----------------------------------------
HasContinuationToken: Yes
ResponseID: resp_0e602ed1ac4f8533006a06d836cea08194aebd3332cbbbe92f
Text:
----------------------------------------8----------------------------------------
HasContinuationToken: No
ResponseID: resp_0e602ed1ac4f8533006a06d836cea08194aebd3332cbbbe92f
Text: 下面是一份**5天日本旅游攻略**(以**东京+周边**为例,适合首次赴日、节奏适中、兼顾文化/购物/美食)。如果你有**具体出发城市、季节、预算或偏好**(如动漫/自然/购物),我可以再为你定制升级版。
---
## 🇯🇵 日本5天旅游行程(东京+周边)
### ✈️ 行前准备
- **签证**:中国大陆需办理日本旅游签证
- **交通卡**:Suica / PASMO(到达机场即可购买或手机绑定)
- **网络**:eSIM / 日本电话卡 / 移动WiFi
- **支付**:现金 + 信用卡(部分小店只收现金)
- **APP推荐**:Google Maps、乘换案内、PayPay、Tabelog
---
## 🗓 行程安排
### **Day 1|抵达东京 · 浅草 + 晴空塔**
**上午 / 中午**
- 抵达东京(成田 / 羽田)
- 入住酒店(建议上野、浅草、新宿)
**下午**
- 浅草寺(东京最古老寺庙)
- 仲见世商店街(和果子、纪念品)
**晚上**
- 东京晴空塔(登塔或商场用餐)
- 推荐美食:天妇罗、鳗鱼饭
✅ 住宿建议:浅草 / 上野
✅ 亮点:传统文化 + 城市夜景
---
### **Day 2|东京市区 · 原宿 + 涩谷 + 新宿**
**上午**
- 明治神宫(都市中的森林)
- 原宿竹下通(年轻潮流)
**下午**
- 涩谷十字路口(打卡地标)
- 涩谷SKY展望台(建议提前预约)
**晚上**
- 新宿歌舞伎町
- 新宿居酒屋体验
✅ 购物:Shibuya 109、唐吉诃德
✅ 美食:拉面、一兰、寿司
---
### **Day 3|富士山一日游**
**推荐方式**
- 报团 or 自由行(巴士/电车)
**行程示例**
- 河口湖
- 天上山缆车
- 八木崎公园(樱花/枫叶季)
- 忍野八海
**晚上**
- 返回东京
✅ 亮点:富士山全景
✅ 季节建议:4月樱花 / 10-11月红叶
---
### **Day 4|镰仓或迪士尼(二选一)**
#### 🎎 方案A:镰仓一日游(文化)
- 镰仓大佛
- 长谷寺
- 镰仓高校前(灌篮高手取景地)
- 小町通购物
#### 🎢 方案B:东京迪士尼(娱乐)
- 东京迪士尼乐园 或 海洋
- 建议早入园+官方APP
✅ 适合:情侣/亲子/动漫迷
---
### **Day 5|上野 + 秋叶原 · 返程**
**上午**
- 上野公园 / 博物馆
- 阿美横町购物
**下午**
- 秋叶原(动漫、电器、手办)
**晚上**
- 前往机场返程
---
## 💰 预算参考(人均)
| 项目 | 费用 |
|----|----|
| 机票 | ¥3000–6000 RMB |
| 酒店 | ¥600–1000 RMB / 晚 |
| 吃喝 | ¥200–300 RMB / 天 |
| 交通 | ¥200–300 RMB |
| 总计 | ¥7000–12000 RMB |
---
## 🍣 日本必吃美食
- 寿司 / 刺身
- 拉面(博多、东京豚骨)
- 烧肉 / 和牛
- 抹茶甜品
---
## 📌 小贴士
- 地铁复杂但准时,别怕换乘
- 垃圾分类严格,街头垃圾桶少
- 公共场所保持安静
- 排队文化非常重要
---
如果你愿意,我可以:
✅ 按**大阪/京都/北海道**重做路线
✅ 制定**情侣/亲子/二次元主题行程**
✅ 生成**详细交通换乘表+地图链接**
告诉我你的偏好即可 😊
从输出结果看出,对于执行RunAsync<T>方法返回的AgentResponse对象,如果它的ContinuationToken不为null,说明Agent正在后台处理这个请求。由于RunAsync方法属于阻塞式调用,结果只能在后台处理完成之后才能返回,所以如果有ContinuationToken,就不会有结果(Text属性为null)。当AgentResponse对象的ContinuationToken为null时,说明后台处理已经完成,这时Text属性才会包含最终的结果。虽然我们对此调用了RunAsync方法,但是ResponseId保持不变,说明这是同一个请求的不同阶段的响应结果。
1.2 流式中断恢复
当后台响应被应用到AIAgent的RunStreamingAsync方法中,如果我们在迭代处理实时响应内容时将ResponseContinuationToken保存下来,当迭代被中断(如网络问题或客户端超时)时,我们可以利用这个ResponseContinuationToken来恢复流式响应的处理。我们可以采用如下的方式来实现流式中断恢复:
using Azure.AI.Projects;
using dotenv.net;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using OpenAI;
using OpenAI.Responses;
using System.ClientModel;
DotEnv.Load();
var model = Environment.GetEnvironmentVariable("MODEL")!;
var apiKey = Environment.GetEnvironmentVariable("API_KEY")!;
var endpoint = Environment.GetEnvironmentVariable("OPENAI_URL")!;
var agent = new OpenAIClient(
credential: new ApiKeyCredential(key: apiKey),
options: new OpenAIClientOptions { Endpoint = new Uri(endpoint) })
.GetResponsesClient()
.AsAIAgent(model: model);
var session = await agent.CreateSessionAsync();
var options = new ChatClientAgentRunOptions
{
AllowBackgroundResponses = true,
};
ResponseContinuationToken? continuationToken = null;
var index = 0;
await foreach (var update in agent.RunStreamingAsync(
message: "制定一份赴日旅游攻略,行程计划5天。",
options: options,
session: session))
{
continuationToken = update.ContinuationToken;
var text = update.Text;
if (!string.IsNullOrWhiteSpace(text))
{
Console.Write(text);
}
index++;
if (index == 200)
{
Console.WriteLine("\n\n-------------模拟中断,稍后继续-------------\n");
break;
}
}
options.ContinuationToken = continuationToken;
await foreach (var update in agent.RunStreamingAsync(
options: options,
session: session))
{
continuationToken = update.ContinuationToken;
var text = update.Text;
if (!string.IsNullOrWhiteSpace(text))
{
Console.Write(text);
}
}
如上面的演示程序所示,我们首先调用RunStreamingAsync方法来请求制定一份赴日旅游攻略的行程计划,并在迭代处理实时响应内容的过程中将每次迭代得到的ResponseContinuationToken保存下来。当迭代处理到第200次时,我们模拟了一个中断,此时我们就退出了第一次的迭代循环。接着我们将之前保存的ResponseContinuationToken赋值给ChatClientAgentRunOptions对象的ContinuationToken属性,并再次调用RunStreamingAsync方法来继续处理剩余的流式响应内容。程序执行后的输出如下所示:
下面是一份**适合第一次赴日的5天日本旅游攻略**,以**东京 + 京都/大阪**为主线,兼顾城市、文化、美食与购物。如果你有特殊偏好(如亲子、动漫、购物、自然风景),我也可以再为你定制调整。
---
## 一、行程总览**Day1:抵达东京|浅草·晴空塔****Day2:东京|原宿·涩谷·新宿****Day3:东京 → 京都|清水寺·祇园****Day4:京都|伏见稻荷·岚山****Day5:大阪|心斋桥·道顿堀·返程**
---
## 二、详细行程安排### **Day1|抵达东京 ·传统与现代**
**上午 / 中午**
- 抵
-------------模拟中断,稍后继续-------------
达东京(成田或羽田机场)
-乘坐机场快线前往市区(N'EX / 京成Skyliner)
- 酒店寄存行李(推荐住在上野、浅草、新宿)
**下午**
- 浅草寺(东京最古老寺庙)
- 仲见世商店街(和风小吃、伴手礼)
**傍晚**
- 东京晴空塔(登塔或拍夜景)
- 晚餐推荐:天妇罗、鳗鱼饭、拉面✅ **住宿**:东京---
### **Day2|东京潮流与都市体验**
**上午**
- 明治神宫(感受宁静森林神社)
- 原宿竹下通(潮流、甜品)
**下午**
- 涩谷十字路口- 忠犬八公像- 涩谷购物(SHIBUYA109、Loft)
**晚上**
- 新宿歌舞伎町夜景- 都厅展望台(免费夜景)
- 可体验居酒屋文化✅ **住宿**:东京---
### **Day3|东京 → 京都(新干线)**
**上午**
- 搭乘新干线(东京 → 京都,约2.5小时)
**下午**
- 清水寺(世界文化遗产)
- 二年坂、三年坂(传统街道)
**傍晚**
-祇园花见小路(有机会遇见艺伎)
- 晚餐推荐:怀石料理、京都汤豆腐✅ **住宿**:京都(推荐町屋或和式酒店)
---
### **Day4|京都深度游**
**上午**
-伏见稻荷大社(千本鸟居)
**中午**
- 京都拉面或抹茶甜品**下午**
- 岚山竹林- 天龙寺- 渡月桥**晚上**
- 可泡温泉或夜游鸭川✅ **住宿**:京都 / 大阪---
### **Day5|大阪 · 美食与购物**
**上午**
- 前往大阪(京都 → 大阪约30分钟)
**游览**
- 大阪城公园(外观)
- 心斋桥购物街- 道顿堀美食:章鱼烧、大阪烧、串炸**返程**
- 前往关西机场返程---
## 三、交通建议- **交通卡**:Suica / ICOCA(地铁、便利店通用)
- **新干线**:东京 → 京都(提前购票或JR Pass)
- 市内以地铁+步行为主---
## 四、预算参考(人均)
-机票:¥3000–6000(视出发地)
-住宿:¥600–1200 / 晚- 餐饮:¥150–300 / 天-交通+门票:¥500–800---
## 五、实用小贴士✅ 日本现金+信用卡并用✅ 提前预约热门景点/餐厅✅ 注意垃圾分类,公共场所保持安静✅便利店美食很值得尝试---
如果你需要:
- **亲子 / 情侣 / 独自旅行版本**
- **动漫巡礼 /购物 / 温泉主题**
- **具体酒店和餐厅推荐**
2. LangChain
虽然我们不能以后台响应的方式来调用LangChain的Agent,但是当我们使用LangGraph客户端SDK来调用Web服务器承载的Agent时,可以以后台任务的方式来调用Agent,并返回一个Run对象来跟踪任务的状态和结果。为了演示这个功能,我们得先构建一个Agent服务器。
2.1 构建Agent服务器
为了我们构建了一个极简的Agent服务器,根目录下只有如下所示的几个必要的文件:
root/
├── .env # 存放环境变量
├── langgraph.json # 配置Graph
├── agent.py # 定义Graph
└── pyproject.toml # 项目配置文件
我们在agent.py调用create_deep_agent函数来创建一个Agent,并将其赋值给一个名为graph的变量。我们指定了模型为ChatOpenAI,并设置了模型名称为gpt-5.2-chat。
from deepagents import create_deep_agent
from langchain_openai import ChatOpenAI
graph = create_deep_agent(model=ChatOpenAI(model="gpt-5.2-chat"))
我们执行命令行langgraph dev以dev模式部署我们定义的Agent,并启动作为宿主的Web服务器。
E:\agent> langgraph dev
INFO:langgraph_api.cli:
Welcome to
╦ ┌─┐┌┐┌┌─┐╔═╗┬─┐┌─┐┌─┐┬ ┬
║ ├─┤││││ ┬║ ╦├┬┘├─┤├─┘├─┤
╩═╝┴ ┴┘└┘└─┘╚═╝┴└─┴ ┴┴ ┴ ┴
- 🚀 API: http://127.0.0.1:2024
- 🎨 Studio UI: https://smith.langchain.com/studio/?baseUrl=http://127.0.0.1:2024
- 📚 API Docs: http://127.0.0.1:2024/docs
2.2 以后台任务的方式调用Agent
在如下所示的客户端程序中,我们调用get_client函数来创建一个LangGraph客户端,并指定url参数为我们Agent服务器的地址。接着我们调用client.runs.create方法来以后台任务的方式调用Agent,并将返回的Run对象中的thread_id和run_id保存下来,以便后续查询任务状态和结果。在随后的while循环中,我们通过不断地调用client.runs.get方法来查询任务的状态,直到任务完成(状态为success、error、timeout或interrupted)。最后我们调用client.runs.join方法来获取最终的结果,是不是和前面演示的以后台响应方式调用Agent的RunAsync方法很相似。
from langgraph_sdk import get_client
from langgraph_sdk.client import RunStatus
from datetime import datetime
import asyncio
async def main():
async with get_client(url="http://localhost:2024") as client:
run = await client.runs.create(
thread_id=None,
assistant_id="travel_agent",
input={
"messages": [
{
"role": "user",
"content": "制定一份赴日旅游攻略,行程计划5天。"
}
]
},
after_seconds=5,
on_completion= "keep"
)
thread_id = run["thread_id"]
run_id = run["run_id"]
status:RunStatus = run["status"]
while True:
run = await client.runs.get(thread_id=thread_id, run_id=run_id)
status = run["status"]
print(f"[{datetime.now()}]当前状态: {status}")
if status in ["success", "error","timeout","interrupted"]:
break
await asyncio.sleep(3)
result = await client.runs.join(thread_id=thread_id,run_id=run_id)
print (result["messages"][-1].get("content",""))
asyncio.run(main())
输出:
[2026-05-15 17:38:37.855502]当前状态: pending
[2026-05-15 17:38:40.866880]当前状态: pending
[2026-05-15 17:38:43.873628]当前状态: running
[2026-05-15 17:38:46.892703]当前状态: running
[2026-05-15 17:38:49.899890]当前状态: running
[2026-05-15 17:38:52.908758]当前状态: running
[2026-05-15 17:38:55.918140]当前状态: running
[2026-05-15 17:38:58.931679]当前状态: success
以下为**5天日本旅游攻略(东京为主,含富士山一日)**,适合**首次赴日、节奏适中**的行程。
---
## 行程概览
- **D1–D3:东京市区**
- **D4:富士山/箱根一日游**
- **D5:东京购物返程**
---
## D1|抵达东京 · 浅草 & 银座
**上午**
- 抵达成田/羽田机场
- 购买交通卡:**Suica / PASMO**
**下午**
- 浅草寺(雷门、仲见世街)
- 隅田川散步(可远观晴空塔)
**晚上**
- 银座逛街(优衣库旗舰店、无印良品)
- 晚餐:寿司或和牛烧肉
---
## D2|东京经典 · 原宿 & 涩谷 & 新宿
**上午**
- 明治神宫
- 原宿竹下通(年轻潮流)
**下午**
- 涩谷十字路口
- SHIBUYA SKY(需预约)
**晚上**
- 新宿歌舞伎町
- 东京都厅免费夜景
---
## D3|东京文化 · 上野 & 秋叶原
**上午**
- 上野公园
- 东京国立博物馆(或动物园)
**下午**
- 秋叶原(动漫、电器)
- 女仆咖啡(体验型,可选)
**晚上**
- 回酒店整理行李
---
## D4|富士山一日游(推荐跟团)
**路线示例**
- 河口湖
- 忍野八海
- 富士五合目(视天气)
**提示**
- 冬季/雨季可能取消五合目
- 自由行需提前查JR与巴士班次
---
## D5|购物 · 回程
**上午**
- 表参道 / 新宿 / 银座补买伴手礼
- 唐吉诃德、Loft、药妆店
**下午**
- 前往机场返程
---
## 住宿建议
- **区域**:上野 / 新宿 / 银座
- **类型**:
- 经济:APA、Super Hotel
- 舒适:三井花园、Hotel Mystays
---
## 预算参考(人均)
- 机票:4000–7000 RMB
- 酒店(4晚):2500–4000 RMB
- 交通 + 餐饮 + 门票:1500–2500 RMB
---
## 必备准备
- 护照 + 签证
- 转换插头(日本双扁)
- 现金 + 信用卡
- 翻译App / Google Maps
---
如需:
- **亲子 / 二次元 / 美食 / 温泉主题**
- **大阪京都版 / 关东+关西**
- **详细到每小时 or 可打印PDF**
直接告诉我你的偏好即可。
Run在LangGraph SDK中代表针对Agent的一次调用,它是与Agent部署相关的几个核心对象之一(其他对象包括Assistant、Thread、Run、Cron Job等),关于Run对象的更多细节,可以参考我们之前的博客文章Agent逻辑在特定任务下的“单次运算实例”。
更多推荐


所有评论(0)