diff --git a/.env.example b/.env.example index d9a9e7e..fedb5ec 100644 --- a/.env.example +++ b/.env.example @@ -1 +1,5 @@ -OPENAI_API_KEY=your_openai_api_key \ No newline at end of file +# LLM密钥 +OPENAI_API_KEY=your_openai_api_key + +# LangSmith 相关配置(用于调试/监控Langgraph流程) -- 未用到可暂时不更改 +LANGCHAIN_API_KEY=your_langsmith_api_key \ No newline at end of file diff --git a/08_langgraph_basics/01_state_node_edge.py b/08_langgraph_basics/01_state_node_edge.py new file mode 100644 index 0000000..73b6202 --- /dev/null +++ b/08_langgraph_basics/01_state_node_edge.py @@ -0,0 +1,42 @@ +from typing import TypedDict +from langgraph.graph import StateGraph,END,START + + +# 1. 定义State(状态) -- 白板上只有一个字段"count" +class State(TypedDict): + count:int + +# 2. 编写Node(节点) -- 两个"工人" +def node_a(state:State): + # 接收当前State状态,返回要更新的部分 + print(f'[Node A]收到状态:{state}') + return {"count":state["count"]+1} + +def node_b(state:State): + print(f'[Node B]收到状态:{state}') + return {"count":state["count"]+1} + + +# 3. 添加Node到图中 +workflow = StateGraph(State) # 创建画布 +workflow.add_node("A",node_a) # 添加节点A +workflow.add_node("B",node_b) # 添加节点B + +# 4. 用Edge连线 +workflow.add_edge(START,"A") # START -> A +workflow.add_edge("A","B") # A -> B +workflow.add_edge("B",END) # B -> END + +# 编译成可运行应用 +app = workflow.compile() + +# 传入初始状态,执行工作流 +print("---开始执行---") +result = app.invoke({"count":1}) +print("最终状态:",result) # 输出{'count':4} + + +# 保存可视化架构图 +with open('01_workflow.png', 'wb') as f: + f.write(app.get_graph().draw_mermaid_png()) +print("图表已保存为 01_workflow.png") \ No newline at end of file diff --git a/08_langgraph_basics/01_workflow.png b/08_langgraph_basics/01_workflow.png new file mode 100644 index 0000000..5fc9720 Binary files /dev/null and b/08_langgraph_basics/01_workflow.png differ diff --git a/08_langgraph_basics/02_langsmith_trace.py b/08_langgraph_basics/02_langsmith_trace.py new file mode 100644 index 0000000..9dd9e92 --- /dev/null +++ b/08_langgraph_basics/02_langsmith_trace.py @@ -0,0 +1,51 @@ +# pip install --pre -U langchain langchain-openai +from dotenv import load_dotenv +import os + + +load_dotenv() +langchain_api_key = os.getenv("LANGCHAIN_API_KEY") + + +from typing import TypedDict +from langgraph.graph import StateGraph,END,START + + +os.environ["LANGCHAIN_TRACING_V2"] = "true" # 总开关,决定启用追踪功能 +os.environ["LANGCHAIN_PROJECT"] = "my_demo" # 自定义项目名 +os.environ["LANGCHAIN_API_KEY"] = langchain_api_key + + +# 1. 定义State(状态) -- 白板上只有一个字段"count" +class State(TypedDict): + count:int + +# 2. 编写Node(节点) -- 两个"工人" +def node_a(state:State): + # 接收当前State状态,返回要更新的部分 + print(f'[Node A]收到状态:{state}') + return {"count":state["count"]+1} + +def node_b(state:State): + print(f'[Node B]收到状态:{state}') + return {"count":state["count"]+1} + + +# 3. 添加Node到图中 +workflow = StateGraph(State) # 创建画布 +workflow.add_node("A",node_a) # 添加节点A +workflow.add_node("B",node_b) # 添加节点B + +# 4. 用Edge连线 +workflow.add_edge(START,"A") # START -> A +workflow.add_edge("A","B") # A -> B +workflow.add_edge("B",END) # B -> END + +# 编译成可运行应用 +app = workflow.compile() + +# 传入初始状态,执行工作流 +print("---开始执行---") +result = app.invoke({"count":1}) +print("最终状态:",result) # 输出{'count':4} + diff --git a/08_langgraph_basics/03_conditional_router.py b/08_langgraph_basics/03_conditional_router.py new file mode 100644 index 0000000..2e27044 --- /dev/null +++ b/08_langgraph_basics/03_conditional_router.py @@ -0,0 +1,87 @@ +from dotenv import load_dotenv +import os + + +load_dotenv() +api_key = os.getenv("OPENAI_API_KEY") +langchain_api_key = os.getenv("LANGCHAIN_API_KEY") + +from langchain.schema import HumanMessage +from langchain.tools import tool +from langchain_openai import ChatOpenAI +from langgraph.graph import StateGraph, MessagesState, END,START +from langgraph.prebuilt import ToolNode + +os.environ["LANGCHAIN_TRACING_V2"] = "true" # 总开关,决定启用追踪功能 +os.environ["LANGCHAIN_PROJECT"] = "demo01" # 自定义项目名 +os.environ["LANGCHAIN_API_KEY"] = langchain_api_key + +# LLM配置 +llm = ChatOpenAI( + model="deepseek-chat", + api_key=api_key, + base_url="https://api.deepseek.com" +) + +# 工具定义 +@tool +def get_weather(loaction): + """模拟获取天气""" + return f'{loaction}当前天气:23℃,晴,风力2级' + + +tools = [get_weather] +llm_with_tools = llm.bind_tools(tools) # 让llm学会调用工具节点 + + +# --- 核心组件:拆解AgentExecutor --- +# ReAct Step1:Thought(LLM决策) +def call_model(state:MessagesState): + response = llm_with_tools.invoke(state['messages']) + return {"messages":[response]} # 新消息追加到状态 + + +# ReAct Step2-3:Action + Observation +tool_node = ToolNode(tools) + +# ReAct Step4:Loop Controller(是否循环) +def should_continue(state:MessagesState): + last_msg = state["messages"][-1] + if hasattr(last_msg,"tool_calls") and len(last_msg.tool_calls) > 0: + return "tools" # 有工具调用 -> 执行工具 + return END # 无工具调用 -> 返回答案 + + +# --- 构建 ReAct 循环图--- +workflow = StateGraph(MessagesState) + +workflow.add_node("agent",call_model) # Thought +workflow.add_node("tools",tool_node) # Action + Observation + +workflow.add_edge(START,"agent") + +# 条件边:Thought -> 决定下一步 +workflow.add_conditional_edges( + "agent", # 从哪个节点出发 + should_continue, # 决定下一步去哪 + { + "tools":"tools", # 如果返回tools,去tools节点 + END:END # 如果返回END,直接结束工作流 + } +) + +workflow.add_edge("tools","agent") + +app = workflow.compile() + +if __name__ == '__main__': + # 触发工具 + result = app.invoke( + {"messages":[ + HumanMessage(content="北京天气如何?") + ]} + ) + print('工具调用结果:',result['messages'][-1].content) + # 不触发工具 + result = app.invoke({"messages":HumanMessage(content="你好")}) + print('直接回答:',result['messages'][-1].content) \ No newline at end of file diff --git a/08_langgraph_basics/04_agent_with_memory.py b/08_langgraph_basics/04_agent_with_memory.py new file mode 100644 index 0000000..309bdca --- /dev/null +++ b/08_langgraph_basics/04_agent_with_memory.py @@ -0,0 +1,113 @@ +from dotenv import load_dotenv +import os + +load_dotenv() +api_key = os.getenv("OPENAI_API_KEY") +langchain_api_key = os.getenv("LANGCHAIN_API_KEY") + +from langchain.schema import HumanMessage +from langchain.tools import tool +from langchain_openai import ChatOpenAI +from langgraph.graph import StateGraph, MessagesState, END,START +from langgraph.prebuilt import ToolNode +from langchain_core.messages import SystemMessage +from langgraph.checkpoint.memory import MemorySaver + +os.environ["LANGCHAIN_TRACING_V2"] = "true" # 总开关,决定启用追踪功能 +os.environ["LANGCHAIN_PROJECT"] = "demo02" # 自定义项目名 +os.environ["LANGCHAIN_API_KEY"] = langchain_api_key + +# LLM配置 +llm = ChatOpenAI( + model="deepseek-chat", + api_key=api_key, + base_url="https://api.deepseek.com" +) + +# Prompt配置 +sys_prompt = "你是一个强大的助手,能查天气,也能回答一般问题。请使用中文回答。" + +# 工具定义 +@tool +def get_weather(loaction): + """模拟获取天气""" + return f'{loaction}当前天气:23℃,晴,风力2级' + + +tools = [get_weather] +llm_with_tools = llm.bind_tools(tools) # 让llm学会调用工具节点 + + +# --- 核心组件:拆解AgentExecutor --- +# ReAct Step1:Thought(LLM决策) +def call_model(state:MessagesState): + # 构造带system prompt 的完整消息列表(仅用于本次LLM调用) + message_for_llm = [SystemMessage(content=sys_prompt)]+state["messages"] + response = llm_with_tools.invoke(message_for_llm) + # 此处只会返回新生成的消息,不包含prompt,防止污染历史 + return {"messages":[response]} # 新消息追加到状态 + + +# ReAct Step2-3:Action + Observation +tool_node = ToolNode(tools) + +# ReAct Step4:Loop Controller(是否循环) +def should_continue(state:MessagesState): + last_msg = state["messages"][-1] + if hasattr(last_msg,"tool_calls") and len(last_msg.tool_calls) > 0: + return "tools" # 有工具调用 -> 执行工具 + return END # 无工具调用 -> 返回答案 + + +# --- 构建 ReAct 循环图--- +workflow = StateGraph(MessagesState) + +workflow.add_node("agent",call_model) # Thought +workflow.add_node("tools",tool_node) # Action + Observation + +workflow.add_edge(START,"agent") + +# 条件边:Thought -> 决定下一步 +workflow.add_conditional_edges( + "agent", # 从哪个节点出发 + should_continue, # 决定下一步去哪 + { + "tools":"tools", # 如果返回tools,去tools节点 + END:END # 如果返回END,直接结束工作流 + } +) + +workflow.add_edge("tools","agent") + + + + + +# 编译时启用记忆 +app = workflow.compile(checkpointer=MemorySaver()) + + + +if __name__ == '__main__': + session_id = "user123" + config = { + "configurable":{"thread_id":session_id} + } + while 1: + user_input = input('\n你:') + if user_input.strip().lower() == 'quit': + break + + result = app.invoke( + {'messages':[HumanMessage(content=user_input)]}, + config=config + ) + + ai_msg = result["messages"][-1] + print(f'AI:{ai_msg.content}') + + + + + + diff --git a/08_langgraph_basics/README.md b/08_langgraph_basics/README.md new file mode 100644 index 0000000..835478a --- /dev/null +++ b/08_langgraph_basics/README.md @@ -0,0 +1,101 @@ +# 🧩 模块说明:LangGraph 基础篇 - 从状态机到智能 Agent + +> 📌 核心知识点:StateGraph 基础结构|MessagesState 对话状态|ToolNode 工具执行|条件路由|MemorySaver 记忆|System Prompt 无污染注入|LangSmith 追踪 + +--- + +### 1. `01_state_node_edge.py` (LangGraph 最小状态机) + +演示 LangGraph 最基础的三要素:State(状态)、Node(节点)、Edge(边)。 + +- ✅ 掌握点: + - 定义 `TypedDict` 作为状态(如 `{"count": int}`)。 + - 编写节点函数(`node_a`, `node_b`)处理状态并返回更新。 + - 使用 `add_edge(START, "A")`、`add_edge("A", "B")` 构建线性流程。 + - 调用 `app.get_graph().draw_mermaid_png()` 生成可视化流程图。 + +- 输出: + - 控制台打印状态变化过程。 + - 生成 `01_workflow.png` 流程图。 + +> 💡 此脚本**不涉及 LLM 或对话**,仅用于理解 LangGraph 底层机制。 + +--- + +### 2. `01_workflow.png` (状态机流程图) + +由 `01_state_node_edge.py` 自动生成的 Mermaid 可视化图。 + +- 内容: + - 展示 `START → A → B → END` 的线性执行流。 + - 节点 `A` 和 `B` 各将 `count` 加 1。 +- 作用: + - 直观理解 LangGraph 的“图”本质。 + - 为后续复杂控制流(如循环、分支)打下认知基础。 + +--- + +### 3. `02_langsmith_trace.py` (状态机 + LangSmith 追踪) + +在 `01` 的基础上启用 LangSmith,实现执行过程可视化追踪。 + +- ✅ 掌握点: + - 设置环境变量:`LANGCHAIN_TRACING_V2=true`、`LANGCHAIN_PROJECT="my_demo"`。 + - 无需修改节点逻辑,自动上报每一步状态变更。 + - 在 [LangSmith UI](https://smith.langchain.com) 查看执行轨迹。 + +- 注意: + - 仍是一个**纯状态机示例**(count +1 +1),**非对话 Agent**。 + - 用于验证 LangSmith 集成是否生效。 + +--- + +### 4. `03_conditional_router.py` (首个 ReAct Agent:无记忆) + +构建第一个真正意义上的 LangGraph Agent:支持工具调用与条件循环。 + +- ✅ 掌握点: + - 使用 `MessagesState` 管理对话历史。 + - 定义 `@tool def get_weather(location)` 并绑定到 LLM。 + - 实现 `should_continue` 函数判断是否需调用工具。 + - 构建 `agent → (条件) → tools → agent` 的 ReAct 循环。 + - 通过 `app.invoke({"messages": [...]})` 触发执行。 + +- 效果: + - 输入“北京天气如何?” → 调用工具 → 返回天气。 + - 输入“你好” → 直接回答,不触发工具。 +- 局限: + - **无记忆**:每次 `invoke` 都是独立会话。 + +--- + +### 5. `04_agent_with_memory.py` (完整 Agent:带记忆 + 系统提示) + +在 `03` 基础上升级为生产级 Agent 范式。 + +- ✅ 掌握点: + - 启用 `MemorySaver()` 实现跨轮次记忆(`checkpointer=MemorySaver()`)。 + - 通过 `config={"configurable": {"thread_id": "user123"}}` 绑定会话。 + - 在 `call_model` 中动态注入 `SystemMessage(content=sys_prompt)`,**但不写入 state**,避免污染历史。 + - 保留 LangSmith 追踪能力。 + +- 效果: + - 支持连续对话(如“查北京天气” → “那上海呢?”)。 + - 系统指令始终生效,但不会出现在消息历史中。 + - 全流程可追踪、可调试。 + +--- + +### 🔔 全局注意事项 + +- **学习路径建议**: + `01`(理解图) → `02`(理解追踪) → `03`(理解 ReAct) → `04`(理解记忆+prompt) +- 所有 `.py` 文件均依赖 `.env` 中的 `OPENAI_API_KEY` 和 `LANGCHAIN_API_KEY`。 +- 若想复用你的 RAG 工具,只需将 `get_weather` 替换为 `search_war_and_peace`,其余逻辑不变。 +- `01_workflow.png` 仅反映 `01_state_node_edge.py` 的简单线性流,**不代表 Agent 结构**。 + +--- + +### 💡 **建议** + +先运行 `01` 和 `02` 理解 LangGraph 底层机制,再重点掌握 `03` 和 `04` —— 它们构成了后续所有进阶 Agent(多工具、人工干预、自定义状态等)的**标准范式**。 \ No newline at end of file diff --git a/README.md b/README.md index d95dee7..c2e2382 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ ## 🧭 模块总览(16 模块) > 💡 每个模块对应独立目录(含代码、说明与示例),可独立运行与学习。 -> 🔥 已更新至 07 Rag 进阶篇,持续更新中! +> 🔥 已更新至 08 LangGraph 基础篇,持续更新中! | 章节 | 模块 | 博客 | 核心关键词 | 难度 | |:-----------:|:--------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------:|:----------------------------------------------------|:-----:| @@ -32,8 +32,8 @@ | | [05 LangChain 进阶篇](https://github.com/Annyfee/agent-craft/tree/main/05_langchain_advanced) | [🏠](https://blog.csdn.net/2401_87328929/article/details/154064397) | Agents · 缓存 · 流式输出 | ⭐⭐⭐ | | | [06 RAG 基础篇](https://github.com/Annyfee/agent-craft/tree/main/06_rag_basics) | [🏠](https://blog.csdn.net/2401_87328929/article/details/154230067) | RAG概念 · Split · Embedding · FAISS · RAG 链 | ⭐⭐ | | | [07 RAG 进阶篇](https://github.com/Annyfee/agent-craft/tree/main/07_rag_advanced) | [🏠](https://blog.csdn.net/2401_87328929/article/details/154408744) | Chroma · Reranker · RAG工具化 · 集成 | ⭐⭐⭐ | -| | 08 LangGraph 基础篇 | 🚧撰写中 | `State` · `Node` · `Conditional Edge` · RAG+反思 | ⭐⭐⭐ | -| | 09 LangGraph 进阶篇 | 🚧 | `Multi-Agent` · `Human-in-the-Loop` · “总控-员工”架构 | ⭐⭐⭐⭐ | +| | [08 LangGraph 基础篇]() | [🏠](https://blog.csdn.net/2401_87328929/article/details/154576812) | Langgraph三要素 · LangSmith · ReAct · 提示词安全注入 | ⭐⭐⭐ | +| | 09 LangGraph 进阶篇 | 🚧撰写中 | `Multi-Agent` · `Human-in-the-Loop` · “总控-员工”架构 | ⭐⭐⭐⭐ | | 🧠 **智能篇** | 10 MCP 基础 (Client) | 🚧 | `MCP` 协议 · `CherryStudio` · 消费公共 MCP | ⭐⭐⭐ | | | 11 MCP 进阶 (Server) | 🚧 | `Streamable HTTP` · `src layout` · 构建私有 MCP | ⭐⭐⭐⭐ | | | 12 Swarm & Agents SDK | 🚧 | `Swarm` · `Handoff` · “航空公司客服”项目 | ⭐⭐⭐⭐ | @@ -46,7 +46,7 @@ ## 🧩 当前学习建议(适用于已完成模块) -目前已开放模块:**01 ~ 07**。 +目前已开放模块:**01 ~ 08**。 建议按顺序学习,每一章都有完整代码示例与注释。 ### ✅ 模块 01 — Agent 入门 & 环境搭建 @@ -84,7 +84,12 @@ - **目标**:学会进阶的Chroma向量数据库,会Reranker精排序,最终将04到07所学的Langchain六大模块完整集成。 - **内容**:Chroma 持久化 | Reranker 精排 | RAG 工具化 | Langchain六大模块集成 -> 📌 后续模块将陆续开放(LangGraph、MCP、多智能体等) +### ✅ 模块 08 — LangGraph 基础篇 + +- **目标**:懂得Langgraph的三要素,会使用LangSmith调试当前agent,并能自己写一个简单的Langgraph流程。 +- **内容**:Langgraph核心三要素|ReAct循环白盒化实现 | LangSmith调试追踪 | 持久化记忆 | 提示词安全注入 + +> 📌 后续模块将陆续开放(MCP、多智能体、Streamlit等) --- @@ -129,9 +134,11 @@ cp .env.example .env ```env OPENAI_API_KEY=your_deepseek_api_key_here +LANGCHAIN_API_KEY=your_langsmith_api_key ``` -> 💡 **获取API Key**: 访问 [DeepSeek官网](https://platform.deepseek.com/) 注册并获取API Key +> 💡 **获取API Key**: 访问 [DeepSeek官网](https://platform.deepseek.com/)、[LangSmith官网](https://smith.langchain.com/) +> 注册并获取API Key ### 3️⃣ 运行示例 diff --git a/requirements.txt b/requirements.txt index c8e4201..9a3945f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,6 +4,8 @@ requests~=2.32.4 langchain~=0.2.16 langchain-community~=0.2.16 langchain-openai~=0.1.15 +langchain-core~=0.2.38 +langgraph~=0.2.0 langchain-huggingface==0.0.3 langchain-chroma==0.1.1 chromadb==0.4.22