[对比学习LangChain和MAF-14]使用消息注入改变对话历史

对话历史作为核心的上下文信息,为LLM建立起对话的语境,是决定LLM响应内容的关键输入之一。对话历史中的每一条消息都绑定这一个固定的角色,典型的角色包括SystemUserAssistantTool。但是这并不意味着每条消息一定要求都要由对应的角色生成,为了提供LLM推理的质量,用户和工具都能根据需求向对话历史注入消息,而且这些消息可以具有任意的角色,前提是消息的结构符合要求。

1. 为什么需要消息注入

对于采用ReAct循环构建的Agent来说,如果不使用Session/Thread,如下的流程体现了对话历史常规的构建模式:

  • 最终作为对话历史的消息列表为空;
  • 用户调用Agent的请求被转换成一条User消息被添加到消息列表中;Agent调用也可以同时指定System消息;
  • 整个对话历史作为输入的一部分提交给LLM,后者创建一个Assistant消息作为响应被添加到消息列表中;
  • 如果回复的Assistant消息携带工具调用,Agent会调用对应的工具,并将每个工具调用生成的结果封装成一个Tool消息被添加到消息列表中;
  • 整个整个对话历史作为输入的一部分在此提交给LLM,后者基于新的对话历史生成新的Assistant消息作为响应被添加到消息列表中;
  • 以此类推,形成一个循环。

如果进一步采用Session/Thread,隶属同一Session的所有消息将会添加到同一个对话历史中,对话历史构建的语境是Agent与LLM进行多轮对话的基础,因为LLM调用是完全无状态的。

在这个典型的ReAct循环中,消息的角色完全与消息的来源是完全一致的的,UserSystem消息由用户输入生成,Assistant消息由LLM生成,Tool消息由工具调用生成,System消息由Agent调用时指定生成。但这并非硬性规定,对话历史仅仅是提供给LLM的上下文信息而已,为了让LLM的响应更符合预期,不仅用户在调用Agent时可以输入一组具有任意角色的消息来模拟一段对话,工具调用也能够在必需的Tool消息之后注入任意的具有有效结构的多条消息。这本质上也是一种典型的提示词工程的手段,典型的应用场景包括:

1.1 复杂工具调用的“少样本示范”

当工具的参数极其复杂(如需要传入特定的JSON结构或SQL 语句),或者需要多工具组合调用时,仅靠系统提示词的文字说明,模型很容易出错。此时可以人工编造1-2轮用户提问 ➔ 模型思考 ➔ 工具返回数据 ➔ 模型最终回答的完整历史,比如:

  • 复杂数据库查询:构建一段历史,展示用户查询“上季度销售额前三的员工”,模型调用execute_sql工具,工具返回一串复杂的JSON数组,模型再将其翻译成话术。让LLM学会如何正确处理工具返回的结构化数据;
  • 多接口级联调用:编造一段历史,演示模型先调用get_user_id,拿到ID后再自动调用get_user_order的过程。模型看到这段历史,就知道自己也可以连续触发工具。

1.2 引导模型进行“反思与纠错”的预热

如果想让Agent具备极强的抗挫折能力(即工具报错时不要懵掉,而是自动换个参数重试),最好的方式就是喂给它一段失败成功史,比如:

  • API权限/参数错误处理:历史中,用户让Agent “查一下明天的天气”。模型第一次调用工具传入了旧版参数,Tool消息返回{"error": "Invalid date format, use YYYY-MM-DD"}。接着构建模型在历史中自我纠错:哦,格式错了,我需要重新调用,并传入正确参数;
  • 代码执行器纠错:在CodeAct场景中,预先注入一段代码运行抛出IndexError的历史,以及模型随后如何修改索引并重新运行成功的历史。这样模型在实际遇到报错时,会模仿历史中的自己去修复Bug。

1.3 规范模型的思考链与内部对白

在Agent中,我们希望模型在调用工具前先写下思考过程,但模型经常会忘记写或者写得很敷衍。此时我们可以构建一段包含高度结构化Assistant思考格式和Tool交互的历史。比如在一个要求极度严谨的财务/医疗推理的Agent中,构建一段历史展现模型在收到用户指令后,先在思考(Thought)中拆解了三个步骤,然后调用Tool A,收到结果后又写了一段对思考的修正计划,再调用Tool B。这相当于给模型立了一个严谨工作的榜样,迫使它在当下的推理中也采用这种深思熟虑的模式。

1.4 模拟历史场景进行“情景剧重现”

有时候我们需要Agent在一个特定的、预设的业务进度中清醒过来并继续工作,而不是从零开始聊天。此时需要生成一段多轮对话和工具调用记录,作为该任务的前情提要。比如:

  • 断点续传/任务接管:一个复杂的自动化分析任务执行到一半中断了。系统在重启任务时,把之前已经发生的对话、已经调用过的工具和拿到的数据,全部打包成一段“历史”喂给新的LLM实例。LLM看到这段构建的历史,就能立刻无缝接管上一个实例的工作进度;
  • 反欺诈/安全演练沙箱:为了测试或训练某个防御性 Agent,系统故意构建一段“黑客(User)正在尝试通过钓鱼工具(Tool)套取信息”的历史,然后让Agent接手最后一轮,看它是否能识别出风险并拒绝。

1.5 冷启动与复杂状态初始化

在用户刚打开软件,还没开始真正聊天时,系统为了给模型注入一些心智背景前置技能,会在首轮对话前,注入几条用户和工具的互动线索。比如在一个作为私人助理Agent初始化时构建几条伪造的记录。虽然用户没说过这话,但LLM读到这段历史后,接下来的所有回复都会自动遵循极简风的设定。

  • User:导入我的个人喜好;
  • Tool:该用户喜欢极简风、讨厌长篇大论。

由于Agent调用自身就支持针对消息列表的输出,所有我们主要关注工具调用后注入消息的场景,接下来我们通过一个简单的例子演示一下基于工具调用的消息注入在MAF和LangChain中该如何实现。

2. 在MAF中利用MessageInjectingChatClient注入消息

在MAF中,我们需要利用MessageInjectingChatClient这个ChatClient中间件向对话历史注入消息。在如下的演示程序中,我们创建了一个ChatClientAgent,并在构建时通过UseMessageInjection方法引入了MessageInjectingChatClient中间件。用户返回指定城市天气信息的工具函数GetWeather模拟了一个工具调用,我们可以返回了不太真实的极端天气信息。

using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Workflows;
using Microsoft.Extensions.AI;
using OpenAI;
using System.ClientModel;
using System.ComponentModel;

dotenv.net.DotEnv.Load();

var endpoint = Environment.GetEnvironmentVariable("OPENAI_URL")!;
var model = Environment.GetEnvironmentVariable("MODEL")!;
var apiKey = Environment.GetEnvironmentVariable("API_KEY")!;

var agent = new OpenAIClient(new ApiKeyCredential(apiKey), new OpenAIClientOptions { Endpoint = new Uri(endpoint) })
    .GetChatClient(model)
    .AsIChatClient()
    .AsBuilder()
    .UseMessageInjection()
    .Build()
    .AsAIAgent(tools: [AIFunctionFactory.Create(GetWeather, nameof(GetWeather))]);
var session = await agent.CreateSessionAsync();
var resonsense = await agent.RunAsync(message: "根据苏州目前天气提供一些着装建议", session:session);
Console.WriteLine(resonsense);

[Description("获取天气信息")]
static string GetWeather([Description("城市")] string city)
{
    var session = AIAgent.CurrentRunContext?.Session
        ?? throw new InvalidOperationException("No active session found.");
    var injector = (AIAgent.CurrentRunContext?.Agent as ChatClientAgent)!
        .ChatClient.GetService<MessageInjectingChatClient>()
        ?? throw new InvalidOperationException("MessageInjectingChatClient is not registered.");

    var reasoning = new ChatMessage(ChatRole.Assistant, [new TextReasoningContent("返回极端天气,真实度存疑,谨慎使用")]);
    injector.EnqueueMessages(session, [reasoning]);
    return $"{city}当前天气晴朗,温度55摄氏度";
}

然会让LLM在推理的时候考虑到提供的天气信息是极端且不太真实的,我们利用注册的MessageInjectingChatClient注入了一条Assistant消息,提供的TextReasoningContent类型的内容模拟了LLM自身的一次推理。添加了这条推理消息,换来的LLM消息内容的最后一条批注:55°C的温度数据似乎有些异常,请确认是否为实际温度。如果是数据有误,可以提供更准确的天气信息,我可以重新给出建议。


我先查询一下苏州当前的天气情况
根据查询结果,苏州目前天气晴朗,温度高达 **55°C**,这属于极端高温天气。以下是着装建议:

---

## 🌞 极端高温着装建议

### 上衣
- **轻薄透气**的短袖T恤、背心或吊带衫
- 选择浅色系(白色、浅灰、浅蓝等),减少吸热
- 优选棉麻、冰丝、速干面料

### 下装
- 短裤、短裙、阔腿裤
- 同样以浅色、透气材质为主

### 鞋履
- 透气网面运动鞋或凉鞋
- 避免厚重密闭的鞋款

### 配饰防护(必备!)
- 🧢 **宽檐遮阳帽**或空顶帽
- 🕶️ **太阳镜**保护眼睛
- ☂️ **遮阳伞**必不可少
- 🧴 **高倍数防晒霜**,定时补涂

### ⚠️ 重要提醒
> 55°C属于极端高温,**尽量减少户外活动**,避免在正午时段(11:00-15:00)外出。多饮水,注意防暑降温,如出现头晕、恶心等症状,及时到阴凉处休息或就医。

---

*注:55°C的温度数据似乎有些异常,请确认是否为实际温度。如果是数据有误,可以提供更准确的天气信息,我可以重新给出建议。*

关于MessageInjectingChatClient的设计与实现,可以参阅我的文章“MessageInjectingChatClient:赋予工具消息注入的能力”

3. 在LangChain中直接修改messages状态

LangChain中并没有所谓的消息注入的概念,不是因为它提供这个功能,而是因为根本就不值得将它作为一个独立的功能被提出来。原因很简单,LangGraph是整个LangChain的基础,任何类型的Agent都是由LangGraph构建的状态图编译而成。整个ReAct循环都是采用状态更新的方式向前推进的,每个节点的输入来源于状态,输出用于更新状态。不论是create_agent还是create_deep_agent创建的Agent,其状态类型都是AgentState,其核心成员messages存储的就是对话历史。既然对话历史已经成员一个常规的状态成员,工具自然就可以采用更新状态成员的方式向对话历史注入消息了。

如下所示的是前面演示实例的LangChain版本,我们在注册的工具函数get_weather返回一个Command对象,并利用设置其update字段来更新状态中的messages成员,从而实现了消息注入的效果。具体来说,我们构造了两个消息,一个使用返回工具执行结果的ToolMessage,另一个是包含了对工具结果的反思内容的AIMessage

from langgraph.types import Command
from langchain.agents import create_agent
from langchain.messages import AIMessage, ToolMessage, ReasoningContentBlock
from langchain.tools import tool, ToolRuntime
from langchain_openai import ChatOpenAI
from dotenv import load_dotenv
import asyncio

load_dotenv()

@tool
def get_weather(location:str, runtime: ToolRuntime,)->Command:
    """根据给定的位置获取当前天气。"""
    reasoning_content= ReasoningContentBlock(type="reasoning", reasoning=f"返回了极端天气,真实度存疑,谨慎使用")
    return Command(update={
        "messages":[
            ToolMessage(content=f"所在地 {location} 的天气是晴天,最高气温55°C。",tool_call_id=runtime.tool_call_id),            
            AIMessage(content_blocks=[reasoning_content]), # type: ignore
        ]
    })

agent = create_agent(
    model=ChatOpenAI(model="gpt-5.2-chat"),
    tools=[get_weather],
)

async def main():
    result = await agent.ainvoke(input={"messages":[{"role":"user","content":"根据当前苏州的天气给我一些穿衣建议。"}]})
    for message in result["messages"]:
        message.pretty_print()

asyncio.run(main())    

为了确认基于ReasoningAIMessage是否真的被注入到对话历史,我们在完成调用后输出了整个对话历史的每一条消息。从如下的输出结果可以看出,这条消息不仅成功注入到了对话历史中,而且LLM在生成最终的回复时也考虑到了这条消息中的内容,并一开始就指出:我查到苏州当前天气为晴天,最高气温 55°C。⚠️ 这个温度异常偏高,可能是天气数据出现错误(正常情况下气温不应达到 55°C)。建议您再确认一下实时天气情况。。而且最终还补了一句:如果您愿意,我也可以帮您再查一次实时天气,或者告诉我您大概出门的时间,我可以给出更具体的建议。

================================ Human Message =================================

根据当前苏州的天气给我一些穿衣建议。
================================== Ai Message ==================================
Tool Calls:
  get_weather (call_xiv2aVXrTAQNeqLaxMcoHoyv)
 Call ID: call_xiv2aVXrTAQNeqLaxMcoHoyv
  Args:
    location: 苏州
================================= Tool Message =================================
Name: get_weather

所在地 苏州 的天气是晴天,最高气温55°C。
================================== Ai Message ==================================

[{'type': 'reasoning', 'reasoning': '返回了极端天气,真实度存疑,谨慎使用'}]

================================== Ai Message ==================================

我查到苏州当前天气为晴天,最高气温 55°C。⚠️ 这个温度异常偏高,可能是天气数据出现错误(正常情况下气温不应达到 55°C)。建议您再确认一下实时天气情况。

如果是**炎热晴天(比如 30°C 以上)**,可以参考以下穿衣建议:

👕 **上衣**:轻薄透气的短袖、背心、速干衣  
👖 **下装**:短裤、薄款长裤或裙装  
👟 **鞋子**:透气运动鞋、凉鞋  
🧢 **防晒**:遮阳帽、太阳镜、防晒霜(SPF30+)  
💧 **其他**:多补充水分,避免正午长时间户外活动  

如果您愿意,我也可以帮您再查一次实时天气,或者告诉我您大概出门的时间,我可以给出更具体的建议。
内容概要:本文介绍了一项创新性未发表的研究,即利用多元宇宙优化算法(Multiverse Optimizer, MVO)对分时电价下的需求响应与综合能源系统调度问题进行建模与求解,旨在实现能源系统的经济性、高效性与可持续性运行。该研究构建了包含多种能源设备(如光伏、风机、燃气轮机、储能系统等)及可调节负荷的综合能源系统模型,充分考虑了用户侧的需求响应行为在分时电价机制下的响应特性,通过MVO算法对系统运行成本、能源利用率、碳排放等多目标进行协同优化,实现了日前调度计划的智能决策。研究还提供了完整的MATLAB代码实现,便于研究人员复现实验、验证算法性能,并为进一步研究提供可靠的仿真基础。; 适合人群:具备一定电力系统、优化算法及MATLAB编程基础的科研人员、研究生以及从事能源互联网、综合能源系统规划与运行的技术工程师。; 使用场景及目标:① 学习并掌握多元宇宙优化算法在复杂能源系统调度中的具体应用方法;② 研究分时电价机制如何通过需求响应引导用户参与电网互动,实现削峰填谷;③ 实现综合能源系统(IES)中冷、热、电、气等多种能源的协同优化调度,以降低运行成本、提高新能源消纳能力系统可靠性;④ 为相关领域的学术研究提供可复现的代码实例仿真平台。; 阅读建议:此资源以MATLAB代码为核心载体,深入剖析了算法应用与系统建模的全过程。建议读者在学习时,不仅应关注代码的实现细节,更要理解其背后的数学模型、优化目标设定约束条件的物理意义。建议结合文档中的模型描述,逐步调试代码,观察不同参数场景下的优化结果,从而深刻掌握综合能源系统优化调度的设计思想与关键技术。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值