v0.1.0
This commit is contained in:
commit
35f41e10f3
11
.gitignore
vendored
Normal file
11
.gitignore
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
# Python-generated files
|
||||
__pycache__/
|
||||
*.py[oc]
|
||||
build/
|
||||
dist/
|
||||
wheels/
|
||||
*.egg-info
|
||||
|
||||
# Virtual environments
|
||||
.venv
|
||||
.env
|
||||
1
.python-version
Normal file
1
.python-version
Normal file
@ -0,0 +1 @@
|
||||
3.13
|
||||
183
README.md
Normal file
183
README.md
Normal file
@ -0,0 +1,183 @@
|
||||
# LangChain Learning Project
|
||||
|
||||
> 基于 LangChain 1.0.3 的 AI Agent 学习项目,展示如何使用 MCP (Model Context Protocol) 构建智能代理
|
||||
|
||||
## 项目概述
|
||||
|
||||
本项目是一个 LangChain 学习与实践项目,演示了如何构建具有外部工具调用能力的 AI Agent。通过集成 MCP 服务器,实现了对外部服务的调用,如天气查询、数学运算、学术搜索等功能。
|
||||
|
||||
## 技术栈
|
||||
|
||||
| 技术 | 版本 | 用途 |
|
||||
|------|------|------|
|
||||
| **LangChain** | 1.0.3 | 核心框架 |
|
||||
| **LangChain-Community** | 0.4.1 | 社区组件 |
|
||||
| **LangChain-MCP-Adapters** | ≥0.1.11 | MCP 客户端 |
|
||||
| **FastMCP** | ≥2.13.0 | MCP 协议实现 |
|
||||
| **LangChain-Ollama** | ≥1.0.0 | Ollama 本地模型 |
|
||||
| **LangGraph** | - | Agent 记忆组件 |
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
langchain-learning/
|
||||
├── agent/
|
||||
│ ├── langchain_agent.py # MCP Agent (Ollama + weather + math)
|
||||
│ └── langchain_agent_tool_mcp.py # 扩展 Agent (+ arxiv + wikipedia + 自定义工具)
|
||||
├── mcp/
|
||||
│ ├── math_server.py # 数学运算 MCP 服务器 (stdio)
|
||||
│ └── get_weather_server.py # 天气查询 MCP 服务器 (HTTP)
|
||||
├── ollama/
|
||||
│ └── ollama_demo.py # Ollama 本地模型调用示例
|
||||
├── main.py # 程序入口
|
||||
├── pyproject.toml # 项目配置
|
||||
└── .env # 环境变量 (API Keys)
|
||||
```
|
||||
|
||||
## 功能特性
|
||||
|
||||
### MCP 服务器
|
||||
|
||||
| 服务器 | 功能 | 传输方式 | 端口 |
|
||||
|--------|------|----------|------|
|
||||
| `math_server` | 加法、乘法运算 | stdio | - |
|
||||
| `weather_server` | 城市天气查询 | HTTP/SSE | 9000 |
|
||||
|
||||
### 支持的 LLM
|
||||
|
||||
| 模型 | 类型 | 调用方式 |
|
||||
|------|------|----------|
|
||||
| `gemma4:e2b` / `gemma4:26b` | Ollama 本地 | `langchain_ollama` |
|
||||
| `Qwen/Qwen3-14B` | SiliconFlow 云端 | OpenAI 兼容接口 |
|
||||
|
||||
### 工具集成
|
||||
|
||||
| 来源 | 工具 |
|
||||
|------|------|
|
||||
| **MCP Servers** | `get_weather`, `add`, `multiply` |
|
||||
| **内置 (load_tools)** | `arxiv`, `wikipedia` |
|
||||
| **自定义** | `greet(name)` |
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 1. 安装依赖
|
||||
|
||||
```bash
|
||||
uv sync
|
||||
```
|
||||
|
||||
### 2. 配置环境变量
|
||||
|
||||
编辑 `.env` 文件:
|
||||
|
||||
```env
|
||||
# Ollama (本地)
|
||||
OLLAMA_API_KEY=ollama
|
||||
OLLAMA_BASE_URL=http://localhost:11434/v1
|
||||
|
||||
# SiliconFlow (可选云端)
|
||||
SILICONFLOW_API_KEY=your_siliconflow_key
|
||||
SILICONFLOW_BASE_URL=https://api.siliconflow.cn/v1
|
||||
|
||||
# 天气API
|
||||
WEATHER_API_KEY=your_weatherapi_key
|
||||
```
|
||||
|
||||
### 3. 启动 MCP 服务器
|
||||
|
||||
```bash
|
||||
# 终端 1: 启动天气服务器 (HTTP)
|
||||
python mcp/get_weather_server.py
|
||||
|
||||
# 终端 2: 启动数学服务器 (stdio)
|
||||
python mcp/math_server.py
|
||||
```
|
||||
|
||||
### 4. 运行 Agent
|
||||
|
||||
```bash
|
||||
# Agent 1: Ollama + MCP (weather + math)
|
||||
python agent/langchain_agent.py
|
||||
|
||||
# Agent 2: Ollama + MCP + 内置工具 + 自定义工具
|
||||
python agent/langchain_agent_tool_mcp.py
|
||||
```
|
||||
|
||||
### 5. Ollama 直接调用示例
|
||||
|
||||
```bash
|
||||
python ollama/ollama_demo.py
|
||||
```
|
||||
|
||||
## 使用示例
|
||||
|
||||
```
|
||||
请输入你的问题(输入exit则退出) > 北京天气怎么样
|
||||
|
||||
计算过程:
|
||||
- 调用工具: get_weather({'city': '北京'})
|
||||
- get_weather 的结果是: {"location":{"name":"北京",...},"current":{"temp_c":22.0,...}}
|
||||
|
||||
最终答案: 北京的天气晴朗,温度约22°C。
|
||||
```
|
||||
|
||||
## Agent 对比
|
||||
|
||||
| Agent | 模型 | MCP | 内置工具 | 自定义工具 |
|
||||
|-------|------|-----|----------|------------|
|
||||
| `langchain_agent.py` | Ollama | ✅ weather, math | ❌ | ❌ |
|
||||
| `langchain_agent_tool_mcp.py` | Ollama | ✅ weather, math | ✅ arxiv, wikipedia | ✅ greet |
|
||||
|
||||
## 核心代码说明
|
||||
|
||||
### MCP 客户端配置
|
||||
|
||||
```python
|
||||
client = MultiServerMCPClient({
|
||||
"weather": {
|
||||
"url": "http://localhost:9000/mcp",
|
||||
"transport": "streamable_http",
|
||||
},
|
||||
"math": {
|
||||
"command": sys.executable, # 使用当前解释器
|
||||
"args": ["./mcp/math_server.py"],
|
||||
"transport": "stdio"
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Agent 创建
|
||||
|
||||
```python
|
||||
from langchain.agents import create_agent
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
|
||||
agent = create_agent(
|
||||
model=llm,
|
||||
tools=tools,
|
||||
checkpointer=InMemorySaver() # 会话记忆
|
||||
)
|
||||
```
|
||||
|
||||
## 依赖列表
|
||||
|
||||
核心依赖见 `pyproject.toml`:
|
||||
|
||||
- `langchain >= 1.0.3`
|
||||
- `langchain-community >= 0.4.1`
|
||||
- `langchain-mcp-adapters >= 0.1.11`
|
||||
- `langchain-ollama >= 1.0.0`
|
||||
- `fastmcp >= 2.13.0.2`
|
||||
- `wikipedia >= 1.4.0`
|
||||
- `arxiv >= 2.2.0`
|
||||
|
||||
## 学习资源
|
||||
|
||||
- [LangChain 官方文档](https://python.langchain.com/)
|
||||
- [MCP 协议规范](https://modelcontextprotocol.io/)
|
||||
- [LangChain GitHub](https://github.com/langchain-ai/langchain)
|
||||
- [FastMCP 文档](https://github.com/jlowin/fastmcp)
|
||||
|
||||
## License
|
||||
|
||||
MIT License
|
||||
113
agent/langchain_agent.py
Normal file
113
agent/langchain_agent.py
Normal file
@ -0,0 +1,113 @@
|
||||
import asyncio
|
||||
import logging
|
||||
import sys
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langchain_mcp_adapters.client import MultiServerMCPClient
|
||||
import os
|
||||
import dotenv
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.DEBUG,
|
||||
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
||||
)
|
||||
|
||||
dotenv.load_dotenv()
|
||||
|
||||
## 设置环境变量
|
||||
os.environ['OPENAI_API_KEY'] = os.getenv("OLLAMA_API_KEY")
|
||||
os.environ['OPENAI_BASE_URL'] = os.getenv("OLLAMA_BASE_URL")
|
||||
|
||||
# 默认的 'model_name': 'deepseek-ai/DeepSeek-V3.1',
|
||||
llm = ChatOpenAI(model="gemma4:e2b")
|
||||
|
||||
|
||||
def print_optimized_result(agent_response):
|
||||
"""
|
||||
解析代理响应并输出优化后的结果。
|
||||
:param agent_response: 代理返回的完整响应
|
||||
"""
|
||||
messages = agent_response.get("messages", [])
|
||||
steps = [] # 用于记录计算步骤
|
||||
final_answer = None # 最终答案
|
||||
|
||||
for message in messages:
|
||||
if hasattr(message, "additional_kwargs") and "tool_calls" in message.additional_kwargs:
|
||||
# 提取工具调用信息
|
||||
tool_calls = message.additional_kwargs["tool_calls"]
|
||||
for tool_call in tool_calls:
|
||||
tool_name = tool_call["function"]["name"]
|
||||
tool_args = tool_call["function"]["arguments"]
|
||||
steps.append(f"调用工具: {tool_name}({tool_args})")
|
||||
elif message.type == "tool":
|
||||
# 提取工具执行结果
|
||||
tool_name = message.name
|
||||
tool_result = message.content
|
||||
steps.append(f"{tool_name} 的结果是: {tool_result}")
|
||||
elif message.type == "ai":
|
||||
# 提取最终答案
|
||||
final_answer = message.content
|
||||
|
||||
# 打印优化后的结果
|
||||
print("\n计算过程:")
|
||||
for step in steps:
|
||||
print(f"- {step}")
|
||||
if final_answer:
|
||||
print(f"\n最终答案: {final_answer}")
|
||||
|
||||
|
||||
async def execute():
|
||||
# 1. 创建langchain中的mcp客户端 —— uv add langchain_mcp_adapters
|
||||
client = MultiServerMCPClient(
|
||||
# mcp.run(transport="streamable-http", host="127.0.0.1", port=9000)
|
||||
{
|
||||
# 这里是定义服务端信息的,可以有多个服务端
|
||||
"weather": {
|
||||
"url": "http://localhost:9000/mcp",
|
||||
"transport": "streamable_http",
|
||||
},
|
||||
"math": {
|
||||
# "command": "python", # npx uvx
|
||||
"command": sys.executable,
|
||||
"args": ["./mcp/math_server.py"],
|
||||
"transport": "stdio"
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
try:
|
||||
# 2. 通过客户端获取工具列表
|
||||
## 这里是通过服务端获取,所以可能会有异常(比如服务端没有启动,或者网络连接有问题
|
||||
tools = await client.get_tools()
|
||||
|
||||
# 3. 创建一个智能代理,能够完成 思考--> 行动 --> 观察 --> 思考 --> 行动 --> ... --> 最终答案
|
||||
|
||||
from langchain.agents import create_agent
|
||||
## 创建一个带记忆和工具的agent
|
||||
agent = create_agent(
|
||||
model=llm,
|
||||
tools=tools,
|
||||
# 配置记忆后,必须增加thread_id, checkpoint_ns, checkpoint_id参数之一,用于区分会话
|
||||
checkpointer=InMemorySaver()
|
||||
)
|
||||
|
||||
while True:
|
||||
user_input = input("请输入你的问题(输入exit则退出) > ")
|
||||
if user_input == "exit":
|
||||
print("感谢使用,再见👋🏻")
|
||||
break
|
||||
agent_response = await agent.ainvoke(
|
||||
{"messages": [{"role": "user", "content": user_input}]},
|
||||
{"configurable": {"thread_id": "1"}},
|
||||
)
|
||||
## agent会自己去调用 tools ,不需要我们去进行调用
|
||||
print_optimized_result(agent_response)
|
||||
|
||||
finally:
|
||||
# 资源回收
|
||||
if hasattr(client, 'close'):
|
||||
client.close()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.run(execute())
|
||||
136
agent/langchain_agent_tool_mcp.py
Normal file
136
agent/langchain_agent_tool_mcp.py
Normal file
@ -0,0 +1,136 @@
|
||||
import asyncio
|
||||
import logging
|
||||
import sys
|
||||
from docutils.nodes import description
|
||||
from langchain_community.agent_toolkits.load_tools import load_tools
|
||||
from langchain_core.tools import tool
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langchain_mcp_adapters.client import MultiServerMCPClient
|
||||
import os
|
||||
import dotenv
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.DEBUG,
|
||||
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
||||
)
|
||||
|
||||
dotenv.load_dotenv()
|
||||
|
||||
## 设置环境变量
|
||||
os.environ['OPENAI_API_KEY'] = os.getenv("OLLAMA_API_KEY")
|
||||
os.environ['OPENAI_BASE_URL'] = os.getenv("OLLAMA_BASE_URL")
|
||||
|
||||
# 默认的 'model_name': 'deepseek-ai/DeepSeek-V3.1',
|
||||
llm = ChatOpenAI(model="gemma4:e2b")
|
||||
|
||||
|
||||
def print_optimized_result(agent_response):
|
||||
"""
|
||||
解析代理响应并输出优化后的结果。
|
||||
:param agent_response: 代理返回的完整响应
|
||||
"""
|
||||
messages = agent_response.get("messages", [])
|
||||
steps = [] # 用于记录计算步骤
|
||||
final_answer = None # 最终答案
|
||||
|
||||
for message in messages:
|
||||
if hasattr(message, "additional_kwargs") and "tool_calls" in message.additional_kwargs:
|
||||
# 提取工具调用信息
|
||||
tool_calls = message.additional_kwargs["tool_calls"]
|
||||
for tool_call in tool_calls:
|
||||
tool_name = tool_call["function"]["name"]
|
||||
tool_args = tool_call["function"]["arguments"]
|
||||
steps.append(f"调用工具: {tool_name}({tool_args})")
|
||||
elif message.type == "tool":
|
||||
# 提取工具执行结果
|
||||
tool_name = message.name
|
||||
tool_result = message.content
|
||||
steps.append(f"{tool_name} 的结果是: {tool_result}")
|
||||
elif message.type == "ai":
|
||||
# 提取最终答案
|
||||
final_answer = message.content
|
||||
|
||||
# 打印优化后的结果
|
||||
print("\n计算过程:")
|
||||
for step in steps:
|
||||
print(f"- {step}")
|
||||
if final_answer:
|
||||
print(f"\n最终答案: {final_answer}")
|
||||
|
||||
|
||||
def greet(name: str) -> str:
|
||||
"""用于打招呼的函数"""
|
||||
return f"你好呀,{name}"
|
||||
|
||||
|
||||
class greetInput(BaseModel):
|
||||
name: str = Field(description="打招呼的对象")
|
||||
|
||||
|
||||
async def execute():
|
||||
# 1. 创建langchain中的mcp客户端 —— uv add langchain_mcp_adapters
|
||||
client = MultiServerMCPClient(
|
||||
# mcp.run(transport="streamable-http", host="127.0.0.1", port=9000)
|
||||
{
|
||||
# 这里是定义服务端信息的,可以有多个服务端
|
||||
# 这里是定义服务端信息的,可以有多个服务端
|
||||
"weather": {
|
||||
"url": "http://localhost:9000/mcp",
|
||||
"transport": "streamable_http",
|
||||
},
|
||||
"math": {
|
||||
# "command": "python", # npx uvx
|
||||
"command": sys.executable,
|
||||
"args": ["./mcp/math_server.py"],
|
||||
"transport": "stdio"
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
try:
|
||||
# 2. 通过客户端获取工具列表
|
||||
## 这里是通过服务端获取,所以可能会有异常(比如服务端没有启动,或者网络连接有问题
|
||||
tools_mcp = await client.get_tools()
|
||||
|
||||
tools = tools_mcp + tools_internal + [tools_custom]
|
||||
# 3. 创建一个智能代理,能够完成 思考--> 行动 --> 观察 --> 思考 --> 行动 --> ... --> 最终答案
|
||||
|
||||
from langchain.agents import create_agent
|
||||
## 创建一个带记忆和工具的agent
|
||||
agent = create_agent(
|
||||
model=llm,
|
||||
tools=tools,
|
||||
# 配置记忆后,必须增加thread_id, checkpoint_ns, checkpoint_id参数之一,用于区分会话
|
||||
checkpointer=InMemorySaver()
|
||||
)
|
||||
|
||||
while True:
|
||||
user_input = input("请输入你的问题(输入exit则退出) > ")
|
||||
if user_input == "exit":
|
||||
print("感谢使用,再见👋🏻")
|
||||
break
|
||||
agent_response = await agent.ainvoke(
|
||||
{"messages": [{"role": "user", "content": user_input}]},
|
||||
{"configurable": {"thread_id": "1"}},
|
||||
)
|
||||
## agent会自己去调用 tools ,不需要我们去进行调用
|
||||
print_optimized_result(agent_response)
|
||||
|
||||
finally:
|
||||
# 资源回收
|
||||
if hasattr(client, 'close'):
|
||||
client.close()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
tools_internal = load_tools(["arxiv", "wikipedia"], llm=llm, allow_dangerous_tools=True)
|
||||
tools_custom = tool(
|
||||
name_or_callable="greet",
|
||||
runnable=greet,
|
||||
description="用于打招呼的函数",
|
||||
args_schema=greetInput
|
||||
)
|
||||
asyncio.run(execute())
|
||||
6
main.py
Normal file
6
main.py
Normal file
@ -0,0 +1,6 @@
|
||||
def main():
|
||||
print("Hello from langchain-learning-new!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
32
mcp/get_weather_server.py
Normal file
32
mcp/get_weather_server.py
Normal file
@ -0,0 +1,32 @@
|
||||
# uv add fastmcp
|
||||
import os
|
||||
import logging
|
||||
import requests
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("mcp demo")
|
||||
WEATHER_API_KEY = os.getenv("WEATHER_API_KEY")
|
||||
|
||||
# @mcp.tool()
|
||||
# async def get_weather(city: str) -> str:
|
||||
# """获取传入的城市的天气信息"""
|
||||
# logging.info(f"调用了查询天气服务,传入的参数为{city}")
|
||||
# return f"{city}的天气很好,阳光明媚,晴空万里"
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
async def get_weather(city: str) -> str:
|
||||
"""获取传入的城市的天气信息"""
|
||||
logging.info("The get_weather method is called: city=%s", city)
|
||||
api_key = os.getenv("WEATHER_API_KEY")
|
||||
# url = f"https://api.weatherapi.com/v1/current.json?key=6056d3b7245449069d0152825252910&q={city}"
|
||||
url = f"https://api.weatherapi.com/v1/current.json?key={WEATHER_API_KEY}&q={city}"
|
||||
response = requests.get(url, timeout=10)
|
||||
response.raise_for_status()
|
||||
return response.text
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
logging.info("启动一个可以通过MCP调用获取天气的服务")
|
||||
mcp.run(transport="streamable-http", host="127.0.0.1", port=9000)
|
||||
# mcp.run(transport="stdio", host="127.0.0.1", port=9000)
|
||||
29
mcp/math_server.py
Normal file
29
mcp/math_server.py
Normal file
@ -0,0 +1,29 @@
|
||||
from fastmcp import FastMCP
|
||||
import logging
|
||||
|
||||
# 配置日志记录器
|
||||
logging.basicConfig(
|
||||
level=logging.INFO, # 设置日志级别为 INFO
|
||||
format="%(asctime)s - %(levelname)s - %(message)s" # 日志格式
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# 创建 FastMCP 实例
|
||||
mcp = FastMCP("Math")
|
||||
|
||||
@mcp.tool()
|
||||
def add(a: int, b: int) -> int:
|
||||
"""Add two numbers"""
|
||||
logger.info("The add method is called: a=%d, b=%d", a, b) # 记录加法调用日志
|
||||
return a + b
|
||||
|
||||
@mcp.tool()
|
||||
def multiply(a: int, b: int) -> int:
|
||||
"""Multiply two numbers"""
|
||||
logger.info("The multiply method is called: a=%d, b=%d", a, b) # 记录乘法调用日志
|
||||
return a * b
|
||||
|
||||
if __name__ == "__main__":
|
||||
logger.info("Start math server through MCP") # 记录服务启动日志
|
||||
# mcp.run(transport="streamable-http",port=8081,path='/mcp') # 启动服务并使用标准输入输出通信
|
||||
mcp.run(transport="stdio") # 启动服务并使用标准输入输出通信(子进程)
|
||||
26
ollama/ollama_demo.py
Normal file
26
ollama/ollama_demo.py
Normal file
@ -0,0 +1,26 @@
|
||||
# 配置一下解释器
|
||||
# uv add langchain_ollama
|
||||
|
||||
from langchain_core.messages import HumanMessage
|
||||
from langchain_ollama import ChatOllama
|
||||
|
||||
#此时调用的是本地的大模型。省略base_url、api-key
|
||||
llm = ChatOllama(
|
||||
model = "gemma4:26b",
|
||||
)
|
||||
|
||||
|
||||
# llm.invoke("你好,请介绍一下你自己!")
|
||||
|
||||
|
||||
messages = [
|
||||
HumanMessage(content="你好,请介绍一下你自己!")
|
||||
]
|
||||
|
||||
# response = llm.invoke(messages)
|
||||
# print(response.content)
|
||||
|
||||
chunks = []
|
||||
for chunk in llm.stream(messages):
|
||||
# chunks.append(chunk)
|
||||
print(chunk.content, end="", flush=True)
|
||||
21
pyproject.toml
Normal file
21
pyproject.toml
Normal file
@ -0,0 +1,21 @@
|
||||
[project]
|
||||
name = "langchain-new"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
requires-python = ">=3.13"
|
||||
dependencies = [
|
||||
"langchain>=1.1.3",
|
||||
"langchain-community>=0.4.1",
|
||||
"langchain-mcp-adapters>=0.1.11",
|
||||
"langchain-openai>=1.0.1",
|
||||
"langchain-text-splitters>=1.0.0",
|
||||
"numexpr>=2.10.2",
|
||||
"wikipedia>=1.4.0",
|
||||
"arxiv>=2.2.0",
|
||||
"httpx>=0.28.1",
|
||||
"socksio>=1.0.0",
|
||||
"fastmcp>=2.13.0.2",
|
||||
"langchain-ollama>=1.0.0",
|
||||
"langgraph-prebuilt>=1.0.9",
|
||||
"uvicorn[standard]>=0.30.0",
|
||||
]
|
||||
Loading…
Reference in New Issue
Block a user