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来轮询结果,直到操作完成。
usingAzure.AI.Projects;usingdotenv.net;usingMicrosoft.Agents.AI;usingMicrosoft.Extensions.AI;usingOpenAI;usingOpenAI.Responses;usingSystem.ClientModel;DotEnv.Load();varmodel=Environment.GetEnvironmentVariable("MODEL")!;varapiKey=Environment.GetEnvironmentVariable("API_KEY")!;varendpoint=Environment.GetEnvironmentVariable("OPENAI_URL")!;varagent=newOpenAIClient(credential:newApiKeyCredential(key:apiKey),options:newOpenAIClientOptions{Endpoint=newUri(endpoint)}).GetResponsesClient().AsAIAgent(model:model);varsession=awaitagent.CreateSessionAsync();varoptions=newChatClientAgentRunOptions{AllowBackgroundResponses=true,};varindex=1;varresponse=awaitagent.RunAsync(message:"制定一份赴日旅游攻略,行程计划5天。",options:options,session:session);PrintResponse(response);vartoken=response.ContinuationToken;while(tokenisnotnull){awaitTask.Delay(2000);options.ContinuationToken=token;response=awaitagent.RunAsync(session:session,options:options);token=response.ContinuationToken;PrintResponse(response);}voidPrintResponse(AgentResponseresponse){Console.WriteLine($"----------------------------------------{index++}----------------------------------------");varhasContinuationToken=response.ContinuationTokenisnull?"No":"Yes";vartext=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来恢复流式响应的处理。我们可以采用如下的方式来实现流式中断恢复:
usingAzure.AI.Projects;usingdotenv.net;usingMicrosoft.Agents.AI;usingMicrosoft.Extensions.AI;usingOpenAI;usingOpenAI.Responses;usingSystem.ClientModel;DotEnv.Load();varmodel=Environment.GetEnvironmentVariable("MODEL")!;varapiKey=Environment.GetEnvironmentVariable("API_KEY")!;varendpoint=Environment.GetEnvironmentVariable("OPENAI_URL")!;varagent=newOpenAIClient(credential:newApiKeyCredential(key:apiKey),options:newOpenAIClientOptions{Endpoint=newUri(endpoint)}).GetResponsesClient().AsAIAgent(model:model);varsession=awaitagent.CreateSessionAsync();varoptions=newChatClientAgentRunOptions{AllowBackgroundResponses=true,};ResponseContinuationToken?continuationToken=null;varindex=0;awaitforeach(varupdateinagent.RunStreamingAsync(message:"制定一份赴日旅游攻略,行程计划5天。",options:options,session:session)){continuationToken=update.ContinuationToken;vartext=update.Text;if(!string.IsNullOrWhiteSpace(text)){Console.Write(text);}index++;if(index==200){Console.WriteLine("\n\n-------------模拟中断,稍后继续-------------\n");break;}}options.ContinuationToken=continuationToken;awaitforeach(varupdateinagent.RunStreamingAsync(options:options,session:session)){continuationToken=update.ContinuationToken;vartext=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。
fromdeepagentsimportcreate_deep_agentfromlangchain_openaiimportChatOpenAI 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/docs2.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方法很相似。
fromlanggraph_sdkimportget_clientfromlanggraph_sdk.clientimportRunStatusfromdatetimeimportdatetimeimportasyncioasyncdefmain():asyncwithget_client(url="http://localhost:2024")asclient:run=awaitclient.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"]whileTrue:run=awaitclient.runs.get(thread_id=thread_id,run_id=run_id)status=run["status"]print(f"[{datetime.now()}]当前状态:{status}")ifstatusin["success","error","timeout","interrupted"]:breakawaitasyncio.sleep(3)result=awaitclient.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逻辑在特定任务下的“单次运算实例”。