第08章:中间件(8)

5、自定义中间件

某些复杂场景下,官方内置的中间件不能完全满足需求,此时可以通过实现LangChain暴露的中间件 hook函数 构建自定义中间件。

说明:尽可能使用内置中间件。

5.1 什么是hook函数(钩子函数)

Hook 函数,中文常叫 钩子函数 ,指的是:在某个既定流程的特定时机,被框架、系统或主程序 自动 调用 的扩展函数

因此,可以把它理解成:

主流程预留了一些插槽,允许你在这些位置挂上自己的函数,这种被挂进去并在特定时机执行的函 数,就是 hook 函数。

核心特点:

  • 不是你主动在业务代码里随便调用的,而是当流程运行到某个"钩子点"时,系统自动触发它
  • 它依附于一个更大的执行流程。比如"请求开始前""模型调用前""任务结束后""异常发生时"等。
  • 它的作用是让你在不改主流程源码的前提下插入自己的逻辑。例如做 日志 、 鉴权 、 修改输入 、 拦截输出 、 清理资源 等。

LangChain的中间件作用在Agent架构中,后者是基于LangGraph构建的流程图。如下列出了六个hook 函数(钩子函数):

无论是官方内置中间件、自定义中间件、还是下文提到的便捷装饰器中间件,通常都是通过实现其中的 一个或多个hook来生效的。

5.2 LangChain的hook函数分类

官方将六个钩子函数按照风格分为两类

类型1:Node-style hooks(节点风格钩子)

顾名思义,它们在流程的 特定节点 运行。 适合顺序逻辑,如记录日志、验证

  • before_agent:在Agent开始运行之前执行
  • before_model:在模型调用之前执行。
  • after_model:在模型调用之后执行。
  • after_agent:在Agent流程全部完成后执行。

类型2:Wrap-style hooks(包装风格钩子)

顾名思义,它们在 模型或工具调用前后 运行。 适合控制流,如重试、回退、缓存。

  • wrap_model_call (包裹模型调用)
  • wrap_tool_call (包裹工具调用)

5.3 Node-style hooks函数用法

支持两种用法

  • 装饰器是函数式挂载,把一个hook快速挂载到Agent的某个节点
  • 类写法是对象化中间件,把中间件封装为一个可配置、可复用、可扩展的组件
1 基本用法

基于装饰器实现

复制代码
from typing import Any

from langchain.agents import AgentState, create_agent
from langchain.agents.middleware import before_model, after_model, before_agent, after_agent, AgentMiddleware
from langchain_core.messages import HumanMessage
from langgraph.runtime import Runtime

@before_model
def before_model_middle(
    state: AgentState,
    runtime: Runtime
) -> dict[str, Any] | None:
    # 模型调用之前
    state["messages"][-1].content += "before_model 额外追加"
    return None


@after_model
def after_model_middle(
    state: AgentState,
    runtime: Runtime
) -> dict[str, Any] | None:
    # 模型调用之后
    state["messages"][-1].content += "after_model 额外追加"
    return None


@before_agent
def before_agent_middle(
    state: AgentState,
    runtime: Runtime
) -> dict[str, Any] | None:
    # Agent 执行之前
    state["messages"][-1].content += "before_agent 额外追加"
    return None


@after_agent
def after_agent_middle(
    state: AgentState,
    runtime: Runtime
) -> dict[str, Any] | None:
    # Agent 执行之后
    state["messages"][-1].content += "after_agent 额外追加"
    return None

from langchain.chat_models import init_chat_model
from dotenv import load_dotenv
import os

# 从 .env 文件中加载环境变量
load_dotenv(override=True)

model = init_chat_model(
    model="deepseek-v4-pro",
    model_provider="deepseek",
    extra_body={"thinking": {"type": "disabled"}},
)
agent = create_agent(
    model=model,
    middleware=[
        before_model_middle,
        after_model_middle,
        before_agent_middle,
        after_agent_middle,
    ]
)
response = agent.invoke({
    "messages": [HumanMessage('你好')]
})
for msg in response["messages"]:
    msg.pretty_print()

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

你好before_agent 额外追加before_model 额外追加

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

你好!我看到你提到了"before_agent"和"before_model"的额外追加,但我需要更多上下文来理解你的具体需求。😊

你是想了解:

  1. 某个框架或系统中的消息处理流程?

  2. Agent 和 Model 之间数据传递的机制?

  3. 还是需要在代码中添加某些前置处理逻辑?

如果你能提供更多细节,比如:

  • 使用的是什么系统/框架(如 LangChain、自研系统等)

  • 具体想实现什么功能

  • 遇到了什么问题

我会更好地帮助你!请随时补充说明。after_model 额外追加after_agent 额外追加

观察HumanMessage可以发现,before_agent钩子先于before_model被执行,二者都在调用模型 之前被执行

. after_agent晚于after_model执行,二者都在模型调用后执行

2. 基于类实现

关键规则:

  1. 必须继承 AgentMiddleware ← 这个固定

  2. 方法名固定 ( before_model , after_model ) ← 这个固定

  3. 类名随意 ← 这个不固定

LangGraph 只看:

是否继承 AgentMiddleware?

是否有 before_model / after_model 等方法?

复制代码
from langchain.agents.middleware import AgentMiddleware
from langchain.agents import AgentState
from langgraph.runtime import Runtime
from typing import Any


class MyMiddleware(AgentMiddleware):

    def before_model(
        self,
        state: AgentState,
        runtime: Runtime,
    ) -> dict[str, Any] | None:
        # 模型调用之前
        state["messages"][-1].content += "before_model 额外追加"
        return None

    def after_model(
        self,
        state: AgentState,
        runtime: Runtime,
    ) -> dict[str, Any] | None:
        # 模型调用之后
        state["messages"][-1].content += "after_model 额外追加"
        return None

    def before_agent(
        self,
        state: AgentState,
        runtime: Runtime,
    ) -> dict[str, Any] | None:
        # Agent 执行之前
        state["messages"][-1].content += "before_agent 额外追加"
        return None

    def after_agent(
        self,
        state: AgentState,
        runtime: Runtime,
    ) -> dict[str, Any] | None:
        # Agent 执行之后
        state["messages"][-1].content += "after_agent 额外追加"
        return None

from langchain.chat_models import init_chat_model
from dotenv import load_dotenv
from langchain.agents import  create_agent
from langchain_core.messages import HumanMessage
test_middleware=MyMiddleware()

# 从 .env 文件中加载环境变量
load_dotenv(override=True)

model = init_chat_model(
    model="deepseek-v4-pro",
    model_provider="deepseek",
    extra_body={"thinking": {"type": "disabled"}},
)
agent = create_agent(
    model=model,
    middleware=[test_middleware]
)
response = agent.invoke({
    "messages": [HumanMessage('你好')]
})
for msg in response["messages"]:
    msg.pretty_print()
  1. before_model 通常的场景:

消息修剪(trim messages ,PII 脱敏,输入验证 ,条件路由

  1. after_model 通常的场景:

输出验证,格式化响应,统计信息,状态更新

2 两种方法的统一

装饰器底层会基于我们重写的方法构造一个 AgentMiddleware子类 的实例,以 @after_model 装饰器 底层实现为例,关键代码如下

复制代码
return type(
    middleware_name,
    (AgentMiddleware,),
    {
        "state_schema": state_schema or AgentState,
        "tools": tools or [],
        "after_model": wrapped,
    },
)()

这是after_model最终返回的内容。

上述代码中的wrapped是after_model内部的装饰器,代码如下

复制代码
def wrapped(
    _self: AgentMiddleware[StateT, ContextT],
    state: StateT,
    runtime: Runtime[ContextT],
) -> dict[str, Any] | Command[Any] | None:
    return func(state, runtime)  # type: ignore[return-value]

上述代码等价于

复制代码
return type(
    middleware_name,
    (AgentMiddleware,),
    {
        "state_schema": state_schema or AgentState,
        "tools": tools or [],
        "after_model": func(state, runtime),
    },
)()

而 func(state, runtime) 正是我们定义的、被 @after_model 修饰的函数,在上述案例中对应的是 after_model_middleware,

上述代码的含义是

    1. 创建一个AgentMiddleware的子类
    1. 类名为middleware_name,即创建agent时传递的中间件名称,上述案例中是 after_model_middleware
    1. 这个子类有两个属性 state_schema 和 tools
    1. 有一个方法: after_model ,逻辑等同于 func(state, runtime) 。
    1. 最后的括号 () 表示实例化子类,返回一个对象

所以,用装饰器最终返回的也是一个AgentMiddleware的子类对象,并且重写了after_model方法,和 基于类的自定义方式本质是一样的。

3 参数说明

Node-style hooks函数有两个参数

state: 是一个AgentState实例,维护Agent运行过程中的状态,这类状态会随着Agent的运行而发生变 化,包括 消息列表

runtime: 是一个Runtime实例,维护Agent运行过程中的上下文环境,包括 上下文 、 长期记忆 等。

4 返回值说明

返回 None:不修改状态(不修改Agent状态)

复制代码
def before_model(self, state, runtime):
    print("日志记录")
    return None  # 不做任何修改,继续流程

返回字典:更新状态

复制代码
def after_model(self, state, runtime):
    count = state.get("count", 0)

    return {
        "count": count + 1
    }  # 更新状态中的 count

返回 {"jump_to": "..."}:控制流程

复制代码
def before_model(self, state, runtime):
    if state.get("count", 0) > 10:
        return {"jump_to": "__end__"}  # 跳过模型,直接结束

    return None

jump_to 目标:

  • "end" - 结束 Agent
  • "tools" - 跳到工具节点
  • 其他自定义节点
5 装饰器参数:can_jump_to

这里就涉及到Node-style的四个hook函数可以接收额外参数 can_jump_to 。

钩子函数可以 改变Agent正常的运行轨迹 。比如:发现上下文窗口溢出,直接跳转至结尾,提前终止整 个Agent。

can_jump_to 决定了钩子函数可以直接跳转至流程的哪些位置,可取值如下:

  • end:跳转至Agent流程末尾,或第一个after_agent钩子,直接终止整个流程。
  • tools:跳转至工具节点。
  • model:跳转至模型节点,或第一个before_model钩子。

1. 基于装饰器实现

复制代码
from typing import Any

from langchain.agents import AgentState, create_agent
from langchain.agents.middleware import after_model, before_model
from langchain.messages import AIMessage, SystemMessage
from langchain.tools import tool
from langgraph.runtime import Runtime

import os
from langchain_openai import ChatOpenAI
from dotenv import load_dotenv

#读取.env配置文件 verbose相关的环境变量以env为准
load_dotenv(verbose=True)

AICODE_KEY = os.getenv("AICODE_KEY")
AICODE_URL = os.getenv("AICODE_URL")
# 获取大模型
model = ChatOpenAI(
    model='gpt-5.5',
    api_key=AICODE_KEY,
    base_url=AICODE_URL,
)


@tool
def get_news() -> str:
    """获取当日新闻"""
    return "美加墨世界杯今日开幕"


# ============================================================
# before_model:在模型(LLM)执行前触发
# 允许跳转到 "tools" 节点
# ============================================================
@before_model(can_jump_to=["tools"])
def force_tool_first(
    state: AgentState,
    runtime: Runtime,
) -> dict[str, Any] | None:
    """
    【业务场景:强行拦截并触发工具】

    如果用户输入包含 "direct tool",
    则跳过本次大模型的思考/生成阶段,
    直接伪造一个大模型的 tool_calls 意图,
    强行把控制权移交给工具执行节点。
    """

    text = state["messages"][-1].content

    # 检查关键词,满足条件则强行干预流程
    if isinstance(text, str) and "direct tool" in text.lower():
        print(
            "[MIDDLEWARE] before_model: "
            "jump_to='tools'"
        )

        # 人工构造一个大模型的消息对象(AIMessage)
        # 欺骗系统,让系统误以为这是模型自己决定要调用的工具
        fake_tool_call = AIMessage(
            content="人工构造的消息",
            tool_calls=[
                {
                    "name": "get_news",
                    "args": {},
                    "id": "call_force_weather_001",
                }
            ],
        )

        # 返回更新后的状态:
        # 1. 注入伪造的 AIMessage
        # 2. 明确指定下一步跳转到 "tools" 节点
        return {
            "messages": [fake_tool_call],
            "jump_to": "tools",
        }

    # 如果不满足触发条件,返回 None,
    # 流程正常向下流转(继续让 LLM 思考)
    return None


# ============================================================
# after_model:在模型(LLM)执行生成之后触发
# 允许重新跳转回 "model" 节点
# ============================================================
@after_model(can_jump_to=["model"])
def retry_with_extra_instruction(
    state: AgentState,
    runtime: Runtime,
) -> dict[str, Any] | None:
    """
    【业务场景:反思/重试机制】

    如果大模型已经生成了回答,
    但发现用户最初的请求包含 "retry model",
    则动态追加一条系统提示词,
    强行让模型重新生成(重试)一次。
    """

    # 倒序遍历消息历史,
    # 找到最近的一次用户输入(human 消息)
    user_text = ""

    for msg in reversed(state["messages"]):
        if getattr(msg, "type", "") == "human":
            user_text = getattr(msg, "content", "")
            break

    # 检查用户输入是否包含触发重试的关键字
    if isinstance(user_text, str) and "retry model" in user_text.lower():

        # 【核心防御】:防止无限循环重跳(死循环)
        #
        # 检查消息历史中是否已经注入过这条特殊的系统提示。
        # 如果有,说明已经重试过,不再重复干预。
        already_injected = any(
            isinstance(getattr(msg, "content", None), str)
            and "你必须以【二次回答】开头" in msg.content
            for msg in state["messages"]
        )

        if already_injected:
            return None

        print(
            "[MIDDLEWARE] after_model: "
            "jump_to='model' with extra system instruction"
        )

        # 返回更新后的状态:
        # 1. 追加强力约束的系统消息
        # 2. 将指针跳回 "model"
        # 3. 重新执行模型
        return {
            "messages": [
                SystemMessage(
                    content="你必须以【二次回答】开头,并且只用一句话回答。"
                )
            ],
            "jump_to": "model",
        }

    return None


# ============================================================
# before_model:在模型(LLM)执行前触发
# 允许直接跳转到 "end" 节点(强行终止)
# ============================================================
@before_model(can_jump_to=["end"])
def overflow_context_processor(
    state: AgentState,
    runtime: Runtime,
) -> dict[str, Any] | None:
    """
    【业务场景:安全卫士/异常拦截】

    模拟上下文窗口溢出(Token 超限)
    或其他严重的系统阻断情况。

    一旦触发,直接熔断流程,
    拒绝让大模型继续处理,
    直接返回兜底文案。
    """

    # 假装发生上下文溢出,
    # 模拟检查最后一条消息是否包含 overflow 标识
    text = state["messages"][-1].content

    if isinstance(text, str) and "overflow" in text.lower():
        print(
            "[MIDDLEWARE] before_model: "
            "jump_to='end' when context window overflow"
        )

        # 构造兜底的结束消息,
        # 并直接指定跳转到 "end" 终止 Agent 运行
        return {
            "messages": [
                AIMessage(
                    content="上下文窗口溢出,终止"
                )
            ],
            "jump_to": "end",
        }

    return None


# ============================================================
# 创建 Agent
# ============================================================
agent = create_agent(
    model=model,
    tools=[get_news],
    # 将定义的中间件按照顺序挂载到 Agent 中
    # 注意:执行顺序会严格按照列表声明顺序
    middleware=[
        force_tool_first,
        retry_with_extra_instruction,
        overflow_context_processor,
    ],
)


# ============================================================
# 执行 Agent
# ============================================================
def run_once(user_input: str):
    result = agent.invoke(
        {
            "messages": [
                {
                    "role": "user",
                    "content": user_input,
                }
            ]
        }
    )

    for msg in result["messages"]:
        msg.pretty_print()


# ============================================================
# 测试
# ============================================================
if __name__ == "__main__":

    # ========================================================
    # Case 1:直接跳 tools
    #
    # 预期表现:
    # 1. 触发 force_tool_first
    # 2. 打印 jump_to='tools'
    # 3. 绕过 LLM 的首轮思考
    # 4. 直接调用 get_news 工具
    # 5. 工具返回结果后,LLM 总结工具结果并输出
    # ========================================================
    print("=" * 30, "-> Case 1 <-", "=" * 30)

    run_once(
        "请帮我查今日新闻 direct tool"
    )

    # ========================================================
    # Case 2:输出后跳回 model
    #
    # 预期表现:
    # 1. 正常进入 LLM,生成第 1 版回答
    # 2. 触发 retry_with_extra_instruction
    # 3. 注入 SystemMessage
    # 4. jump_to='model'
    # 5. LLM 被重新拉回并生成第 2 版回答
    # 6. 最终输出应该带有"【二次回答】"
    # ========================================================
    print("=" * 30, "-> Case 2 <-", "=" * 30)

    run_once(
        "请随便介绍一下 LangChain retry model"
    )

    # ========================================================
    # Case 3:上下文溢出
    #
    # 预期表现:
    # 1. 触发 overflow_context_processor
    # 2. 打印终止信息
    # 3. 直接跳转到 end
    # 4. LLM 根本不会接收到这个请求
    # ========================================================
    print("=" * 30, "-> Case 3 <-", "=" * 30)

    run_once(
        "你好 overflow"
    )

    # ========================================================
    # Case 4:正常流程
    #
    # 预期表现:
    # 1. 没有任何中间件被触发
    # 2. Agent 走正常的 OOTB
    #    (Out of the box)标准工作流
    #
    # User
    #   ↓
    # Model
    #   ↓
    # Call Tool
    #   ↓
    # Model
    #   ↓
    # End
    # ========================================================
    print("=" * 30, "-> Case 4 <-", "=" * 30)

    run_once(
        "今日新闻摘要?"
    )

============================== -> Case 1 <- ==============================

MIDDLEWARE before_model: jump_to='tools'

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

请帮我查今日新闻 direct tool

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

人工构造的消息

Tool Calls:

get_news (call_force_weather_001)

Call ID: call_force_weather_001

Args:

================================= Tool Message =================================

Name: get_news

美加墨世界杯今日开幕

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

今日新闻:美加墨世界杯今日开幕。

============================== -> Case 2 <- ==============================

MIDDLEWARE after_model: jump_to='model' with extra system instruction

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

请随便介绍一下 LangChain retry model

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

LangChain 里的 "retry model" 通常指两类重试能力:一种是对 **LLM 调用失败** 的重试,另一种是对 **模型输出不符合格式** 的重试修复。

**1. LLM 调用失败重试**

如果模型接口因为网络、限流、服务暂时不可用等原因失败,LangChain 可以自动重试。很多 LangChain 的 runnable/model 都支持 `.with_retry()`。

例如:

```python

from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model="gpt-4o-mini")

llm_with_retry = llm.with_retry(

stop_after_attempt=3

)

response = llm_with_retry.invoke("介绍一下 LangChain")

print(response.content)

```

这里的意思是:调用失败时最多尝试 3 次。它主要解决的是工程层面的不稳定问题,比如超时、429、临时 500 错误。

**2. 输出解析失败重试**

另一种常见场景是:你要求模型输出 JSON、结构化字段或某种格式,但模型返回的内容不符合解析器要求。LangChain 提供了 `RetryOutputParser` 之类的工具,让模型根据原始 prompt 和错误输出再试一次。

大致流程是:

```text

用户请求 -> 模型输出 -> parser 解析失败 -> 把错误和原始要求再发给模型 -> 模型重新生成

```

它不是简单地重复调用,而是会告诉模型哪里错了,让它"修正输出"。

典型用途:

```python

from langchain.output_parsers import RetryOutputParser

```

现在新版 LangChain 更推荐用 LCEL / Runnable 风格来组合 parser、model 和 retry 逻辑。

**3. retry 适合解决什么问题**

适合:

  • API 临时失败

  • 模型返回格式偶尔不稳定

  • JSON 解析失败

  • 工具调用参数格式错误

  • Agent 某一步偶发失败

不适合:

  • prompt 本身写得不清楚

  • schema 设计不合理

  • 模型能力不足

  • 业务逻辑错误

  • 每次都失败的确定性错误

也就是说,retry 是"兜底机制",不是 prompt 设计或系统设计的替代品。

**4. 常见参数**

常见会配置这些东西:

```python

.with_retry(

stop_after_attempt=3,

wait_exponential_jitter=True

)

```

含义大概是:

  • `stop_after_attempt`: 最多重试几次

  • `wait_exponential_jitter`: 使用指数退避并加随机抖动,避免短时间内反复打爆 API

**5. 实际建议**

生产环境里一般会这样用:

```python

llm = ChatOpenAI(

model="gpt-4o-mini",

timeout=30,

max_retries=2

)

chain = prompt | llm | parser

chain = chain.with_retry(stop_after_attempt=3)

```

如果是结构化输出,最好优先用:

```python

llm.with_structured_output(MySchema)

```

再配合 retry,而不是完全依赖模型"自觉"输出合法 JSON。

简单说,LangChain retry model 的核心价值就是:让 LLM 应用在面对接口抖动和模型输出不稳定时更可靠。但它应该作为可靠性补充,而不是主要控制手段。

================================ System Message ================================

你必须以【二次回答】开头,并且只用一句话回答。

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

【二次回答】LangChain retry model 通常用于在模型调用失败或输出解析失败时自动重试,例如通过 `.with_retry(stop_after_attempt=3)` 处理网络、限流、超时等临时错误,或用 `RetryOutputParser` 让模型根据解析错误重新生成结构化结果,但它只适合作为可靠性兜底,不能替代清晰的 prompt、合理的 schema 和正确的业务逻辑。

============================== -> Case 3 <- ==============================

MIDDLEWARE before_model: jump_to='end' when context window overflow

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

你好 overflow

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

上下文窗口溢出,终止

============================== -> Case 4 <- ==============================

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

今日新闻摘要?

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

Tool Calls:

get_news (call_vvyUBdFjyHQkzyR9whajZWpc)

Call ID: call_vvyUBdFjyHQkzyR9whajZWpc

Args:

================================= Tool Message =================================

Name: get_news

美加墨世界杯今日开幕

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

今日新闻摘要:美加墨世界杯今日开幕。

我们提前判定需要调用工具,直接在before_model中跳转至工具节点,省去了一次模型调用

通过约定的 retry model标记 ,在after_model之后再次跳转到模型节点,触发模型重复调用

通过约定的 overflow标记 ,模拟上下文窗口溢出,在before_model中直接跳转至结尾,提前终 止流程

Case 4 是没有被干预的正常Agent流程,作为对照。

  1. 基于类实现

和基于装饰器实现的关键区别在于:需要引入额外的装饰器 @hook_config 为 can_jump_to 传参。

复制代码
from typing import Any

from langchain.agents import create_agent
from langchain.agents.middleware import hook_config, AgentState, AgentMiddleware
from langchain.messages import AIMessage, SystemMessage
from langchain.tools import tool
from langgraph.runtime import Runtime


@tool
def get_news() -> str:
    """获取当日新闻"""
    return f"美加墨世界杯今日开幕"


class MyMiddleware(AgentMiddleware):

    @hook_config(can_jump_to=["tools", "end"])
    def before_model(
        self,
        state: AgentState,
        runtime: Runtime,
    ) -> dict[str, Any] | None:

        text = state["messages"][-1].content

        # 假装溢出
        if "overflow" in text:
            print(
                "[MIDDLEWARE] before_model: "
                "jump_to='end' when context window overflow"
            )

            return {
                "messages": [
                    AIMessage("上下文窗口溢出,终止")
                ],
                "jump_to": "end",
            }

        if isinstance(text, str) and "direct tool" in text.lower():
            print("[MIDDLEWARE] before_model: jump_to='tools'")

            fake_tool_call = AIMessage(
                content="人工构造的消息",
                tool_calls=[
                    {
                        "name": "get_news",
                        "args": {},
                        "id": "call_force_weather_001",
                    }
                ],
            )

            return {
                "messages": [fake_tool_call],
                "jump_to": "tools",
            }

        return None

    @hook_config(can_jump_to=["model"])
    def after_model(
        self,
        state: AgentState,
        runtime: Runtime,
    ) -> dict[str, Any] | None:

        user_text = ""

        for msg in reversed(state["messages"]):
            if getattr(msg, "type", "") == "human":
                user_text = getattr(msg, "content", "")
                break

        if isinstance(user_text, str) and "retry model" in user_text.lower():

            # 防止无限重跳:如果已经加过提示,就不再跳
            already_injected = any(
                isinstance(getattr(msg, "content", None), str)
                and "你必须以【二次回答】开头" in msg.content
                for msg in state["messages"]
            )

            if already_injected:
                return None

            print(
                "[MIDDLEWARE] after_model: "
                "jump_to='model' with extra system instruction"
            )

            return {
                "messages": [
                    SystemMessage(
                        "你必须以【二次回答】开头,并且只用一句话回答。"
                    )
                ],
                "jump_to": "model",
            }

        return None


agent = create_agent(
    model=model,
    tools=[get_news],
    middleware=[MyMiddleware()],
)


def run_once(user_input: str):
    result = agent.invoke(
        {
            "messages": [
                {
                    "role": "user",
                    "content": user_input,
                }
            ]
        }
    )

    for msg in result["messages"]:
        msg.pretty_print()


if __name__ == "__main__":

    # Case 1: 直接跳 tools
    print("=" * 30, "-> Case 1 <-", "=" * 30)
    run_once("请帮我查今日新闻 direct tool")

    # Case 2: 输出后跳回 model
    print("=" * 30, "-> Case 2 <-", "=" * 30)
    run_once("请随便介绍一下 LangChain retry model")

    # Case 3: 上下文窗口溢出,直接结束
    print("=" * 30, "-> Case 3 <-", "=" * 30)
    run_once("你好 overflow")

    # Case 4: 正常流程
    print("=" * 30, "-> Case 4 <-", "=" * 30)
    run_once("今日新闻摘要?")
相关推荐
睡觉时不困4429 小时前
15.LangChain 1.0+ 第三篇:LangSmith——给 AI 应用装上监控台.发布清单
langchain
gis分享者10 小时前
LangChain 和 LlamaIndex 有什么区别?各自适合什么场景?
人工智能·ai·langchain·场景·区别·llamaindex
北斗落凡尘21 小时前
LangGraph 入门实战(12)--使用MCP
后端·python·langchain
赵广陆1 天前
企业实战:Web服务端搭建
前端·langchain·langgraph
扬大平仔1 天前
用 Java 对齐 LangChain Deep Agents Harness(二)
java·python·langchain
北斗落凡尘2 天前
LangGraph 入门实战(11)--输出模式
后端·python·langchain
赵广陆2 天前
企业实战:数据图与状态定义
pycharm·langchain·langgraph
青山是哪个青山2 天前
LangChain 学习笔记(九):上下文与记忆
笔记·学习·langchain
一只叫煤球的猫2 天前
Spring AI 2.0 源码解析(二):Starter 如何自动装配 ChatClient ?
后端·面试·langchain