diff --git a/.env b/.env new file mode 100644 index 0000000..1e4c6a1 --- /dev/null +++ b/.env @@ -0,0 +1,48 @@ +# LLM配置 +MODEL_NAME="qwen3-max" +API_KEY="sk-7a1334a1e5f1449ca05f642d7f68590a" +BASE_URL="https://dashscope.aliyuncs.com/compatible-mode/v1" +DASHSCOPE_MODEL_NAME="qwen3-vl-plus" + +# 本地部署qwen3-14b模型配置(vLLM默认不需要API_KEY认证,但是需要提供任意值) +LOCAL_MODEL_NAME="qwen3-14b" +LOCAL_API_KEY="whatever_api_key" +LOCAL_BASE_URL="http://115.190.61.185:6008/v1" + +# 向量模型配置 +BGE_M3_MODEL_NAME="bge-m3" +BGE_M3_BASE_URL="http://115.190.61.185:6006/v1" +BGE_M3_API_KEY="whatever_api_key" + +# 排序模型配置 +BGE_RERANKER_MODEL_NAME="bge-reranker-v2-m3" +BGE_RERANKER_BASE_URL="http://115.190.61.185:8899/score" + +# Mysql配置 +MYSQL_HOST="115.190.61.185" +MYSQL_PORT="6033" +MYSQL_USER="root" +MYSQL_PASSWORD="liangfangxing123" +MYSQL_DATABASE="medical_assistant" + +# ES配置 +ES_HOST="https://115.190.61.185:9200" +ES_USERNAME="elastic" +ES_PASSWORD="v*0tedJ=PEhfYkHj8Lge" + +# Milvus配置 +MILVUS_HOST="115.190.61.185" +MILVUS_PORT="19530" +MILVUS_USER="root" +MILVUS_PASSWORD="liangfangxing123" + +# Redis配置 +REDIS_HOST="115.190.61.185" +REDIS_PORT="6379" +REDIS_PASSWORD="liangfangxing123" +RAG_CACHE_EXPIRE=36000 + +# 其他配置 +MAX_DOC_LENGTH=5 +FAQ_THRESHOLD=0.82 +RECALL_THRESHOLD=0.5 \ No newline at end of file diff --git a/conf.py b/conf.py new file mode 100644 index 0000000..e590999 --- /dev/null +++ b/conf.py @@ -0,0 +1,79 @@ +from pydantic_settings import BaseSettings +from pydantic import ConfigDict +import pathlib + +APP_DIR = pathlib.Path(__file__).parent + + +class Settings(BaseSettings): + # LLM配置 + model_name: str + api_key: str + base_url: str + dashscope_model_name:str + + # 本地部署qwen3-14b模型配置 + local_model_name: str + local_api_key: str + local_base_url: str + + # 向量模型配置 + bge_m3_model_name: str + bge_m3_base_url: str + bge_m3_api_key: str + + # 排序模型配置 + bge_reranker_model_name: str + bge_reranker_base_url: str + + # Mysql配置 + mysql_host: str + mysql_port: str + mysql_user: str + mysql_password: str + mysql_database: str + + # ES配置 + es_host: str + es_username: str + es_password: str + + # ES配置 + milvus_host: str + milvus_port: str + milvus_user: str + milvus_password: str + + # Redis配置 + redis_host: str + redis_port: str + redis_password: str + rag_cache_expire: int + + # 其他配置 + max_doc_length: int + faq_threshold: float + recall_threshold: float + + + # 映射配置文件的配置 + model_config = ConfigDict( + extra='allow', + env_file=str(APP_DIR / '.env'), + case_sensitive=False, + ) + + # @property + # def url(self): + # mysql_url = f"mysql+pymysql://{settings.mysql_user}:{self.mysql_password}@{self.mysql_host}:{self.mysql_port}/{self.mysql_database}" + # return mysql_url + + +settings = Settings() + + +if __name__ == '__main__': + print(APP_DIR) + print(settings.model_name) + print(settings.api_key) + print(settings.base_url) diff --git a/demo_function_call/_@tool.py b/demo_function_call/_@tool.py new file mode 100644 index 0000000..efa5bcd --- /dev/null +++ b/demo_function_call/_@tool.py @@ -0,0 +1,69 @@ +from langchain_openai import ChatOpenAI +from langchain_core.messages import HumanMessage, ToolMessage +from langchain_core.tools import tool +from conf import settings + + +# todo: 第一步:定义工具函数 +@tool +def add(a: int, b: int) -> int: + """ + 将数字a与数字b相加 + Args: + a: 第一个数字 + b: 第二个数字 + """ + return a + b + +@tool +def multiply(a: int, b: int) -> int: + """ + 将数字a与数字b相乘 + Args: + a: 第一个数字 + b: 第二个数字 + """ + return a * b + +# 定义 JSON 格式的工具 schema +tools = [add, multiply] + + +# todo: 第二步:初始化模型 +llm = ChatOpenAI( + base_url=settings.base_url, + api_key=settings.api_key, + model=settings.model_name, + temperature=0.1 +) +# 绑定工具,允许模型自动选择工具 +llm_with_tools = llm.bind_tools(tools, tool_choice="auto") + +# todo: 第三步:调用回复 +query = "2+1等于多少?" +messages = [HumanMessage(query)] + +try: + # todo: 第一次调用 + ai_msg = llm_with_tools.invoke(messages) + messages.append(ai_msg) + print(f"\n第一轮调用后结果:\n{messages}") + + # 处理工具调用 + # 判断消息中是否有tool_calls,以判断工具是否被调用 + if hasattr(ai_msg, 'tool_calls') and ai_msg.tool_calls: + for tool_call in ai_msg.tool_calls: + # todo: 处理工具调用 + selected_tool = {"add": add, "multiply": multiply}[tool_call["name"].lower()] + tool_output = selected_tool.invoke(tool_call["args"]) # 需要使用invoke进行调用 + messages.append(ToolMessage(content=tool_output, tool_call_id=tool_call["id"])) + print(f"\n第二轮 message中增加tool_output 之后:\n{messages}") + + # todo: 第二次调用,将工具结果传回模型以生成最终回答 + final_response = llm_with_tools.invoke(messages) + print(f"\n最终模型响应:\n{final_response.content}") + else: + print("模型未生成工具调用,直接返回文本:") + print(ai_msg.content) +except Exception as e: + print(f"模型调用失败: {str(e)}") \ No newline at end of file diff --git a/demo_function_call/_pydantic.py b/demo_function_call/_pydantic.py new file mode 100644 index 0000000..5015781 --- /dev/null +++ b/demo_function_call/_pydantic.py @@ -0,0 +1,83 @@ +from langchain_openai import ChatOpenAI +from langchain_core.messages import HumanMessage, ToolMessage +""" +Pydantic 是一个 Python 库,用于数据验证和序列化。 +它通过使用 Python 类型注解(type hints)来定义数据模型, +并提供强大的数据验证功能。Pydantic 基于 Python 的 dataclasses 和 typing 模块, +允许开发者定义结构化的数据模型,并自动验证输入数据是否符合指定的类型和约束。 +""" +from pydantic.v1 import BaseModel, Field + +from conf import settings + + + +# todo: 第一步:定义工具函数 +class Add(BaseModel): + """ + 将两个数字相加 + """ + a: int = Field(..., description="第一个数字") + b: int = Field(..., description="第二个数字") + + def invoke(self, args): + # 验证参数 + tool_instance = self.__class__(**args) # 自动验证 a 和 b + return tool_instance.a + tool_instance.b + +class Multiply(BaseModel): + """ + 将两个数字相乘 + """ + a: int = Field(..., description="第一个数字") + b: int = Field(..., description="第二个数字") + + def invoke(self, args): + # 验证参数 + tool_instance = self.__class__(**args) # 自动验证 a 和 b + return tool_instance.a * tool_instance.b + +# 定义 JSON 格式的工具 schema +tools = [Add, Multiply] + + +# todo: 第二步:初始化模型 +llm = ChatOpenAI( + base_url=settings.base_url, + api_key=settings.api_key, + model=settings.model_name, + temperature=0.1 +) +# 绑定工具,允许模型自动选择工具 +llm_with_tools = llm.bind_tools(tools, tool_choice="auto") + +# todo: 第三步:调用回复 +query = "2+1等于多少?" +messages = [HumanMessage(query)] + +try: + # todo: 第一次调用 + ai_msg = llm_with_tools.invoke(messages) + messages.append(ai_msg) + print(f"\n第一轮调用后结果:\n{messages}") + + # 处理工具调用 + # 判断消息中是否有tool_calls,以判断工具是否被调用 + if hasattr(ai_msg, 'tool_calls') and ai_msg.tool_calls: + for tool_call in ai_msg.tool_calls: + # todo: 处理工具调用 + selected_tool = {"add": Add, "multiply": Multiply}[tool_call["name"].lower()] + # 实例化工具类并调用 invoke + tool_instance = selected_tool(**tool_call["args"]) + tool_output = tool_instance.invoke(tool_call["args"]) + messages.append(ToolMessage(content=tool_output, tool_call_id=tool_call["id"])) + print(f"\n第二轮 message中增加tool_output 之后:\n{messages}") + + # todo: 第二次调用,将工具结果传回模型以生成最终回答 + final_response = llm_with_tools.invoke(messages) + print(f"\n最终模型响应:\n{final_response.content}") + else: + print("模型未生成工具调用,直接返回文本:") + print(ai_msg.content) +except Exception as e: + print(f"模型调用失败: {str(e)}") \ No newline at end of file diff --git a/demo_function_call/agent.py b/demo_function_call/agent.py new file mode 100644 index 0000000..53980c8 --- /dev/null +++ b/demo_function_call/agent.py @@ -0,0 +1,51 @@ +from langchain.agents import initialize_agent, AgentType +from langchain_openai import ChatOpenAI +from langchain_core.tools import tool + +from conf import settings + + +# todo: 第一步:定义工具函数 +@tool +def add(a: int, b: int) -> int: + """ + 将数字a与数字b相加 + Args: + a: 第一个数字 + b: 第二个数字 + """ + return a + b + +@tool +def multiply(a: int, b: int) -> int: + """ + 将数字a与数字b相乘 + Args: + a: 第一个数字 + b: 第二个数字 + """ + return a * b + +# 加载工具 +tools = [add, multiply] + +# todo: 第二步:初始化模型 +llm = ChatOpenAI( + base_url=settings.base_url, + api_key=settings.api_key, + model=settings.model_name, + temperature=0.1 +) + +# todo: 第三步:创建Agent +agent = initialize_agent( + tools, + llm, + AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION, + verbose=True +) + +# todo: 第四步:调用Agent +query = "2+1等于多少?" +result = agent.invoke(query) +print(f'result: {result["output"]}') \ No newline at end of file diff --git a/demo_function_call/json_schema.py b/demo_function_call/json_schema.py new file mode 100644 index 0000000..8a98f54 --- /dev/null +++ b/demo_function_call/json_schema.py @@ -0,0 +1,115 @@ +from langchain_openai import ChatOpenAI +from langchain_core.messages import HumanMessage, ToolMessage + +from conf import settings + + +# todo: 第一步:定义工具函数 +def add(a: int, b: int) -> int: + """ + 将数字a与数字b相加 + Args: + a: 第一个数字 + b: 第二个数字 + """ + return a + b + + +def multiply(a: int, b: int) -> int: + """ + 将数字a与数字b相乘 + Args: + a: 第一个数字 + b: 第二个数字 + """ + return a * b + +tools = [ + { + "type": "function", + "function": { + "name": "add", + "description": "将数字a与数字b相加", + "parameters": { + "type": "object", + "properties": { + "a": { + "type": "integer", + "description": "第一个数字" + }, + "b": { + "type": "integer", + "description": "第二个数字" + } + }, + "required": ["a", "b"] + } + } + }, + { + "type": "function", + "function": { + "name": "multiply", + "description": "将数字a与数字b相乘", + "parameters": { + "type": "object", + "properties": { + "a": { + "type": "integer", + "description": "第一个数字" + }, + "b": { + "type": "integer", + "description": "第二个数字" + } + }, + "required": ["a", "b"] + } + } + } +] + + +# todo: 第二步:初始化模型 +llm = ChatOpenAI( + base_url=settings.base_url, + api_key=settings.api_key, + model=settings.model_name, + temperature=0.1 +) +llm_with_tools = llm.bind(tools=tools, tool_choice="auto") + + +# todo: 第三步:调用回复 +query = "2+1等于多少?" +messages = [HumanMessage(query)] + +try: + # todo: 第一次调用 + ai_msg = llm_with_tools.invoke(messages) + messages.append(ai_msg) + print(f"\n第一轮调用后结果:\n{messages}") + + # 处理工具调用 + # 判断消息中是否有tool_calls,以判断工具是否被调用 + if hasattr(ai_msg, "tool_calls") and ai_msg.tool_calls: + for tool_call in ai_msg.tool_calls: + # todo: 处理工具调用 + selected_tool = {"add": add, "multiply": multiply}[tool_call["name"].lower()] + tool_output = selected_tool(**tool_call["args"]) + messages.append(ToolMessage(content=tool_output, tool_call_id=tool_call["id"])) + print(f"\n第二轮 message中增加tool_output 之后:\n{messages}") + + # todo: 第二次调用,将工具结果传回模型以生成最终回答 + final_response = llm_with_tools.invoke(messages) + print(f"\n最终模型响应:\n{final_response.content}") + else: + print("模型未生成工具调用,直接返回文本:") + print(ai_msg.content) +except Exception as e: + print(f"模型调用失败: {str(e)}") + +# llm.invoke(messages, tools=tools, ...): +# 绑定方式: 直接在 .invoke() 调用中传入 tools 参数。这是一种临时、一次性的绑定方式,仅对本次调用有效。 +# 调用方式: 如果你想再次调用模型并使用工具,你必须在下一次 .invoke() 调用中再次传递 tools 参数。 +# 适用场景: 适用于简单、单次的工具调用需求, \ No newline at end of file diff --git a/guangzhou.txt b/guangzhou.txt deleted file mode 100644 index 4bcedb5..0000000 --- a/guangzhou.txt +++ /dev/null @@ -1 +0,0 @@ -广州 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..f6a6a44 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,127 @@ +aiohappyeyeballs==2.6.1 +aiohttp==3.12.15 +aiosignal==1.4.0 +altair==5.5.0 +annotated-types==0.7.0 +anthropic==0.60.0 +anyio==4.9.0 +attrs==25.3.0 +beautifulsoup4==4.13.4 +blinker==1.9.0 +boto3==1.39.16 +botocore==1.39.16 +bs4==0.0.2 +cachetools==6.1.0 +certifi==2025.7.14 +cffi==1.17.1 +charset-normalizer==3.4.2 +click==8.2.1 +colorama==0.4.6 +colorlog==6.9.0 +cryptography==45.0.5 +dashscope==1.24.0 +dataclasses-json==0.6.7 +distro==1.9.0 +fastapi==0.116.1 +filelock==3.18.0 +Flask==3.1.1 +frozenlist==1.7.0 +fsspec==2025.7.0 +gitdb==4.0.12 +GitPython==3.1.45 +greenlet==3.2.3 +h11==0.16.0 +hf-xet==1.1.5 +httpcore==1.0.9 +httpx==0.28.1 +httpx-sse==0.4.1 +huggingface-hub==0.34.3 +idna==3.10 +itsdangerous==2.2.0 +Jinja2==3.1.6 +jiter==0.10.0 +jmespath==1.0.1 +jsonpatch==1.33 +jsonpointer==3.0.0 +jsonschema==4.25.0 +jsonschema-specifications==2025.4.1 +langchain==0.3.26 +langchain-community==0.3.27 +langchain-core==0.3.72 +langchain-deepseek==0.1.4 +langchain-openai==0.3.28 +langchain-text-splitters==0.3.9 +langsmith==0.3.45 +lxml==6.0.0 +MarkupSafe==3.0.2 +marshmallow==3.26.1 +mcp==1.18.0 +mcp-server==0.1.4 +mpmath==1.3.0 +multidict==6.6.3 +mypy_extensions==1.1.0 +mysql-connector-python==9.4.0 +mysqlclient==2.2.7 +narwhals==2.0.1 +networkx==3.5 +numpy==2.3.2 +openai==1.97.1 +orjson==3.11.1 +packaging==25.0 +pandas==2.3.1 +pillow==11.3.0 +propcache==0.3.2 +protobuf==6.31.1 +pyarrow==21.0.0 +pycparser==2.22 +pydantic==2.11.7 +pydantic-settings==2.10.1 +pydantic_core==2.33.2 +pydeck==0.9.1 +PyMySQL==1.1.1 +python-a2a==0.5.4 +python-dateutil==2.9.0.post0 +python-dotenv==1.1.1 +python-multipart==0.0.20 +pytz==2025.2 +pywin32==311 +PyYAML==6.0.2 +referencing==0.36.2 +regex==2025.7.31 +requests==2.32.4 +requests-toolbelt==1.0.0 +rpds-py==0.26.0 +s3transfer==0.13.1 +safetensors==0.5.3 +setuptools==78.1.1 +six==1.17.0 +smmap==5.0.2 +sniffio==1.3.1 +soupsieve==2.7 +SQLAlchemy==2.0.42 +sse-starlette==3.0.2 +starlette==0.47.2 +streamlit==1.47.1 +sympy==1.14.0 +tenacity==9.1.2 +tiktoken==0.9.0 +tokenizers==0.21.4 +toml==0.10.2 +torch==2.7.1 +tornado==6.5.1 +tqdm==4.67.1 +transformers==4.54.1 +typing-inspect==0.9.0 +typing-inspection==0.4.1 +typing_extensions==4.14.1 +tzdata==2025.2 +urllib3==2.5.0 +uvicorn==0.35.0 +watchdog==6.0.0 +websocket-client==1.8.0 +Werkzeug==3.1.3 +wheel==0.45.1 +yarl==1.20.1 +zstandard==0.23.0 +schedule==1.2.2 +langchain-mcp-adapters==0.1.11 \ No newline at end of file