day15-LangChain高级组件之工具-短期记忆-护栏-MCP-人机交互

今日内容

# 1 工具调用策略--》自定义消息内容
	-ToolStrategy 有个参数:tool_message_content--》控制自定义输出的内容
    
from langchain.agents import create_agent
from langchain_openai import ChatOpenAI
from typing import Literal
from langchain.tools import tool
from pydantic import BaseModel, Field
from langchain.agents.structured_output import ToolStrategy
# 1 连接model
model = ChatOpenAI(
    model="qwen-plus",
    api_key="sk-6459007b813946289da12857950c955b",
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)
# 2  会议行动类---》格式化输出,大模型返回这个类的对象:MeetingAction
class MeetingAction(BaseModel):
    """Action items extracted from a meeting transcript."""
    task: str = Field(description="The specific task to be completed")
    assignee: str = Field(description="Person responsible for the task")
    priority: Literal["low", "medium", "high"] = Field(description="Priority")

# 3 工具:保存会议---》调用数据库,保存--》案例中只是做了打印
@tool
def save_meeting(name: str,action:str):
    """Action item captured and added to meeting notes."""
    print('---',name)
    print('---',action)


# 4 创建agent
agent = create_agent(
    model=model,
    tools=[save_meeting],
    response_format=ToolStrategy( # 返回格式
        schema=MeetingAction,
        tool_message_content="Action item captured and added to meeting notes!"
    )
)

result = agent.invoke({
    "messages": [{"role": "user", "content": "From our meeting: Sarah needs to update the project timeline as soon as possible"}]
})
print(result['messages'][-1])

# 至于tool是否被调用---》是大模型自己决定的,不是我们
# 没有带tool_message_content:返回格式这样:MeetingAction 对象
# Returning structured response: task='Update the project timeline' assignee='Sarah' priority='high'" name='MeetingAction'

# 带tool_message_content---》再返回的时候,格式就不是 MeetingAction 对象格式,而是-Action item captured and added to meeting notes!

# 但是我们取 result[‘response_struct’]---》还是 MeetingAction 对象

1 LangChain核心组件之工具

# 1 概念
许多 AI 应用程序通过自然语言与用户交互。然而,某些用例要求模型使用结构化输入直接与外部系统(例如 **API**、**数据库** 或 **文件系统**)对接。

**工具** 是 代理(agents)调用来执行操作的组件。它们通过允许模型通过定义明确的输入和输出与世界交互来扩展模型的功能。工具封装了一个可调用的函数及其输入架构(schema)。这些可以传递给兼容的 聊天模型(chat models),让模型决定是否以及使用什么参数来调用工具。在这些场景中,**工具调用** 使模型能够生成符合指定输入架构的请求

# 总结:通过工具,来扩展大模型的能力
	-大模型不能联网
    -无法知道实时消息
    -公司内部的规章
# model 调用工具--》流程跟之前我们讲 function calling一样,需要自己做
# agent 调用工具--》只需要传入工具,LangChain会自动调用工具

# 知识点
	-创建工具-》参数
    -工具中访问上下文【context】
    -ToolNode--》langraph
    -预构建工具--》LangChain内置的工具
    -大模型服务的工具调用--》大模型本身支持工具

1.1 创建工具

#1  基本工具定义 (Basic tool definition)
创建工具最简单的方法是使用 @tool 装饰器。默认情况下,函数的 文档字符串(doc string)会成为工具的描述,帮助模型理解何时使用它

# 2 自定义工具属性 (Customize tool properties)
	-自定义工具名称 (Custom tool name)
    -自定义工具描述 (Custom tool description)
    
# 3 高级架构定义 (Advanced schema definition)
#######  1 基本定义####### ####### ####### ####### 
'''
基本工具定义:创建工具最简单的方法是使用 @tool 装饰器。默认情况下,函数的 文档字符串(docstring)会成为工具的描述,帮助模型理解何时使用它
类型提示 是必需的,因为它们定义了工具的输入架构。文档字符串应该信息丰富且简洁,以帮助模型理解工具的用途

from langchain.tools import tool
@tool
def search_database(query: str, limit: int = 10) -> str:
    """Search the customer database for records matching the query.

    Args:
        query: Search terms to look for
        limit: Maximum number of results to return
    """
    return f"Found {limit} results for '{query}'"
'''
from langchain.agents import create_agent

####### ####### ####### ####### 2 自定义工具描述 (Custom tool description):覆盖自动生成的工具描述,以提供更清晰的模型指导####### ####### ####### ####### ####### ####### ####### ####### ####### ####### ####### ####### 
'''
from langchain.tools import tool
@tool("calculator", description="Performs arithmetic calculations. Use this for any math problems.")
def calc(expression: str) -> str:
    """Evaluate mathematical expressions."""
    return str(eval(expression))
'''

####### ####### ####### #######  3 高级架构定义 (Advanced schema definition):使用 Pydantic 模型 或 JSON 架构 定义复杂的输入####### ####### ####### ####### ####### ####### ####### ####### 
### 描述每个参数的含义####
''' 3.1 Pydantic 模型

from pydantic import BaseModel, Field
from typing import Literal
from langchain.tools import tool
class WeatherInput(BaseModel):
    """Input for weather queries."""
    location: str = Field(description="City name or coordinates")
    units: Literal["celsius", "fahrenheit"] = Field(
        default="celsius",
        description="Temperature unit preference"
    )
    include_forecast: bool = Field(
        default=False,
        description="Include 5-day forecast"
    )
# 工具作用,通过 函数注释,给大模型理解
# 描述每个参数的含义---》
@tool(args_schema=WeatherInput)
def get_weather(location: str, units: str = "celsius", include_forecast: bool = False) -> str:
    """Get current weather and optional forecast."""
    temp = 22 if units == "celsius" else 72
    result = f"Current weather in {location}: {temp} degrees {units[0].upper()}"
    if include_forecast:
        result += "\nNext 5 days: Sunny"
    return result
    
'''

'''

## 3.2 json形式
from langchain.tools import tool
weather_schema = {
    "type": "object",
    "properties": {
        "location": {"type": "string"},
        "units": {"type": "string"},
        "include_forecast": {"type": "boolean"}
    },
    "required": ["location", "units", "include_forecast"]
}

@tool(args_schema=weather_schema)
def get_weather(location: str, units: str = "celsius", include_forecast: bool = False) -> str:
    """Get current weather and optional forecast."""
    temp = 22 if units == "celsius" else 72
    result = f"Current weather in {location}: {temp} degrees {units[0].upper()}"
    if include_forecast:
        result += "\nNext 5 days: Sunny"
    return result
'''

1.2 访问上下文

# 1 当工具可以访问Agent 状态、运行时上下文和长期记忆时,它们的功能最强大。这使得工具能够做出上下文感知的决策、个性化响应并在对话中维护信息。

# 2 工具可以通过 ToolRuntime 参数访问运行时信息,该参数提供:
	# 使用 ToolRuntime 在一个参数中访问所有运行时信息。只需将 runtime: ToolRuntime 添加到您的工具签名中,它就会被自动注入,而不会暴露给 LLM
    State(状态) - 流经执行的可变数据(消息、计数器、自定义字段)
    Context(上下文) - 不可变的配置,如用户 ID、会话详细信息或特定于应用程序的配置
    Store(存储) - 跨对话的持久长期记忆
    
    Stream Writer(流写入器) - 在工具执行时流式传输自定义更新
    Config(配置) - 执行的 RunnableConfig
    Tool Call ID(工具调用 ID) - 当前工具调用的 ID
    
    
# 3 知识点
    Short-term memory (State) 短期记忆
    Context  上下文
    Long-term memory (Store)  长期记忆
    Stream writer  流写入器
Component 组成 Description 描述 Use case 使用场景
State 状态 短期记忆——当前对话(消息、计数器、自定义字段)存在的可变数据 访问通话记录,跟踪工具通话次数
Context 上下 不可变配置在调用时传递(用户 ID,会话信息) 根据用户身份个性化回答
Store 存储 长期记忆——能够在对话中保存下来的持久数据 保存用户偏好,维护知识库
**Stream Writer **流写入器 在工具执行过程中实时发布更新 展示长期运营的进展
Config 配置 用于执行 RunnableConfig 访问回调、标签和元数据
Tool Call ID 工具调用 ID 前工具调用的唯一标识符 日志调用和模型调用的相关工具

image-20260301005725971

# 1 Short-term memory (State) 短期记忆:State代表对话期间存在的短期记忆。它包括消息历史和你在graph state 中定义的任何自定义字段
#-tools中可以使用 ToolRuntime 访问当前的state

from langchain.tools import tool, ToolRuntime
from langchain.messages import HumanMessage
from langgraph.types import Command
from langchain_openai import ChatOpenAI
from langchain.agents import create_agent
from langchain.agents import AgentState

# 1 创建模型
# model = ChatOpenAI(
#     model="qwen-plus",
#     api_key="sk-6459007b813946289da12857950c955b",
#     base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
# )
model = ChatOpenAI(
    model="deepseek-chat",
    api_key="sk-8b83ef66aa604b449bfb3b900405b050",
    base_url="https://api.deepseek.com",
)


# 2 定义了一个类:AgentState---》短期记忆---》
class CustomState(AgentState):
    user_preferences: dict

# 3 定于一个工具 获取最近一条用户消息
@tool
def get_last_user_message(runtime: ToolRuntime) -> str:
    """获取最近一条用户消息."""
    # 3.1 短期 记忆,记录用户的历史对话--》拿出来所有对话【用户消息,AI消息,工具消息】
    messages = runtime.state["messages"]
    # Find the last human message
    # 3.2 反转列表
    for message in reversed(messages):
        # 3.3 这条消息是不是用户消息
        if isinstance(message, HumanMessage):
            return message.content
    return "No user messages found"

# 4 获取用户信息 :
# state 中的 字段fields :tool_runtime 参数对模型是隐藏的。对于示例,模型在工具架构中只看到 pref_name - tool_runtime 不包含在请求中
@tool
def get_user_preference(
    pref_name: str,
    runtime: ToolRuntime
) -> str:
    """获取用户传入的参数."""

    # 333--工具中,通过runtim,拿到user_preferences--》就是用户invoke调用大模型时传入的
    preferences = runtime.state.get("user_preferences", {}) # 当时用户交互时传入的 {"username": "liuqingzheng", "age": "19"}
    print(preferences)
    # pref_name 是动态变化的:假设是 username--》
    return preferences.get(pref_name, "Not set")



# 5 用 runtime来更新Agent的状态
@tool
def set_user_name(new_name: str,runtime: ToolRuntime):
    """修改用户传入的额外信息中的username属性."""
    print(new_name)
    runtime.state["user_preferences"]["username"] = new_name
    # return Command(update={"username": new_name})

# 6  创建智能体
agent = create_agent(
    model,
    tools=[get_last_user_message,get_user_preference,set_user_name],
    # 1111 ---在这 传了 state_schema---》规定类型:CustomState 必须继承 AgentState
    state_schema=CustomState
)

# 7 智能体现在可以跟踪消息之外的额外状态
result = agent.invoke({
    "messages": [
        {"role": "user", "content": "你是一个喜剧演员"},
        {"role": "user", "content": "我是最帅的"},
        {"role": "user", "content": "获取最近一条用户消息"},           # 执行工具get_last_user_message 获取的
        {"role": "user", "content": "获取用户的username属性"},      # 执行工具:get_user_preference,解析出username 传入,获取---》liuqingzheng
        {"role": "user", "content": "设置用户的username属性为xxx"}, # 执行工具set_user_name--》改了名字
        {"role": "user", "content": "获取用户的username属性"},      # 执行工具:get_user_preference,解析出username 传入,获取---》xxx
    ],
    # 222--- 在这传入字典--》创建出 CustomState的对象--》把字典给CustomState的对象---》后续再runtime中就能拿到
    "user_preferences": {"username": "liuqingzheng", "age": "19"}, # 创建agent时,写了state_schema,才能这样传入,传入后是CustomState的对象
    # user_preferences={"username": "liuqingzheng", "age": "19"}
})
print(result['messages'][-1])

# 2 Context
# 上下文:通过 runtime.context 访问不可变的配置和上下文数据,例如用户 ID、会话详细信息或特定于应用程序的配置
from langchain.tools import tool, ToolRuntime
from langchain.messages import HumanMessage
from langgraph.types import Command
from langchain_openai import ChatOpenAI
from langchain.agents import create_agent
from langchain.agents import AgentState
from dataclasses import dataclass
model = ChatOpenAI(
    model="qwen-plus",
    api_key="sk-6459007b813946289da12857950c955b",
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
)

USER_DATABASE = {
    "user123": {
        "name": "Alice Johnson",
        "account_type": "Premium",
        "balance": 5000,
        "email": "alice@example.com"
    },
    "user456": {
        "name": "Bob Smith",
        "account_type": "Standard",
        "balance": 1200,
        "email": "bob@example.com"
    }
}

@dataclass
class UserContext:
    user_id: str

# 定义工具--》获取用户信息
@tool
def get_account_info(runtime: ToolRuntime[UserContext]) -> str:
    """Get the current user's account information."""
    user_id = runtime.context.user_id
    if user_id in USER_DATABASE:
        user = USER_DATABASE[user_id]
        return f"Account holder: {user['name']}\nType: {user['account_type']}\nBalance: ${user['balance']}"
    return "User not found"

# 创建agent
agent = create_agent(
    model,
    tools=[get_account_info],
    context_schema=UserContext, # 传入 context_schema--》UserContext类型--》指定类型
    system_prompt="You are a financial assistant."
)
result = agent.invoke(
    # 你是谁?我哪里知道你有多少钱?---》有个工具get_account_info--》调用工具--》再工具中通过 runtime拿到对话中上下文的user_id
    # 本地有个数据库---》根据数据库,拿到user_id 对应人的金钱数
    # 举个例子--》张三 也问豆包,  李四也问豆包--》我有多少钱?
    # 返回的钱:张三是张三的,  李四是李四的
    {"messages": [{"role": "user", "content": "What's my current balance?"}]},
    
    context=UserContext(user_id="user123")
)
print(result['messages'][-1])


# 3 Long-term memory (Store)  长期记忆:
# 使用 存储(store) 访问跨对话的持久数据。通过 runtime.store 访问存储,它允许您保存和检索特定于用户或特定于应用程序的数据。
from langchain.tools import tool, ToolRuntime
from langchain_openai import ChatOpenAI
from langchain.agents import create_agent
from langgraph.store.memory import InMemoryStore
from typing import Any
model = ChatOpenAI(
    model="qwen-plus",
    api_key="sk-6459007b813946289da12857950c955b",
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
)

# Access memory
@tool
def get_user_info(user_id: str, runtime: ToolRuntime) -> str:
    """Look up user info."""
    store = runtime.store
    user_info = store.get(("users",), user_id)
    return str(user_info.value) if user_info else "Unknown user"

# Update memory
@tool
def save_user_info(user_id: str, user_info: dict[str, Any], runtime: ToolRuntime) -> str:
    """Save user info."""
    store = runtime.store
    store.put(("users",), user_id, user_info)
    return "Successfully saved user info."

#1  长期记忆,必须创建一个 store对象---》这个放在内存中--》后期可以放到数据库,redis中。。。
store = InMemoryStore()

agent = create_agent(
    model,
    tools=[get_user_info, save_user_info],
    store=store # 创建agent时,传入
)
# 2 让大模型保存用户的信息---》大模型做不了--》找工具:save_user_info-->执行工具--》保存到长期记忆中了
result = agent.invoke({
    "messages": [{"role": "user", "content": "Save the following user: userid: lqz123, name: Liuqingzheng, age: 18, email: 616564099@qq.com"}]
})
print(result['messages'][-1])
# Second session: get user info
# # 2 让大模型查用户的信息---》大模型做不了--》找工具:get_user_info-->执行工具--》从长期记忆中获取
result = agent.invoke({
    "messages": [{"role": "user", "content": "Get user info for user with id 'lqz123'"}]
})
print(result['messages'][-1])
# 4 Stream writer  流写入器:使用 runtime.stream_writer 在工具执行时流式传输自定义更新。这对于向用户提供有关工具正在做什么的实时反馈很有用
from langchain.tools import tool, ToolRuntime
from langchain.messages import HumanMessage
from langgraph.types import Command
from langchain_openai import ChatOpenAI
from langchain.agents import create_agent
from langchain.agents import AgentState
from dataclasses import dataclass
from langgraph.store.memory import InMemoryStore
from typing import Any
model = ChatOpenAI(
    model="qwen-plus",
    api_key="sk-6459007b813946289da12857950c955b",
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
)



@tool
def get_weather(city: str, runtime: ToolRuntime) -> str:
    """获取某个城市的天气."""
    writer = runtime.stream_writer
    # 如果你在tool中使用 runtime.stream_writer,必须在 LangGraph 执行上下文中调用该工具
    writer(f"搜索城市天气: {city}")
    writer(f"获取城市天气: {city}")
    return f"阳光很好,风和日丽 {city}!"

agent = create_agent(
    model,
    tools=[get_weather],
)
full = None  # None | AIMessageChunk
for chunk in agent.stream({
    "messages": [{"role": "user", "content": "今天上海天气如何?"}]
}):
    print(chunk)

1.3 ToolNode-工具节点

`ToolNode`是一个预构建的节点,用于在 LangGraph 工作流程中执行工具。它自动处理并行工具执行、错误处理和状态注入

1.4 Prebuilt tools预构建工具

LangChain 提供了大量预构建的工具包,用于网页搜索、代码解释、数据库访问等常见任务。这些现成工具可以直接集成到你的代理中,无需编写自定义代码
	-https://docs.langchain.com/oss/python/integrations/tools

1.5 Server-side tool use -服务器端工具使用

部分聊天模型内置工具,由模型提供者在服务器端执行。这些功能包括网页搜索和代码解释器,无需你定义或托管工具逻辑
	-跟模型厂商有关
from langchain.chat_models import init_chat_model

model = init_chat_model("gpt-4.1-mini")
tool = {"type": "web_search"}
model_with_tools = model.bind_tools([tool])

response = model_with_tools.invoke("What was a positive news story from today?")
print(response.content_blocks)

2 LangChain核心组件之短期记忆

# 1 概述
# 1.1 记忆是一个系统,用于记住关于先前交互的信息。对于 AI 代理 (AI agents) 而言,记忆至关重要,因为它能让他们记住之前的交互、从反馈中学习并适应用户偏好。随着代理处理涉及大量用户交互的更复杂任务,这种能力对于效率和用户满意度都变得至关重要。
# 1.2 短期记忆让您的应用程序能够记住单个线程或对话中的先前交互
# 1.3 注意:
一个线程 (thread) 在一个会话 (session) 中组织多次交互,类似于电子邮件将消息分组到一个对话中的方式。

# 1.4 会话历史记录 (Conversation history) 是短期记忆最常见的形式。对于当今的 LLM (大型语言模型) 来说,长对话是一个挑战;完整的历史记录可能无法完全容纳在一个 LLM 的上下文窗口 (context window) 内,从而导致上下文丢失 (context loss) 或错误。

# 1.5 即使您的模型支持完整的上下文长度,大多数 LLM 在处理长上下文时的表现仍然不佳。它们会被陈旧或跑题的内容“分心”,同时还会导致响应时间变慢和成本更高。

# 1.6 聊天模型使用 消息 (messages) 接受上下文,这些消息包括指令(系统消息 system message)和输入(人类消息 human messages)。在聊天应用程序中,消息在人类输入和模型响应之间交替,导致消息列表随着时间推移而变长。由于上下文窗口是有限的,许多应用程序可以受益于使用技术来移除或**“遗忘”**陈旧信息。


# 2 用法
	2.1 要向agent添加短期记忆(线程级持久性),您需要在创建agent时指定一个 checkpointer
    2.2 LangChain 的agent将短期记忆作为代理状态 (state) 的一部分进行管理
    2.3 通过将这些存储在图 (graph) 的状态中,代理可以访问给定对话的完整上下文,同时保持不同线程之间的分离。
    2.4 状态使用 checkpointer 持久化到数据库(或内存)中,以便线程可以随时恢复。
    2.5 当代理被调用或一个步骤(如工具调用)完成时,短期记忆会更新,并在每个步骤开始时读取状态

2.1 基本使用

# 1 基本使用
from langchain.agents import create_agent
from langgraph.checkpoint.memory import InMemorySaver
from langchain_openai import ChatOpenAI
#  连接大模型
model = ChatOpenAI(
    model="qwen-plus",
    api_key="sk-6459007b813946289da12857950c955b",
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)
# 创建智能体
agent = create_agent(
    model,
    checkpointer=InMemorySaver(),  # 加入短期记忆
)
res=agent.invoke(
    {"messages": [{"role": "user", "content": "Hi! My name is Bob."}]},
    {"configurable": {"thread_id": "1"}},
)
print(res['messages'][-1])

2.2 生产环境

# 豆包:聊天交互--》有之前的对话内容
		-再开一个新的聊天框-->之前的对话内容就没了--》开了个新线程聊天
        -换个机器登陆--》之前对话内容还在--》存在数据库中
        
        
# 生产环境 可以把对话内容,存到数据库中
# 2 生产环境-存入数据库-mysql
from langchain.agents import create_agent
from langchain_openai import ChatOpenAI
from langgraph.checkpoint.memory import InMemorySaver
# pip install langgraph-checkpoint-mysql[pymysql]
# pip install pymysql
import pymysql
from langgraph.checkpoint.mysql.pymysql import PyMySQLSaver
# 连接大模型
model = ChatOpenAI(
    model="qwen-plus",
    api_key="sk-6459007b813946289da12857950c955b",
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)
### 把短期记忆存在数据库中:库需要我们创建--》表和数据都是langchain自动写入的
# 方式一:了解
# DB_URI = f"mysql://root:lqz123@127.0.0.1:3306/langchain01"
# with PyMySQLSaver.from_conn_string(DB_URI) as checkpointer:
#     checkpointer.setup() # auto create tables in mysql
#     agent = create_agent(
#         model,
#         [],
#         checkpointer=checkpointer,
#     )
# 方式二:
connection = pymysql.connect(host='localhost', user='root', password='lqz123?', database='langchain02', autocommit=True)
# PyMySQLSaver 创建一个对象--》等同于:InMemorySaver() 存在内存中,但是 PyMySQLSaver存在数据库中
checkpointer = PyMySQLSaver(connection)
checkpointer.setup()  # 自动创建表--》用于存储数据
agent = create_agent(
    model,
    [],
    checkpointer=checkpointer,
)
res=agent.invoke(
    {"messages": [{"role": "user", "content": "Hi! My name is Bob."}]},
    {"configurable": {"thread_id": "1"}},
)
print(res)

2.3 自定义Agent记忆 (Customizing agent memory)

# 默认情况下,代理使用 AgentState 来管理短期记忆,特别是通过 messages 键来管理会话历史记录。

# 您可以扩展 AgentState 以添加额外的字段。自定义状态模式通过 state_schema 参数传递给 create_agent。
# from langchain.agents import create_agent, AgentState
# from langchain_openai import ChatOpenAI
# import pymysql
# from langgraph.checkpoint.mysql.pymysql import PyMySQLSaver
# 
# model = ChatOpenAI(
#     model="qwen-plus",
#     api_key="sk-6459007b813946289da12857950c955b",
#     base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
# )
# 
# # 自定义 AgentState: user_id 字段和  preferences 字段
# class CustomAgentState(AgentState):
#     user_id: str
#     preferences: dict
# 
# 
# connection = pymysql.connect(host='localhost', user='root', password='lqz123?', database='langchain02', autocommit=True)
# checkpointer = PyMySQLSaver(connection)
# checkpointer.setup()  # auto create tables in mysql
# agent = create_agent(
#     model,
#     [],
#     state_schema=CustomAgentState,
#     checkpointer=checkpointer,
# )
# res = agent.invoke(
#     {
#         "messages": [{"role": "user", "content": "Hello"}],
#         "user_id": "user_123",
#         "preferences": {"theme": "dark"}
#     },
#     {"configurable": {"thread_id": "1"}},
# )
# print(res['messages'][-1])

# 使用InMemorySaver
from langchain.agents import create_agent, AgentState
from langchain_openai import ChatOpenAI
from langgraph.checkpoint.memory import InMemorySaver

model = ChatOpenAI(
    model="qwen-plus",
    api_key="sk-6459007b813946289da12857950c955b",
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)


class CustomAgentState(AgentState):
    user_id: str
    preferences: dict
agent = create_agent(
    model,
    [],
    state_schema=CustomAgentState,
    checkpointer=InMemorySaver(),
)
res = agent.invoke(
    {
        "messages": [{"role": "user", "content": "Hello"}],
        "user_id": "user_123",
        "preferences": {"theme": "dark"}
    },
    {"configurable": {"thread_id": "1"}},
)
print(res)

2.4 常见模式 (Common patterns)

# 启用短期记忆后,会记录会话历史,长对话可能会超出 LLM 的上下文窗口。常见的解决方案有

# 这使得代理能够在不超出 LLM 上下文窗口的情况下跟踪对话
模式 描述
修剪消息 (Trim messages) ✂️ 移除最初或最后的 N 条消息(在调用 LLM 之前)。
删除消息 (Delete messages) 🗑️ 从 LangGraph 状态中永久删除消息。
总结消息 (Summarize messages) 📝 总结历史记录中较早的消息,并用摘要替换它们。
自定义策略 (Custom strategies) ⚙️ 自定义策略(例如:消息过滤等)。
##############  1  修剪消息 (Trim messages)
大多数 LLM 都有一个最大支持的上下文窗口(以 token 计)。
决定何时截断消息的一种方法是计算消息历史记录中的 token 数,并在接近该限制时进行截断。如果您使用 LangChain,可以使用修剪消息实用程序 (trim messages utility) 并指定要从列表中保留的 token 数量,以及用于处理边界的 strategy(例如:保留最后的 max_tokens)。
要在代理中修剪消息历史记录,请使用 @before_model 中间件装饰器

################ 2 删除消息 (Delete messages)
'''
您可以从图状态中删除消息以管理消息历史记录。

当您想要删除特定消息或清除整个消息历史记录时,这非常有用。

要从图状态中删除消息,可以使用 RemoveMessage。

要使 RemoveMessage 工作,您需要使用具有 add_messages [reducer] 的状态键。

默认的 AgentState 提供了此功能。

#### 删除特定消息
from langchain.messages import RemoveMessage 

def delete_messages(state):
    messages = state["messages"]
    if len(messages) > 2:
        # remove the earliest two messages
        return {"messages": [RemoveMessage(id=m.id) for m in messages[:2]]}  
### 删除所有消息:
from langgraph.graph.message import REMOVE_ALL_MESSAGES  # [!code highlight]

def delete_messages(state):
    return {"messages": [RemoveMessage(id=REMOVE_ALL_MESSAGES)]}  # [!code highlight]

###  警告:
删除消息时,请确保生成的消息历史记录是有效的。请检查您正在使用的 LLM 提供商的限制。例如:

一些提供商期望消息历史记录以 user 消息开始
大多数提供商要求带有工具调用的 assistant 消息后跟相应的 tool 结果消息。
'''

###########3 总结消息 (Summarize messages) 谁总结? 还是需要大模型总结-->要会
本质是:100条消息--》发给大模型--》总结成100个字
'''
如上所示,修剪或删除消息的问题是您可能会因为删除消息队列而丢失信息。
因此,一些应用程序受益于使用聊天模型来总结消息历史记录的更复杂方法。

要在agent中总结消息历史记录,请使用内置的 SummarizationMiddleware
'''

# 如果上下文对话过多--》超token,如何解决?你们公司如何解决的?
	-可以删除之前的消息
    -我们使用了总结消息
from langchain.agents import create_agent, AgentState
from langchain_openai import ChatOpenAI
from langgraph.checkpoint.memory import InMemorySaver
from langchain.agents.middleware import before_model
from langgraph.runtime import Runtime
from typing import Any
from langchain.messages import RemoveMessage
from langgraph.graph.message import REMOVE_ALL_MESSAGES
from langchain_core.runnables import RunnableConfig
model = ChatOpenAI(
    model="qwen-plus",
    api_key="sk-6459007b813946289da12857950c955b",
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)

# 1 中间件 :before_model 跟model交互之前执行:只保留最近的 3条消息
@before_model
def trim_messages(state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
    """Keep only the last few messages to fit context window."""
    messages = state["messages"]
    first_msg = messages[0]
    new_messages = [first_msg]
    print('--------')
    return {
        "messages": [
            RemoveMessage(id=REMOVE_ALL_MESSAGES),
            *new_messages
        ]
    }


agent = create_agent(
    model,
    [],
    middleware=[trim_messages], # 中间件配置一下
    checkpointer=InMemorySaver(),
)
config: RunnableConfig = {"configurable": {"thread_id": "1"}}
# 是同一个线程:记录所有历史消息
agent.invoke({"messages": "hi, my name is bob"}, config)
agent.invoke({"messages": "my age is 30"}, config)
agent.invoke({"messages": "write a short poem about cats"}, config)
agent.invoke({"messages": "now do the same but for dogs"}, config)
agent.invoke({"messages": "my height is 180"}, config)
final_response = agent.invoke({"messages": "我多大了?"}, config)


print(final_response["messages"][-1].pretty_print()) # 它不知道我的名字--》使用了消息修剪,只剩三条
from langchain.agents import create_agent, AgentState
from langchain_openai import ChatOpenAI
from langgraph.checkpoint.memory import InMemorySaver
from langchain.agents.middleware import before_model,after_model
from langgraph.runtime import Runtime
from typing import Any
from langchain.messages import RemoveMessage
from langgraph.graph.message import REMOVE_ALL_MESSAGES
from langchain_core.runnables import RunnableConfig
model = ChatOpenAI(
    model="qwen-plus",
    api_key="sk-6459007b813946289da12857950c955b",
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)


@after_model
def delete_old_messages(state: AgentState, runtime: Runtime) -> dict | None:
    """Remove old messages to keep conversation manageable."""
    messages = state["messages"]
    if len(messages) > 2:
        # remove the earliest two messages
        return {"messages": [RemoveMessage(id=m.id) for m in messages[:2]]}
    return None

agent = create_agent(
    model,
    [],
    middleware=[delete_old_messages],
    checkpointer=InMemorySaver(),
)
config: RunnableConfig = {"configurable": {"thread_id": "1"}}

config: RunnableConfig = {"configurable": {"thread_id": "1"}}
agent.invoke({"messages": "hi, my name is lqz"}, config)
agent.invoke({"messages": "write a short poem about cats"}, config)
agent.invoke({"messages": "now do the same but for dogs"}, config)
final_response = agent.invoke({"messages": "what's my name?"}, config)

print(final_response["messages"][-1].pretty_print())


from langchain.agents import create_agent, AgentState
from langchain_openai import ChatOpenAI
from langgraph.checkpoint.memory import InMemorySaver
from langchain.agents.middleware import SummarizationMiddleware
from langchain_core.runnables import RunnableConfig
model = ChatOpenAI(
    model="qwen-plus",
    api_key="sk-6459007b813946289da12857950c955b",
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)
mid=SummarizationMiddleware(
            model=model,
            trigger=("tokens", 4000),  # Trigger summarization at 4000 tokens
            keep=("messages", 20),  # Keep last 20 messages after summary
        )
agent = create_agent(
    model,
    [],
    middleware=[mid],
    checkpointer=InMemorySaver(),
)

config: RunnableConfig = {"configurable": {"thread_id": "1"}}
agent.invoke({"messages": "hi, my name is lqz"}, config)
agent.invoke({"messages": "write a short poem about cats"}, config)
agent.invoke({"messages": "now do the same but for dogs"}, config)
final_response = agent.invoke({"messages": "what's my name?"}, config)

print(final_response["messages"][-1].pretty_print())

2.5 访问记忆(Access memory)

# 通过如下方式访问记忆-->上下文的消息
 	工具 (Tools):  runtime.context
    提示 (Prompt): request.runtime.context["user_name"]
    模型之前 (Before model) :messages = state["messages"]
    模型之后 (After model)  :messages = state["messages"]

2.5.1 工具中使用短期记忆

# 1 获取
# 2 写入
'''1
在工具中读取短期记忆 (Read short-term memory in a tool)
使用 ToolRuntime 参数在工具中访问短期记忆(状态)。
tool_runtime 参数对工具签名是隐藏的(因此模型看不到它),但工具可以通过它访问状态。
'''


from langchain.agents import create_agent, AgentState
from langchain_openai import ChatOpenAI
from langchain.tools import tool, ToolRuntime

class CustomState(AgentState):
    user_id: str

@tool
def get_user_info(
    runtime: ToolRuntime
) -> str:
    """Look up user info."""
    user_id = runtime.state["user_id"]
    print(user_id)
    return "User is lqz" if user_id == "user_123" else "Unknown user"

model = ChatOpenAI(
    model="qwen-plus",
    api_key="sk-6459007b813946289da12857950c955b",
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)

agent = create_agent(
    model,
    tools=[get_user_info],
    state_schema=CustomState,  # 短期记忆
)

result = agent.invoke({
    "messages": "look up user information",  # 大模型解释不了--》调用 get_user_info工具
    "user_id": "user_aaa"
})
print(result["messages"][-1])
'''1
在工具中读取短期记忆 (Read short-term memory in a tool)
使用 ToolRuntime 参数在工具中访问短期记忆(状态)。
tool_runtime 参数对工具签名是隐藏的(因此模型看不到它),但工具可以通过它访问状态。
'''

########### 读取#################
# from langchain.agents import create_agent, AgentState
# from langchain_openai import ChatOpenAI
# from langchain.tools import tool, ToolRuntime
#
# class CustomState(AgentState):
#     user_id: str
#
# @tool
# def get_user_info(
#     runtime: ToolRuntime
# ) -> str:
#     """Look up user info."""
#     user_id = runtime.state["user_id"]
#     print(user_id)
#     return "User is lqz" if user_id == "user_123" else "Unknown user"
#
# model = ChatOpenAI(
#     model="qwen-plus",
#     api_key="sk-6459007b813946289da12857950c955b",
#     base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
# )
#
# agent = create_agent(
#     model,
#     tools=[get_user_info],
#     state_schema=CustomState,  # 短期记忆
# )
#
# result = agent.invoke({
#     "messages": "look up user information",  # 大模型解释不了--》调用 get_user_info工具
#     "user_id": "user_aaa"
# })
# print(result["messages"][-1])


############ 写入############
'''2
从工具写入短期记忆 (Write short-term memory from tools)
要在执行期间修改代理的短期记忆(状态),您可以直接从工具返回状态更新 (state updates)。

这对于持久化中间结果或使信息可供后续工具或提示访问非常有用。
'''

from langgraph.types import Command
from langchain.agents import create_agent, AgentState
from langchain_openai import ChatOpenAI
from langchain.tools import tool, ToolRuntime
from pydantic import BaseModel
from langchain.messages import ToolMessage
class CustomState(AgentState):  # [!code highlight]
    user_name: str

class CustomContext(BaseModel):
    user_id: str



@tool
def update_user_info(
    runtime: ToolRuntime[CustomContext, CustomState],
) -> Command:
    """Look up and update user info."""
    user_id = runtime.context.user_id
    name = "John Smith" if user_id == "user_123" else "Unknown user"
    print('---',name)
    return Command(update={
        "user_name": name,
        # update the message history
        "messages": [
            ToolMessage(
                "Successfully looked up user information",
                tool_call_id=runtime.tool_call_id
            )
        ]
    })

@tool
def greet(
    runtime: ToolRuntime[CustomContext, CustomState]
) -> str | Command:
    """Use this to greet the user once you found their info."""
    # state取出用户名user_name 是什么不知道
    user_name = runtime.state.get("user_name", None)
    print(user_name)
    if user_name is None:
       # 如果获取不到用户--》就会先调用 update_user_info
       # Command创建工具消息
       return Command(update={
            "messages": [
                ToolMessage(
                    "Please call the 'update_user_info' tool it will get and update the user's name.",
                    tool_call_id=runtime.tool_call_id
                )
            ]
        })
    # 如果不为空,返回 你好 xxx

    return f"Hello {user_name}!"

model = ChatOpenAI(
    model="qwen-plus",
    api_key="sk-6459007b813946289da12857950c955b",
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)

agent = create_agent(
    model,
    tools=[update_user_info, greet],
    state_schema=CustomState,      # 短期记忆
    context_schema=CustomContext,  # 上下文
)

result = agent.invoke(
    {"messages": [{"role": "user", "content": "greet the user"}]}, # 大模型不知道干嘛--》掉工具greet,查用户名,查不到--》又调用update_user_info把用户名写入--》再调用就能拿到John Smith
    context=CustomContext(user_id="user_123"),
)
print(result["messages"][-1])

2.5.2 提示

'''3
提示 (Prompt)
在中间件 (middleware) 中访问短期记忆(状态),以基于对话历史记录或自定义状态字段创建动态提示 (dynamic prompts)。
'''

from langchain.agents import create_agent, AgentState
from langchain_openai import ChatOpenAI
from langchain.agents.middleware import dynamic_prompt, ModelRequest
from typing import TypedDict
class CustomContext(TypedDict):
    user_name: str

def get_weather(city: str) -> str:
    """Get the weather in a city."""
    return f"The weather in {city} is always sunny!"


# 动态系统提示词--》获取短期记忆
@dynamic_prompt
def dynamic_system_prompt(request: ModelRequest) -> str:
    user_name = request.runtime.context["user_name"]
    system_prompt = f"You are a helpful assistant. Address the user as {user_name}."
    return system_prompt

model = ChatOpenAI(
    model="qwen-plus",
    api_key="sk-6459007b813946289da12857950c955b",
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)

agent = create_agent(
    model,
    tools=[get_weather],
    middleware=[dynamic_system_prompt],
    context_schema=CustomContext,
)

result = agent.invoke(
    {"messages": [{"role": "user", "content": "What is the weather in SF?"}]}, # 调用工具:get_weather
    context=CustomContext(user_name="John Smith"), # 传了上下文
)
for msg in result["messages"]:
    msg.pretty_print()

2.5.3 模型之前 (Before model)

'''4
模型之前 (Before model)
在 @before_model 中间件中访问短期记忆(状态),以在模型调用之前处理消息。
'''
from langchain.agents import create_agent, AgentState
from langchain_openai import ChatOpenAI
from langchain.agents.middleware import before_model
from langgraph.runtime import Runtime
from langchain.messages import RemoveMessage
from langgraph.graph.message import REMOVE_ALL_MESSAGES
from typing import Any
from langchain_core.runnables import RunnableConfig

# 每次跟模型交互之前都会执行
@before_model
def trim_messages(state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
    """Keep only the last few messages to fit context window."""
    print('------')
    messages = state["messages"]  # before_model的中间件中拿到短期记忆
    if len(messages) <= 3:
        return None  # No changes needed
    first_msg = messages[0]
    recent_messages = messages[-3:] if len(messages) % 2 == 0 else messages[-4:]
    new_messages = [first_msg] + recent_messages
    return {
        "messages": [
            RemoveMessage(id=REMOVE_ALL_MESSAGES),
            *new_messages
        ]
    }

model = ChatOpenAI(
    model="qwen-plus",
    api_key="sk-6459007b813946289da12857950c955b",
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)

agent = create_agent(
    model,
    middleware=[trim_messages]
)

config: RunnableConfig = {"configurable": {"thread_id": "1"}}

agent.invoke({"messages": "hi, my name is bob"}, config)
agent.invoke({"messages": "write a short poem about cats"}, config)
agent.invoke({"messages": "now do the same but for dogs"}, config)
final_response = agent.invoke({"messages": "what's my name?"}, config)

print(final_response["messages"][-1].pretty_print())

2.5.4 模型之后 (After model)

'''5
模型之后 (After model)
在 @after_model 中间件中访问短期记忆(状态),以在模型调用之后处理消息。
'''
from langchain.agents import create_agent, AgentState
from langchain_openai import ChatOpenAI
from langchain.agents.middleware import before_model,after_model
from langgraph.runtime import Runtime
from langchain.messages import RemoveMessage
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.graph.message import REMOVE_ALL_MESSAGES
from typing import Any
from langchain_core.runnables import RunnableConfig
@after_model
def validate_response(state: AgentState, runtime: Runtime) -> dict | None:
    """Remove messages containing sensitive words."""
    STOP_WORDS = ["password", "secret"]
    last_message = state["messages"][-1]
    if any(word in last_message.content for word in STOP_WORDS):
        return {"messages": [RemoveMessage(id=last_message.id)]}
    return None
model = ChatOpenAI(
    model="qwen-plus",
    api_key="sk-6459007b813946289da12857950c955b",
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)

agent = create_agent(
    model,
    tools=[],
    middleware=[validate_response],
    checkpointer=InMemorySaver(),
)

config: RunnableConfig = {"configurable": {"thread_id": "1"}}

agent.invoke({"messages": "hi, my name is bob"}, config)
agent.invoke({"messages": "write a short poem about cats"}, config)
agent.invoke({"messages": "now do the same but for dogs"}, config)
agent.invoke({"messages": "my password is lqz12345"}, config)
final_response = agent.invoke({"messages": "what's my password?"}, config)

print(final_response["messages"][-1].pretty_print())

3 LangChain高级组件之中间件

4 LangChain高级组件之护栏Guardrails

# 1 Guardrails是什么
	为智能体实施安全检查和内容过滤
	护栏通过在您的智能体执行的关键点验证和过滤内容,帮助您构建安全、合规的 AI 应用。它们可以在问题发生前检测敏感信息、强制执行内容策略、验证输出并防止不安全行为。
    
    问智能体:
    	-如何制作zha药?---》交个大模型?不应该
        -如何使用comfyui生成黄图?---》交个大模型?不应该
		-政治敏感话题---》没进到大模型的调用--》就被护栏拦截了
        
        -豆包生图:美女穿比基尼在沙滩图--》生成不了--》说了我一顿
        	-图片大模型能不能生?能生
            -还没调用 图片大模型 时,就被护栏拦截了
        
    我们写的智能体,也要带护栏--》拦截我们不想回答,处理的问题
    
    我们做成的产品--》要符合法律--》必须加护栏
    
# 2 常见用例
    防止 PII(个人身份信息)泄露
    检测和阻止提示注入 (prompt injection) 攻击
    阻止不当或有害内容
    强制执行业务规则和合规要求
    验证输出质量和准确性
可以使用 中间件 (middleware) 来实施守卫,在策略性节点拦截执行——在智能体开始前、完成后,或围绕模型和工具调用时

# 3 护栏Guardrails的两种方法-护栏Guardrails可以通过两种互补的方法实施:
    -确定性守卫 (Deterministic guardrails)	使用基于规则的逻辑,如正则表达式、关键词匹配或明确检查。快速、可预测、经济高效,但可能会错过细微的违规行为。
    -基于模型的守卫 (Model-based guardrails)	使用 LLMs 或分类器通过语义理解来评估内容。可以捕获规则遗漏的微妙问题,但速度较慢且成本较高。

4.1 内置护栏 (Built-in guardrails)

# 1 非常多,没法都演示:可以看官网--》只给大家演示一个 PII(个人身份信息)
	-用户输入了信用卡号,密码:都能够,遮盖,抛异常。。。
from langchain.agents import create_agent
from langchain_openai import ChatOpenAI
from langchain.agents.middleware import PIIMiddleware # 内置的护栏:拦截用户信息的的
model = ChatOpenAI(
    model="qwen-plus",
    api_key="sk-6459007b813946289da12857950c955b",
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)


agent = create_agent(
    model=model,
    middleware=[
        # 在发送给模型之前,将用户输入中的电子邮件编辑掉
        PIIMiddleware(
            "email",
            strategy="redact",
            apply_to_input=True,
        ),
        # 遮盖用户输入中的信用卡
        PIIMiddleware(
            "credit_card",
            strategy="mask",
            apply_to_input=True,
        ),
        # 阻止 API 密钥 - 如果检测到则抛出错误
        PIIMiddleware(
            "api_key",
            detector=r"sk-[a-zA-Z0-9]{32}",
            strategy="block",
            apply_to_input=True,
        ),
    ],
)

# result = agent.invoke({
#     "messages": [{"role": "user", "content": "My email is john.doe@example.com and card is 4532-1234-5678-9010"}]
# })
result = agent.invoke({
    "messages": [{"role": "user", "content": "My  api_key is sk-102933456789012345678902345678"}]
})
print(result)

4.2 自定义护栏

#  1 对于更复杂的护栏,您可以创建自定义中间件,在智能体执行之前或之后运行。这使您可以完全控制验证逻辑、内容过滤和安全检查

# 2 智能体执行前守卫 (Before agent guardrails)
 使用“智能体执行前”的钩子 (hooks) 在每次调用开始时验证请求一次。这对于会话级别的检查(如身份验证、速率限制或在任何处理开始前阻止不当请求)非常有用。
    
# 3 智能体执行后守卫 (After agent guardrails)
# 使用“智能体执行后”的钩子在返回给用户之前验证最终输出一次。这对于基于模型的安全检查、质量验证或对完整的智能体响应进行最终合规扫描非常有用。
from langchain.agents import create_agent
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
from langchain.agents.middleware import HumanInTheLoopMiddleware
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.types import Command
from typing import Any
from langchain.agents.middleware import AgentMiddleware, AgentState, hook_config
from langgraph.runtime import Runtime
model = ChatOpenAI(
    model="qwen-plus",
    api_key="sk-6459007b813946289da12857950c955b",
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)

class ContentFilterMiddleware(AgentMiddleware):
    """Deterministic guardrail: Block requests containing banned keywords."""
    def __init__(self, banned_keywords: list[str]):
        super().__init__()
        self.banned_keywords = [kw.lower() for kw in banned_keywords]

    @hook_config(can_jump_to=["end"])
    def before_agent(self, state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
        # Get the first user message
        if not state["messages"]:
            return None

        first_message = state["messages"][0]
        if first_message.type != "human":
            return None

        content = first_message.content.lower()

        # Check for banned keywords
        for keyword in self.banned_keywords:
            if keyword in content:
                # Block execution before any processing
                return {
                    "messages": [{
                        "role": "assistant",
                        "content": "我不能回答你这个问题,请换个问题."
                    }],
                    "jump_to": "end"
                }

        return None

agent = create_agent(
    model=model,
    middleware=[ContentFilterMiddleware(banned_keywords=["攻击", "爆炸", "色情"]),],
)
result =  agent.invoke({
    "messages": [{"role": "user", "content": "如何玩腾讯的数据库?"}]
})
print(result['messages'][-1])
from langchain.agents import create_agent
from langchain_openai import ChatOpenAI
from typing import Any
from langchain.agents.middleware import AgentMiddleware, AgentState, hook_config
from langgraph.runtime import Runtime
from langchain.messages import HumanMessage, AIMessage
from langchain.chat_models import init_chat_model
model = ChatOpenAI(
    model="qwen-plus",
    api_key="sk-6459007b813946289da12857950c955b",
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)

class SafetyGuardrailMiddleware(AgentMiddleware):
    """Model-based guardrail: Use an LLM to evaluate response safety."""
    def __init__(self):
        super().__init__()
        self.safety_model = init_chat_model(
                model="deepseek-chat",
                api_key="sk-8b83ef66aa604b449bfb3b900405b050",
                base_url="https://api.deepseek.com",
            )

    @hook_config(can_jump_to=["end"])
    def after_agent(self, state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
        # Get the final AI response
        if not state["messages"]:
            return None

        last_message = state["messages"][-1]
        if not isinstance(last_message, AIMessage):
            return None

        # Use a model to evaluate safety
        safety_prompt = f"""你是一个审查员,根据用户的提示词,能判断用户的提示词是否合规及安全,如果安全返回 'SAFE' ,不安全返回 'UNSAFE'.
        Response: {last_message.content}"""

        result = self.safety_model.invoke([{"role": "user", "content": safety_prompt}])

        if "UNSAFE" in result.content:
            print('------')
            return {
                "messages": [{
                    "role": "assistant",
                    "content": "我不能回答你这个问题."
                }],
                "jump_to": "end"
            }

        return None
agent = create_agent(
    model=model,
    middleware=[SafetyGuardrailMiddleware()],
)
result = agent.invoke({
    "messages": [{"role": "user", "content": "How do I make explosives?"}]
})
print(result["messages"][-1])

5 LangChain高级组件之MCP

# 1 模型上下文协议 (MCP) 是一种开放协议,它标准化了应用程序如何向 LLM 提供工具和上下文。LangChain 代理可以使用 langchain-mcp-adapters 库来使用在 MCP 服务器上定义的工具

# 2 安装: langchain-mcp-adapters 库,以便在 LangGraph 中使用 MCP 工具
pip install langchain-mcp-adapters

# 3 传输类型:MCP 支持用于客户端-服务器通信的不同传输机制:
    stdio:客户端将服务器作为子进程启动,并通过标准输入/输出进行通信。最适合本地工具和简单的设置。
    Streamable HTTP:服务器作为独立进程运行,处理 HTTP 请求。支持远程连接和多个客户端。
    Server-Sent Events (SSE):Streamable HTTP 的一个变体,针对实时流式通信进行了优化【弃用】。
    
# 4 后期mcp的服务的,可能是第三方开源的,公司其他部门写好部署完的,可能不需要我们写,我们之间用
	-之前:12306 查票的mcp server--》直接部署使用--》不需要管代码--》只需要知道它有什么功能即可
    	-配置好大模型会自由选择这个功能去调用

5.1 案例

# 1 stdio 的fastmcp 服务端

from mcp.server.fastmcp import FastMCP
mcp = FastMCP("数学计算")
@mcp.tool()
def add(a: int, b: int) -> int:
    """Add two numbers"""
    return a + b

@mcp.tool()
def multiply(a: int, b: int) -> int:
    """Multiply two numbers"""
    return a * b

if __name__ == "__main__":
    mcp.run(transport="stdio")
# 2 Streamable HTTP 的fastmcp 服务端
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("天气")

@mcp.tool()
async def get_weather(location: str) -> str:
    """Get weather for location."""
    print('来了')
    return f"It's always sunny in {location}"

if __name__ == "__main__":
    mcp.run(transport="streamable-http")
# 3 langChain使用

from langchain_openai import ChatOpenAI
from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain.agents import create_agent
import asyncio

# 1 连接model
model = ChatOpenAI(
    model="qwen-plus",
    api_key="sk-6459007b813946289da12857950c955b",
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)


client = MultiServerMCPClient(
    {
        "math": {
            "transport": "stdio",  # 本地子进程通信,用的少,以后尽量用 http
            "command": "python",
            # 您的stdio的fastmcp服务端.py 文件的绝对路径
            "args": [r"D:\project\PythonProject\day12_LangChain\10-LangChain高级组件之MCP\stdio的fastmcp服务端.py"],
        },
        "weather": {
            "transport": "streamable_http",  # 基于 HTTP 的远程服务器
            # 确保您在 8000 端口启动了您的天气服务器
            "url": "http://localhost:8000/mcp",
        }
    }
)


# 协程函数
async def main():
    # 3 我们作为mcp的客户端,获取到所有的mcp工具---》异步调用
    tools = await client.get_tools() # 返回了所有可用的MCP工具
    # 2 创建agent
    agent = create_agent(
        model=model,
        tools=tools # 原来传个列表---》自己写的函数,使用@tool装饰器装饰,现在使用mcp,不需要自己写了
    )

    math_response = await agent.ainvoke(
        {"messages": [{"role": "user", "content": "what's (3 + 5) x 12?"}]}
    )
    weather_response = await agent.ainvoke(
        {"messages": [{"role": "user", "content": "what is the weather in nyc?"}]}
    )

    print(math_response['messages'][-1])
    print(weather_response['messages'][-1])


# 运行协程函数
asyncio.run(main())

龙虾:大模型 + mcp 服务+自带的函数--》AI智能---》这就是我们课程内容

自动发朋友圈:

​ 1 生成文案:大模型

​ 2 打开电脑端微信发朋友圈功能,发送:tool做的

自动发邮件

​ 1 生成文案:大模型

​ 2 tool操作电脑,打开邮箱,指定位置填入后,发送给老板

现在龙虾火了---》很多很多第三方的mcpserver----》龙虾团队内置了一些tool:打开浏览器,删除文件。。。。

6 LangChain高级组件之人机交互 HITL

# 1 概念
# 人机交互 (HITL) 中间件允许您在agent工具调用中添加人工监督。

# 当模型提出一个可能需要人工审查的操作时——例如,写入文件或执行 SQL——该中间件可以暂停执行并等待决策。

# 它是通过对照一个可配置的策略检查每个工具调用来实现的。如果需要干预,中间件会发出一个中断 (interrupt) 来停止执行。图的状态会使用 LangGraph 的持久化层进行保存,以便执行可以安全地暂停并稍后恢复。

# 随后的人工决策将决定接下来发生什么:该操作可以按原样批准 (approve)、修改后运行 (edit),或带反馈拒绝 (reject)。

# 2 中断决策类型 (Interrupt decision types)
# 中间件定义了三种内置的人类响应中断的方式:

决策类型	                描述	                                       示例用例
approve                该操作按原样批准并执行,不进行任何更改。	   完全按照草稿发送电子邮件
edit	               工具调用经过修改后执行。	                在发送电子邮件前更改收件人
reject	               工具调用被拒绝,并将解释添加到对话中。	        拒绝电子邮件草稿并解释如何重写
每种工具可用的决策类型取决于您在 interrupt_on 中配置的策略。

# 当多个工具调用同时暂停时,每个操作都需要单独的决策。决策必须以与操作在中断请求中出现的相同顺序提供。


# 总结:智能体在做危险操作[工具调用时]---》需要人工介入--》
	确认让他做,再做:approve
    不让做就不做了:reject
    换别的工具调用:edit

6.1 人工决策


# LangChain 核心组件
from langchain_openai import ChatOpenAI
from langchain.tools import tool
from langchain.agents import create_agent
from langchain.agents.middleware import HumanInTheLoopMiddleware
from langgraph.checkpoint.memory import MemorySaver
from langgraph.types import Command, Interrupt


#  1 定义工具:读取数据
@tool
def read_data_tool(query: str) -> str:
    """读取数据,无需批准的安全操作。"""
    return f"读取数据成功: {query}"

# 1 定义工具:执行sql
@tool
def execute_sql_tool(query: str) -> str:
    """执行 SQL 语句。这是一个危险操作,需要人工批准。"""
    # 模拟执行
    print('删库了')
    if "DELETE" in query.upper():
        return f"已执行危险操作: {query}"
    return f"已执行: {query}"

# 1 定义工具:写入数据
@tool
def write_file_tool(filename: str, content: str) -> str:
    """写入文件。需要人工批准,且允许编辑。"""
    return f"文件 {filename} 已写入。"



# 2 定义哪些工具需要中断
interrupt_config = {
    "write_file_tool": True,  # True 表示允许 approve, edit, reject
    "execute_sql_tool": {
        "allowed_decisions": ["approve", "reject"]  # 只允许批准或拒绝,不允许编辑 SQL
    },
    "read_data_tool": False  # False 表示不需要批准
}

# 3 使用中间件
middleware = HumanInTheLoopMiddleware(
    interrupt_on=interrupt_config,
    description_prefix="⚠️ [安全审查] 待批准的操作:",
)

# 4 创建智能体
model = ChatOpenAI(
    model="qwen-plus",
    api_key="sk-6459007b813946289da12857950c955b",
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)
agent = create_agent(
    model=model,
    tools=[read_data_tool, execute_sql_tool, write_file_tool],
    middleware=[middleware],
    checkpointer=MemorySaver(),  # 临时记忆
)

# 5 模拟流程
# 唯一的线程 ID,用于关联对话状态
thread_id = "user_123_session_abc"
config = {"configurable": {"thread_id": thread_id}}

print(">>> 场景:用户要求删除旧数据 (触发 HITL)")
user_input = {
    "messages": [
        {"role": "user", "content": "执行sql,删除数据库中用户的记录。"} # 大模型删不了--》发现有个工具execute_sql_tool--》调用这个工具--》需要人工确认
    ]
}
result = agent.invoke(user_input, config=config)
print(result["messages"][-1])

#5.1 需要人工确认的,AI返回中,会有个 __interrupt__  字段
if "__interrupt__" in result:
    interrupts = result["__interrupt__"]
    print('需要安全检查')
    print(result["__interrupt__"])
    # 5.2 需要用户确认
    i=input('要删除数据,请按 y 确认,n 取消')
    if i=='y':
        # 假设安全检查通过,直接运行
        res=agent.invoke(
            Command(
                resume={"decisions": [{"type": "approve"}]}
            ),
            config=config
        )
        print(res)
    else:
        res = agent.invoke(
            Command(
                resume={"decisions": [{"type": "reject"}]} # 拒绝
            ),
            config=config
        )
        print(res)
posted @ 2026-03-19 19:58  凫弥  阅读(7)  评论(0)    收藏  举报