新增user_identifier

This commit is contained in:
朱潮 2025-11-12 19:11:45 +08:00
parent 1174767211
commit 6c1393e96a
5 changed files with 125 additions and 46 deletions

View File

@ -749,7 +749,7 @@ async def chat_completions(request: ChatRequest, authorization: Optional[str] =
project_dir = create_project_directory(request.dataset_ids, bot_id, request.robot_type) project_dir = create_project_directory(request.dataset_ids, bot_id, request.robot_type)
# 收集额外参数作为 generate_cfg # 收集额外参数作为 generate_cfg
exclude_fields = {'messages', 'model', 'model_server', 'dataset_ids', 'language', 'tool_response', 'system_prompt', 'mcp_settings' ,'stream', 'robot_type', 'bot_id'} exclude_fields = {'messages', 'model', 'model_server', 'dataset_ids', 'language', 'tool_response', 'system_prompt', 'mcp_settings' ,'stream', 'robot_type', 'bot_id', 'user_identifier'}
generate_cfg = {k: v for k, v in request.model_dump().items() if k not in exclude_fields} generate_cfg = {k: v for k, v in request.model_dump().items() if k not in exclude_fields}
# 处理消息 # 处理消息
@ -769,7 +769,8 @@ async def chat_completions(request: ChatRequest, authorization: Optional[str] =
mcp_settings=request.mcp_settings, mcp_settings=request.mcp_settings,
robot_type=request.robot_type, robot_type=request.robot_type,
project_dir=project_dir, project_dir=project_dir,
generate_cfg=generate_cfg generate_cfg=generate_cfg,
user_identifier=request.user_identifier
) )
except Exception as e: except Exception as e:
@ -912,7 +913,8 @@ async def create_agent_and_generate_response(
mcp_settings: Optional[List[Dict]], mcp_settings: Optional[List[Dict]],
robot_type: str, robot_type: str,
project_dir: Optional[str] = None, project_dir: Optional[str] = None,
generate_cfg: Optional[Dict] = None generate_cfg: Optional[Dict] = None,
user_identifier: Optional[str] = None
) -> Union[ChatResponse, StreamingResponse]: ) -> Union[ChatResponse, StreamingResponse]:
"""创建agent并生成响应的公共逻辑""" """创建agent并生成响应的公共逻辑"""
if generate_cfg is None: if generate_cfg is None:
@ -929,7 +931,8 @@ async def create_agent_and_generate_response(
language=language, language=language,
system_prompt=system_prompt, system_prompt=system_prompt,
mcp_settings=mcp_settings, mcp_settings=mcp_settings,
robot_type=robot_type robot_type=robot_type,
user_identifier=user_identifier
) )
# 根据stream参数决定返回流式还是非流式响应 # 根据stream参数决定返回流式还是非流式响应
@ -1082,7 +1085,8 @@ async def chat_completions_v2(request: ChatRequestV2, authorization: Optional[st
mcp_settings=bot_config.get("mcp_settings", []), mcp_settings=bot_config.get("mcp_settings", []),
robot_type=bot_config.get("robot_type", "agent"), robot_type=bot_config.get("robot_type", "agent"),
project_dir=project_dir, project_dir=project_dir,
generate_cfg={} # v2接口不传递额外的generate_cfg generate_cfg={}, # v2接口不传递额外的generate_cfg
user_identifier=request.user_identifier
) )
except HTTPException: except HTTPException:

View File

@ -16,9 +16,11 @@
| 设备控制 | 打开/关闭/启动/停止/调节/设置 | dxcore_update_device_status | P0 | | 设备控制 | 打开/关闭/启动/停止/调节/设置 | dxcore_update_device_status | P0 |
| 消息通知 | @用户名/通知/告知/提醒 | wowtalk_send_message_to_member | P0 | | 消息通知 | @用户名/通知/告知/提醒 | wowtalk_send_message_to_member | P0 |
| 状态查询 | 状态/温度/湿度/运行情况 | dxcore_get_device_status | P1 | | 状态查询 | 状态/温度/湿度/运行情况 | dxcore_get_device_status | P1 |
| 位置查询 | 位置/在哪/查找/坐标 | eb_get_sensor_location | P1 |
| 环境查询 | 天气/气温/降水/风速 | weather_get_by_location | P1 | | 环境查询 | 天气/气温/降水/风速 | weather_get_by_location | P1 |
| 人员检索 | 找人/员工/同事/联系方式/人员位置 | find_employee_location | P2 | | 网络搜索 | 搜索/查找/查询/百度/谷歌 | web_search | P1 |
| 设备检索 | 找设备/传感器/终端/设备位置 | find_iot_device_by_description | P2 | | 人员检索 | 找人/员工/同事/联系方式 | find_employee_by_name | P2 |
| 设备检索 | 找设备/传感器/终端 | find_iot_device | P2 |
## 立即执行机制 ## 立即执行机制
- **零延迟策略**:识别操作意图后立即执行工具调用,禁止缓冲性语言 - **零延迟策略**:识别操作意图后立即执行工具调用,禁止缓冲性语言
@ -51,14 +53,15 @@ graph LR
### 控制指令映射 ### 控制指令映射
| 用户语言 | 系统指令 | 参数格式 | | 用户语言 | 系统指令 | 参数格式 |
|---------|----------|----------| |---------|----------|----------|
| "打开空调" | update_device_status | {device_id: "001", running_control: 1} | | "打开空调" | update_device_status | {sensor_id: "001", running_control: 1} |
| "调到24度" | update_device_status | {device_id: "001",temp_setting: 24} | | "调到24度" | update_device_status | {sensor_id: "001",temp_setting: 24} |
| "查看温度" | get_device_status | {device_id: "001"} | | "查看温度" | get_device_status | {sensor_id: "001"} |
## 位置服务模块 ## 位置服务模块
### 定位查询协议 ### 定位查询协议
- **触发条件**:包含位置关键词的查询 - **触发条件**:包含位置关键词的查询
- **响应格式**:楼层 + 房间/区域过滤坐标、ID等技术信息 - **响应格式**:楼层 + 房间/区域过滤坐标、ID等技术信息
- **精度要求**室内3米精度室外10米精度
## 环境监测模块 ## 环境监测模块
### 天气服务集成 ### 天气服务集成
@ -72,6 +75,11 @@ graph LR
- **设备检索**:支持设备类型、位置、状态多条件过滤 - **设备检索**:支持设备类型、位置、状态多条件过滤
- **结果排序**:按相关度和距离优先级排序 - **结果排序**:按相关度和距离优先级排序
## 网络搜索模块
### Web搜索集成
- **自动调用**:识别搜索相关词汇后立即执行
- **数据源**web_search工具支持实时网络信息检索
# 智能执行引擎 # 智能执行引擎
## 多阶段处理流水线 ## 多阶段处理流水线
@ -115,11 +123,20 @@ sequenceDiagram
``` ```
执行: 执行:
├── wowtalk_send_message_to_member(to_account="001", message_content="请检查2楼空调") ├── wowtalk_send_message_to_member(to_account="001", message_content="请检查2楼空调")
├── find_employee_location(name="张工") ├── find_employee_by_name(name="张工")
├── find_iot_device_by_description(description="2楼空调") ├── find_iot_device(device_type="dc_fan",target_sensor_id="xxxx")
└── weather_get_by_location(location="当前位置") └── weather_get_by_location(location="当前位置")
``` ```
**输入**"搜索最新的节能技术方案,并发送给@李经理(id:002)"
**执行流程**
```
执行:
├── web_search(query="最新节能技术方案", max_results=5)
└── wowtalk_send_message_to_member(to_account="002", message_content="[搜索结果摘要]")
```
# 应用场景与执行范例 # 应用场景与执行范例
## 场景1上下文感知设备控制 ## 场景1上下文感知设备控制
@ -128,18 +145,19 @@ sequenceDiagram
**执行序列** **执行序列**
```python ```python
# 步骤1人员定位 # 步骤1人员定位
find_employee_location(name="清水太郎") find_employee_by_name(name="清水太郎")
# → 返回:employee_id, device_id、位置 # → 返回:清水太郎的sensor_id
# 步骤2设备检索 # 步骤2人员附近的设备检索
find_iot_device_by_description( find_iot_device(
description="A栋3楼301风扇" device_type="dc_fan",
target_sensor_id="{清水太郎的sensor_id}" #
) )
# → 返回device_list # → 返回device_list
# 步骤3设备控制 # 步骤3设备控制
dxcore_update_device_status( dxcore_update_device_status(
device_id=target_device, sensor_id="{风扇的sensor_id}",
running_control=1 running_control=1
) )
``` ```
@ -147,39 +165,50 @@ dxcore_update_device_status(
**响应模板**"已为您开启301室的风扇当前运行正常。" **响应模板**"已为您开启301室的风扇当前运行正常。"
## 场景2智能消息路由 ## 场景2智能消息路由
**用户请求**"通知@管理员(id:001)会议室温度过高需要调节" **用户请求**"通知清水太郎会议室温度过高需要调节"
**执行逻辑** **执行逻辑**
```python ```python
# 步骤1人员信息查询
find_employee_by_name(name="清水太郎")
# 返回获取wowtalkid和位置信息
# 步骤2人员通知
wowtalk_send_message_to_member( wowtalk_send_message_to_member(
to_account="001", to_account="{清水太郎wowtalk_id}",
message_content="会议室温度过高需要调节" message_content="会议室温度过高需要调节"
) )
``` ```
**响应模板**"消息已发送至管理员,将会尽快处理温度问题。" **响应模板**"消息已发送至清水太郎,将会尽快处理温度问题。"
## 场景3多维度协同处理 ## 场景3多维度协同处理
**用户请求**"5楼传感器电量异常,通知维修团队并报告具体位置" **用户请求**"5楼风扇电量异常,通知清水太郎并报告具体位置"
**并行执行策略** **并行执行策略**
```python ```python
# 任务组1故障诊断 # 步骤1查找设备列表
dxcore_get_device_status(device_id="5楼传感器") find_iot_device(
device_type="dc_fan"
)
# 返回获取5楼风扇的sensor_id
# 步骤2故障诊断
dxcore_get_device_status(sensor_id="{风扇的sensor_id}")
# → 获取电量百分比、故障代码 # → 获取电量百分比、故障代码
# 任务组2人员通知 # 步骤4人员信息查询获取wowtalkid和位置信息
wowtalk_send_message_to_member( find_employee_by_name(name="清水太郎")
to_account="维修部组ID",
message_content="5楼传感器电量异常请及时处理"
)
# 任务组3定位服务 # 步骤5人员通知
find_iot_device_by_description(description="5楼传感器") wowtalk_send_message_to_member(
to_account="{清水太郎wowtalk_id}",
message_content="5楼风扇电量异常请及时处理"
)
# → 返回精确定位信息 # → 返回精确定位信息
``` ```
**响应模板**"已通知维修团队,传感器位于5楼东侧走廊当前电量15%。" **响应模板**"已通知清水太郎,风扇位于5楼东侧走廊当前电量15%。"
# 系统集成与技术规范 # 系统集成与技术规范
@ -189,9 +218,11 @@ find_iot_device_by_description(description="5楼传感器")
| 消息路由 | wowtalk_send_message_to_member | 实时消息推送 | P0 | | 消息路由 | wowtalk_send_message_to_member | 实时消息推送 | P0 |
| 设备控制 | dxcore_update_device_status | 设备状态变更 | P0 | | 设备控制 | dxcore_update_device_status | 设备状态变更 | P0 |
| 设备查询 | dxcore_get_device_status | 设备状态读取 | P1 | | 设备查询 | dxcore_get_device_status | 设备状态读取 | P1 |
| 位置服务 | eb_get_sensor_location | 空间定位查询 | P1 |
| 环境监测 | weather_get_by_location | 天气数据获取 | P1 | | 环境监测 | weather_get_by_location | 天气数据获取 | P1 |
| 人员检索 | find_employee_location | 员工信息查询 | P2 | | 网络搜索 | web_search | 互联网信息查询 | P1 |
| 设备检索 | find_iot_device_by_description | IoT设备搜索 | P2 | | 人员检索 | find_employee_by_name | 员工信息查询 | P2 |
| 设备检索 | find_iot_device | IoT设备搜索 | P2 |
# 异常处理与容错机制 # 异常处理与容错机制
@ -264,7 +295,6 @@ find_iot_device_by_description(description="5楼传感器")
## 系统配置参数 ## 系统配置参数
- **bot_id**: {bot_id} - **bot_id**: {bot_id}
# 执行保证机制 # 执行保证机制
1. **工具调用优先**:可执行操作必须通过工具实现 1. **工具调用优先**:可执行操作必须通过工具实现
2. **状态一致性**:所有操作结果与实际设备状态同步 2. **状态一致性**:所有操作结果与实际设备状态同步

View File

@ -51,6 +51,7 @@ class ChatRequest(BaseModel):
system_prompt: Optional[str] = None system_prompt: Optional[str] = None
mcp_settings: Optional[List[Dict]] = None mcp_settings: Optional[List[Dict]] = None
robot_type: Optional[str] = "agent" robot_type: Optional[str] = "agent"
user_identifier: Optional[str] = ""
class ChatRequestV2(BaseModel): class ChatRequestV2(BaseModel):
@ -59,6 +60,7 @@ class ChatRequestV2(BaseModel):
tool_response: Optional[bool] = False tool_response: Optional[bool] = False
bot_id: str bot_id: str
language: Optional[str] = "ja" language: Optional[str] = "ja"
user_identifier: Optional[str] = ""
class FileProcessRequest(BaseModel): class FileProcessRequest(BaseModel):

View File

@ -110,7 +110,8 @@ class FileLoadedAgentManager:
language: Optional[str] = None, language: Optional[str] = None,
system_prompt: Optional[str] = None, system_prompt: Optional[str] = None,
mcp_settings: Optional[List[Dict]] = None, mcp_settings: Optional[List[Dict]] = None,
robot_type: Optional[str] = "agent") -> Assistant: robot_type: Optional[str] = "agent",
user_identifier: Optional[str] = None) -> Assistant:
"""获取或创建文件预加载的助手实例 """获取或创建文件预加载的助手实例
Args: Args:
@ -131,7 +132,7 @@ class FileLoadedAgentManager:
import os import os
# 实现参数优先级逻辑:传入参数 > 项目配置 > 默认配置 # 实现参数优先级逻辑:传入参数 > 项目配置 > 默认配置
final_system_prompt = load_system_prompt(project_dir, language, system_prompt, robot_type, bot_id) final_system_prompt = load_system_prompt(project_dir, language, system_prompt, robot_type, bot_id, user_identifier)
final_mcp_settings = load_mcp_settings(project_dir, mcp_settings, bot_id, robot_type) final_mcp_settings = load_mcp_settings(project_dir, mcp_settings, bot_id, robot_type)
cache_key = self._get_cache_key(bot_id, model_name, api_key, model_server, cache_key = self._get_cache_key(bot_id, model_name, api_key, model_server,

View File

@ -4,10 +4,38 @@ System prompt and MCP settings loader utilities
""" """
import os import os
import json import json
from typing import List, Dict, Optional from typing import List, Dict, Optional, Any
def load_system_prompt(project_dir: str, language: str = None, system_prompt: str=None, robot_type: str = "agent", bot_id: str="") -> str: def safe_replace(text: str, placeholder: str, value: Any) -> str:
"""
安全的字符串替换函数确保 value 被转换为字符串
Args:
text: 原始文本
placeholder: 要替换的占位符 '{user_identifier}'
value: 用于替换的值可以是任意类型
Returns:
str: 替换后的文本
"""
if not isinstance(text, str):
text = str(text)
# 如果占位符为空,不进行替换
if not placeholder:
return text
# 将 value 转换为字符串,处理 None 等特殊情况
if value is None:
replacement = ""
else:
replacement = str(value)
return text.replace(placeholder, replacement)
def load_system_prompt(project_dir: str, language: str = None, system_prompt: str=None, robot_type: str = "agent", bot_id: str="", user_identifier: str = "") -> str:
# 获取语言显示名称 # 获取语言显示名称
language_display_map = { language_display_map = {
'zh': '中文', 'zh': '中文',
@ -19,7 +47,11 @@ def load_system_prompt(project_dir: str, language: str = None, system_prompt: st
# 如果存在{language} 占位符,那么就直接使用 system_prompt # 如果存在{language} 占位符,那么就直接使用 system_prompt
if system_prompt and "{language}" in system_prompt: if system_prompt and "{language}" in system_prompt:
return system_prompt.replace("{language}", language_display).replace('{bot_id}', bot_id) or "" prompt = system_prompt
prompt = safe_replace(prompt, "{language}", language_display)
prompt = safe_replace(prompt, '{bot_id}', bot_id)
prompt = safe_replace(prompt, '{user_identifier}', user_identifier)
return prompt or ""
elif robot_type == "agent" or robot_type == "catalog_agent": elif robot_type == "agent" or robot_type == "catalog_agent":
""" """
优先使用项目目录的system_prompt_catalog_agent.md没有才使用默认的system_prompt_default.md 优先使用项目目录的system_prompt_catalog_agent.md没有才使用默认的system_prompt_default.md
@ -51,11 +83,20 @@ def load_system_prompt(project_dir: str, language: str = None, system_prompt: st
if os.path.exists(readme_path): if os.path.exists(readme_path):
with open(readme_path, "r", encoding="utf-8") as f: with open(readme_path, "r", encoding="utf-8") as f:
readme = f.read().strip() readme = f.read().strip()
system_prompt_default = system_prompt_default.replace("{readme}", str(readme)) system_prompt_default = safe_replace(system_prompt_default, "{readme}", str(readme))
return system_prompt_default.replace("{language}", language_display).replace("{extra_prompt}", system_prompt or "").replace('{bot_id}', bot_id) or "" prompt = system_prompt_default
prompt = safe_replace(prompt, "{language}", language_display)
prompt = safe_replace(prompt, "{extra_prompt}", system_prompt or "")
prompt = safe_replace(prompt, '{bot_id}', bot_id)
prompt = safe_replace(prompt, '{user_identifier}', user_identifier)
return prompt or ""
else: else:
return system_prompt.replace("{language}", language_display).replace('{bot_id}', bot_id) or "" prompt = system_prompt
prompt = safe_replace(prompt, "{language}", language_display)
prompt = safe_replace(prompt, '{bot_id}', bot_id)
prompt = safe_replace(prompt, '{user_identifier}', user_identifier)
return prompt or ""
@ -72,15 +113,16 @@ def replace_mcp_placeholders(mcp_settings: List[Dict], dataset_dir: str, bot_id:
for key, value in obj.items(): for key, value in obj.items():
if key == 'args' and isinstance(value, list): if key == 'args' and isinstance(value, list):
# 特别处理 args 列表 # 特别处理 args 列表
obj[key] = [item.replace('{dataset_dir}', dataset_dir).replace('{bot_id}', bot_id) if isinstance(item, str) else item obj[key] = [safe_replace(safe_replace(item, '{dataset_dir}', dataset_dir), '{bot_id}', bot_id) if isinstance(item, str) else item
for item in value] for item in value]
elif isinstance(value, (dict, list)): elif isinstance(value, (dict, list)):
obj[key] = replace_placeholders_in_obj(value) obj[key] = replace_placeholders_in_obj(value)
elif isinstance(value, str): elif isinstance(value, str):
obj[key] = value.replace('{dataset_dir}', dataset_dir).replace('{bot_id}', bot_id) obj[key] = safe_replace(value, '{dataset_dir}', dataset_dir)
obj[key] = safe_replace(obj[key], '{bot_id}', bot_id)
elif isinstance(obj, list): elif isinstance(obj, list):
return [replace_placeholders_in_obj(item) if isinstance(item, (dict, list)) else return [replace_placeholders_in_obj(item) if isinstance(item, (dict, list)) else
item.replace('{dataset_dir}', dataset_dir).replace('{bot_id}', bot_id) if isinstance(item, str) else item safe_replace(safe_replace(item, '{dataset_dir}', dataset_dir), '{bot_id}', bot_id) if isinstance(item, str) else item
for item in obj] for item in obj]
return obj return obj