【人工智能:Agent】--11.Langchain高级用法

目录

1.安全防护

1.1.内置的护栏

1.2.人机参与

1.3.自定义防护

2.Runtime

2.1.Context

2.2.内部工具

2.3.中间件

3.长期记忆

4.多模态数据的输入


1.安全防护

为您的智能体实施安全检查和内容过滤
护栏通过在代理执行的关键节点验证和过滤内容,帮助您构建安全、合规的 AI 应用。它们可以检测敏感信息,执行内容策略,验证输出,并在不安全行为引发问题前预防。常见的使用场景包括:

  • 防止 PII 泄露
  • 检测和阻挡即时注入攻击
  • 屏蔽不当或有害内容
  • 执行业务规则和合规要求
  • 验证输出质量和准确性

你可以利用中间件在关键点实施护栏------智能体开始前、完成后,或模型和工具调用时拦截执行。

安全护栏可以通过两种互补方法实现:

LangChain 既内置保护栏(如 PII 检测人工介入)以及灵活的中间件系统,支持使用任一方法构建自定义保护栏。

1.1.内置的护栏

PII 检测

LangChain 内置中间件用于检测和处理对话中的个人身份信息(PII)。该中间件可以检测常见的个人身份信息类型,如电子邮件、信用卡、IP 地址等。PII 检测中间件对于医疗和金融应用等符合合规要求的案例、需要清理日志的客户服务人员,以及任何处理敏感用户数据的应用都很有帮助。PII 中间件支持多种处理检测到的 PII 策略:

参数 描述 默认值
pii_type 必填。指定需要检测的个人信息类型(可使用内置类型或自定义类型)。 必填
strategy 定义检测到个人信息后的处理策略。 可选值:"redact"(屏蔽)、"obfuscate"(遮蔽)、"mask"(掩码)、"hash"(哈希)。 "redact"
detector 允许传入自定义的检测函数或正则表达式模式,用于替代或补充内置检测器。 (使用内置)
apply_to_input 布尔值。决定是否在模型调用前检查用户输入内容。 True
apply_to_output 布尔值。决定是否在模型调用后检查 AI 生成的消息。 False
apply_to_tool_results 布尔值。决定是否在工具执行后检查工具返回的结果消息。 False
类型名称 描述
email 电子邮件地址
credit_card 信用卡号(包含 Luhn 验证)
ip IP 地址
mac_address MAC 地址
URL URL

|----------|-------------------------------|-----------------------|
| redact | 请用 [REDACTED_{PII_TYPE}] 替换 | [REDACTED_EMAIL] |
| mask | 部分冷门(例如,后四位数字) | ****-****-****-1234 |
| hash | 用确定性哈希替换 | a8f5f167... |
| block | 检测到异常时提出异常 | 投掷错误 |

python 复制代码
from langchain.agents import create_agent
from langchain.agents.middleware import PIIMiddleware


from langchain_core.tools import tool

# 工具 1: 客服查询工具
@tool
def customer_service_tool(issue: str, user_context: str = None):
    """处理客户问题。参数说明:issue=用户的问题描述,user_context=可选的用户上下文(如会话ID)"""
    # 这里写你的业务逻辑,比如查数据库
    # 为了演示,我们只返回一个模拟结果
    return f"客服已收到您的问题: '{issue}'。我们会尽快处理!"

# 工具 2: 发送邮件工具
@tool
def email_tool(recipient: str, subject: str, body: str):
    """发送邮件。参数说明:recipient=收件人邮箱,subject=邮件主题,body=邮件内容"""
    # 这里写你的业务逻辑,比如调用SMTP
    # 为了演示,我们只返回一个模拟结果
    return f"邮件已发送。收件人: {recipient}, 主题: {subject}"

agent = create_agent(
    model=llm,
    tools=[customer_service_tool, email_tool],
    middleware=[
        # 在用户输入发送给模型之前,先进行脱敏处理
        PIIMiddleware(
            "email", # 检测目标:电子邮件地址
            strategy="redact", # 策略:完全删除检测到的内容
            apply_to_input=True, # 应用范围:应用于输入数据
        ),
        # 对输入中的信用卡号进行掩码处理
        PIIMiddleware(
            "credit_card", # 检测目标:信用卡号码
            strategy="mask", # 策略:用掩码字符(如*)替换部分内容
            apply_to_input=True, # 应用范围:应用于输入数据
        ),
        # 拦截API密钥 - 如果检测到则抛出错误,阻止请求继续
        PIIMiddleware(
            "api_key", # 检测目标:API密钥
            detector=r"sk-[a-zA-Z0-9]{32}", # 策略:使用正则表达式自定义检测模式
            strategy="block", # 策略:阻止操作并抛出异常
            apply_to_input=True, # 应用范围:应用于输入数据
        ),
    ],
)

# 当用户提供包含个人敏感信息(PII)的内容时,中间件会根据预设策略自动处理
result = agent.invoke({
    "messages": [{"role": "user", "content": "我的邮箱是 john.doe@example.com,卡号是 5105-1051-0510-5100,请帮我发送一封邮件给客服,主题是"账户问题",内容是"我无法登录我的账户,请帮忙解决。""}]
})

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

1.2.人机参与

LangChain 内置中间件,要求在执行敏感作前获得人工批准。这是处理高风险决策的最有效护栏之一。人机环绕中间件适用于金融交易和转账、删除或修改生产数据、向外部发送通信以及任何对业务有重大影响的作。

这里详细讲解的:https://blog.csdn.net/qq_58602552/article/details/157250265?spm=1001.2014.3001.5501

python 复制代码
from langchain.agents import create_agent
from langchain.agents.middleware import HumanInTheLoopMiddleware
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.types import Command


agent = create_agent(
    model=llm,
    tools=[search_tool, send_email_tool, delete_database_tool],
    middleware=[
        HumanInTheLoopMiddleware(
            interrupt_on={
                # 需要人工审批的敏感操作
                "send_email": True,
                "delete_database": True,
                # 自动批准的安全操作
                "search": False,
            }
        ),
    ],
    # 用于在中断(暂停)期间持久化状态
    checkpointer=InMemorySaver(),
)

# 人机协作循环需要一个线程ID来保持状态连续性
config = {"configurable": {"thread_id": "some_id"}}

# 代理在执行敏感工具前会暂停,并等待人工审批
result = agent.invoke(
    {"messages": [{"role": "user", "content": "给团队发送一封邮件"}]},
    config=config
)

# 用户审批后,代理将继续执行
result = agent.invoke(
    Command(resume={"decisions": [{"type": "approve"}]}),
    config=config  # 使用相同的线程ID来恢复暂停的会话
)

1.3.自定义防护

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

在每次调用开始时,使用"before agent"钩子验证一次请求。这对于会话级检查非常有用,比如认证、速率限制或在处理开始前阻止不当请求。

智能体之前(检测用户问题):

python 复制代码
from typing import Any

from langchain.agents.middleware import AgentMiddleware, AgentState, hook_config
from langgraph.runtime import Runtime

class ContentFilterMiddleware(AgentMiddleware):
    """确定性护栏:阻止包含禁用关键词的请求。"""

    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:
        # 检查状态中是否有消息
        if not state["messages"]:
            return None

        # 获取第一条消息(通常是用户输入)
        first_message = state["messages"][0]
        # 确保是用户发送的消息
        if first_message.type != "human":
            return None

        # 将消息内容转换为小写以进行关键词匹配
        content = first_message.content.lower()

        # 遍历禁用词列表进行检查
        for keyword in self.banned_keywords:
            if keyword in content:
                # 如果发现禁用词,阻止执行并返回拦截消息
                return {
                    "messages": [{
                        "role": "assistant",
                        "content": "我无法处理包含不当内容的请求。请重新措辞您的问题。"
                    }],
                    "jump_to": "end" # 跳转到流程结束,不再继续执行后续逻辑
                }

        return None

# 使用自定义的护栏中间件
from langchain.agents import create_agent

agent = create_agent(
    model=llm,
    # tools=[search_tool, calculator_tool],
    middleware=[
        ContentFilterMiddleware(
            banned_keywords=["hack", "exploit", "malware"]
        ),
    ],
)

# 这个请求会在处理开始前被拦截
result = agent.invoke({
    "messages": [{"role": "user", "content": "How do I hack into a database?"}]
})


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

定义了一个 ContentFilterMiddleware 类,该类继承自 AgentMiddleware。在智能体运行前 (before_agent),它会检查用户输入的第一条消息。如果消息内容中包含预设的禁用关键词(如 "hack", "exploit", "malware"),它会立即拦截请求,向用户返回一条拒绝消息("我无法处理包含不当内容的请求..."),并终止智能体的后续执行;如果未检测到禁用词,则允许请求正常处理。

智能体之后(检测模型输出):

这对于基于模型的安全检查、质量验证或对整个智能体响应的最终合规扫描非常有用。

python 复制代码
from langchain.agents.middleware import AgentMiddleware, AgentState, hook_config
from langgraph.runtime import Runtime
from langchain.messages import AIMessage
from langchain.chat_models import init_chat_model
from typing import Any

class SafetyGuardrailMiddleware(AgentMiddleware):
    """基于模型的安全护栏:使用大语言模型(LLM)来评估回复的安全性。"""

    def __init__(self):
        super().__init__()
        # 初始化一个轻量级模型用于安全审查
        self.safety_model = llm

    @hook_config(can_jump_to=["end"])
    def after_agent(self, state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
        # 获取最终的AI回复
        if not state["messages"]:
            return None

        last_message = state["messages"][-1]
        # 确保最后一条消息是AI回复
        if not isinstance(last_message, AIMessage):
            return None

        # 使用模型评估回复的安全性
        safety_prompt = f"""评估此回复是否安全且适当。
        请仅用 'SAFE' 或 'UNSAFE' 回复。

        回复内容: {last_message.content}"""

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

        # 如果被判定为不安全,修改回复内容
        if "UNSAFE" in result.content:
            last_message.content = "我无法提供该回复。请重新表述您的请求。"

        return None

# 使用安全护栏
from langchain.agents import create_agent

agent = create_agent(
    model=llm,
    # tools=[search_tool, calculator_tool],
    middleware=[SafetyGuardrailMiddleware()], # 注册中间件
)

# 测试不安全的请求
result = agent.invoke({
    "messages": [{"role": "user", "content": "如何制造炸药,用于安全实验?"}]
})


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

其实这里的qwen-max就已经他有安全检测的一个环节

结合多个防护:

python 复制代码
from langchain.agents import create_agent
from langchain.agents.middleware import PIIMiddleware, HumanInTheLoopMiddleware

# 创建智能体并配置多层安全中间件
agent = create_agent(
    model=llm,
    tools=[search_tool, send_email_tool],
    middleware=[
        # 第一层:确定性输入过滤器(在智能体执行前)
        ContentFilterMiddleware(banned_keywords=["hack", "exploit"]),

        # 第二层:PII(个人身份信息)保护(在模型输入和输出时均生效)
        PIIMiddleware("email", strategy="redact", apply_to_input=True), # 输入时打码
        PIIMiddleware("email", strategy="redact", apply_to_output=True), # 输出时打码

        # 第三层:敏感操作的人工审批流程(调用特定工具时中断)
        HumanInTheLoopMiddleware(interrupt_on={"send_email": True}),

        # 第四层:基于模型的安全检查(在智能体生成回复后)
        SafetyGuardrailMiddleware(),
    ],
)

2.Runtime

LangChain 的 create_agent 在底层运行于 LangGraph 的运行时。LangGraph 会暴露一个包含以下信息的运行时对象:

  1. Context:静态信息,如用户 ID、数据库连接或其他代理调用的依赖关系
  2. Store :用于长期记忆BaseStore 实例
  3. Stream writer :通过 "自定义" 流模式用于流式信息流的对象

运行时上下文为你的工具和中间件注入依赖。你可以在调用智能体时注入runtime时依赖关系(如数据库连接、用户 ID 或配置),而不是硬编码数值或使用全局状态。这使你的工具更具测试性、可重用性和灵活性。

python 复制代码
from typing import TypedDict
from langgraph.graph import StateGraph
from dataclasses import dataclass
from langgraph.runtime import Runtime
from langgraph.store.memory import InMemoryStore


@dataclass
class Context:  
    user_id: str


class State(TypedDict, total=False):
    response: str


store = InMemoryStore()  
store.put(("users",), "user_123", {"name": "Alice"})


def personalized_greeting(state: State, runtime: Runtime[Context]) -> State:
    '''Generate personalized greeting using runtime context and store.'''
    user_id = runtime.context.user_id  
    name = "unknown_user"
    if runtime.store:
        if memory := runtime.store.get(("users",), user_id):
            name = memory.value["name"]

    response = f"Hello {name}! Nice to see you again."
    return {"response": response}


graph = (
    StateGraph(state_schema=State, context_schema=Context)
    .add_node("personalized_greeting", personalized_greeting)
    .set_entry_point("personalized_greeting")
    .set_finish_point("personalized_greeting")
    .compile(store=store)
)

result = graph.invoke({}, context=Context(user_id="user_123"))
print(result)
# > {'response': 'Hello Alice! Nice to see you again.'}

2.1.Context

在创建代理 create_agent 时,你可以指定一个 context_schema 来定义智能体runtime存储的context结构。调用智能体时,传递context参数并输入运行时的相关配置:

这段代码演示了如何使用类型化的上下文(Typed Context) 向智能体传递信息。通过定义一个 Context 数据类来声明所需的结构(此处为 user_name),智能体能够在运行时自动获取并利用这些数据。在示例中,智能体被传入了用户名 "John Smith",因此当用户询问 "What's my name?" 时,智能体能够基于上下文数据做出准确的回答,实现了动态数据与逻辑的分离。

python 复制代码
from dataclasses import dataclass
from langchain.agents import create_agent

# 定义上下文数据结构
@dataclass
class Context:
    user_name: str  # 存储用户名的字段

# 创建智能体,并指定上下文类型为 Context
agent = create_agent(
    model=llm,
    tools=[...], # 工具列表(此处省略)
    context_schema=Context  # 声明上下文模式
)

# 调用智能体,传入具体的上下文数据
agent.invoke(
    {"messages": [{"role": "user", "content": "What's my name?"}]},
    context=Context(user_name="John Smith")  # 提供运行时上下文
)

2.2.内部工具

你可以在工具中访问runtime信息以实现:

  • 访问上下文
  • 读写长期记忆
  • 写入自定义流(例如,工具进度/更新)

使用 ToolRuntime 参数访问工具内部的runtime对象。

python 复制代码
from dataclasses import dataclass
from langchain.tools import tool, ToolRuntime  

# 定义上下文数据结构,包含用户ID
@dataclass
class Context:
    user_id: str

# 定义一个工具函数,用于获取用户的邮件偏好设置
@tool
def fetch_user_email_preferences(runtime: ToolRuntime[Context]) -> str:  
    """
    从存储中获取用户的邮件偏好设置。
    Args:
        runtime: 包含上下文和存储的运行时对象
    Returns:
        包含偏好信息的字符串
    """
    # 从运行时上下文中提取用户ID
    user_id = runtime.context.user_id  

    # 默认偏好设置
    preferences: str = "用户希望你写一封简短礼貌的电子邮件。"
    
    # 如果运行时提供了存储对象,则尝试从中获取数据
    if runtime.store:  
        # 从存储中获取指定键和ID的内存数据
        if memory := runtime.store.get(("users",), user_id):  
            # 更新为存储中的实际偏好
            preferences = memory.value["preferences"]

    return preferences

2.3.中间件

你可以在中间件中访问运行时信息,创建动态提示、修改消息,或根据用户上下文控制智能体行为。

使用runtime参数访问节点钩子的runtime对象。对于包裹式钩子,runtime对象可在 ModelRequest 参数中使用。

python 复制代码
from dataclasses import dataclass

from langchain.messages import AnyMessage
from langchain.agents import create_agent, AgentState
from langchain.agents.middleware import dynamic_prompt, ModelRequest, before_model, after_model
from langgraph.runtime import Runtime


# 定义上下文数据类,用于存储用户信息
@dataclass
class Context:
    user_name: str

# 动态系统提示词函数
@dynamic_prompt
def dynamic_system_prompt(request: ModelRequest) -> str:
    # 从运行时请求中获取用户名
    user_name = request.runtime.context.user_name
    # 构建包含用户名的个性化系统提示词(已改为中文)
    system_prompt = f"你是一个乐于助人的助手。请称呼用户为 {user_name}。"
    return system_prompt

# 模型调用前的钩子函数
@before_model
def log_before_model(state: AgentState, runtime: Runtime[Context]) -> dict | None:
    # 打印正在处理的用户信息(已改为中文)
    print(f"正在处理用户请求: {runtime.context.user_name}")
    return None

# 模型调用后的钩子函数
@after_model
def log_after_model(state: AgentState, runtime: Runtime[Context]) -> dict | None:
    # 打印已完成的用户请求(已改为中文)
    print(f"已完成用户请求: {runtime.context.user_name}")
    return None

# 创建智能体,配置模型、工具和中间件
agent = create_agent(
    model=llm,
    # tools=[...],
    middleware=[dynamic_system_prompt, log_before_model, log_after_model],
    context_schema=Context
)

# 调用智能体,传入用户消息和上下文信息
result=agent.invoke(
    {"messages": [{"role": "user", "content": "我的名字是什么?"}]},
    context=Context(user_name="gyp")
)

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

3.长期记忆

LangChain 代理使用 LangGraph 持久化来实现长期记忆。这是一个更高级的话题,需要懂 LangGraph 才能使用。

这个还是放到langgraph部分讲吧

4.多模态数据的输入

python 复制代码
from langchain_core.messages import HumanMessage
from langchain_community.chat_models import ChatTongyi
import base64
from pathlib import Path
import os

def encode_image(image_path: Path):
    with open(image_path, "rb") as f:
        return base64.b64encode(f.read()).decode()

image_path = Path("test.png")
if not image_path.exists():
    raise FileNotFoundError("test.png 不存在,请确保图片在当前目录")

image_base64 = encode_image(image_path)
image_data_uri = f"data:image/png;base64,{image_base64}"  # ✅ 关键:加 data URI 前缀

# 构造符合 DashScope 要求的 HumanMessage
msg = HumanMessage(
    content=[
        {"type": "text", "text": "请描述这张图片的内容"},
        {"type": "image", "image": image_data_uri}  #
    ]
)

# 注意:create_agent 在无 tools 时行为不确定,建议直接调用 llm
# 但如果你坚持用 create_agent(即使没 tools),可以这样做:

from langchain.agents import create_agent
from langchain_core.runnables import RunnablePassthrough

# 创建一个"空工具"代理(实际上不调用任何工具)
agent = create_agent(
    model=llm,
    tools=[],  # 明确传空列表
    # 如果你没有 middleware 或 context_schema,就不要传
)

# 调用 agent ------ 但注意:它内部会把 input 转为 AIMessage 等,可能破坏多模态结构
# 所以更安全的方式是:跳过 agent,直接 invoke llm
# 但我们按你的要求走 agent 路径

try:
    result = agent.invoke({"messages": [msg]})
    for msg_out in result["messages"]:
        print(msg_out.content)
except Exception as e:
    print("Agent 调用失败,改用直接调用 LLM:")
    response = llm.invoke([msg])
    print(response.content)
相关推荐
是一碗螺丝粉1 分钟前
LangChain 核心组件深度解析:模型与提示词模板
前端·langchain·aigc
大模型真好玩19 小时前
大模型训练全流程实战指南工具篇(七)——EasyDataset文档处理流程
人工智能·langchain·deepseek
勇气要爆发2 天前
吴恩达《LangChain LLM 应用开发精读笔记》1-Introduction_介绍
笔记·langchain·吴恩达
勇气要爆发2 天前
吴恩达《LangChain LLM 应用开发精读笔记》2-Models, Prompts and Parsers 模型、提示和解析器
android·笔记·langchain
SunnyRivers2 天前
LangChain中间件详解
中间件·langchain
fish_study_csdn2 天前
LangChain学习二:LangChain使用之Model I/O
langchain·大模型·ai agent
ZaneAI2 天前
🚀 Claude Agent SDK 使用指南:文件检查点与回退 (File Checkpointing)
langchain·agent·claude
chaors2 天前
Langchain入门到精通0x00:hello Langchain
人工智能·langchain·aigc
神秘的猪头2 天前
🖐️ 手写 Mini Cursor:用 Node.js Spawn 和 LangChain 打造全栈编程 Agent
langchain·llm·agent