diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..63ac784 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.git +.idea diff --git a/.idea/agent_proj.iml b/.idea/agent_proj.iml index b9ed519..5e376b1 100644 --- a/.idea/agent_proj.iml +++ b/.idea/agent_proj.iml @@ -2,7 +2,7 @@ - + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 1511473..f86cb7f 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -3,5 +3,5 @@ - + \ No newline at end of file diff --git a/agent/chroma_db/chroma.sqlite3 b/agent/chroma_db/chroma.sqlite3 new file mode 100644 index 0000000..88acd6f Binary files /dev/null and b/agent/chroma_db/chroma.sqlite3 differ diff --git a/agent/react_agent.py b/agent/react_agent.py new file mode 100644 index 0000000..4bb2f86 --- /dev/null +++ b/agent/react_agent.py @@ -0,0 +1,39 @@ +from langchain.agents import create_agent +from model.factory import chat_model +from utils.prompt_loader import load_system_prompts +from agent.tools.agent_tools import (rag_summarize, get_weather, get_user_id, + get_user_location, get_current_month, + fill_context_for_report, fetch_external_data) +from agent.tools.middleware import monitor_tool, log_before_model, repoet_prompt_switch + + +class ReactAgent: + def __init__(self): + self.agent = create_agent( + model=chat_model, + system_prompt=load_system_prompts(), + tools=[rag_summarize, get_weather, get_user_location, get_user_id, + get_current_month, fetch_external_data, fill_context_for_report], + middleware=[monitor_tool, log_before_model, repoet_prompt_switch], + ) + + def excute_stream(self, query: str): + input_dict = { + "messages": [ + {"role": "user", "content": query} + ] + } + + # 上下文runtime信息,做提示词切换标记 + response = self.agent.stream(input_dict, stream_mode="values", context={"report": False}) + for chunk in response: + latest_message = chunk["messages"][-1] + if latest_message.content: + yield latest_message.content.strip() + "\n" + + +if __name__ == '__main__': + agent = ReactAgent(); + stream = agent.excute_stream("扫地机器人在我所在地的气温下如何保养") + for chunk in stream: + print(chunk, end="", flush=True) diff --git a/agent/tools/__pycache__/agent_tools.cpython-312.pyc b/agent/tools/__pycache__/agent_tools.cpython-312.pyc new file mode 100644 index 0000000..97c39ee Binary files /dev/null and b/agent/tools/__pycache__/agent_tools.cpython-312.pyc differ diff --git a/agent/tools/__pycache__/middleware.cpython-312.pyc b/agent/tools/__pycache__/middleware.cpython-312.pyc new file mode 100644 index 0000000..37b237c Binary files /dev/null and b/agent/tools/__pycache__/middleware.cpython-312.pyc differ diff --git a/agent/tools/agent_tools.py b/agent/tools/agent_tools.py new file mode 100644 index 0000000..603f9ed --- /dev/null +++ b/agent/tools/agent_tools.py @@ -0,0 +1,99 @@ +import pandas +from langchain_core.tools import tool +from rag.rag_service import RagSummarizeService +import random +from utils.config_handler import agent_conf +from utils.path_tool import get_abs_path +from utils.logger_handler import logger +import os + +rag = RagSummarizeService() + +user_ids = [str(i) for i in range(1001, 1011, 1)] +month_arr = [f"2025-{str(i).zfill(2)}" for i in range(1, 13, 1)] +external_data = {} + + +@tool(description="向量存储中检索参考资料") +def rag_summarize(query: str) -> str: + return rag.rag_summarize(query) + + +@tool(description="获取指定城市天气,消息以字符串方式返回") +def get_weather(city: str) -> str: + return f"城市{city}天气为晴天,温度为26摄氏度,湿度50%,南风1级,AQI21,最近6小时降雨概率极低" + + +@tool(description="获取用户所在城市名称,以纯字符串形式返回") +def get_user_location() -> str: + return random.choice(["深圳", "合肥", "杭州"]) + + +@tool(description="获取用户ID,以纯字符串形式返回") +def get_user_id() -> str: + return random.choice(user_ids) + + +@tool(description="获取当前月份,以纯字符串形式返回") +def get_current_month() -> str: + return random.choice(month_arr) + + +def generate_external_data(): + """得到用户字典, + { + "user_id": { + "month": {"特征: ..., "效率:} + } + }""" + + if not external_data: + external_data_path = agent_conf['external_data_path'] + external_data_path = get_abs_path(external_data_path) + + if not os.path.exists(external_data_path): + raise FileExistsError(f"外部文件{external_data_path}不存在") + + with open(external_data_path, 'r', encoding='utf-8') as f: + for line in f.readlines(): + arr = line.strip().split(',') + user_id = arr[0].replace('"', "") + feature = arr[1].replace('"', "") + efficiency = arr[2].replace('"', "") + consumables = arr[3].replace('"', "") + comparison = arr[4].replace('"', "") + time = arr[5].replace('"', "") + + if user_id not in external_data: + external_data[user_id] = {} + + external_data[user_id][time] = { + "特征": feature, + "效率": efficiency, + "耗材": consumables, + "对比": comparison, + } + return external_data + + +@tool(description="从外部系统获取用户使用记录,以春字符串形式返回,如果未检索到,返回空字符串") +def fetch_external_data(user_id: str, month: str) -> str: + generate_external_data() + + try: + return external_data[user_id][month] + except KeyError: + logger.warning(f"[fetch_external_data]未检索到用户:{user_id}在{month}的使用数据") + return "" + + +@tool(description="无入参,无返回值,调用后自动触发中间件,自动为报告生成动态场景上下文" + "+") +def fill_context_for_report(): + return "fill_context_for_report已调用" + +# if __name__ == '__main__': +# user_id = "1005" +# month = "2025-06" +# res = fetch_external_data(user_id, month) +# print(res) diff --git a/agent/tools/chroma_db/chroma.sqlite3 b/agent/tools/chroma_db/chroma.sqlite3 new file mode 100644 index 0000000..3b39f56 Binary files /dev/null and b/agent/tools/chroma_db/chroma.sqlite3 differ diff --git a/agent/tools/middleware.py b/agent/tools/middleware.py new file mode 100644 index 0000000..d60abfe --- /dev/null +++ b/agent/tools/middleware.py @@ -0,0 +1,51 @@ +from langchain.agents.middleware import wrap_tool_call, before_model, dynamic_prompt, ModelRequest +from langchain.tools.tool_node import ToolCallRequest +from typing import Callable +from langchain_core.messages import ToolMessage +from langgraph.types import Command +from utils.logger_handler import logger +from langchain.agents import AgentState +from langgraph.runtime import Runtime +from utils.prompt_loader import load_system_prompts, load_report_prompts + +@wrap_tool_call +def monitor_tool( + # 函数 + request: ToolCallRequest, + # 入参 + handler: Callable[[ToolCallRequest], ToolMessage | Command], +) -> ToolMessage | Command: + logger.info(f"[tool monitor]执行工具:{request.tool_call['name']}") + logger.info(f"[tool monitor]传入参数:{request.tool_call['args']}") + + try: + result = handler(request) + logger.info(f"[tool minitor]工具{request.tool_call['name']}调用成功") + + if request.tool_call['name'] == 'fill_context_for_report': + request.runtime.context["report"] = True + + return result + except Exception as e: + logger.error(f"工具{request.tool_call['name']}调用失败,原因: {str(e)}") + raise e + + +@before_model +def log_before_model( + state: AgentState, # 状态记录 + runtine: Runtime, # 执行过程 上下文信息 +): + logger.info(f"[log_before_model]即将调用模型,带有{len(state["messages"])}条消息") + logger.debug(f"[log_before_model]{type(state["messages"][-1]).__name__} | " + f"消息内容:{state["messages"][-1].content.strip()}") + return None + + +@dynamic_prompt # 每一次提示词生成前,调用 +def repoet_prompt_switch(request: ModelRequest): + is_report = request.runtime.context.get("report", False) + if is_report: + return load_report_prompts() + + return load_system_prompts() diff --git a/config/agent.yaml b/config/agent.yaml index e69de29..eb994ba 100644 --- a/config/agent.yaml +++ b/config/agent.yaml @@ -0,0 +1 @@ +external_data_path: data/external/records.csv diff --git a/logs/Agent_20260301.log b/logs/Agent_20260301.log new file mode 100644 index 0000000..600704c --- /dev/null +++ b/logs/Agent_20260301.log @@ -0,0 +1,28 @@ +2026-03-01 00:10:11,186 - Agent - WARNING - agent_tools.py:86 - [fetch_external_data]未检索到用户:1004在2月的使用数据 +2026-03-01 00:10:15,880 - Agent - WARNING - agent_tools.py:86 - [fetch_external_data]未检索到用户:1008在8月的使用数据 +2026-03-01 11:06:16,324 - Agent - INFO - middleware.py:39 - [log_before_model]即将调用模型,带有1条消息 +2026-03-01 11:06:16,324 - Agent - DEBUG - middleware.py:40 - [log_before_model]HumanMessage | 消息内容:扫地机器人在我所在地的气温下如何保养 +2026-03-01 11:06:50,251 - Agent - INFO - middleware.py:39 - [log_before_model]即将调用模型,带有1条消息 +2026-03-01 11:06:50,251 - Agent - DEBUG - middleware.py:40 - [log_before_model]HumanMessage | 消息内容:扫地机器人在我所在地的气温下如何保养 +2026-03-01 11:08:04,788 - Agent - INFO - middleware.py:39 - [log_before_model]即将调用模型,带有1条消息 +2026-03-01 11:08:04,789 - Agent - DEBUG - middleware.py:40 - [log_before_model]HumanMessage | 消息内容:扫地机器人在我所在地的气温下如何保养 +2026-03-01 11:08:09,924 - Agent - INFO - middleware.py:18 - [tool monitor]执行工具:get_user_location +2026-03-01 11:08:09,925 - Agent - INFO - middleware.py:19 - [tool monitor]传入参数:{} +2026-03-01 11:08:09,926 - Agent - INFO - middleware.py:23 - [tool minitor]工具get_user_location调用成功 +2026-03-01 11:08:09,927 - Agent - INFO - middleware.py:39 - [log_before_model]即将调用模型,带有3条消息 +2026-03-01 11:08:09,927 - Agent - DEBUG - middleware.py:40 - [log_before_model]ToolMessage | 消息内容:杭州 +2026-03-01 11:08:13,076 - Agent - INFO - middleware.py:18 - [tool monitor]执行工具:get_weather +2026-03-01 11:08:13,076 - Agent - INFO - middleware.py:19 - [tool monitor]传入参数:{'city': '杭州'} +2026-03-01 11:08:13,078 - Agent - INFO - middleware.py:23 - [tool minitor]工具get_weather调用成功 +2026-03-01 11:08:13,079 - Agent - INFO - middleware.py:39 - [log_before_model]即将调用模型,带有5条消息 +2026-03-01 11:08:13,079 - Agent - DEBUG - middleware.py:40 - [log_before_model]ToolMessage | 消息内容:城市杭州天气为晴天,温度为26摄氏度,湿度50%,南风1级,AQI21,最近6小时降雨概率极低 +2026-03-01 11:08:16,346 - Agent - INFO - middleware.py:18 - [tool monitor]执行工具:rag_summarize +2026-03-01 11:08:16,346 - Agent - INFO - middleware.py:19 - [tool monitor]传入参数:{'query': '扫地机器人在26摄氏度温度和50%湿度环境下的保养方法'} +2026-03-01 11:08:19,183 - Agent - INFO - middleware.py:23 - [tool minitor]工具rag_summarize调用成功 +2026-03-01 11:08:19,185 - Agent - INFO - middleware.py:39 - [log_before_model]即将调用模型,带有7条消息 +2026-03-01 11:08:19,185 - Agent - DEBUG - middleware.py:40 - [log_before_model]ToolMessage | 消息内容:参考资料中未提及扫地机器人在26摄氏度温度和50%湿度环境下的具体保养方法。 +2026-03-01 11:08:21,538 - Agent - INFO - middleware.py:18 - [tool monitor]执行工具:rag_summarize +2026-03-01 11:08:21,539 - Agent - INFO - middleware.py:19 - [tool monitor]传入参数:{'query': '扫地机器人日常保养方法'} +2026-03-01 11:08:25,493 - Agent - INFO - middleware.py:23 - [tool minitor]工具rag_summarize调用成功 +2026-03-01 11:08:25,494 - Agent - INFO - middleware.py:39 - [log_before_model]即将调用模型,带有9条消息 +2026-03-01 11:08:25,494 - Agent - DEBUG - middleware.py:40 - [log_before_model]ToolMessage | 消息内容:定期清理滚刷和边刷上的缠绕物,清空尘盒,擦拭传感器和充电触点,检查并清理滤网,避免在潮湿或有水区域使用,及时更换磨损配件。 diff --git a/middleware.py b/middleware.py deleted file mode 100644 index e69de29..0000000 diff --git a/rag/__pycache__/rag_service.cpython-312.pyc b/rag/__pycache__/rag_service.cpython-312.pyc new file mode 100644 index 0000000..674b120 Binary files /dev/null and b/rag/__pycache__/rag_service.cpython-312.pyc differ diff --git a/rag/__pycache__/vector_store.cpython-312.pyc b/rag/__pycache__/vector_store.cpython-312.pyc new file mode 100644 index 0000000..d2480ba Binary files /dev/null and b/rag/__pycache__/vector_store.cpython-312.pyc differ diff --git a/utils/__pycache__/file_handler.cpython-312.pyc b/utils/__pycache__/file_handler.cpython-312.pyc index 6f64494..5a064df 100644 Binary files a/utils/__pycache__/file_handler.cpython-312.pyc and b/utils/__pycache__/file_handler.cpython-312.pyc differ diff --git a/utils/__pycache__/logger_handler.cpython-312.pyc b/utils/__pycache__/logger_handler.cpython-312.pyc index 218a339..c86f36c 100644 Binary files a/utils/__pycache__/logger_handler.cpython-312.pyc and b/utils/__pycache__/logger_handler.cpython-312.pyc differ diff --git a/utils/__pycache__/prompt_loader.cpython-312.pyc b/utils/__pycache__/prompt_loader.cpython-312.pyc new file mode 100644 index 0000000..267e267 Binary files /dev/null and b/utils/__pycache__/prompt_loader.cpython-312.pyc differ