diff --git a/.env b/.env index 7617490..ff95a3e 100644 --- a/.env +++ b/.env @@ -3,9 +3,11 @@ SILICONFLOW_API_KEY = "sk-sylilrjrtxlvecwhfusjkutclmppzuzhncfcfxtekxrzyjee" SILICONFLOW_BASE_URL = "https://api.siliconflow.cn/v1" +# gemma4:e2b OLLAMA_API_KEY = "ollama" OLLAMA_BASE_URL = "http://localhost:11434/v1" +# MiniMax-M2.7 MINIMAX_API_KEY = "sk-cp-wWkzvRP-BiQia-6izxvqgehEsHSz8v4_PtDJAuT3OI0s8QFcEOsxIHcQoZC2cVQTK3L09EUuu5HDArYMvKXFnf91jk8LuZ0tteS7-Wd4Lk2zDm8RqrKkrd4" MINIMAX_BASE_URL = "https://api.minimaxi.com/v1" diff --git a/README.md b/README.md index 868bfd6..0f354cc 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # LangChain Learning -[![](https://img.shields.io/badge/version-0.0.7-blue.svg)](https://github.com/your-repo/langchain-learning) +[![](https://img.shields.io/badge/version-0.0.10-blue.svg)](https://github.com/your-repo/langchain-learning) [![](https://img.shields.io/badge/python-3.11+-green.svg)](https://www.python.org/) [![](https://img.shields.io/badge/LangChain-v1.2-orange.svg)](https://www.langchain.com/) @@ -13,6 +13,7 @@ - **流式响应**:实时流式输出,带来更好的使用体验 - **Prompt 工程**:多种 Prompt 模板构建方式 - **输出解析**:支持 JSON 等格式解析 +- **工具调用 (Tool Calling)**:支持 @tool 装饰器和 StructuredTool 定义工具 - **Token 用量追踪**:轻松监控 API 调用消耗 - **内存管理**:实现对话历史持久化(ConversationBufferMemory, SummaryMemory) - **Rich 终端界面**:支持 Markdown 渲染、多行输入等高级交互 @@ -24,7 +25,7 @@ ### 1. 安装依赖 ```bash -pip install langchain>=1.2.15 langchain-community>=0.4.1 langchain-siliconflow>=1.0.0 requests>=2.33.1 rich openai +pip install langchain>=1.2.15 langchain-community>=0.4.1 langchain-siliconflow>=1.0.0 requests>=2.33.1 rich openai pydantic ``` ### 2. 配置环境变量 @@ -72,6 +73,13 @@ OLLAMA_API_KEY=ollama |------|------|------| | 基础 RAG | `python rag/rag_demo.py` | 基于 FAISS 向量库的检索问答系统 | +**工具调用示例** + +| 示例 | 命令 | 说明 | +|------|------|------| +| 工具定义 | `python tools/tool_definition.py` | 演示 @tool 装饰器和 StructuredTool 定义方式 | +| 工具调用 | `python tools/tool_demo.py` | 演示模型如何调用工具并获取结果 | + **Token 用量示例** | 示例 | 命令 | 说明 | @@ -113,6 +121,9 @@ langchain-learning/ │ └── json_parser_demo.py # JSON 输出解析示例 ├── rag/ │ └── rag_demo.py # RAG 检索增强生成示例 +├── tools/ +│ ├── tool_definition.py # 工具定义方式演示 +│ └── tool_demo.py # 工具调用完整流程演示 ├── token/ │ └── token_demo.py # Token 用量追踪示例 ├── memory/ @@ -138,6 +149,7 @@ langchain-learning/ **Ollama (本地)** - `gemma4:26b` +- `gemma4:e2b` - `deepseek-v3.1:671b-cloud` ## 技术栈 @@ -148,6 +160,7 @@ langchain-learning/ | LLM 提供商 | SiliconFlow, Ollama | | 向量库 | FAISS | | 终端美化 | Rich | +| 数据验证 | Pydantic | | 语言 | Python 3.11+ | ## 许可证 diff --git a/tools/tool_definition.py b/tools/tool_definition.py new file mode 100644 index 0000000..5a5c13b --- /dev/null +++ b/tools/tool_definition.py @@ -0,0 +1,56 @@ +import sys + +from langchain_core.tools import tool, StructuredTool +from pydantic import BaseModel, Field +from sqlalchemy import True_ + + +# 定义函数/tool + +## 方式1:使用注解 +@tool +def get_weather1(city: str) -> str: + """查询指定城市的最新天气信息""" + # 通过api调用天气网站的接口,得到最新的天气信息 + return f"{city}当前的温度为18°C" + + +class FieldInfo(BaseModel): + city: str = Field(description="要查询的城市名称") + +## 注解中可以通过传参覆盖原有函数的描述等信息 +@tool( + name_or_callable="get_weather1", + args_schema=FieldInfo, + description="查询某个城市的天气,并返回温度信息", + return_direct=True +) +def get_weather2(city: str) -> str: + """查询指定城市的最新天气信息""" + # 通过api调用天气网站的接口,得到最新的天气信息 + return f"{city}当前的温度为18°C" + + +## 方式2: +def get_weather3(city: str) -> str: + """查询指定城市的最新天气信息""" + # 通过api调用天气网站的接口,得到最新的天气信息 + return f"{city}当前的温度为18°C" + +get_weather3_tool = StructuredTool.from_function( + func=get_weather3, + name="get_weather3", + args_schema=FieldInfo, + description="第三个返回天气的函数" +) + + +if __name__ == '__main__': + # 调用函数,并打印返回结果 + print(get_weather1("北京")) + print(get_weather1("巴黎")) + + print(f"name={get_weather3_tool.name}") + print(f"args={get_weather3_tool.args}") + print(f"description={get_weather3_tool.description}") + print(f"return_direct={get_weather3_tool.return_direct}") # 直接返回:如果为false,就是会将返回值给到大模型,让大模型进一步加工后再返回。如果是true,则直接返回给用户。 diff --git a/tools/tool_demo.py b/tools/tool_demo.py new file mode 100644 index 0000000..10fc919 --- /dev/null +++ b/tools/tool_demo.py @@ -0,0 +1,98 @@ +import sys + +from langchain_core.messages import HumanMessage, ToolMessage +from langchain_core.tools import tool, StructuredTool +from pydantic import BaseModel, Field + +import logging + +from langchain_openai import ChatOpenAI +import os +import dotenv + + +# 定义函数/tool + +## 方式1:使用注解 +class FieldInfo(BaseModel): + city: str = Field(description="要查询的城市名称") + + +## 注解中可以通过传参覆盖原有函数的描述等信息 +@tool( + name_or_callable="get_weather", + args_schema=FieldInfo, + description="查询某个城市的天气,并返回温度信息", + return_direct=True +) +def get_weather(city: str) -> str: + """查询指定城市的最新天气信息""" + # 通过api调用天气网站的接口,得到最新的天气信息 + return f"{city}当前的温度为18°C" + +@tool +def test(city: str) -> str: + """这个是一个测试函数""" + # 通过api调用天气网站的接口,得到最新的天气信息 + return f"{city}当前的温度为18°C" +###################################################################### + +logging.basicConfig( + level=logging.DEBUG, + format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" +) + +dotenv.load_dotenv() + +## 设置环境变量 +# os.environ['OPENAI_API_KEY'] = os.getenv("SILICONFLOW_API_KEY") +# os.environ['OPENAI_BASE_URL'] = os.getenv("SILICONFLOW_BASE_URL") +os.environ['OPENAI_API_KEY'] = os.getenv("OLLAMA_API_KEY") +os.environ['OPENAI_BASE_URL'] = os.getenv("OLLAMA_BASE_URL") +# os.environ['OPENAI_API_KEY'] = os.getenv("MINIMAX_API_KEY") +# os.environ['OPENAI_BASE_URL'] = os.getenv("MINIMAX_BASE_URL") + +# 如果要使用函数调用,需要选择支持的模型 +# 如果不支持,会报错:Error code: 400 - {'code': 20037, 'message': 'Function call is not supported for this model.', 'data': None} +# llm = ChatOpenAI(model="Qwen/Qwen2.5-7B-Instruct") +llm = ChatOpenAI(model="gemma4:e2b") +# llm = ChatOpenAI(model="MiniMax-M2.7") + + +## 将模型与工具进行绑定 +llm_with_tools = llm.bind_tools([get_weather,test],tool_choice="auto") + +messages = [HumanMessage(content="巴黎现在的气温是多少")] + +## 使用哪个工具,由大模型自己选择 +response = llm_with_tools.invoke(messages) +print(response) + +## 模型会返回:需要调用get_weather,并且识别出来传参为 Paris,但是不会进行真正的调用 + +# 如果需要调用工具,则执行并把结果回传 +for call in getattr(response, "tool_calls", []) or []: + # print(call) + # {'name': 'get_weather', 'args': {'city': 'Paris'}, 'id': '019a305388b40ad0d961da5696e9fd2f', 'type': 'tool_call'} + if call["name"] == "get_weather": + args = call["args"] # 例如 {"city": "Paris"} + result = get_weather.invoke(args) + messages.append(response) # 把模型消息加入对话 + messages.append( + ToolMessage( + content=result, + tool_call_id=call["id"], # 必须把这次tool调用的id对上 + ) + ) + # 5) 第二轮:把工具结果交回给模型,让它产出最终回答 + final_msg = llm.invoke(messages) + print("FINAL:", final_msg.content) + break +else: + # 模型未请求调用工具,直接给出回答 + print("FINAL(no-tool):", response.content) + + + + +