From 7b7433e4a57edd55bfa5a91f977be08c72ead82b Mon Sep 17 00:00:00 2001 From: old-tom <892955278@msn.cn> Date: Mon, 31 Mar 2025 17:05:04 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/markdown.xml | 9 +++++++ README.md | 59 ++++++++++++++++++++++++++++++++++++++++++ env.toml | 5 +++- llmagent/__init__.py | 6 ++--- llmagent/llm_agent.py | 7 ++--- llmagent/llm_config.py | 26 +++++++++++++++---- llmtools/tool_impl.py | 5 ++-- main.py | 6 ++--- requirements.txt | 1 + vector_db.py | 2 +- 10 files changed, 108 insertions(+), 18 deletions(-) create mode 100644 .idea/markdown.xml create mode 100644 README.md diff --git a/.idea/markdown.xml b/.idea/markdown.xml new file mode 100644 index 0000000..1e34094 --- /dev/null +++ b/.idea/markdown.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..9863d14 --- /dev/null +++ b/README.md @@ -0,0 +1,59 @@ +# LLM function calling 示例 + +## 功能介绍 + +## 安装 +1. clone 本项目 +```shell +git clone http://1.14.96.249:3000/old-tom/llmFunctionCallDemo.git +``` +2. 创建虚拟环境 ++ 这里使用的是pipenv,可以换成uv或者conda ++ python 版本为 3.10或以上稳定版即可 +```shell +cd llmFunctionCallDemo (clone的代码目录) +``` +```shell +pipenv install --python 3.10 +``` +3. 安装依赖 +```shell +pip install -r requirements.txt +``` +4. 向量库部署和初始化 (docker) +```shell +docker run --name marqo -it --privileged -p 8882:8882 --add-host host.docker.internal:host-gateway marqoai/marqo:latest +``` +初始化:执行[vector_db.py](vector_db.py) create_and_set_index()方法 + +测试:执行[vector_db.py](vector_db.py) query_vector_db() 方法,参数为任意字符串 + +5. 配置文件 +[env.toml](env.toml) +```toml +[base] +# 多轮对话历史存储类型(memory:内存) +history_chat_store = 'memory' +# 相似度阈值 +similarity_threshold = 0.93 +# dev +dev = true +####### 模型配置 ####### +[siliconflow] +# 硅基流动 +# 密钥 +api_key = '' +# 模型名称 +model = '' +# API地址 +base_url = '' +# 最大token数 +max_tokens = 4096 +# 温度系数 +temperature = 0.6 +# 是否流式返回 +streaming = true +``` +## TestCase + +## TODO \ No newline at end of file diff --git a/env.toml b/env.toml index be5b064..4045264 100644 --- a/env.toml +++ b/env.toml @@ -3,7 +3,10 @@ history_chat_store = 'memory' # 相似度阈值 similarity_threshold = 0.93 - +# debug模式 (不适用于流式输出) +debug = false +# 详细输出 (不适用于流式输出) +verbose = false ####### 模型配置 ####### [siliconflow] # 硅基流动 diff --git a/llmagent/__init__.py b/llmagent/__init__.py index d7c4c4e..05c7421 100644 --- a/llmagent/__init__.py +++ b/llmagent/__init__.py @@ -23,7 +23,7 @@ PROMPT_TEMPLATE = { 1. 北卡口入境摄像头出场1号通道 2. 北卡口出口道路监控 3. 北卡口入境摄像头出场2号通道 - 您需要选择哪个选项?(请回复选项前的数字) + 您需要选择哪个选项?(请回复选项前的数字)\n 6. 如果工具返回单个结果,不要有多余输出及思考过程。强制格式为:正在执行xxx操作,请等待完成。 7. 在任何情况下,都不要修改或扩展提供的工具参数 @@ -44,13 +44,13 @@ PROMPT_TEMPLATE = { 1.根据指令和提供的工具描述选择最合适的工具,并仔细阅读工具参数说明。 2.你需要根据工具参数说明进行参数校验,评估用户输入的参数是否满足条件,如果不满足需要返回参数校验错误信息。 3.在任何情况下,都不要修改或扩展提供的工具参数 - 4.如果用户的指令是任意表示序号的数字或者语句,比如:1、第一、第1个、第二个。你需要回顾上一轮对话中用户指令并推断出本轮用户指令。 + 4.如果用户的指令是任意表示序号的数字或者语句,比如:1、第一、第1个、第二个。你需要回顾上一轮对话的用户指令并推断出本轮用户指令。 例如上一轮对话中的用户指令为:打开相机,你的回复是:请确认您要查看的相机具体名称: 1. 北卡口入境摄像头出场1号通道 2. 北卡口出口道路监控 3. 北卡口入境摄像头出场2号通道 您需要选择哪个选项?(请回复选项前的数字) - 本轮用户指令为:1, 你需要推断出本轮用户指令为:打开北卡口入境摄像头出场1号通道相机 + 本轮用户指令为:1, 你需要推断出本轮用户指令为:打开北卡口入境摄像头出场1号通道相机,并强制调用工具 """ } } diff --git a/llmagent/llm_agent.py b/llmagent/llm_agent.py index 64205df..783e6bc 100644 --- a/llmagent/llm_agent.py +++ b/llmagent/llm_agent.py @@ -18,10 +18,11 @@ from langchain_core.chat_history import BaseChatMessageHistory, InMemoryChatMess from langchain_core.runnables.history import RunnableWithMessageHistory from langchain_core.messages import HumanMessage from log_conf import log +from llmagent.llm_config import base_conf # debug模式,有更多输出 -set_debug(False) -set_verbose(False) +set_debug(base_conf.debug) +set_verbose(base_conf.verbose) # 默认系统提示词 DEFAULT_SYS_PROMPT = '' @@ -136,7 +137,7 @@ class BaseChatAgent(ABC): # 总体任务描述及提示词 user_msg = PROMPT_TEMPLATE.get('VOICE_ASSISTANT')['template'].format(user_input=user_input) messages = [HumanMessage(user_msg)] - llm_with_tools = llm.bind_tools(STRUCT_TOOLS) + llm_with_tools = llm.bind_tools(STRUCT_TOOLS, tool_choice='auto') # 判断使用哪个工具,需要加提示词让模型判断参数是否符合规则 user_input = PROMPT_TEMPLATE.get('TOOL_CALLER')['template'].format(user_input=user_input) # 工具chain加入历史对话 diff --git a/llmagent/llm_config.py b/llmagent/llm_config.py index fdf5179..321b265 100644 --- a/llmagent/llm_config.py +++ b/llmagent/llm_config.py @@ -27,6 +27,15 @@ class ConfigNotFoundError(Exception): Exception.__init__(self, msg) +def load_env(): + if not os.path.isfile(DEFAULT_CONF_PATH): + raise ConfigNotFoundError(f'模型配置文件{DEFAULT_CONF_NAME}不存在') + return toml.load(DEFAULT_CONF_PATH) + + +conf = load_env() + + class LLMConf(BaseModel): api_key: str model: str @@ -38,12 +47,19 @@ class LLMConf(BaseModel): class LLMConfigLoader(object): @staticmethod - def load(item_name, conf_path: str = DEFAULT_CONF_PATH) -> LLMConf: + def load(item_name) -> LLMConf: """ 校验并加载配置 :return: """ - if not os.path.isfile(conf_path): - raise ConfigNotFoundError(f'模型配置文件{DEFAULT_CONF_NAME}不存在') - conf = toml.load(conf_path) - return LLMConf(**conf[item_name]) \ No newline at end of file + return LLMConf(**conf[item_name]) + + +class BaseConf(BaseModel): + history_chat_store: str + similarity_threshold: float + debug: bool = False + verbose: bool = False + + +base_conf = BaseConf(**conf['base']) diff --git a/llmtools/tool_impl.py b/llmtools/tool_impl.py index 9362935..2c1b7af 100644 --- a/llmtools/tool_impl.py +++ b/llmtools/tool_impl.py @@ -11,14 +11,15 @@ from typing import Annotated from langchain_core.tools import tool from vector_db import query_vector_db from log_conf import log +from llmagent.llm_config import base_conf @tool("play_video", description="播放、查看、打开实时视频") def play_video(camera_name: Annotated[str, "相机名称,例如:南卡口1号相机"]) -> str: camera_info = query_camera_from_db(camera_name) + log.info('【function】play_video 输入 [{}]', camera_name) if camera_info: if len(camera_info) > 1: - # TODO 需要多轮对话,确认相机名称 hit_camera_names = [x['carme_name'] for x in camera_info] return f"找到以下相机,请选择一个:{hit_camera_names}" else: @@ -74,7 +75,7 @@ def query_camera_from_db(camera_name: str, top_n: int = 3) -> str: top_one = rt['hits'][0] # 相似度评分 score = top_one['_score'] - if score > 0.93: + if score > base_conf.similarity_threshold: return rt['hits'][0:1] else: return rt['hits'][0:top_n] diff --git a/main.py b/main.py index 640d1e7..1fa1f8b 100644 --- a/main.py +++ b/main.py @@ -15,7 +15,7 @@ if __name__ == '__main__': ########## 测试 function call ######### # print(dsr.invoke_with_tool_call('播放南卡口相机')) ## [{'name': 'play_video', 'args': {'camera_name': '北卡口1号道相机'}, 'id': 'call_apnb8fiqdkaz313katcs3tjf', 'type': 'tool_call'}] - dsr.multi_with_tool_call_stream('将大屏切换为-1分屏', 1) + # dsr.multi_with_tool_call_stream('将大屏切换为-1分屏', 1) ## [{'name': 'split_screen', 'args': {'split_n': 2}, 'id': 'call_2o7c94f591xag8p6lcyice9q', 'type': 'tool_call'}] # print(dsr.invoke_with_tool('播放北卡口入境1号道录像,从今天到2025-03-16 02:09:31')) ## 由于大模型没有联网,所以无法判断‘今天’ @@ -40,5 +40,5 @@ if __name__ == '__main__': # dsr.multi_with_stream('我的第一个问题是什么?请直接返回问题,不要有多余输出及思考过程', 1) ########## 测试 多轮对话-相机选择 ######### - # dsr.multi_with_tool_call_stream('播放南卡口相机', 1) - # dsr.multi_with_tool_call_stream('1', 1) + dsr.multi_with_tool_call_stream('播放南卡口相机', 1) + dsr.multi_with_tool_call_stream('1', 1) diff --git a/requirements.txt b/requirements.txt index 6523f27..bd2c199 100644 --- a/requirements.txt +++ b/requirements.txt @@ -25,6 +25,7 @@ langchain-core==0.3.45 langchain-openai==0.3.8 langchain-text-splitters==0.3.7 langsmith==0.3.15 +loguru==0.7.3 marqo==3.11.0 marshmallow==3.26.1 multidict==6.2.0 diff --git a/vector_db.py b/vector_db.py index 38ad4da..e01890c 100644 --- a/vector_db.py +++ b/vector_db.py @@ -619,7 +619,7 @@ def query_vector_db(query): if __name__ == '__main__': # create_and_set_index() - rt = query_vector_db('南卡口出境2号相机') + rt = query_vector_db('1') # TODO 根据 _score字段 取出相似度最高的结果 if rt: for ele in rt['hits']: