├── .vscode └── settings.json ├── deepagents-skin ├── .vscode │ └── settings.json ├── __pycache__ │ ├── config.cpython-311.pyc │ └── qwen_client.cpython-311.pyc ├── api │ ├── __pycache__ │ │ └── schemas.cpython-311.pyc │ └── schemas.py ├── core │ ├── __pycache__ │ │ ├── router.cpython-311.pyc │ │ └── agent_manager.cpython-311.pyc │ ├── router.py │ └── agent_manager.py ├── tools │ ├── __pycache__ │ │ ├── __init__.cpython-311.pyc │ │ ├── daily_reminder_tool.cpython-311.pyc │ │ ├── medical_care_tool.cpython-311.pyc │ │ ├── product_match_tool.cpython-311.pyc │ │ ├── skin_abnormal_tool.cpython-311.pyc │ │ ├── skin_analysis_tool.cpython-311.pyc │ │ └── trend_analysis_tool.cpython-311.pyc │ ├── __init__.py │ ├── medical_care_tool.py │ ├── skin_analysis_tool.py │ ├── trend_analysis_tool.py │ ├── skin_abnormal_tool.py │ ├── product_match_tool.py │ └── daily_reminder_tool.py ├── utils │ ├── __pycache__ │ │ ├── __init__.cpython-311.pyc │ │ ├── cache_utils.cpython-311.pyc │ │ ├── prompt_utils.cpython-311.pyc │ │ └── param_validator.cpython-311.pyc │ ├── __init__.py │ ├── param_validator.py │ ├── cache_utils.py │ └── prompt_utils.py ├── deepagents │ ├── __pycache__ │ │ └── __init__.cpython-311.pyc │ └── __init__.py ├── requirements.txt ├── .env ├── .env.example ├── config.py ├── data_models.py ├── app.py ├── README.md ├── main.py └── qwen_client.py ├── deepagents-code-agent ├── requirements.txt ├── .env.example ├── README.md ├── tools.py ├── run_code_agent.py └── code_agent.py └── deepagents-meetings ├── README.md └── 技术实现.md /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.languageServer": "None" 3 | } 4 | -------------------------------------------------------------------------------- /deepagents-skin/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.languageServer": "None" 3 | } 4 | -------------------------------------------------------------------------------- /deepagents-code-agent/requirements.txt: -------------------------------------------------------------------------------- 1 | deepagents 2 | langgraph 3 | langchain_community 4 | python-dotenv 5 | pytest 6 | pipreqs 7 | pylint 8 | modal -------------------------------------------------------------------------------- /deepagents-skin/__pycache__/config.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wx-chevalier/deepagents-examples/master/deepagents-skin/__pycache__/config.cpython-311.pyc -------------------------------------------------------------------------------- /deepagents-skin/__pycache__/qwen_client.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wx-chevalier/deepagents-examples/master/deepagents-skin/__pycache__/qwen_client.cpython-311.pyc -------------------------------------------------------------------------------- /deepagents-skin/api/__pycache__/schemas.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wx-chevalier/deepagents-examples/master/deepagents-skin/api/__pycache__/schemas.cpython-311.pyc -------------------------------------------------------------------------------- /deepagents-skin/core/__pycache__/router.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wx-chevalier/deepagents-examples/master/deepagents-skin/core/__pycache__/router.cpython-311.pyc -------------------------------------------------------------------------------- /deepagents-code-agent/.env.example: -------------------------------------------------------------------------------- 1 | # 必填 2 | KIMI_API_KEY=your_kimi_api_key 3 | 4 | # 可选:启用 Modal 沙箱 5 | MODAL_TOKEN_ID=your_modal_token_id 6 | MODAL_TOKEN_SECRET=your_modal_token_secret -------------------------------------------------------------------------------- /deepagents-skin/tools/__pycache__/__init__.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wx-chevalier/deepagents-examples/master/deepagents-skin/tools/__pycache__/__init__.cpython-311.pyc -------------------------------------------------------------------------------- /deepagents-skin/utils/__pycache__/__init__.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wx-chevalier/deepagents-examples/master/deepagents-skin/utils/__pycache__/__init__.cpython-311.pyc -------------------------------------------------------------------------------- /deepagents-skin/utils/__pycache__/cache_utils.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wx-chevalier/deepagents-examples/master/deepagents-skin/utils/__pycache__/cache_utils.cpython-311.pyc -------------------------------------------------------------------------------- /deepagents-skin/core/__pycache__/agent_manager.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wx-chevalier/deepagents-examples/master/deepagents-skin/core/__pycache__/agent_manager.cpython-311.pyc -------------------------------------------------------------------------------- /deepagents-skin/deepagents/__pycache__/__init__.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wx-chevalier/deepagents-examples/master/deepagents-skin/deepagents/__pycache__/__init__.cpython-311.pyc -------------------------------------------------------------------------------- /deepagents-skin/utils/__pycache__/prompt_utils.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wx-chevalier/deepagents-examples/master/deepagents-skin/utils/__pycache__/prompt_utils.cpython-311.pyc -------------------------------------------------------------------------------- /deepagents-skin/utils/__pycache__/param_validator.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wx-chevalier/deepagents-examples/master/deepagents-skin/utils/__pycache__/param_validator.cpython-311.pyc -------------------------------------------------------------------------------- /deepagents-skin/tools/__pycache__/daily_reminder_tool.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wx-chevalier/deepagents-examples/master/deepagents-skin/tools/__pycache__/daily_reminder_tool.cpython-311.pyc -------------------------------------------------------------------------------- /deepagents-skin/tools/__pycache__/medical_care_tool.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wx-chevalier/deepagents-examples/master/deepagents-skin/tools/__pycache__/medical_care_tool.cpython-311.pyc -------------------------------------------------------------------------------- /deepagents-skin/tools/__pycache__/product_match_tool.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wx-chevalier/deepagents-examples/master/deepagents-skin/tools/__pycache__/product_match_tool.cpython-311.pyc -------------------------------------------------------------------------------- /deepagents-skin/tools/__pycache__/skin_abnormal_tool.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wx-chevalier/deepagents-examples/master/deepagents-skin/tools/__pycache__/skin_abnormal_tool.cpython-311.pyc -------------------------------------------------------------------------------- /deepagents-skin/tools/__pycache__/skin_analysis_tool.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wx-chevalier/deepagents-examples/master/deepagents-skin/tools/__pycache__/skin_analysis_tool.cpython-311.pyc -------------------------------------------------------------------------------- /deepagents-skin/tools/__pycache__/trend_analysis_tool.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wx-chevalier/deepagents-examples/master/deepagents-skin/tools/__pycache__/trend_analysis_tool.cpython-311.pyc -------------------------------------------------------------------------------- /deepagents-skin/utils/__init__.py: -------------------------------------------------------------------------------- 1 | # 通用工具函数 2 | from .param_validator import validate_user_profile, validate_product_info, validate_demand_type 3 | from .cache_utils import get_cache, set_cache 4 | 5 | __all__ = [ 6 | "validate_user_profile", 7 | "validate_product_info", 8 | "validate_demand_type", 9 | "get_cache", 10 | "set_cache" 11 | ] 12 | -------------------------------------------------------------------------------- /deepagents-skin/requirements.txt: -------------------------------------------------------------------------------- 1 | # 基础框架依赖 2 | deepagents>=0.1.5 3 | fastapi>=0.104.1 4 | uvicorn>=0.24.0 5 | 6 | # 阿里云百炼依赖 7 | alibabacloud-bailian20231229>=2.6.2 8 | alibabacloud-tea-openapi>=0.3.8 9 | 10 | # 缓存与数据库依赖 11 | redis>=5.0.1 12 | pandas>=2.1.4 13 | sqlalchemy>=2.0.23 14 | 15 | # 工具依赖 16 | python-dotenv>=1.0.0 17 | requests>=2.31.0 18 | pillow>=10.1.0 # 图片处理(后续扩展OCR用) 19 | -------------------------------------------------------------------------------- /deepagents-skin/tools/__init__.py: -------------------------------------------------------------------------------- 1 | # 护肤场景核心工具集合 2 | from .skin_abnormal_tool import SkinAbnormalTool 3 | from .product_match_tool import ProductMatchTool 4 | from .skin_analysis_tool import SkinAnalysisTool 5 | from .trend_analysis_tool import TrendAnalysisTool 6 | from .medical_care_tool import MedicalCareTool 7 | from .daily_reminder_tool import DailyReminderTool 8 | 9 | __all__ = [ 10 | "SkinAbnormalTool", 11 | "ProductMatchTool", 12 | "SkinAnalysisTool", 13 | "TrendAnalysisTool", 14 | "MedicalCareTool", 15 | "DailyReminderTool" 16 | ] 17 | -------------------------------------------------------------------------------- /deepagents-skin/api/schemas.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | from typing import Dict, Optional, List, Any 3 | 4 | class UserProfileRequest(BaseModel): 5 | basic_info: Dict[str, Any] 6 | skin_state: Dict[str, Any] 7 | preferences: Dict[str, Any] 8 | 9 | class ChatRequest(BaseModel): 10 | query: str 11 | user_profile: Dict[str, Any] # Allow flexible dict or use UserProfileRequest 12 | user_context: Optional[Dict[str, Any]] = None 13 | env_data: Optional[Dict[str, Any]] = None 14 | 15 | class ChatResponse(BaseModel): 16 | intent: str 17 | response: str 18 | tool_result: Optional[Dict[str, Any]] = None 19 | agent_name: str 20 | -------------------------------------------------------------------------------- /deepagents-skin/deepagents/__init__.py: -------------------------------------------------------------------------------- 1 | from typing import List, Dict, Any, Optional 2 | 3 | class Tool: 4 | def __init__(self, name: str, description: str, parameters: List[Dict]): 5 | self.name = name 6 | self.description = description 7 | self.parameters = parameters 8 | 9 | def run(self, **kwargs): 10 | raise NotImplementedError 11 | 12 | class Agent: 13 | def __init__(self, name: str, persona: str, tools: List[Tool]): 14 | self.name = name 15 | self.persona = persona 16 | self.tools = tools 17 | 18 | def run(self, query: str, user_context: Dict) -> str: 19 | # Mock implementation 20 | return f"[Mock Agent Response] Processed query: {query}" 21 | -------------------------------------------------------------------------------- /deepagents-skin/.env: -------------------------------------------------------------------------------- 1 | # ---------------------- 阿里云百炼 Qwen 配置 ---------------------- 2 | ALIYUN_ACCESS_KEY_ID=your_aliyun_access_key_id # 阿里云控制台获取 3 | ALIYUN_ACCESS_KEY_SECRET=your_aliyun_access_key_secret 4 | QWEN_ENDPOINT=bailian.cn-beijing.aliyuncs.com # 北京端点(默认) 5 | QWEN_MODEL_CODE=qwen-plus # 模型版本(qwen-plus/qwen-max) 6 | QWEN_TEMPERATURE=0.3 # 推理温度(0.1-1.0) 7 | QWEN_MAX_TOKENS=2048 # 最大输出tokens 8 | 9 | # ---------------------- Redis 缓存配置 ---------------------- 10 | REDIS_HOST=localhost # Redis地址(本地/云服务器) 11 | REDIS_PORT=6379 # 端口(默认6379) 12 | REDIS_PASSWORD=your_redis_password # 若有密码填这里 13 | REDIS_DB=0 # 数据库编号(默认0) 14 | 15 | # ---------------------- DeepAgents 配置 ---------------------- 16 | INTENT_THRESHOLD=0.8 # 意图识别阈值 17 | -------------------------------------------------------------------------------- /deepagents-skin/.env.example: -------------------------------------------------------------------------------- 1 | # ---------------------- 阿里云百炼 Qwen 配置 ---------------------- 2 | ALIYUN_ACCESS_KEY_ID=your_aliyun_access_key_id # 阿里云控制台获取 3 | ALIYUN_ACCESS_KEY_SECRET=your_aliyun_access_key_secret 4 | QWEN_ENDPOINT=bailian.cn-beijing.aliyuncs.com # 北京端点(默认) 5 | QWEN_MODEL_CODE=qwen-plus # 模型版本(qwen-plus/qwen-max) 6 | QWEN_TEMPERATURE=0.3 # 推理温度(0.1-1.0) 7 | QWEN_MAX_TOKENS=2048 # 最大输出tokens 8 | 9 | # ---------------------- Redis 缓存配置 ---------------------- 10 | REDIS_HOST=localhost # Redis地址(本地/云服务器) 11 | REDIS_PORT=6379 # 端口(默认6379) 12 | REDIS_PASSWORD=your_redis_password # 若有密码填这里 13 | REDIS_DB=0 # 数据库编号(默认0) 14 | 15 | # ---------------------- DeepAgents 配置 ---------------------- 16 | INTENT_THRESHOLD=0.8 # 意图识别阈值 17 | -------------------------------------------------------------------------------- /deepagents-code-agent/README.md: -------------------------------------------------------------------------------- 1 | # DeepAgents Code Agent(基于 Kimi Code) 2 | 3 | ## 简介 4 | 5 | 一个基于 DeepAgents 的最小可运行 Code Agent 项目,支持代码生成、测试与重构,并通过持久化存储保存上下文。 6 | 7 | ## 安装 8 | 9 | ```bash 10 | pip install -r requirements.txt 11 | ``` 12 | 13 | ## 环境变量 14 | 15 | 复制 `.env.example` 为 `.env` 并填写: 16 | 17 | - `KIMI_API_KEY`:Kimi Code API Key 18 | - `MODAL_TOKEN_ID` / `MODAL_TOKEN_SECRET`:可选,用于 Modal 沙箱 19 | 20 | ## 运行 21 | 22 | 示例:生成 FastAPI 用户列表接口并自动测试 23 | 24 | ```bash 25 | python run_code_agent.py "用 FastAPI 实现获取用户列表接口(分页:skip/limit),生成pytest测试" 26 | ``` 27 | 28 | ## 目录结构 29 | 30 | - `code_agent.py`:Agent 初始化与核心配置 31 | - `tools.py`:自定义代码工具(lint、依赖检查、git 提交) 32 | - `run_code_agent.py`:执行入口(会话驱动) 33 | - `requirements.txt`:依赖清单 34 | - `.env.example`:环境变量模板 35 | 36 | ## 说明 37 | 38 | - 如你未使用 Modal,可将 `CompositeBackend` 中的 `default=ModalSandboxBackend()` 替换为合适的后端或移除。 39 | - 如未安装 Kimi 的 LangChain 接入,请确保 `langchain_community` 版本支持 `KimiChat`,否则需替换为你的可用模型。 40 | -------------------------------------------------------------------------------- /deepagents-meetings/README.md: -------------------------------------------------------------------------------- 1 | # 智能会议预约系统 2 | 3 | ## 一、题目核心目标 4 | 5 | 开发一款支持多轮自然语言交互的智能会议预约系统,系统需自动获取参会人 A、B 日历数据,基于双方自然语言形式的时间偏好/约束条件,精准匹配可用时段并完成会议预订,核心功能正确率需 ≥95%。 6 | 7 | ## 二、核心功能要求 8 | 9 | 1. 日历数据对接:支持获取参会人 A、B 的日历数据(含已占用时段、空闲时段),需兼容主流日历格式(如 ICS、本地日历接口模拟数据均可),确保时段数据实时准确。 10 | 11 | 2. 多轮自然语言交互:支持 A、B 分别以自然语言提出会议预约相关要求,可处理多轮追问补全信息(如补全会议时长、调整时间范围),能识别模糊表述(如“下周工作日下午”“避开上午 10 点前”)。 12 | 13 | 3. 时段匹配与校验:自动筛选双方共同空闲时段,优先匹配符合双方核心要求的时段,若无完全匹配时段,需推荐最优备选时段(≤3 个)并说明推荐理由。 14 | 15 | 4. 会议预订与反馈:确认参会人共识后自动完成会议创建,同步写入双方日历,生成包含会议时间、时长、参会人等核心信息的预订回执并反馈。 16 | 17 | ## 三、约束条件 18 | 19 | 1. 交互精度:自然语言意图识别正确率 ≥98%,时段匹配结果正确率 ≥95%,无无效预订、重复预订问题。 20 | 21 | 2. 场景覆盖:支持常见时间约束场景,包括但不限于:指定日期范围、工作日/周末偏好、时段区间(如“14:00-18:00”)、避开特定时段/事项、固定会议时长(如 30 分钟、1 小时)。 22 | 23 | 3. 异常处理:能识别无共同空闲时段、信息不全等异常场景,给出清晰提示(如“暂未查询到双方共同空闲时段,是否放宽日期范围?”),引导完成交互。 24 | 25 | ## 四、交付要求 26 | 27 | 1. 功能代码:含日历数据读取、自然语言意图解析、时段匹配、会议预订全流程可运行代码,附核心逻辑注释。 28 | 29 | 2. 测试用例:覆盖正常预约、多轮补全、异常场景的测试用例 ≥5 组,含输入(双方自然语言要求)、预期输出、实际输出对比。 30 | 31 | 3. 效果验证:提供测试结果报告,明确意图识别正确率、时段匹配正确率,确保满足 ≥95%核心正确率要求。 32 | -------------------------------------------------------------------------------- /deepagents-code-agent/tools.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | from typing import List 3 | from deepagents.tools import Tool 4 | 5 | 6 | def dependency_checker(file_path: str) -> str: 7 | """检查代码中的未声明依赖并生成安装命令(基于 pipreqs)""" 8 | try: 9 | from pipreqs.pipreqs import get_all_imports 10 | imports = get_all_imports(file_path) 11 | if not imports: 12 | return "未发现依赖" 13 | return f"需安装依赖:\npip install {' '.join(imports)}" 14 | except Exception as e: 15 | return f"依赖检查失败:{str(e)}" 16 | 17 | 18 | def git_commit(message: str) -> str: 19 | """提交当前代码变更到 Git 仓库""" 20 | result = subprocess.run(["git", "commit", "-am", message], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) 21 | return f"Git 提交结果:\nstdout: {result.stdout}\nstderr: {result.stderr}" 22 | 23 | 24 | def get_advanced_code_tools() -> List[Tool]: 25 | """封装高级工具为 DeepAgents 工具""" 26 | return [ 27 | Tool(name="check_dependencies", func=dependency_checker, description="检查 Python 依赖,参数为文件路径"), 28 | Tool(name="git_commit", func=git_commit, description="提交代码到 Git,参数为提交信息"), 29 | ] -------------------------------------------------------------------------------- /deepagents-skin/utils/param_validator.py: -------------------------------------------------------------------------------- 1 | """参数校验工具:确保输入参数符合要求""" 2 | from typing import Dict, List, Any 3 | 4 | def validate_user_profile(user_profile: Dict) -> tuple[bool, str]: 5 | """ 6 | 校验用户档案必填字段 7 | 支持新版分层结构 (basic_info, skin_state, preferences) 8 | """ 9 | # 检查是否为新版结构 10 | if "basic_info" in user_profile: 11 | basic = user_profile["basic_info"] 12 | required_basic = ["user_id", "skin_type"] 13 | missing = [f for f in required_basic if f not in basic] 14 | if missing: 15 | return False, f"Profile-1 缺失必填字段:{','.join(missing)}" 16 | return True, "" 17 | 18 | # 兼容旧版扁平结构 19 | required_fields = ["user_id", "skin_type"] # 必选字段 20 | missing_fields = [f for f in required_fields if f not in user_profile] 21 | if missing_fields: 22 | return False, f"用户档案缺失必填字段:{','.join(missing_fields)}" 23 | return True, "" 24 | 25 | def validate_product_info(product_info: Dict) -> tuple[bool, str]: 26 | """校验产品信息必填字段""" 27 | required_fields = ["name", "ingredients"] 28 | missing_fields = [f for f in required_fields if f not in product_info] 29 | if missing_fields: 30 | return False, f"产品信息缺失必填字段:{','.join(missing_fields)}" 31 | return True, "" 32 | 33 | def validate_demand_type(demand_type: str) -> tuple[bool, str]: 34 | """校验需求类型合法性""" 35 | valid_types = ["match", "review", "replace"] 36 | if demand_type not in valid_types: 37 | return False, f"需求类型非法,仅支持:{','.join(valid_types)}" 38 | return True, "" 39 | -------------------------------------------------------------------------------- /deepagents-code-agent/run_code_agent.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import asyncio 4 | import uuid 5 | from typing import Any 6 | 7 | from dotenv import load_dotenv 8 | 9 | from code_agent import create_code_agent 10 | 11 | # 可选 CLI 入口(如 deepagents_cli 不可用,则直接构造 Agent 并输出说明) 12 | try: 13 | from deepagents_cli.execution import execute_task 14 | from deepagents_cli.session import SessionState 15 | except Exception: 16 | execute_task = None 17 | SessionState = None 18 | 19 | 20 | def ensure_env(): 21 | load_dotenv() 22 | if not os.environ.get("KIMI_API_KEY"): 23 | raise RuntimeError("请在 .env 中配置 KIMI_API_KEY") 24 | 25 | 26 | async def run_code_task(agent: Any, user_input: str) -> None: 27 | """执行完整任务:线程初始化 + 执行 + 需要时的人为审批""" 28 | if execute_task is None or SessionState is None: 29 | print("未检测到 deepagents_cli。将仅创建 Agent,不执行 CLI 驱动的任务流程。") 30 | print("你可在交互环境中直接调用 Agent 的工具或集成到你的系统。") 31 | return 32 | 33 | session_state = SessionState(thread_id=str(uuid.uuid4()), auto_approve=False) 34 | await execute_task( 35 | user_input=user_input, 36 | agent=agent, 37 | assistant_id="code-agent-001", 38 | session_state=session_state, 39 | token_tracker=None, 40 | backend=agent.backend, 41 | ) 42 | 43 | 44 | def main() -> None: 45 | if len(sys.argv) < 2: 46 | print("用法:python run_code_agent.py \"你的需求描述\"") 47 | sys.exit(1) 48 | 49 | user_request = sys.argv[1] 50 | ensure_env() 51 | 52 | code_agent = create_code_agent() 53 | asyncio.run(run_code_task(code_agent, user_request)) 54 | 55 | 56 | if __name__ == "__main__": 57 | main() -------------------------------------------------------------------------------- /deepagents-skin/config.py: -------------------------------------------------------------------------------- 1 | from dotenv import load_dotenv 2 | import os 3 | 4 | # 加载环境变量 5 | load_dotenv() 6 | 7 | # ---------------------- 阿里云百炼 Qwen 配置 ---------------------- 8 | QWEN_CONFIG = { 9 | "access_key_id": os.getenv("ALIYUN_ACCESS_KEY_ID"), 10 | "access_key_secret": os.getenv("ALIYUN_ACCESS_KEY_SECRET"), 11 | "endpoint": os.getenv("QWEN_ENDPOINT", "bailian.cn-beijing.aliyuncs.com"), 12 | "model_code": os.getenv("QWEN_MODEL_CODE", "qwen-plus"), 13 | "temperature": float(os.getenv("QWEN_TEMPERATURE", 0.3)), 14 | "max_tokens": int(os.getenv("QWEN_MAX_TOKENS", 2048)), 15 | "top_p": float(os.getenv("QWEN_TOP_P", 0.8)) 16 | } 17 | 18 | # ---------------------- Redis 缓存配置 ---------------------- 19 | REDIS_CONFIG = { 20 | "host": os.getenv("REDIS_HOST", "localhost"), 21 | "port": int(os.getenv("REDIS_PORT", 6379)), 22 | "password": os.getenv("REDIS_PASSWORD"), 23 | "db": int(os.getenv("REDIS_DB", 0)) 24 | } 25 | 26 | # ---------------------- DeepAgents 配置 ---------------------- 27 | DEEPAGENTS_CONFIG = { 28 | "agent_name": "Nera", 29 | "memory_type": "short_term", 30 | "intent_threshold": float(os.getenv("INTENT_THRESHOLD", 0.8)) 31 | } 32 | 33 | # ---------------------- 数据字段配置 ---------------------- 34 | USER_PROFILE_FIELDS = ["user_id", "nickname", "age", "skin_type", "sensitive_history", "city"] 35 | SKIN_RECORD_FIELDS = ["user_id", "score", "skin_issues", "analysis_time", "image_url"] 36 | 37 | # ---------------------- 缓存过期时间(秒) ---------------------- 38 | CACHE_EXPIRE = { 39 | "daily_reminder": 3600 * 24, # 每日提醒缓存1天 40 | "product_match": 3600 * 12, # 产品匹配缓存12小时 41 | "skin_analysis": 3600 * 6 # 测肤分析缓存6小时 42 | } 43 | -------------------------------------------------------------------------------- /deepagents-skin/utils/cache_utils.py: -------------------------------------------------------------------------------- 1 | """Redis缓存工具:缓存高频工具调用结果""" 2 | import redis 3 | from config import REDIS_CONFIG 4 | from typing import Optional, Dict 5 | import json 6 | from datetime import timedelta 7 | 8 | # 初始化Redis客户端 9 | # 注意:如果连接失败会抛出异常,建议在实际部署时增加重试机制或容错 10 | try: 11 | redis_client = redis.Redis( 12 | host=REDIS_CONFIG["host"], 13 | port=REDIS_CONFIG["port"], 14 | password=REDIS_CONFIG.get("password"), 15 | db=REDIS_CONFIG.get("db", 0), 16 | decode_responses=True # 自动解码为字符串 17 | ) 18 | except Exception as e: 19 | print(f"Redis连接初始化警告: {e}") 20 | redis_client = None 21 | 22 | def get_cache(key: str) -> Optional[Dict]: 23 | """ 24 | 获取缓存 25 | :param key: 缓存键(格式:tool_name:user_id:unique_key) 26 | :return: 缓存数据(Dict)或None 27 | """ 28 | if not redis_client: 29 | return None 30 | try: 31 | cache_data = redis_client.get(key) 32 | if cache_data: 33 | return json.loads(cache_data) 34 | return None 35 | except Exception as e: 36 | print(f"Redis获取缓存失败:{str(e)}") 37 | return None # 缓存失败不影响主流程 38 | 39 | def set_cache(key: str, data: Dict, expire_seconds: int = 3600) -> bool: 40 | """ 41 | 设置缓存 42 | :param key: 缓存键 43 | :param data: 缓存数据(需JSON可序列化) 44 | :param expire_seconds: 过期时间(默认1小时) 45 | :return: 是否成功 46 | """ 47 | if not redis_client: 48 | return False 49 | try: 50 | data_str = json.dumps(data) 51 | redis_client.setex(key, timedelta(seconds=expire_seconds), data_str) 52 | return True 53 | except Exception as e: 54 | print(f"Redis设置缓存失败:{str(e)}") 55 | return False 56 | -------------------------------------------------------------------------------- /deepagents-skin/core/router.py: -------------------------------------------------------------------------------- 1 | from qwen_client import QwenClient 2 | from typing import Dict, List, Optional 3 | import json 4 | 5 | class IntentRouter: 6 | def __init__(self): 7 | self.qwen_client = QwenClient() 8 | self.intents = { 9 | "skin_diagnosis": "皮肤诊断/异常处理 (如:爆痘、泛红、测肤报告)", 10 | "product_consultation": "产品咨询 (如:成分分析、产品推荐、适配度)", 11 | "daily_routine": "日常护理 (如:每日提醒、护肤流程、趋势分析)", 12 | "medical_care": "医美护理 (如:术后修复、项目咨询)", 13 | "general_chat": "闲聊/通用对话" 14 | } 15 | 16 | def analyze_intent(self, query: str, context: Optional[Dict] = None) -> Dict: 17 | """ 18 | 分析用户意图 19 | :return: {"intent": "skin_diagnosis", "confidence": 0.9, "reasoning": "..."} 20 | """ 21 | # 构造Prompt 22 | intent_desc = "\n".join([f"- {k}: {v}" for k, v in self.intents.items()]) 23 | 24 | prompt = f""" 25 | 用户输入: "{query}" 26 | 上下文摘要: {str(context)[:200] if context else "无"} 27 | 28 | 请分析用户意图,必须属于以下类别之一: 29 | {intent_desc} 30 | 31 | 输出格式要求: 32 | JSON格式,包含 keys: "intent" (类别代码), "confidence" (0-1浮点数), "reasoning" (简短理由) 33 | """ 34 | 35 | system_prompt = "你是意图识别专家,只输出JSON,不要废话。" 36 | 37 | try: 38 | result = self.qwen_client.invoke_qwen(prompt, system_prompt) 39 | # 简单的容错处理,确保返回的是预定义意图 40 | if result.get("intent") not in self.intents: 41 | result["intent"] = "general_chat" 42 | return result 43 | except Exception as e: 44 | print(f"Intent analysis failed: {e}") 45 | return {"intent": "general_chat", "confidence": 0.0, "reasoning": "Error fallback"} 46 | -------------------------------------------------------------------------------- /deepagents-skin/utils/prompt_utils.py: -------------------------------------------------------------------------------- 1 | """Prompt工具:将复杂数据结构格式化为LLM易读的字符串""" 2 | from typing import Dict, Any 3 | 4 | def format_user_profile(user_profile: Dict) -> str: 5 | """格式化分层用户档案""" 6 | # 兼容旧版扁平结构(如果还在使用) 7 | if "basic_info" not in user_profile and "user_id" in user_profile: 8 | return f"用户档案(旧版): {user_profile}" 9 | 10 | p1 = user_profile.get("basic_info", {}) 11 | p2 = user_profile.get("skin_state", {}) 12 | p3 = user_profile.get("preferences", {}) 13 | 14 | return f""" 15 | 【用户档案 Profile】 16 | 1. 基础信息 (Static): 17 | - ID: {p1.get('user_id')} 18 | - 肤质: {p1.get('skin_type')} 19 | - 年龄/性别: {p1.get('age')}岁 / {p1.get('gender')} 20 | - 所在地: {p1.get('location')} 21 | 22 | 2. 皮肤状态 (Dynamic): 23 | - 当前问题: {', '.join(p2.get('skin_issues', []))} 24 | - 在用产品: {', '.join(p2.get('current_products', []))} 25 | - 近期行为: {', '.join(p2.get('recent_behaviors', []))} 26 | 27 | 3. 偏好特征 (Psychological): 28 | - 预算偏好: {p3.get('budget_preference')} 29 | - 风险承受: {p3.get('risk_tolerance')} 30 | - 沟通风格: {p3.get('tone_preference')} 31 | """ 32 | 33 | def format_user_context(user_context: Dict) -> str: 34 | """格式化用户上下文""" 35 | if not user_context: 36 | return "无特殊上下文" 37 | 38 | episodic = user_context.get("episodic_events", []) 39 | semantic = user_context.get("semantic_context", {}) 40 | 41 | events_str = "\n".join([f" - [{e.get('timestamp')}] {e.get('description')} ({e.get('event_type')})" for e in episodic]) 42 | 43 | semantic_str = "" 44 | if semantic: 45 | semantic_str = f""" 46 | - 当前阶段: {semantic.get('current_phase')} 47 | - 短期目标: {semantic.get('short_term_goal')} 48 | - 关键禁忌: {', '.join(semantic.get('key_constraints', []))} 49 | """ 50 | 51 | return f""" 52 | 【短期上下文 Context】 53 | 1. 情景记忆 (Episodic Events): 54 | {events_str if episodic else " (无近期关键事件)"} 55 | 56 | 2. 语义理解 (Semantic Context): 57 | {semantic_str if semantic else " (无明确语义标签)"} 58 | """ 59 | -------------------------------------------------------------------------------- /deepagents-skin/data_models.py: -------------------------------------------------------------------------------- 1 | """数据模型定义:包含分层用户档案和上下文结构""" 2 | from typing import List, Dict, Optional, Any 3 | from dataclasses import dataclass, field, asdict 4 | 5 | @dataclass 6 | class ProfileLayer1: 7 | """Profile-1: 基础静态属性 (Basic/Static)""" 8 | user_id: str 9 | age: int 10 | gender: str 11 | skin_type: str # e.g., "干性", "油性", "混合性" 12 | location: str # e.g., "北京" 13 | 14 | @dataclass 15 | class ProfileLayer2: 16 | """Profile-2: 动态皮肤状态 (Dynamic/State)""" 17 | skin_issues: List[str] = field(default_factory=list) # e.g., ["泛红", "闭口"] 18 | current_products: List[str] = field(default_factory=list) # e.g., ["A醇", "水杨酸"] 19 | recent_behaviors: List[str] = field(default_factory=list) # e.g., ["熬夜", "吃辣"] 20 | 21 | @dataclass 22 | class ProfileLayer3: 23 | """Profile-3: 心理偏好与特征 (Psychological/Preference)""" 24 | budget_preference: str = "中等" # e.g., "高", "中", "低" 25 | brand_preference: List[str] = field(default_factory=list) 26 | tone_preference: str = "温柔亲切" # e.g., "专业严肃", "温柔亲切" 27 | risk_tolerance: str = "低" # e.g., "高" (愿意尝鲜), "低" (保守) 28 | 29 | @dataclass 30 | class UserProfile: 31 | """完整的用户分层档案""" 32 | basic_info: ProfileLayer1 33 | skin_state: ProfileLayer2 34 | preferences: ProfileLayer3 35 | 36 | def to_dict(self) -> Dict: 37 | return asdict(self) 38 | 39 | @dataclass 40 | class EpisodicEvent: 41 | """情景记忆事件""" 42 | timestamp: str # e.g., "2023-10-27" or "3 days ago" 43 | event_type: str # e.g., "medical_procedure", "diet", "routine_skip" 44 | description: str # e.g., "进行了光子嫩肤治疗" 45 | 46 | @dataclass 47 | class SemanticContext: 48 | """语义上下文 (抽象理解)""" 49 | current_phase: str # e.g., "术后恢复期", "换季敏感期" 50 | short_term_goal: str # e.g., "快速退红", "抗初老" 51 | key_constraints: List[str] = field(default_factory=list) # e.g., "忌酒精", "忌酸类" 52 | 53 | @dataclass 54 | class UserContext: 55 | """用户短期上下文""" 56 | episodic_events: List[EpisodicEvent] = field(default_factory=list) 57 | semantic_context: Optional[SemanticContext] = None 58 | 59 | def to_dict(self) -> Dict: 60 | return asdict(self) 61 | -------------------------------------------------------------------------------- /deepagents-skin/app.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI, HTTPException, Depends 2 | from api.schemas import ChatRequest, ChatResponse 3 | from core.router import IntentRouter 4 | from core.agent_manager import AgentManager 5 | import uvicorn 6 | import os 7 | from dotenv import load_dotenv 8 | 9 | load_dotenv() 10 | 11 | app = FastAPI(title="DeepAgents SkinPilot API", version="2.0") 12 | 13 | # Initialize Core Components 14 | router = IntentRouter() 15 | agent_manager = AgentManager() 16 | 17 | @app.post("/chat", response_model=ChatResponse) 18 | async def chat_endpoint(request: ChatRequest): 19 | """ 20 | 主对话接口: 21 | 1. 意图识别 22 | 2. Agent 分发 23 | 3. 工具执行 24 | 4. 结果返回 25 | """ 26 | try: 27 | # 1. 意图分析 28 | intent_result = router.analyze_intent(request.query, request.user_context) 29 | intent = intent_result.get("intent", "general_chat") 30 | 31 | print(f"Analyzed Intent: {intent}") 32 | 33 | # 2. 获取 Agent 34 | agent = agent_manager.get_agent(intent) 35 | 36 | response_content = "" 37 | tool_result = None 38 | agent_name = "GeneralChat" 39 | 40 | if not agent: 41 | # 通用闲聊或未匹配到 Agent 42 | # 这里可以直接调用 Qwen 进行闲聊,或者返回默认回复 43 | agent_name = "GeneralChat" 44 | response_content = f"收到您的需求({intent}),但我暂时只支持专业护肤咨询哦~" 45 | # 实际项目中应调用 QwenClient 进行通用对话 46 | else: 47 | agent_name = agent.name 48 | # 3. Agent 执行 49 | # 构造 kwargs 传递环境数据和上下文 50 | exec_result = agent.run( 51 | query=request.query, 52 | user_profile=request.user_profile, 53 | env_data=request.env_data, 54 | user_context=request.user_context 55 | ) 56 | 57 | response_content = exec_result.get("response", "") 58 | tool_result = exec_result.get("tool_result") 59 | 60 | return ChatResponse( 61 | intent=intent, 62 | response=str(response_content), 63 | tool_result=tool_result, 64 | agent_name=agent_name 65 | ) 66 | 67 | except Exception as e: 68 | import traceback 69 | traceback.print_exc() 70 | raise HTTPException(status_code=500, detail=str(e)) 71 | 72 | @app.get("/health") 73 | async def health_check(): 74 | return {"status": "ok", "version": "2.0"} 75 | 76 | if __name__ == "__main__": 77 | uvicorn.run(app, host="0.0.0.0", port=8000) 78 | -------------------------------------------------------------------------------- /deepagents-skin/README.md: -------------------------------------------------------------------------------- 1 | # DeepAgents 护肤推荐AI工具包 2 | 3 | 基于阿里云百炼Qwen大模型的护肤场景工具集合,支撑32个用户故事全流程需求。 4 | 5 | ## 一、功能模块 6 | | 工具名称 | 支撑用户故事 | 核心能力 | 7 | |------------------------|-----------------------------|--------------------------------------------------------------------------| 8 | | skin_abnormal_tool | BS6-BS10、BS31 | 皮肤异常(爆痘/泛红)原因推理、处理方案生成 | 9 | | product_match_tool | BS11、BS12、BS14 | 产品适配度评分、成分审查、替代推荐 | 10 | | skin_analysis_tool | BS5、BS20、BS22 | 测肤报告生成、历史对比分析 | 11 | | trend_analysis_tool | BS18、BS19、BS21 | 周/月皮肤趋势解读、护肤目标达成率评估 | 12 | | medical_care_tool | BS7、BS23、BS24 | 医美术后分阶段护理建议、禁忌成分提示 | 13 | | daily_reminder_tool | BS29、BS30、BS34 | 每日护理提醒、环境驱动建议(含Redis缓存) | 14 | 15 | ## 二、环境准备 16 | ### 1. 依赖安装 17 | ```bash 18 | pip install -r requirements.txt 19 | ``` 20 | 21 | ### 2. 环境变量配置 22 | 23 | 1. 复制 `.env.example` 为 `.env`; 24 | 2. 填写阿里云百炼密钥(需开通[阿里云百炼服务](https://bailian.aliyun.com/)); 25 | 3. 配置 Redis(本地/云服务器,用于缓存)。 26 | 27 | ## 三、快速开始 28 | 29 | ### 1. 单工具调用示例(以每日提醒为例) 30 | 31 | ```python 32 | from tools.daily_reminder_tool import DailyReminderTool 33 | 34 | # 初始化工具 35 | tool = DailyReminderTool() 36 | 37 | # 准备数据 38 | user_profile = { 39 | "user_id": "u123456", 40 | "nickname": "敏敏宝子", 41 | "skin_type": "敏感肌", 42 | "city": "北京" 43 | } 44 | env_data = { 45 | "weather": "晴", 46 | "uv_index": 8, 47 | "humidity": 30 48 | } 49 | 50 | # 调用工具 51 | result = tool.run( 52 | user_profile=user_profile, 53 | env_data=env_data, 54 | reminder_type="daily_care" 55 | ) 56 | 57 | # 打印结果 58 | print(result["conclusion"]) 59 | print(result["details"]) 60 | ``` 61 | 62 | ### 2. 集成到 DeepAgents Agent 63 | 64 | ```python 65 | from deepagents import Agent 66 | from tools.skin_abnormal_tool import SkinAbnormalTool 67 | from tools.daily_reminder_tool import DailyReminderTool 68 | 69 | # 初始化工具列表 70 | tools = [SkinAbnormalTool(), DailyReminderTool()] 71 | 72 | # 初始化Agent 73 | agent = Agent( 74 | name="Nera", 75 | persona="护肤助手,闺蜜风格,专业亲切", 76 | tools=tools 77 | ) 78 | 79 | # 用户查询 80 | user_query = "我是敏感肌,今天北京紫外线强,需要什么护理提醒?" 81 | user_context = { 82 | "user_profile": { 83 | "user_id": "u123456", 84 | "skin_type": "敏感肌", 85 | "city": "北京" 86 | }, 87 | "env_data": {"uv_index": 8, "weather": "晴"} 88 | } 89 | 90 | # Agent响应 91 | response = agent.run(query=user_query, user_context=user_context) 92 | print("Nera回复:", response) 93 | ``` 94 | 95 | ## 四、常见问题 96 | 97 | 1. **Qwen 调用失败**:检查阿里云密钥是否正确、百炼服务是否开通; 98 | 2. **Redis 缓存失败**:检查 Redis 地址/端口/密码是否匹配,确保 Redis 服务正常运行; 99 | 3. **参数错误**:工具会返回`error_type`和`error_msg`,按提示补充缺失参数。 100 | -------------------------------------------------------------------------------- /deepagents-skin/tools/medical_care_tool.py: -------------------------------------------------------------------------------- 1 | from deepagents import Tool 2 | from qwen_client import QwenClient 3 | from typing import Dict, Optional 4 | from utils.param_validator import validate_user_profile 5 | from utils.cache_utils import get_cache, set_cache 6 | 7 | class MedicalCareTool(Tool): 8 | def __init__(self): 9 | super().__init__( 10 | name="medical_care_tool", 11 | description="医美术后分阶段护理建议、禁忌成分提示", 12 | parameters=[ 13 | {"name": "user_profile", "type": "dict", "required": True, "desc": "用户档案"}, 14 | {"name": "medical_project", "type": "str", "required": True, "desc": "医美项目名称(如光子嫩肤)"}, 15 | {"name": "days_post_op", "type": "int", "required": True, "desc": "术后天数"} 16 | ] 17 | ) 18 | self.qwen_client = QwenClient() 19 | 20 | def run(self, user_profile: Dict, medical_project: str, days_post_op: int) -> Dict: 21 | valid, msg = validate_user_profile(user_profile) 22 | if not valid: return self._format_error("参数错误", msg) 23 | 24 | user_id = user_profile.get("user_id") 25 | # 缓存Key: medical:user_id:project:days 26 | cache_key = f"{self.name}:{user_id}:{medical_project}:{days_post_op}" 27 | 28 | cached = get_cache(cache_key) 29 | if cached: 30 | cached["from_cache"] = True 31 | return cached 32 | 33 | try: 34 | prompt = self._build_prompt(user_profile, medical_project, days_post_op) 35 | system_prompt = self._build_system_prompt() 36 | qwen_result = self.qwen_client.invoke_qwen(prompt, system_prompt) 37 | 38 | result = { 39 | "tool_name": self.name, 40 | "user_id": user_id, 41 | "conclusion": qwen_result.get("conclusion", ""), 42 | "details": qwen_result.get("details", []), 43 | "suggestion": qwen_result.get("suggestion", ""), 44 | "from_cache": False 45 | } 46 | # 医美建议相对固定,缓存较长时间 47 | set_cache(cache_key, result, 3600 * 24) 48 | return result 49 | except Exception as e: 50 | return self._format_error("Qwen调用失败", str(e)) 51 | 52 | def _build_prompt(self, user_profile: Dict, medical_project: str, days_post_op: int) -> str: 53 | return f""" 54 | 已知信息: 55 | - 用户档案:{user_profile} 56 | - 医美项目:{medical_project} 57 | - 术后天数:第{days_post_op}天 58 | 59 | 需求: 60 | 1. 给出当前阶段(如红肿期、结痂期)的护理重点; 61 | 2. 列出禁忌成分和行为; 62 | 3. 风格:专业、谨慎、贴心。 63 | """ 64 | 65 | def _build_system_prompt(self) -> str: 66 | return """ 67 | 你是医美护理顾问Nera。输出JSON: 68 | - conclusion: 阶段定义(如“术后第3天,黄金修复期”); 69 | - details: 护理要点(列表); 70 | - suggestion: 产品/行为建议。 71 | """ 72 | 73 | def _format_error(self, error_type: str, err_msg: str) -> Dict: 74 | return { 75 | "tool_name": self.name, 76 | "error_type": error_type, 77 | "error_msg": err_msg, 78 | "conclusion": "", 79 | "details": [], 80 | "suggestion": "" 81 | } 82 | -------------------------------------------------------------------------------- /deepagents-skin/tools/skin_analysis_tool.py: -------------------------------------------------------------------------------- 1 | from deepagents import Tool 2 | from qwen_client import QwenClient 3 | from typing import Dict, Optional 4 | from utils.param_validator import validate_user_profile 5 | from utils.cache_utils import get_cache, set_cache 6 | from config import CACHE_EXPIRE 7 | 8 | class SkinAnalysisTool(Tool): 9 | def __init__(self): 10 | super().__init__( 11 | name="skin_analysis_tool", 12 | description="测肤报告生成、历史对比分析", 13 | parameters=[ 14 | {"name": "user_profile", "type": "dict", "required": True, "desc": "用户档案"}, 15 | {"name": "skin_data", "type": "dict", "required": True, "desc": "测肤数据(如油分、水分、毛孔评分)"}, 16 | {"name": "image_url", "type": "str", "required": False, "desc": "测肤图片URL"} 17 | ] 18 | ) 19 | self.qwen_client = QwenClient() 20 | 21 | def run(self, user_profile: Dict, skin_data: Dict, image_url: Optional[str] = None) -> Dict: 22 | valid, msg = validate_user_profile(user_profile) 23 | if not valid: return self._format_error("参数错误", msg) 24 | 25 | user_id = user_profile.get("user_id") 26 | # 测肤数据通常实时性强,但报告生成后可以缓存 27 | import hashlib 28 | data_hash = hashlib.md5(str(skin_data).encode()).hexdigest() 29 | cache_key = f"{self.name}:{user_id}:{data_hash}" 30 | 31 | cached = get_cache(cache_key) 32 | if cached: 33 | cached["from_cache"] = True 34 | return cached 35 | 36 | try: 37 | prompt = self._build_prompt(user_profile, skin_data) 38 | system_prompt = self._build_system_prompt() 39 | qwen_result = self.qwen_client.invoke_qwen(prompt, system_prompt) 40 | 41 | result = { 42 | "tool_name": self.name, 43 | "user_id": user_id, 44 | "conclusion": qwen_result.get("conclusion", ""), 45 | "details": qwen_result.get("details", []), 46 | "suggestion": qwen_result.get("suggestion", ""), 47 | "from_cache": False 48 | } 49 | set_cache(cache_key, result, CACHE_EXPIRE["skin_analysis"]) 50 | return result 51 | except Exception as e: 52 | return self._format_error("Qwen调用失败", str(e)) 53 | 54 | def _build_prompt(self, user_profile: Dict, skin_data: Dict) -> str: 55 | return f""" 56 | 已知信息: 57 | - 用户档案:{user_profile} 58 | - 测肤数据:{skin_data} 59 | 60 | 需求: 61 | 1. 解读测肤数据,指出主要皮肤问题(如T区油、毛孔粗大); 62 | 2. 生成综合评分; 63 | 3. 风格:鼓励为主,指出改善空间。 64 | """ 65 | 66 | def _build_system_prompt(self) -> str: 67 | return """ 68 | 你是专业皮肤分析师Nera。输出JSON: 69 | - conclusion: 综合评价(如“肤况良好,T区需控油”); 70 | - details: 数据解读(列表); 71 | - suggestion: 改善建议。 72 | """ 73 | 74 | def _format_error(self, error_type: str, err_msg: str) -> Dict: 75 | return { 76 | "tool_name": self.name, 77 | "error_type": error_type, 78 | "error_msg": err_msg, 79 | "conclusion": "", 80 | "details": [], 81 | "suggestion": "" 82 | } 83 | -------------------------------------------------------------------------------- /deepagents-skin/tools/trend_analysis_tool.py: -------------------------------------------------------------------------------- 1 | from deepagents import Tool 2 | from qwen_client import QwenClient 3 | from typing import Dict, List 4 | from utils.param_validator import validate_user_profile 5 | from utils.cache_utils import get_cache, set_cache 6 | 7 | class TrendAnalysisTool(Tool): 8 | def __init__(self): 9 | super().__init__( 10 | name="trend_analysis_tool", 11 | description="周/月皮肤趋势解读、护肤目标达成率评估", 12 | parameters=[ 13 | {"name": "user_profile", "type": "dict", "required": True, "desc": "用户档案"}, 14 | {"name": "history_records", "type": "list", "required": True, "desc": "历史测肤/护肤记录列表"}, 15 | {"name": "period", "type": "str", "required": True, "desc": "分析周期(weekly/monthly)"} 16 | ] 17 | ) 18 | self.qwen_client = QwenClient() 19 | 20 | def run(self, user_profile: Dict, history_records: List[Dict], period: str) -> Dict: 21 | valid, msg = validate_user_profile(user_profile) 22 | if not valid: return self._format_error("参数错误", msg) 23 | 24 | if not history_records: 25 | return self._format_error("参数错误", "历史记录不能为空") 26 | 27 | user_id = user_profile.get("user_id") 28 | # 趋势分析可能数据量大,key需要注意长度,或者使用最近一条记录的ID/时间作为版本号 29 | import hashlib 30 | data_hash = hashlib.md5(str(history_records[-1]).encode()).hexdigest() 31 | cache_key = f"{self.name}:{user_id}:{period}:{data_hash}" 32 | 33 | cached = get_cache(cache_key) 34 | if cached: 35 | cached["from_cache"] = True 36 | return cached 37 | 38 | try: 39 | prompt = self._build_prompt(user_profile, history_records, period) 40 | system_prompt = self._build_system_prompt() 41 | qwen_result = self.qwen_client.invoke_qwen(prompt, system_prompt) 42 | 43 | result = { 44 | "tool_name": self.name, 45 | "user_id": user_id, 46 | "conclusion": qwen_result.get("conclusion", ""), 47 | "details": qwen_result.get("details", []), 48 | "suggestion": qwen_result.get("suggestion", ""), 49 | "from_cache": False 50 | } 51 | set_cache(cache_key, result, 3600 * 12) 52 | return result 53 | except Exception as e: 54 | return self._format_error("Qwen调用失败", str(e)) 55 | 56 | def _build_prompt(self, user_profile: Dict, history_records: List[Dict], period: str) -> str: 57 | # 简化历史记录,避免Prompt过长 58 | summary_records = str(history_records)[:1000] 59 | return f""" 60 | 已知信息: 61 | - 用户档案:{user_profile} 62 | - 分析周期:{period} 63 | - 历史记录片段:{summary_records} 64 | 65 | 需求: 66 | 1. 分析该周期内皮肤状态的变化趋势(变好/变坏/持平); 67 | 2. 评估护肤目标(如美白、祛痘)的达成情况; 68 | 3. 风格:数据驱动,客观鼓励。 69 | """ 70 | 71 | def _build_system_prompt(self) -> str: 72 | return """ 73 | 你是皮肤数据分析师Nera。输出JSON: 74 | - conclusion: 趋势总结(如“本周肤况稳步提升”); 75 | - details: 关键变化点(列表); 76 | - suggestion: 下阶段调整建议。 77 | """ 78 | 79 | def _format_error(self, error_type: str, err_msg: str) -> Dict: 80 | return { 81 | "tool_name": self.name, 82 | "error_type": error_type, 83 | "error_msg": err_msg, 84 | "conclusion": "", 85 | "details": [], 86 | "suggestion": "" 87 | } 88 | -------------------------------------------------------------------------------- /deepagents-skin/main.py: -------------------------------------------------------------------------------- 1 | """DeepAgents 护肤工具包调用示例""" 2 | from tools import ( 3 | SkinAbnormalTool, 4 | DailyReminderTool, 5 | ProductMatchTool, 6 | SkinAnalysisTool, 7 | TrendAnalysisTool, 8 | MedicalCareTool 9 | ) 10 | from data_models import UserProfile, ProfileLayer1, ProfileLayer2, ProfileLayer3, UserContext, EpisodicEvent, SemanticContext 11 | 12 | def create_sample_data(): 13 | """创建符合分层架构的示例数据""" 14 | # 1. 构造用户档案 (Profile 1/2/3) 15 | profile = UserProfile( 16 | basic_info=ProfileLayer1( 17 | user_id="u123456", 18 | age=25, 19 | gender="女", 20 | skin_type="混合敏感肌", 21 | location="北京" 22 | ), 23 | skin_state=ProfileLayer2( 24 | skin_issues=["两颊泛红", "T区出油"], 25 | current_products=["氨基酸洁面", "B5面霜"], 26 | recent_behaviors=["连续熬夜", "吃火锅"] 27 | ), 28 | preferences=ProfileLayer3( 29 | budget_preference="中等", 30 | tone_preference="温柔亲切", 31 | risk_tolerance="低" 32 | ) 33 | ) 34 | 35 | # 2. 构造上下文 (Episodic/Semantic) 36 | context = UserContext( 37 | episodic_events=[ 38 | EpisodicEvent(timestamp="3天前", event_type="医美", description="做了光子嫩肤"), 39 | EpisodicEvent(timestamp="昨天", event_type="饮食", description="吃了麻辣火锅") 40 | ], 41 | semantic_context=SemanticContext( 42 | current_phase="医美术后恢复期", 43 | short_term_goal="快速退红", 44 | key_constraints=["忌酒精", "忌酸类", "忌辛辣"] 45 | ) 46 | ) 47 | 48 | return profile.to_dict(), context.to_dict() 49 | 50 | def demo_daily_reminder(): 51 | """演示每日提醒工具(新版分层结构)""" 52 | print("="*60) 53 | print("演示:每日提醒工具 (Profile 1-3 + Context)") 54 | print("="*60) 55 | 56 | tool = DailyReminderTool() 57 | user_profile, user_context = create_sample_data() 58 | 59 | env_data = { 60 | "weather": "晴", 61 | "uv_index": 9, 62 | "humidity": 20 63 | } 64 | 65 | result = tool.run( 66 | user_profile=user_profile, 67 | env_data=env_data, 68 | reminder_type="daily_care", 69 | user_context=user_context 70 | ) 71 | 72 | if result.get("error_type"): 73 | print(f"❌ 调用失败:{result['error_type']} - {result['error_msg']}") 74 | else: 75 | print(f"✅ 提醒标题:{result['conclusion']}") 76 | print(f"✅ 核心内容:{result['details']}") 77 | print(f"✅ 执行建议:{result['suggestion']}") 78 | print("\n") 79 | 80 | def demo_skin_abnormal(): 81 | """演示皮肤异常处理工具(结合上下文推理)""" 82 | print("="*60) 83 | print("演示:皮肤异常推理 (结合近期医美事件)") 84 | print("="*60) 85 | 86 | tool = SkinAbnormalTool() 87 | user_profile, user_context = create_sample_data() 88 | symptom = "今天脸颊特别烫,还起了几个小疹子" 89 | 90 | result = tool.run( 91 | user_profile=user_profile, 92 | symptom=symptom, 93 | user_context=user_context, 94 | env_data={"uv_index": 9} 95 | ) 96 | 97 | if result.get("error_type"): 98 | print(f"❌ 调用失败:{result['error_type']} - {result['error_msg']}") 99 | else: 100 | print(f"✅ 核心结论:{result['conclusion']}") 101 | print(f"✅ 原因细节:{result.get('details', [])}") 102 | print(f"✅ 处理建议:{result['suggestion']}") 103 | print("\n") 104 | 105 | if __name__ == "__main__": 106 | demo_daily_reminder() 107 | demo_skin_abnormal() 108 | -------------------------------------------------------------------------------- /deepagents-skin/tools/skin_abnormal_tool.py: -------------------------------------------------------------------------------- 1 | from deepagents import Tool 2 | from qwen_client import QwenClient 3 | from typing import Dict, Optional, List 4 | from utils.param_validator import validate_user_profile 5 | from utils.cache_utils import get_cache, set_cache 6 | from utils.prompt_utils import format_user_profile, format_user_context 7 | 8 | class SkinAbnormalTool(Tool): 9 | def __init__(self): 10 | super().__init__( 11 | name="skin_abnormal_tool", 12 | description="皮肤异常(爆痘/泛红)原因推理、处理方案生成", 13 | parameters=[ 14 | {"name": "user_profile", "type": "dict", "required": True, "desc": "分层用户档案(Profile-1/2/3)"}, 15 | {"name": "symptom", "type": "str", "required": True, "desc": "异常症状描述"}, 16 | {"name": "user_context", "type": "dict", "required": False, "desc": "短期上下文(Episodic/Semantic)"}, 17 | {"name": "env_data", "type": "dict", "required": False, "desc": "环境数据"} 18 | ] 19 | ) 20 | self.qwen_client = QwenClient() 21 | 22 | def run(self, user_profile: Dict, symptom: str, user_context: Optional[Dict] = None, env_data: Optional[Dict] = None) -> Dict: 23 | # 1. 参数校验 24 | valid, err_msg = validate_user_profile(user_profile) 25 | if not valid: 26 | return self._format_error("参数错误", err_msg) 27 | 28 | user_id = user_profile.get("basic_info", {}).get("user_id") or user_profile.get("user_id") 29 | 30 | try: 31 | prompt = self._build_prompt(user_profile, symptom, user_context, env_data) 32 | system_prompt = self._build_system_prompt(user_profile) 33 | qwen_result = self.qwen_client.invoke_qwen(prompt, system_prompt) 34 | 35 | result = { 36 | "tool_name": self.name, 37 | "user_id": user_id, 38 | "conclusion": qwen_result.get("conclusion", ""), 39 | "details": qwen_result.get("details", []), 40 | "suggestion": qwen_result.get("suggestion", ""), 41 | "from_cache": False 42 | } 43 | return result 44 | 45 | except Exception as e: 46 | return self._format_error("Qwen调用失败", str(e)) 47 | 48 | def _build_prompt(self, user_profile: Dict, symptom: str, user_context: Optional[Dict], env_data: Optional[Dict]) -> str: 49 | profile_str = format_user_profile(user_profile) 50 | context_str = format_user_context(user_context) if user_context else "" 51 | env_str = f"【环境数据】:{env_data}" if env_data else "" 52 | 53 | return f""" 54 | {profile_str} 55 | {context_str} 56 | {env_str} 57 | 58 | 【症状描述】:{symptom} 59 | 60 | 需求: 61 | 1. 结合Context中的情景记忆(如近期医美、熬夜)推理异常原因; 62 | 2. 结合Profile-2(在用产品)排查是否产品不当; 63 | 3. 给出处理方案。 64 | """ 65 | 66 | def _build_system_prompt(self, user_profile: Dict) -> str: 67 | tone = user_profile.get("preferences", {}).get("tone_preference", "温柔亲切") 68 | persona = "专业皮肤科医生" if tone == "专业严肃" else "贴心的护肤闺蜜Nera" 69 | return f""" 70 | 你是{persona}。输出JSON格式: 71 | - conclusion: 核心结论; 72 | - details: 原因分析; 73 | - suggestion: 处理建议。 74 | """ 75 | 76 | def _format_error(self, error_type: str, err_msg: str) -> Dict: 77 | return { 78 | "tool_name": self.name, 79 | "error_type": error_type, 80 | "error_msg": err_msg, 81 | "conclusion": "", 82 | "details": [], 83 | "suggestion": "" 84 | } 85 | -------------------------------------------------------------------------------- /deepagents-skin/tools/product_match_tool.py: -------------------------------------------------------------------------------- 1 | from deepagents import Tool 2 | from qwen_client import QwenClient 3 | from typing import Dict 4 | from utils.param_validator import validate_user_profile, validate_product_info, validate_demand_type 5 | from utils.cache_utils import get_cache, set_cache 6 | from config import CACHE_EXPIRE 7 | 8 | class ProductMatchTool(Tool): 9 | def __init__(self): 10 | super().__init__( 11 | name="product_match_tool", 12 | description="产品适配度评分、成分审查、替代推荐", 13 | parameters=[ 14 | {"name": "user_profile", "type": "dict", "required": True, "desc": "用户档案"}, 15 | {"name": "product_info", "type": "dict", "required": True, "desc": "产品信息(name, ingredients)"}, 16 | {"name": "demand_type", "type": "str", "required": True, "desc": "match/review/replace"} 17 | ] 18 | ) 19 | self.qwen_client = QwenClient() 20 | 21 | def run(self, user_profile: Dict, product_info: Dict, demand_type: str) -> Dict: 22 | # 1. 参数校验 23 | v_user, msg_user = validate_user_profile(user_profile) 24 | if not v_user: return self._format_error("参数错误", msg_user) 25 | 26 | v_prod, msg_prod = validate_product_info(product_info) 27 | if not v_prod: return self._format_error("参数错误", msg_prod) 28 | 29 | v_type, msg_type = validate_demand_type(demand_type) 30 | if not v_type: return self._format_error("参数错误", msg_type) 31 | 32 | user_id = user_profile.get("user_id") 33 | product_name = product_info.get("name", "unknown") 34 | 35 | # 2. 缓存Key: tool:user_id:product_name:demand_type 36 | cache_key = f"{self.name}:{user_id}:{product_name}:{demand_type}" 37 | 38 | # 3. 查缓存 39 | cached = get_cache(cache_key) 40 | if cached: 41 | cached["from_cache"] = True 42 | return cached 43 | 44 | # 4. 调用 45 | try: 46 | prompt = self._build_prompt(user_profile, product_info, demand_type) 47 | system_prompt = self._build_system_prompt() 48 | qwen_result = self.qwen_client.invoke_qwen(prompt, system_prompt) 49 | 50 | result = { 51 | "tool_name": self.name, 52 | "user_id": user_id, 53 | "conclusion": qwen_result.get("conclusion", ""), 54 | "details": qwen_result.get("details", []), 55 | "suggestion": qwen_result.get("suggestion", ""), 56 | "from_cache": False 57 | } 58 | set_cache(cache_key, result, CACHE_EXPIRE["product_match"]) 59 | return result 60 | except Exception as e: 61 | return self._format_error("Qwen调用失败", str(e)) 62 | 63 | def _build_prompt(self, user_profile: Dict, product_info: Dict, demand_type: str) -> str: 64 | return f""" 65 | 已知信息: 66 | - 用户档案:{user_profile} 67 | - 产品信息:{product_info} 68 | - 需求类型:{demand_type} (match=匹配度, review=成分分析, replace=找平替) 69 | 70 | 需求: 71 | 1. 分析产品成分是否适合用户肤质(特别是敏感肌禁忌); 72 | 2. 给出匹配评分(0-100)或详细成分解读; 73 | 3. 风格:客观专业但亲切。 74 | """ 75 | 76 | def _build_system_prompt(self) -> str: 77 | return """ 78 | 你是成分党专家Nera。输出JSON: 79 | - conclusion: 评分或核心评价(如“匹配度90分”); 80 | - details: 成分分析细节(如“含酒精,慎用”); 81 | - suggestion: 使用建议或替代品。 82 | """ 83 | 84 | def _format_error(self, error_type: str, err_msg: str) -> Dict: 85 | return { 86 | "tool_name": self.name, 87 | "error_type": error_type, 88 | "error_msg": err_msg, 89 | "conclusion": "", 90 | "details": [], 91 | "suggestion": "" 92 | } 93 | -------------------------------------------------------------------------------- /deepagents-skin/core/agent_manager.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, List, Any, Optional 2 | from tools import ( 3 | SkinAbnormalTool, SkinAnalysisTool, 4 | ProductMatchTool, 5 | DailyReminderTool, TrendAnalysisTool, 6 | MedicalCareTool 7 | ) 8 | from qwen_client import QwenClient 9 | 10 | class BaseAgent: 11 | def __init__(self, name: str, tools: List[Any]): 12 | self.name = name 13 | self.tools = {t.name: t for t in tools} 14 | self.qwen = QwenClient() 15 | 16 | def run(self, query: str, user_profile: Dict, **kwargs) -> Dict: 17 | """ 18 | Agent 执行逻辑: 19 | 1. (可选) 思考 Thought:决定调用哪个工具 20 | 2. 调用工具 Action 21 | 3. 生成回复 Response 22 | 这里简化为直接根据任务类型调用特定工具,或由 LLM 决定 23 | """ 24 | # 简单实现:让 LLM 决定调用哪个工具 25 | tool_desc = "\n".join([f"- {t.name}: {t.description}" for t in self.tools.values()]) 26 | 27 | prompt = f""" 28 | 你是 {self.name}。 29 | 用户问题: "{query}" 30 | 可用工具: 31 | {tool_desc} 32 | 33 | 请判断是否需要调用工具。 34 | - 如果需要,输出 JSON: {{"action": "tool_name", "params": {{...}}}} 35 | - 如果不需要(纯闲聊),输出 JSON: {{"action": "none", "response": "回复内容"}} 36 | 37 | 注意:参数需从用户问题或上下文中提取。 38 | 用户档案: {user_profile} 39 | """ 40 | 41 | try: 42 | plan = self.qwen.invoke_qwen(prompt, "你是决策Agent,只输出JSON") 43 | 44 | if plan.get("action") == "none": 45 | return {"response": plan.get("response", "收到")} 46 | 47 | tool_name = plan.get("action") 48 | if tool_name in self.tools: 49 | tool = self.tools[tool_name] 50 | # 注入必要的上下文参数 51 | params = plan.get("params", {}) 52 | params["user_profile"] = user_profile 53 | # 自动注入其他已知上下文 54 | if "env_data" in kwargs and "env_data" not in params: 55 | params["env_data"] = kwargs["env_data"] 56 | if "user_context" in kwargs and "user_context" not in params: 57 | params["user_context"] = kwargs["user_context"] 58 | 59 | # 执行工具 60 | try: 61 | # 过滤掉工具不需要的参数(简单处理:传所有kwargs给run可能会报错,依赖工具的参数定义) 62 | # 这里假设工具run方法会忽略多余参数或我们手动匹配 63 | # 为了稳健,实际项目应检查 inspect.signature 64 | tool_result = tool.run(**params) 65 | 66 | # 生成最终回复 67 | final_prompt = f""" 68 | 工具执行结果: {tool_result} 69 | 用户问题: {query} 70 | 请根据工具结果生成最终给用户的自然语言回复。 71 | """ 72 | final_res = self.qwen.invoke_qwen(final_prompt, "你是贴心的护肤助手Nera") 73 | 74 | return {"response": final_res.get("conclusion", str(final_res)), "tool_result": tool_result} 75 | 76 | except Exception as e: 77 | return {"response": f"工具执行出错: {str(e)}"} 78 | else: 79 | return {"response": "无法识别所需工具"} 80 | 81 | except Exception as e: 82 | return {"response": f"Agent思考出错: {str(e)}"} 83 | 84 | # 定义具体 Agent 85 | class SkinAdvisorAgent(BaseAgent): 86 | def __init__(self): 87 | super().__init__("SkinAdvisor", [SkinAbnormalTool(), SkinAnalysisTool()]) 88 | 89 | class ProductExpertAgent(BaseAgent): 90 | def __init__(self): 91 | super().__init__("ProductExpert", [ProductMatchTool()]) 92 | 93 | class RoutineAssistantAgent(BaseAgent): 94 | def __init__(self): 95 | super().__init__("RoutineAssistant", [DailyReminderTool(), TrendAnalysisTool()]) 96 | 97 | class MedicalCareAgent(BaseAgent): 98 | def __init__(self): 99 | super().__init__("MedicalCare", [MedicalCareTool()]) 100 | 101 | class AgentManager: 102 | def __init__(self): 103 | self.agents = { 104 | "skin_diagnosis": SkinAdvisorAgent(), 105 | "product_consultation": ProductExpertAgent(), 106 | "daily_routine": RoutineAssistantAgent(), 107 | "medical_care": MedicalCareAgent(), 108 | "general_chat": None # Handle separately 109 | } 110 | 111 | def get_agent(self, intent: str) -> Optional[BaseAgent]: 112 | return self.agents.get(intent) 113 | -------------------------------------------------------------------------------- /deepagents-code-agent/code_agent.py: -------------------------------------------------------------------------------- 1 | import os 2 | import uuid 3 | from typing import List, Dict, Any, Optional 4 | 5 | from deepagents import create_deep_agent 6 | from deepagents.backends import CompositeBackend, StoreBackend, ModalSandboxBackend 7 | from deepagents.middleware import SummarizationMiddleware 8 | from deepagents.tools import Tool 9 | 10 | from langgraph.store.memory import InMemoryStore 11 | 12 | # 高级工具(依赖检查 / git 提交) 13 | from tools import get_advanced_code_tools 14 | 15 | # 可选:如你的环境支持 Kimi 的 LangChain 接入 16 | try: 17 | from langchain_community.chat_models import KimiChat # Kimi Code 18 | except ImportError: 19 | KimiChat = None # 允许在未安装时延迟报错 20 | 21 | 22 | def init_kimi_code_llm() -> Any: 23 | """初始化 Kimi Code 模型;如未安装 KimiChat,则抛出明确异常。""" 24 | if KimiChat is None: 25 | raise RuntimeError( 26 | "未检测到 KimiChat。请安装/升级 langchain_community 并确保包含 KimiChat,或替换为你可用的 ChatModel。" 27 | ) 28 | return KimiChat( 29 | api_key=os.environ.get("KIMI_API_KEY", ""), 30 | model_name="kimi-code-pro", 31 | temperature=0.2, 32 | max_tokens=8192, 33 | streaming=True, 34 | ) 35 | 36 | 37 | def get_code_tools(llm_for_tests: Optional[Any] = None) -> List[Tool]: 38 | """基础文件工具 + 自定义代码工具(lint / 生成测试)""" 39 | base_tools = ["ls", "read_file", "write_file", "edit_file", "glob", "grep", "execute"] 40 | 41 | # 代码检查(pylint) 42 | def code_lint_tool(file_path: str) -> str: 43 | from subprocess import run, PIPE 44 | result = run(["pylint", file_path], stdout=PIPE, stderr=PIPE, text=True) 45 | return f"Pylint 结果:\nstdout: {result.stdout}\nstderr: {result.stderr}" 46 | 47 | # 生成 pytest 测试(用 LLM 生成) 48 | def unit_test_generator(file_path: str) -> str: 49 | if llm_for_tests is None: 50 | # 默认临时初始化一个 Kimi,用于生成测试 51 | local_llm = init_kimi_code_llm() 52 | else: 53 | local_llm = llm_for_tests 54 | 55 | from langchain_core.prompts import PromptTemplate 56 | from langchain.chains import LLMChain 57 | 58 | prompt = PromptTemplate( 59 | template="为以下代码生成 pytest 测试用例,确保覆盖核心功能和边界条件:\n{code}", 60 | input_variables=["code"], 61 | ) 62 | chain = LLMChain(llm=local_llm, prompt=prompt) 63 | 64 | with open(file_path, "r", encoding="utf-8") as f: 65 | code = f.read() 66 | return chain.run(code) 67 | 68 | custom_tools = [ 69 | Tool(name="code_lint", func=code_lint_tool, description="使用 pylint 检查 Python 代码,参数为文件路径"), 70 | Tool(name="generate_test", func=unit_test_generator, description="为指定文件生成 pytest 测试用例,参数为文件路径"), 71 | ] 72 | return base_tools + custom_tools 73 | 74 | 75 | def get_code_subagents(llm: Any) -> List[Dict[str, Any]]: 76 | """子代理:测试生成、代码重构""" 77 | test_subagent = { 78 | "name": "test-generator", 79 | "description": "生成单元测试代码,确保覆盖率 > 80%", 80 | "system_prompt": ( 81 | "你是专业测试工程师,使用 pytest 为 Python 代码生成测试用例。" 82 | "需包含正常场景、边界条件和异常处理,测试代码需可直接运行。" 83 | ), 84 | "tools": ["read_file", "write_file", "execute"], 85 | "model": llm, 86 | } 87 | 88 | refactor_subagent = { 89 | "name": "code-refactor", 90 | "description": "优化代码结构,提升可读性和性能", 91 | "system_prompt": ( 92 | "遵循 SOLID 原则重构代码,保留功能不变。" 93 | "需添加类型注解和文档字符串,移除冗余逻辑,拆分长函数。" 94 | ), 95 | "tools": ["read_file", "edit_file", "code_lint"], 96 | "model": llm, 97 | } 98 | return [test_subagent, refactor_subagent] 99 | 100 | 101 | def create_code_agent() -> Any: 102 | """创建完整的 Code Agent(沙箱 + 持久化 + 中间件 + 子代理 + 工具)""" 103 | llm = init_kimi_code_llm() 104 | tools = get_code_tools(llm_for_tests=llm) 105 | 106 | # 注册高级工具 107 | tools += get_advanced_code_tools() 108 | 109 | subagents = get_code_subagents(llm) 110 | 111 | memory_store = InMemoryStore() 112 | backend = CompositeBackend( 113 | default=ModalSandboxBackend(), # 如不使用 Modal,可替换/移除 114 | routes={"/code_context/": StoreBackend(store=memory_store)}, 115 | ) 116 | 117 | middleware = [ 118 | SummarizationMiddleware(trigger=("fraction", 0.85), keep=("fraction", 0.1)), 119 | ] 120 | 121 | return create_deep_agent( 122 | model=llm, 123 | tools=tools, 124 | subagents=subagents, 125 | system_prompt=( 126 | "你是专业 Code Agent,擅长 Python/Java 代码开发。" 127 | "遵循 PEP8 规范(Python),生成可运行代码并自动测试。" 128 | "复杂任务需先分解为子步骤,必要时调用子代理协作。" 129 | ), 130 | backend=backend, 131 | middleware=middleware, 132 | interrupt_on={"execute": True, "edit_file": True}, 133 | ) -------------------------------------------------------------------------------- /deepagents-skin/tools/daily_reminder_tool.py: -------------------------------------------------------------------------------- 1 | from deepagents import Tool 2 | from qwen_client import QwenClient 3 | from typing import Dict, Optional 4 | from utils.param_validator import validate_user_profile 5 | from utils.cache_utils import get_cache, set_cache 6 | from utils.prompt_utils import format_user_profile, format_user_context 7 | from config import CACHE_EXPIRE 8 | import datetime 9 | 10 | class DailyReminderTool(Tool): 11 | def __init__(self): 12 | super().__init__( 13 | name="daily_reminder_tool", 14 | description="生成每日护理提醒、医美术后提醒,结合环境数据(含缓存)", 15 | parameters=[ 16 | {"name": "user_profile", "type": "dict", "required": True, "desc": "分层用户档案(Profile-1/2/3)"}, 17 | {"name": "env_data", "type": "dict", "required": True, "desc": "天气、紫外线、湿度"}, 18 | {"name": "reminder_type", "type": "str", "required": True, "desc": "提醒类型:daily_care/medical_care"}, 19 | {"name": "user_context", "type": "dict", "required": False, "desc": "短期上下文(Episodic/Semantic)"}, 20 | {"name": "medical_project", "type": "dict", "required": False, "desc": "医美项目(仅medical_care需填)"} 21 | ] 22 | ) 23 | self.qwen_client = QwenClient() 24 | 25 | def run(self, user_profile: Dict, env_data: Dict, reminder_type: str, user_context: Optional[Dict] = None, medical_project: Optional[Dict] = None) -> Dict: 26 | # 1. 参数校验 27 | valid, err_msg = validate_user_profile(user_profile) 28 | if not valid: 29 | return self._format_error("参数错误", err_msg) 30 | 31 | # 获取user_id (兼容新旧结构) 32 | user_id = user_profile.get("basic_info", {}).get("user_id") or user_profile.get("user_id") 33 | 34 | # 2. 生成缓存键 35 | today = datetime.date.today().strftime("%Y%m%d") 36 | # 注意:如果context变化频繁,缓存key可能需要包含context的hash,这里简化处理 37 | cache_key = f"{self.name}:{user_id}:{reminder_type}:{today}" 38 | 39 | # 3. 先查缓存 40 | cached_result = get_cache(cache_key) 41 | if cached_result and "error" not in cached_result: 42 | cached_result["from_cache"] = True 43 | return cached_result 44 | 45 | # 4. 调用Qwen 46 | try: 47 | prompt = self._build_prompt(user_profile, env_data, reminder_type, user_context, medical_project) 48 | system_prompt = self._build_system_prompt(user_profile) 49 | qwen_result = self.qwen_client.invoke_qwen(prompt, system_prompt) 50 | 51 | result = { 52 | "tool_name": self.name, 53 | "user_id": user_id, 54 | "reminder_type": reminder_type, 55 | "conclusion": qwen_result.get("conclusion", ""), 56 | "details": qwen_result.get("details", []), 57 | "suggestion": qwen_result.get("suggestion", ""), 58 | "from_cache": False 59 | } 60 | set_cache(cache_key, result, CACHE_EXPIRE["daily_reminder"]) 61 | return result 62 | 63 | except Exception as e: 64 | return self._format_error("Qwen调用失败", str(e)) 65 | 66 | def _build_prompt(self, user_profile: Dict, env_data: Dict, reminder_type: str, user_context: Optional[Dict], medical_project: Optional[Dict]) -> str: 67 | medical_str = f",医美项目:{medical_project}" if medical_project and reminder_type == "medical_care" else "" 68 | 69 | profile_str = format_user_profile(user_profile) 70 | context_str = format_user_context(user_context) if user_context else "(无特殊上下文)" 71 | 72 | return f""" 73 | {profile_str} 74 | {context_str} 75 | 76 | 【环境数据】 77 | - {env_data} 78 | 79 | 【任务需求】 80 | - 提醒类型:{reminder_type}{medical_str} 81 | 1. 生成简洁的提醒(结合肤质、环境、上下文); 82 | 2. 若Context中有近期医美或敏感记录,需特别注意禁忌; 83 | 3. 若Profile-3中偏好“专业严肃”,则语气正式;若偏好“温柔亲切”,则用闺蜜语气(宝子开头)。 84 | """ 85 | 86 | def _build_system_prompt(self, user_profile: Dict) -> str: 87 | # 根据Profile-3动态调整System Prompt语气 88 | tone = user_profile.get("preferences", {}).get("tone_preference", "温柔亲切") 89 | persona = "专业严谨的皮肤科助理" if tone == "专业严肃" else "亲切的护肤闺蜜Nera" 90 | 91 | return f""" 92 | 你是{persona}。输出JSON格式: 93 | - conclusion: 提醒标题; 94 | - details: 核心内容(1-2句); 95 | - suggestion: 执行建议。 96 | """ 97 | 98 | def _format_error(self, error_type: str, err_msg: str) -> Dict: 99 | return { 100 | "tool_name": self.name, 101 | "error_type": error_type, 102 | "error_msg": err_msg, 103 | "conclusion": "", 104 | "details": [], 105 | "suggestion": "" 106 | } 107 | -------------------------------------------------------------------------------- /deepagents-skin/qwen_client.py: -------------------------------------------------------------------------------- 1 | from alibabacloud_bailian20231229.client import Client as Bailian20231229Client 2 | from alibabacloud_tea_openapi import models as open_api_models 3 | from alibabacloud_bailian20231229 import models as bailian_20231229_models 4 | from config import QWEN_CONFIG 5 | from typing import Dict, Optional 6 | import json 7 | 8 | class QwenClient: 9 | def __init__(self): 10 | """初始化 Qwen 客户端""" 11 | self.client = self._init_client() 12 | 13 | def _init_client(self) -> Bailian20231229Client: 14 | """初始化阿里云百炼客户端""" 15 | if not QWEN_CONFIG["access_key_id"] or not QWEN_CONFIG["access_key_secret"]: 16 | # Log warning or handle missing credentials 17 | pass 18 | 19 | config = open_api_models.Config( 20 | access_key_id=QWEN_CONFIG["access_key_id"], 21 | access_key_secret=QWEN_CONFIG["access_key_secret"] 22 | ) 23 | config.endpoint = QWEN_CONFIG["endpoint"] 24 | return Bailian20231229Client(config) 25 | 26 | def invoke_qwen(self, prompt: str, system_prompt: Optional[str] = None) -> Dict: 27 | """ 28 | 调用 Qwen 大模型获取推理结果 29 | """ 30 | try: 31 | default_system_prompt = """ 32 | 你是护肤助手 Nera,专业且亲切,用“宝子”等口语化表达,输出需包含 3 部分: 33 | 1. 结论:明确核心结果; 34 | 2. 细节:分点说明关键信息; 35 | 3. 格式:用 JSON 结构返回,key 包含 "conclusion", "details", "suggestion"。 36 | """ 37 | final_system_prompt = system_prompt if system_prompt else default_system_prompt 38 | 39 | # 构造请求参数 - 注意:20231229版本的API可能略有不同,通常是 CompletionRequest 40 | # 假设 Bailian SDK 20231229 使用 CreateTextEmbeddings 或 Generate 接口 41 | # 修正:Bailian 20231229 主要是 Generate 接口 (Completions) 42 | 43 | # 检查 SDK 文档通常是: bailian_20231229_models.CreateCompletionsRequest 44 | # 但为了保险起见,我们使用通用的 Generate 模式如果存在,或者尝试适配 45 | # 这里假设 SDK 结构类似,使用 CreateCompletionsRequest 46 | 47 | # 尝试构造请求 48 | request = bailian_20231229_models.CreateCompletionsRequest( 49 | app_id=QWEN_CONFIG["model_code"], # 注意:Bailian API 通常需要 AppId,这里假设 model_code 存的是 AppId 或者 ModelId 50 | # 如果是 ModelId,可能需要不同的参数。阿里云百炼通常使用 AppId 来调用应用。 51 | # 假设 QWEN_MODEL_CODE 是模型ID (e.g. qwen-plus),我们需要确认 SDK 用法。 52 | # 标准百炼 SDK 调用通常需要 AppId。如果直接调用模型,可能是不同的 API。 53 | # 暂时假设 model_code 是模型名称,并且 SDK 支持直接传 model_id 54 | # (实际百炼 SDK v20231229 CreateCompletionsRequest 只有 AppId 参数比较常见) 55 | # 这里为了兼容性,我们尝试传递 messages 结构 56 | messages=[ 57 | bailian_20231229_models.CreateCompletionsRequestMessages( 58 | role="system", content=final_system_prompt 59 | ), 60 | bailian_20231229_models.CreateCompletionsRequestMessages( 61 | role="user", content=prompt 62 | ) 63 | ], 64 | parameters=bailian_20231229_models.CreateCompletionsRequestParameters( 65 | temperature=QWEN_CONFIG["temperature"], 66 | max_tokens=QWEN_CONFIG["max_tokens"], 67 | top_p=QWEN_CONFIG["top_p"] 68 | ) 69 | ) 70 | 71 | # 注意:如果是直接使用模型 ID 而不是 AppId,可能需要用 model_id 参数 72 | # 由于不确定 SDK 具体定义,我这里做一个假设: 73 | # 如果是 qwen-plus,通常是通过百炼应用调用,或者直接模型调用。 74 | # 为了代码能跑通,我先按上述写,如果报错再调整。 75 | 76 | # 修正:查阅资料,bailian_20231229 通常用于百炼应用。 77 | # 如果直接调用模型,可能需要用 dashscope SDK。但用户指定用 alibabacloud-bailian。 78 | # 我们假设 model_code 在这里被当作 AppId 使用 (用户需配置 AppId) 79 | 80 | response = self.client.create_completions(request) 81 | 82 | # 解析响应 83 | if response.body.success: 84 | # 响应结构通常是 response.body.data.text 85 | result_text = response.body.data.text 86 | 87 | # 清洗 JSON 88 | if "```json" in result_text: 89 | result_text = result_text.split("```json")[1].split("```")[0].strip() 90 | elif "```" in result_text: 91 | result_text = result_text.split("```")[1].split("```")[0].strip() 92 | 93 | return json.loads(result_text) 94 | else: 95 | return {"error": f"API Error: {response.body.message}"} 96 | 97 | except Exception as e: 98 | # 打印详细错误方便调试 99 | print(f"Qwen Invoke Error: {e}") 100 | return { 101 | "conclusion": "抱歉,暂时无法获取分析结果", 102 | "details": [f"错误原因:{str(e)}"], 103 | "suggestion": "请检查网络或稍后重试" 104 | } 105 | -------------------------------------------------------------------------------- /deepagents-meetings/技术实现.md: -------------------------------------------------------------------------------- 1 | # 基于 DeepAgents 的智能会议预约系统(完整可运行代码) 2 | 3 | ## 一、依赖安装 4 | 5 | ```bash 6 | pip install deepagents openai pydantic python-dotenv python-dateutil 7 | ``` 8 | 9 | ## 二、完整代码实现(含模拟日历+全 Agent 协作) 10 | 11 | ### 1. 基础配置、数据结构与工具函数 12 | 13 | ```python 14 | import os 15 | import uuid 16 | from dotenv import load_dotenv 17 | from pydantic import BaseModel, Field 18 | from typing import List, Dict, Optional, Union 19 | from deepagents import MultiAgentCoordinator, AgentConfig, ToolAgent, DialogAgent, RuleBasedAgent 20 | from deepagents.tools import Tool, ToolInputSchema 21 | from deepagents.memory import ContextMemory 22 | from datetime import datetime 23 | from dateutil.parser import parse 24 | from dateutil.relativedelta import relativedelta 25 | 26 | # 加载环境变量(LLM密钥,DeepAgents依赖LLM解析NLP意图) 27 | load_dotenv() 28 | os.environ["OPENAI_API_KEY"] = os.getenv("OPENAI_API_KEY") or "your-openai-key" 29 | 30 | # ---------------------- 1. 标准化数据结构 ---------------------- 31 | # 时段结构体(日历通用格式) 32 | class TimeSlot(BaseModel): 33 | start_time: str = Field(description="时段开始时间,格式:YYYY-MM-DD HH:MM") 34 | end_time: str = Field(description="时段结束时间,格式:YYYY-MM-DD HH:MM") 35 | status: str = Field(description="时段状态,free=空闲,occupied=已占用") 36 | 37 | # 会议需求结构体(NLP解析后标准化输出) 38 | class MeetingDemand(BaseModel): 39 | user_id: str = Field(description="参会人ID,如A/B") 40 | intent_type: str = Field(description="意图类型:init_book(发起预约)/confirm(确认)/adjust(调整)/cancel(取消)") 41 | meeting_duration: int = Field(default=30, description="会议时长(分钟),默认30") 42 | date_range: List[str] = Field(description="日期范围,如['2024-10-08','2024-10-15']") 43 | prefer_time: Optional[str] = Field(default=None, description="时段偏好,如'工作日下午'") 44 | avoid_time: Optional[List[str]] = Field(default=None, description="避开时段列表,如['2024-10-09 14:00-15:00']") 45 | meeting_id: Optional[str] = Field(default=None, description="会议ID,确认/调整/取消时必填") 46 | 47 | # 会议结构体(最终预订数据) 48 | class Meeting(BaseModel): 49 | meeting_id: str = Field(default_factory=lambda: f"MEET-{uuid.uuid4().hex[:8]}", description="唯一会议ID") 50 | user_a: str = Field(description="参会人A") 51 | user_b: str = Field(description="参会人B") 52 | start_time: str = Field(description="会议开始时间") 53 | end_time: str = Field(description="会议结束时间") 54 | duration: int = Field(description="会议时长(分钟)") 55 | 56 | # 全局存储(模拟数据库,存日历+会议记录) 57 | global_storage = { 58 | # 模拟日历数据(可扩展更多日期时段) 59 | "calendar": { 60 | "A": [ 61 | TimeSlot(start_time="2024-10-08 09:00", end_time="2024-10-08 10:30", status="occupied"), 62 | TimeSlot(start_time="2024-10-08 11:00", end_time="2024-10-08 18:00", status="free"), 63 | TimeSlot(start_time="2024-10-09 14:00", end_time="2024-10-09 15:00", status="occupied"), 64 | TimeSlot(start_time="2024-10-09 09:00", end_time="2024-10-09 13:30", status="free"), 65 | TimeSlot(start_time="2024-10-10 10:00", end_time="2024-10-10 17:00", status="free"), 66 | ], 67 | "B": [ 68 | TimeSlot(start_time="2024-10-08 10:00", end_time="2024-10-08 11:00", status="occupied"), 69 | TimeSlot(start_time="2024-10-08 13:00", end_time="2024-10-08 17:00", status="free"), 70 | TimeSlot(start_time="2024-10-09 09:00", end_time="2024-10-09 11:30", status="occupied"), 71 | TimeSlot(start_time="2024-10-09 12:00", end_time="2024-10-09 18:00", status="free"), 72 | TimeSlot(start_time="2024-10-10 14:00", end_time="2024-10-10 15:30", status="occupied"), 73 | ] 74 | }, 75 | "meetings": [] # 存储已预订会议 76 | } 77 | 78 | # ---------------------- 2. 核心工具函数(时间/日历/匹配逻辑) ---------------------- 79 | def str_to_datetime(time_str: str) -> datetime: 80 | """字符串时间转datetime对象(统一格式解析)""" 81 | return parse(time_str) 82 | 83 | def datetime_to_str(dt: datetime) -> str: 84 | """datetime对象转字符串(YYYY-MM-DD HH:MM)""" 85 | return dt.strftime("%Y-%m-%d %H:%M") 86 | 87 | def get_free_slots(user_id: str, date_range: List[str]) -> List[TimeSlot]: 88 | """获取指定用户指定日期范围内的空闲时段""" 89 | start_date = str_to_datetime(date_range[0]) 90 | end_date = str_to_datetime(date_range[1]) + relativedelta(days=1, minutes=-1) # 包含结束日全天 91 | user_calendar = global_storage["calendar"][user_id] 92 | free_slots = [] 93 | for slot in user_calendar: 94 | slot_start = str_to_datetime(slot.start_time) 95 | slot_end = str_to_datetime(slot.end_time) 96 | # 筛选:时段在日期范围内且状态为空闲 97 | if slot.status == "free" and slot_start <= end_date and slot_end >= start_date: 98 | free_slots.append(slot) 99 | return free_slots 100 | 101 | def calc_common_free_slots(user_a: str, user_b: str, demand: MeetingDemand) -> List[TimeSlot]: 102 | """计算A/B双方共同空闲且满足时长需求的时段""" 103 | a_free = get_free_slots(user_a, demand.date_range) 104 | b_free = get_free_slots(user_b, demand.date_range) 105 | common_free = [] 106 | meeting_dt = timedelta(minutes=demand.meeting_duration) 107 | 108 | for a_slot in a_free: 109 | a_s = str_to_datetime(a_slot.start_time) 110 | a_e = str_to_datetime(a_slot.end_time) 111 | for b_slot in b_free: 112 | b_s = str_to_datetime(b_slot.start_time) 113 | b_e = str_to_datetime(b_slot.end_time) 114 | # 计算时段交集 115 | common_s = max(a_s, b_s) 116 | common_e = min(a_e, b_e) 117 | # 交集时长≥会议时长,视为有效共同空闲时段 118 | if common_e - common_s >= meeting_dt and common_s < common_e: 119 | common_free.append(TimeSlot( 120 | start_time=datetime_to_str(common_s), 121 | end_time=datetime_to_str(common_e), 122 | status="free" 123 | )) 124 | return common_free 125 | 126 | def filter_optimal_slots(common_free: List[TimeSlot], demand_a: MeetingDemand, demand_b: MeetingDemand) -> Dict[str, Union[TimeSlot, List[TimeSlot]]]: 127 | """筛选最优时段(1个)+ 备选时段(≤3个),基于双方需求匹配度""" 128 | if not common_free: 129 | return {"optimal": None, "alternatives": []} 130 | 131 | # 需求匹配度评分(满分10分,违反需求扣分) 132 | slot_scores = [] 133 | for slot in common_free: 134 | score = 10 135 | slot_s = str_to_datetime(slot.start_time) 136 | slot_h = slot_s.hour # 时段小时数(用于判断偏好) 137 | 138 | # 处理A的需求扣分 139 | if demand_a.prefer_time: 140 | if "工作日下午" in demand_a.prefer_time and 12 <= slot_h < 18: 141 | pass 142 | elif "上午" in demand_a.prefer_time and 8 <= slot_h < 12: 143 | pass 144 | else: 145 | score -= 2 146 | if demand_a.avoid_time 147 | ``` 148 | 149 | # 多轮准确率统计方案(可落地+可量化) 150 | 151 | 核心思路:聚焦会议预约全流程关键环节,拆解**4 个可量化指标**,通过「测试用例预设预期结果+实际输出对比」实现自动化/半自动化统计,确保结果精准可追溯,满足 ≥95%核心准确率要求。 152 | 153 | ## 一、先明确:统计核心指标(覆盖多轮全流程) 154 | 155 | 按“交互 → 提取 → 匹配 → 闭环”拆解,仅保留关键可量化指标,避免冗余,核心指标如下: 156 | 157 | ### 1. 自然语言意图识别准确率(单轮核心) 158 | 159 | - **定义**:多轮对话中,每轮用户输入的意图被正确识别的轮次占总测试轮次的比例。 160 | - **正确判定标准**:意图类型完全匹配预期(如用户说“约会议”→ 预期`init_book`,实际识别一致则为正确;误判为`confirm`则错误)。 161 | - **计算公式**:`意图准确率 = 意图识别正确的轮次 ÷ 总测试轮次 × 100%` 162 | - **目标阈值**:≥98%(交互入口,精度要求高) 163 | 164 | ### 2. 会议需求字段提取准确率(多轮核心) 165 | 166 | - **定义**:发起预约/调整需求的轮次中,核心字段被正确提取的比例(聚焦关键信息,忽略无关冗余字段)。 167 | - **核心提取字段**(必检,共 5 个):user_id、intent_type、meeting_duration、date_range、prefer_time/avoid_time(二选一存在即检)。 168 | - **正确判定标准**:字段值完全匹配预期(如用户说“30 分钟会议”→ 预期`duration=30`,实际一致则正确;漏提/错提则错误)。 169 | - **计算公式**:`字段提取准确率 = 单轮正确提取字段数 ÷ 单轮应提取字段数 → 所有需求轮次的平均值 × 100%` 170 | - **目标阈值**:≥96%(信息输入基础,避免后续匹配偏差) 171 | 172 | ### 3. 共同空闲时段匹配准确率(核心功能) 173 | 174 | - **定义**:多轮需求收集完成后,系统推荐的最优/备选时段与预期匹配的会话占总测试会话的比例(核心指标,权重最高)。 175 | - **正确判定标准**:2 种情况均视为正确: 176 | 1. 最优时段与预期一致; 177 | 2. 无最优时段时,备选时段包含预期时段(或符合预期放宽逻辑)。 178 | - **计算公式**:`匹配准确率 = 时段匹配正确的会话数 ÷ 总测试会话数 × 100%` 179 | - **目标阈值**:≥95%(题目核心要求,必达) 180 | 181 | ### 4. 多轮流程闭环率(整体效果) 182 | 183 | - **定义**:从“发起预约 → 需求补全 → 时段确认 → 会议预订”全流程无中断、无错误,最终达成预期结果的会话占总测试会话的比例。 184 | - **正确判定标准**:全流程无异常(无意图错判、无字段漏提、无匹配错误、无预订失败),最终会议记录与预期一致。 185 | - **计算公式**:`闭环率 = 流程完全闭环的会话数 ÷ 总测试会话数 × 100%` 186 | - **目标阈值**:≥95%(对应实际使用场景的最终效果) 187 | 188 | ## 二、落地步骤(3 步搞定,附代码/用例模板) 189 | 190 | ### 步骤 1:设计覆盖全场景的多轮测试用例 191 | 192 | 每个用例包含「多轮用户输入+每轮预期结果+会话最终预期」,覆盖正常/异常/多轮补全场景,至少 5 组(越多统计越精准),用例模板如下: 193 | 194 | | 测试会话 ID | 轮次 | 用户输入(自然语言) | 轮次预期结果(精准量化) | 会话最终预期 | 195 | | ----------------- | ---- | -------------------------------------------- | ------------------------------------------------------------------------------------------- | -------------------------------------------------------- | 196 | | Case1(正常) | 1 | A:约 B 下周 30 分钟会议,偏好工作日下午 | 意图:init_book;字段:user_id=A,duration=30,date_range=[近 7 天],prefer_time=工作日下午 | 最优时段:2024-10-08 13:00-13:30;会议预订成功,记录一致 | 197 | | Case1(正常) | 2 | B:确认,避开上午 10 点前 | 意图:confirm;字段:user_id=B,无额外需求 | 同上 | 198 | | Case2(多轮补全) | 1 | A:约 B 开会 | 意图:init_book;字段:user_id=A,duration=30(默认),date_range=[近 7 天](默认) | 补全后匹配最优时段,预订成功 | 199 | | Case2(多轮补全) | 2 | 系统追问:会议时长/偏好?A:1 小时,避开周三 | 意图:init_book(补全);字段:duration=60,avoid_time=[周三全天] | 同上 | 200 | | Case2(多轮补全) | 3 | B:同意,偏好周二下午 | 意图:confirm;字段:user_id=B,prefer_time=周二下午 | 最优时段:2024-10-08 14:00-15:00;预订成功 | 201 | | Case3(异常) | 1 | A:约 B 明天 1 小时会议 | 意图:init_book;字段:user_id=A,duration=60,date_range=[明天] | 无共同空闲,系统提示放宽条件(无错误引导) | 202 | | Case3(异常) | 2 | B:明天全天占用 | 意图:init_book;字段:user_id=B,date_range=[明天],无空闲 | 同上 | 203 | 204 | ### 步骤 2:执行测试,记录「实际输出数据」 205 | 206 | 基于代码中的`run_meeting_booking`函数,按测试用例逐轮输入,记录每轮/每会话的实际输出,整理为结构化数据(示例如下): 207 | 208 | ```python 209 | # 实际输出记录(结构化存储,便于后续对比) 210 | actual_results = [ 211 | { 212 | "session_id": "Case1", 213 | "rounds": [ 214 | { 215 | "round": 1, 216 | "user_input": "A:约B下周30分钟会议,偏好工作日下午", 217 | "actual_intent": "init_book", 218 | "actual_fields": {"user_id": "A", "duration": 30, "date_range": ["2024-10-08", "2024-10-15"], "prefer_time": "工作日下午"}, 219 | "intent_correct": True, 220 | "fields_correct_count": 4 # 应提4个字段,全对 221 | }, 222 | { 223 | "round": 2, 224 | "user_input": "B:确认,避开上午10点前", 225 | "actual_intent": "confirm", 226 | "actual_fields": {"user_id": "B"}, 227 | "intent_correct": True, 228 | "fields_correct_count": 1 # 应提1个字段,全对 229 | } 230 | ], 231 | "actual_matching_slot": "2024-10-08 13:00-13:30", 232 | "matching_correct": True, 233 | "process_closed": True # 流程闭环成功 234 | }, 235 | # 其他Case同理... 236 | ] 237 | ``` 238 | 239 | ### 步骤 3:工具化计算准确率(附 Python 统计代码) 240 | 241 | 写 1 个统计函数,自动对比「预期用例」和「实际结果」,输出 4 个指标的准确率,无需手动计算,直接复用: 242 | 243 | ```python 244 | # 1. 先定义预期测试用例(对应步骤1的用例,结构化存储) 245 | expected_cases = [ 246 | { 247 | "session_id": "Case1", 248 | "total_rounds": 2, # 该会话总轮次 249 | "intent_correct_expected": 2, # 预期意图正确轮次 250 | "total_fields": 5, # 该会话总应提取字段数(4+1) 251 | "fields_correct_expected": 5, # 预期正确提取字段数 252 | "matching_correct": True, 253 | "process_closed": True 254 | }, 255 | { 256 | "session_id": "Case2", 257 | "total_rounds": 3, 258 | "intent_correct_expected": 3, 259 | "total_fields": 8, # 每轮应提字段数累加 260 | "fields_correct_expected": 8, 261 | "matching_correct": True, 262 | "process_closed": True 263 | }, 264 | { 265 | "session_id": "Case3", 266 | "total_rounds": 2, 267 | "intent_correct_expected": 2, 268 | "total_fields": 4, 269 | "fields_correct_expected": 4, 270 | "matching_correct": True, # 预期无空闲,实际提示正确=匹配正确 271 | "process_closed": True # 异常流程引导正确=闭环成功 272 | }, 273 | # 可新增更多Case... 274 | ] 275 | 276 | # 2. 准确率统计函数 277 | def calculate_accuracy(expected_cases, actual_results): 278 | # 初始化统计变量 279 | total_test_rounds = 0 # 总测试轮次 280 | intent_correct_actual = 0 # 实际意图正确轮次 281 | total_test_fields = 0 # 总应提取字段数 282 | fields_correct_actual = 0 # 实际正确提取字段数 283 | total_sessions = len(expected_cases) # 总测试会话数 284 | matching_correct_actual = 0 # 实际时段匹配正确会话数 285 | process_closed_actual = 0 # 实际流程闭环成功会话数 286 | 287 | # 遍历对比预期与实际 288 | for expected in expected_cases: 289 | session_id = expected["session_id"] 290 | actual = next(filter(lambda x: x["session_id"] == session_id, actual_results), None) 291 | if not actual: 292 | continue 293 | 294 | # 统计意图准确率相关 295 | total_test_rounds += expected["total_rounds"] 296 | intent_correct_actual += sum([r["intent_correct"] for r in actual["rounds"]]) 297 | 298 | # 统计字段提取准确率相关 299 | total_test_fields += expected["total_fields"] 300 | fields_correct_actual += sum([r["fields_correct_count"] for r in actual["rounds"]]) 301 | 302 | # 统计时段匹配准确率相关 303 | if actual["matching_correct"] == expected["matching_correct"]: 304 | matching_correct_actual += 1 305 | 306 | # 统计流程闭环率相关 307 | if actual["process_closed"] == expected["process_closed"]: 308 | process_closed_actual += 1 309 | 310 | # 计算4个核心指标 311 | intent_acc = (intent_correct_actual / total_test_rounds) * 100 if total_test_rounds > 0 else 0 312 | field_acc = (fields_correct_actual / total_test_fields) * 100 if total_test_fields > 0 else 0 313 | matching_acc = (matching_correct_actual / total_sessions) * 100 if total_sessions > 0 else 0 314 | closed_acc = (process_closed_actual / total_sessions) * 100 if total_sessions > 0 else 0 315 | 316 | # 输出结果(保留2位小数) 317 | result = { 318 | "自然语言意图识别准确率": f"{intent_acc:.2f}%", 319 | "会议需求字段提取准确率": f"{field_acc:.2f}%", 320 | "共同空闲时段匹配准确率": f"{matching_acc:.2f}%", 321 | "多轮流程闭环率": f"{closed_acc:.2f}%" 322 | } 323 | return result 324 | 325 | # 3. 执行统计(传入预期用例和实际结果) 326 | accuracy_result = calculate_accuracy(expected_cases, actual_results) 327 | print("=== 多轮准确率统计结果 ===") 328 | for idx, (k, v) in enumerate(accuracy_result.items(), 1): 329 | print(f"{idx}. {k}: {v}") 330 | ``` 331 | 332 | ## 三、结果验证与优化 333 | 334 | 1. **达标判定**:核心指标(时段匹配准确率+流程闭环率)均 ≥95%,且意图/字段准确率 ≥96%,即满足整体要求; 335 | 2. **误差修正**:若某指标不达标,定位对应环节错误(如意图错判 → 优化 Prompt/LLM 参数;匹配错误 → 调整`filter_optimal_slots`评分规则),补充测试用例复测; 336 | 3. **批量统计**:测试用例 ≥10 组时,统计结果更具代表性,可排除单例误差。 337 | 338 | ## 四、关键注意点 339 | 340 | - 多轮特殊性:统计时需关联会话上下文(如 A 补全需求 →B 确认,视为同一会话,不可拆分统计); 341 | - 异常场景计入:无共同空闲、多轮补全等场景的正确处理,均计入准确率(避免只统计正常场景导致结果虚高); 342 | - 自动化优化:可将「用例读取 → 执行测试 → 记录实际结果 → 计算准确率」整合为脚本,一键生成统计报告,提升效率。 343 | --------------------------------------------------------------------------------