目录
1.Agent入门初体验
一个demo快速落地
python
from langchain.agents import create_agent
from langchain_community.chat_models.tongyi import ChatTongyi
from langchain_core.tools import tool
@tool(description="查询天气")
def get_weather() -> str:
return "晴天"
agent = create_agent(
model=ChatTongyi(model="qwen3-max"), # 智能体的大脑LLM
tools=[get_weather], # 向智能体提供工具列表
system_prompt="你是一个聊天助手,可以回答用户问题。",
)
res = agent.invoke(
{
"messages": [
{"role": "user", "content": "明天深圳的天气如何?"},
]
}
)
for msg in res["messages"]:
print(type(msg).__name__, msg.content)
2.agent流式输出
python
from langchain.agents import create_agent
from langchain_community.chat_models.tongyi import ChatTongyi
from langchain_core.tools import tool
@tool(description="获取股价,传入股票名称,返回字符串信息")
def get_price(name: str) -> str:
return f"股票{name}的价格是20元"
@tool(description="获取股票信息,传入股票名称,返回字符串信息")
def get_info(name: str) -> str:
return f"股票{name},是一家A股上市公司,专注于IT职业教育。"
agent = create_agent(
model=ChatTongyi(model="qwen3-max"),
tools=[get_price, get_info],
system_prompt="你是一个智能助手,可以回答股票相关问题,记住请告知我思考过程,让我知道你为什么调用某个工具"
)
for chunk in agent.stream(
{"messages": [{"role": "user", "content": "传智教育股价多少,并介绍一下"}]},
stream_mode="values"
):
latest_message = chunk['messages'][-1]
if latest_message.content:
print(type(latest_message).__name__, latest_message.content)
try:
if latest_message.tool_calls:
print(f"工具调用: { [tc['name'] for tc in latest_message.tool_calls] }")
except AttributeError as e:
pass
3.ReAct演示
python
from langchain.agents import create_agent
from langchain_community.chat_models.tongyi import ChatTongyi
from langchain_core.tools import tool
@tool(description="获取体重,返回值是整数,单位千克")
def get_weight() -> int:
return 90
@tool(description="获取身高,返回值是整数,单位厘米")
def get_height() -> int:
return 172
agent = create_agent(
model=ChatTongyi(model="qwen3-max"),
tools=[get_weight, get_height],
system_prompt="""你是严格遵循ReAct框架的智能体,必须按「思考→行动→观察→再思考」的流程解决问题,
且**每轮仅能思考并调用1个工具**,禁止单次调用多个工具。
并告知我你的思考过程,工具的调用原因,按思考、行动、观察三个结构告知我""",
)
for chunk in agent.stream(
{"messages": [{"role": "user", "content": "计算我的BMI"}]},
stream_mode="values"
):
latest_message = chunk['messages'][-1]
if latest_message.content:
print(type(latest_message).__name__, latest_message.content)
try:
if latest_message.tool_calls:
print(f"工具调用: { [tc['name'] for tc in latest_message.tool_calls] }")
except AttributeError as e:
pass
4.middleware中间件
监控agent行为,在agent、model等执行前后可以进行干预
python
from langchain.agents import create_agent, AgentState
from langchain.agents.middleware import before_agent, after_agent, before_model, after_model, wrap_model_call, \
wrap_tool_call
from langchain_community.chat_models.tongyi import ChatTongyi
from langchain_core.tools import tool
from langgraph.runtime import Runtime
@tool(description="查询天气,传入城市名称字符串,返回字符串天气信息")
def get_weather(city: str) -> str:
return f"{city}天气:晴天"
"""
1. agent执行前
2. agent执行后
3. model执行前
4. model执行后
5. 工具执行中
6. 模型执行中
"""
@before_agent
def log_before_agent(state: AgentState, runtime: Runtime) -> None:
# agent执行前会调用这个函数并传入state和runtime两个对象
print(f"[before agent]agent启动,并附带{len(state['messages'])}消息")
@after_agent
def log_after_agent(state: AgentState, runtime: Runtime) -> None:
print(f"[after agent]agent结束,并附带{len(state['messages'])}消息")
@before_model
def log_before_model(state: AgentState, runtime: Runtime) -> None:
print(f"[before_model]模型即将调用,并附带{len(state['messages'])}消息")
@after_model
def log_after_model(state: AgentState, runtime: Runtime) -> None:
print(f"[after_model]模型调用结束,并附带{len(state['messages'])}消息")
@wrap_model_call
def model_call_hook(request, handler):
print("模型调用啦")
return handler(request)
@wrap_tool_call
def monitor_tool(request, handler):
print(f"工具执行:{request.tool_call['name']}")
print(f"工具执行传入参数:{request.tool_call['args']}")
return handler(request)
agent = create_agent(
model=ChatTongyi(model="qwen3-max"),
tools=[get_weather],
middleware=[log_before_agent, log_after_agent, log_before_model, log_after_model, model_call_hook, monitor_tool]
)
res = agent.invoke({"messages": [{"role": "user", "content": "深圳今天的天气如何呀,如何穿衣"}]})
print("**********\n", res)
5.实战项目结合
rag+agent统统用到,代码风格也会更加配合企业开发规范
【提供核心代码】
1.react_agent.py
python
from langchain.agents import create_agent #导包省略..
class ReactAgent:
def __init__(self):
self.agent = create_agent(
model=chat_model,
system_prompt=load_system_prompts(),
tools=[rag_summarize, get_weather, get_user_location, get_user_id,
get_current_month, fetch_external_data, fill_context_for_report],
middleware=[monitor_tool, log_before_model, report_prompt_switch],
)
def execute_stream(self, query: str):
input_dict = {
"messages": [
{"role": "user", "content": query},
]
}
# 第三个参数context就是上下文runtime中的信息,就是我们做提示词切换的标记
for chunk in self.agent.stream(input_dict, stream_mode="values", context={"report": False}):
latest_message = chunk["messages"][-1]
if latest_message.content:
yield latest_message.content.strip() + "\n"
if __name__ == '__main__':
agent = ReactAgent()
for chunk in agent.execute_stream("给我生成我的使用报告"):
print(chunk, end="", flush=True)
2.agent_tool.py
python
import os
from utils.logger_handler import logger
from langchain_core.tools import tool
from rag.rag_service import RagSummarizeService
import random
from utils.config_handler import agent_conf
from utils.path_tool import get_abs_path
rag = RagSummarizeService()
user_ids = ["1001", "1002", "1003", "1004", "1005", "1006", "1007", "1008", "1009", "1010",]
month_arr = ["2025-01", "2025-02", "2025-03", "2025-04", "2025-05", "2025-06",
"2025-07", "2025-08", "2025-09", "2025-10", "2025-11", "2025-12", ]
external_data = {}
@tool(description="从向量存储中检索参考资料")
def rag_summarize(query: str) -> str:
return rag.rag_summarize(query)
@tool(description="获取指定城市的天气,以消息字符串的形式返回")
def get_weather(city: str) -> str:
return f"城市{city}天气为晴天,气温26摄氏度,空气湿度50%,南风1级,AQI21,最近6小时降雨概率极低"
@tool(description="获取用户所在城市的名称,以纯字符串形式返回")
def get_user_location() -> str:
return random.choice(["深圳", "合肥", "杭州"])
@tool(description="获取用户的ID,以纯字符串形式返回")
def get_user_id() -> str:
return random.choice(user_ids)
@tool(description="获取当前月份,以纯字符串形式返回")
def get_current_month() -> str:
return random.choice(month_arr)
def generate_external_data():
"""
{
"user_id": {
"month" : {"特征": xxx, "效率": xxx, ...}
"month" : {"特征": xxx, "效率": xxx, ...}
"month" : {"特征": xxx, "效率": xxx, ...}
...
},
"user_id": {
"month" : {"特征": xxx, "效率": xxx, ...}
"month" : {"特征": xxx, "效率": xxx, ...}
"month" : {"特征": xxx, "效率": xxx, ...}
...
},
"user_id": {
"month" : {"特征": xxx, "效率": xxx, ...}
"month" : {"特征": xxx, "效率": xxx, ...}
"month" : {"特征": xxx, "效率": xxx, ...}
...
},
...
}
:return:
"""
if not external_data:
external_data_path = get_abs_path(agent_conf["external_data_path"])
if not os.path.exists(external_data_path):
raise FileNotFoundError(f"外部数据文件{external_data_path}不存在")
with open(external_data_path, "r", encoding="utf-8") as f:
for line in f.readlines()[1:]:
arr: list[str] = line.strip().split(",")
user_id: str = arr[0].replace('"', "")
feature: str = arr[1].replace('"', "")
efficiency: str = arr[2].replace('"', "")
consumables: str = arr[3].replace('"', "")
comparison: str = arr[4].replace('"', "")
time: str = arr[5].replace('"', "")
if user_id not in external_data:
external_data[user_id] = {}
external_data[user_id][time] = {
"特征": feature,
"效率": efficiency,
"耗材": consumables,
"对比": comparison,
}
@tool(description="从外部系统中获取指定用户在指定月份的使用记录,以纯字符串形式返回, 如果未检索到返回空字符串")
def fetch_external_data(user_id: str, month: str) -> str:
generate_external_data()
try:
return external_data[user_id][month]
except KeyError:
logger.warning(f"[fetch_external_data]未能检索到用户:{user_id}在{month}的使用记录数据")
return ""
@tool(description="无入参,无返回值,调用后触发中间件自动为报告生成的场景动态注入上下文信息,为后续提示词切换提供上下文信息")
def fill_context_for_report():
return "fill_context_for_report已调用"
3.middleware.py
python
from typing import Callable
from utils.prompt_loader import load_system_prompts, load_report_prompts
from langchain.agents import AgentState
from langchain.agents.middleware import wrap_tool_call, before_model, dynamic_prompt, ModelRequest
from langchain.tools.tool_node import ToolCallRequest
from langchain_core.messages import ToolMessage
from langgraph.runtime import Runtime
from langgraph.types import Command
from utils.logger_handler import logger
@wrap_tool_call
def monitor_tool(
# 请求的数据封装
request: ToolCallRequest,
# 执行的函数本身
handler: Callable[[ToolCallRequest], ToolMessage | Command],
) -> ToolMessage | Command: # 工具执行的监控
logger.info(f"[tool monitor]执行工具:{request.tool_call['name']}")
logger.info(f"[tool monitor]传入参数:{request.tool_call['args']}")
try:
result = handler(request)
logger.info(f"[tool monitor]工具{request.tool_call['name']}调用成功")
if request.tool_call['name'] == "fill_context_for_report":
request.runtime.context["report"] = True
return result
except Exception as e:
logger.error(f"工具{request.tool_call['name']}调用失败,原因:{str(e)}")
raise e
@before_model
def log_before_model(
state: AgentState, # 整个Agent智能体中的状态记录
runtime: Runtime, # 记录了整个执行过程中的上下文信息
): # 在模型执行前输出日志
logger.info(f"[log_before_model]即将调用模型,带有{len(state['messages'])}条消息。")
logger.debug(f"[log_before_model]{type(state['messages'][-1]).__name__} | {state['messages'][-1].content.strip()}")
return None
@dynamic_prompt # 每一次在生成提示词之前,调用此函数
def report_prompt_switch(request: ModelRequest): # 动态切换提示词
is_report = request.runtime.context.get("report", False)
if is_report: # 是报告生成场景,返回报告生成提示词内容
return load_report_prompts()
return load_system_prompts()
python
from abc import ABC, abstractmethod
from typing import Optional
from langchain_core.embeddings import Embeddings
from langchain_community.chat_models.tongyi import BaseChatModel
from langchain_community.embeddings import DashScopeEmbeddings
from langchain_community.chat_models.tongyi import ChatTongyi
from utils.config_handler import rag_conf
class BaseModelFactory(ABC):
@abstractmethod
def generator(self) -> Optional[Embeddings | BaseChatModel]:
pass
class ChatModelFactory(BaseModelFactory): #模型工厂,拓展性
def generator(self) -> Optional[Embeddings | BaseChatModel]:
return ChatTongyi(model=rag_conf["chat_model_name"])
class EmbeddingsFactory(BaseModelFactory): #embedding统一获取入口,拓展
def generator(self) -> Optional[Embeddings | BaseChatModel]:
return DashScopeEmbeddings(model=rag_conf["embedding_model_name"])
chat_model = ChatModelFactory().generator()
embed_model = EmbeddingsFactory().generator()
4.rag_service.py
python
class RagSummarizeService(object):
def __init__(self):
self.vector_store = VectorStoreService()
self.retriever = self.vector_store.get_retriever()
self.prompt_text = load_rag_prompts()
self.prompt_template = PromptTemplate.from_template(self.prompt_text)
self.model = chat_model
self.chain = self._init_chain()
def _init_chain(self):
chain = self.prompt_template | print_prompt | self.model | StrOutputParser()
return chain
def retriever_docs(self, query: str) -> list[Document]:
return self.retriever.invoke(query)
def rag_summarize(self, query: str) -> str:
context_docs = self.retriever_docs(query)
context = ""
counter = 0
for doc in context_docs:
counter += 1
context += f"【参考资料{counter}】: 参考资料:{doc.page_content} | 参考元数据:{doc.metadata}\n"
return self.chain.invoke(
{
"input": query,
"context": context,
}
)
if __name__ == '__main__':
rag = RagSummarizeService()
print(rag.rag_summarize("小户型适合哪些扫地机器人"))
5.vector_store.py
python
from langchain_chroma import Chroma
from langchain_core.documents import Document
from utils.config_handler import chroma_conf
from model.factory import embed_model
from langchain_text_splitters import RecursiveCharacterTextSplitter
from utils.path_tool import get_abs_path
from utils.file_handler import pdf_loader, txt_loader, listdir_with_allowed_type, get_file_md5_hex
from utils.logger_handler import logger
import os
class VectorStoreService:
def __init__(self):
self.vector_store = Chroma( #持久化
collection_name=chroma_conf["collection_name"],
embedding_function=embed_model, #embedding模型
persist_directory=chroma_conf["persist_directory"],
)
self.spliter = RecursiveCharacterTextSplitter( #文本分割器
chunk_size=chroma_conf["chunk_size"],
chunk_overlap=chroma_conf["chunk_overlap"],
separators=chroma_conf["separators"],
length_function=len,
)
def get_retriever(self): #检索器
return self.vector_store.as_retriever(search_kwargs={"k": chroma_conf["k"]})
def load_document(self): #加载文档,注意重复文档重复加载,所有引入MD5
"""
从数据文件夹内读取数据文件,转为向量存入向量库
要计算文件的MD5做去重
:return: None
"""
def check_md5_hex(md5_for_check: str):
if not os.path.exists(get_abs_path(chroma_conf["md5_hex_store"])):
# 创建文件
open(get_abs_path(chroma_conf["md5_hex_store"]), "w", encoding="utf-8").close()
return False # md5 没处理过
with open(get_abs_path(chroma_conf["md5_hex_store"]), "r", encoding="utf-8") as f:
for line in f.readlines():
line = line.strip()
if line == md5_for_check:
return True # md5 处理过
return False # md5 没处理过
def save_md5_hex(md5_for_check: str):
with open(get_abs_path(chroma_conf["md5_hex_store"]), "a", encoding="utf-8") as f:
f.write(md5_for_check + "\n")
def get_file_documents(read_path: str):
if read_path.endswith("txt"):
return txt_loader(read_path)
if read_path.endswith("pdf"):
return pdf_loader(read_path)
return []
allowed_files_path: list[str] = listdir_with_allowed_type(
get_abs_path(chroma_conf["data_path"]),
tuple(chroma_conf["allow_knowledge_file_type"]),
)
for path in allowed_files_path:
# 获取文件的MD5
md5_hex = get_file_md5_hex(path)
if check_md5_hex(md5_hex):
logger.info(f"[加载知识库]{path}内容已经存在知识库内,跳过")
continue
try:
documents: list[Document] = get_file_documents(path)
if not documents:
logger.warning(f"[加载知识库]{path}内没有有效文本内容,跳过")
continue
split_document: list[Document] = self.spliter.split_documents(documents)
if not split_document:
logger.warning(f"[加载知识库]{path}分片后没有有效文本内容,跳过")
continue
# 将内容存入向量库
self.vector_store.add_documents(split_document)
# 记录这个已经处理好的文件的md5,避免下次重复加载
save_md5_hex(md5_hex)
logger.info(f"[加载知识库]{path} 内容加载成功")
except Exception as e:
# exc_info为True会记录详细的报错堆栈,如果为False仅记录报错信息本身
logger.error(f"[加载知识库]{path}加载失败:{str(e)}", exc_info=True)
continue
if __name__ == '__main__':
vs = VectorStoreService()
vs.load_document()
retriever = vs.get_retriever()
res = retriever.invoke("迷路")
for r in res:
print(r.page_content)
print("-"*20)