修复agent 中英文混乱问题,修复背景信息未使用问题

This commit is contained in:
zcr
2026-04-30 16:05:20 +08:00
parent 8fc93077fc
commit 60c669a10e
5 changed files with 194 additions and 110 deletions

View File

@@ -102,24 +102,23 @@ async def chat_stream(request: DeepAgentChatRequest):
响应流包含三种类型的事件:会话开始、节点消息、会话结束 响应流包含三种类型的事件:会话开始、节点消息、会话结束
""" """
if request.thread_id: # ===================== 简洁优化版 =====================
need_title = False # 1. 线程与标题标记
else: need_title = not request.thread_id
need_title = True
source_thread_id = request.thread_id source_thread_id = request.thread_id
checkpoint_id = request.checkpoint_id checkpoint_id = request.checkpoint_id
# 1. 確定目標 thread_id # 2. 目标线程 ID
is_branching = source_thread_id and checkpoint_id is_branching = all([source_thread_id, checkpoint_id])
target_thread_id = str(uuid.uuid4())[:8] if is_branching else (source_thread_id or str(uuid.uuid4())[:8]) target_thread_id = str(uuid.uuid4())[:8] if is_branching else (source_thread_id or str(uuid.uuid4())[:8])
# 构建主agent
workspace_dir = os.path.join(PROJECT_ROOT, f"agent_workspace/{target_thread_id}") # 3. Agent 初始化
logger.info(f"chat request data: {request} | target_thread_id : workspace_dir: {workspace_dir}") workspace_dir = os.path.join(PROJECT_ROOT, "agent_workspace", target_thread_id)
logger.info(f"chat request data: {request} | target_thread_id: {target_thread_id}, workspace_dir: {workspace_dir}")
main_agent = build_main_agent(workspace_dir, request.enable_thinking) main_agent = build_main_agent(workspace_dir, request.enable_thinking)
# 2. 配置參數 # 4. 配置
temp = request.config_params.temperature if request.config_params else 0.7 temp = request.config_params.temperature if request.config_params else 0.7
current_config = { current_config = {
"recursion_limit": 120, "recursion_limit": 120,
"configurable": { "configurable": {
@@ -129,37 +128,38 @@ async def chat_stream(request: DeepAgentChatRequest):
} }
} }
# 3. 初始化消息 + 系統提示 TODO 写入数据库 # 5. 初始化系统消息
initial_messages = [] initial_messages = []
if not source_thread_id or is_branching: if not source_thread_id or is_branching:
if request.config_params: cp = request.config_params
cp = request.config_params if cp:
system_prompt = ( config_items = [
f"Current furniture design background settings\n" ("type", cp.type),
f"- type: {cp.type}\n" ("space/region", cp.region),
f"- space/region: {cp.region}\n" ("style tendency", cp.style)
f"- style tendency: {cp.style}\n" ]
f"Please strictly follow the above settings in subsequent conversations。" valid_lines = [f"- {k}: {v}" for k, v in config_items if v]
) if valid_lines:
initial_messages.append(SystemMessage(content=system_prompt)) system_prompt = (
"Current furniture design background settings\n"
+ "\n".join(valid_lines) + "\n"
"Please strictly follow the above settings in subsequent conversations。"
)
initial_messages.append(SystemMessage(content=system_prompt))
# 4. 處理分支(從歷史 checkpoint 複製狀態) # 6. 分支处理
if is_branching: if is_branching:
source_config = { source_config = {"configurable": {"thread_id": source_thread_id, "checkpoint_id": checkpoint_id}}
"configurable": {
"thread_id": source_thread_id,
"checkpoint_id": checkpoint_id
}
}
last_checkpoint_id = await get_branch_checkpoint_id(main_agent, source_config) last_checkpoint_id = await get_branch_checkpoint_id(main_agent, source_config)
older_state = await main_agent.aget_state(source_config) older_state = await main_agent.aget_state(source_config)
combined_values = older_state.values.copy() combined_values = older_state.values.copy()
if initial_messages: if initial_messages:
combined_values["messages"] = list(combined_values.get("messages", [])) + initial_messages combined_values["messages"] = combined_values.get("messages", []) + initial_messages
await main_agent.aupdate_state(current_config, combined_values) await main_agent.aupdate_state(current_config, combined_values)
else: else:
last_checkpoint_id = await get_checkpoint_id(main_agent, current_config) last_checkpoint_id = await get_checkpoint_id(main_agent, current_config)
# 7. 事件流生成
async def event_generator() -> AsyncGenerator[str, None]: async def event_generator() -> AsyncGenerator[str, None]:
is_first = True is_first = True
content = [{"type": "text", "text": request.message}] content = [{"type": "text", "text": request.message}]
@@ -168,50 +168,52 @@ async def chat_stream(request: DeepAgentChatRequest):
"quote_image": "", "quote_image": "",
"current_image": "" "current_image": ""
} }
# 用户上传图片 input_image_content = ""
input_image_content = ''
# 处理上传图片
if request.input_image_paths: if request.input_image_paths:
input_image_content += "\n【附件上传图片路径】\n" input_image_content += "\n【附件上传图片路径】\n"
for i, path in enumerate(request.input_image_paths): for i, path in enumerate(request.input_image_paths):
input_image_content += f"- 上传图片{i}: {path}\n" input_image_content += f"- 上传图片{i}: {path}\n"
bucket, obj = path.split("/", 1)
bucket, object_name = path.split('/', 1) minio_client.copy_object("fida-public-bucket", path, CopySource(bucket, obj))
# image_url = get_presigned_url(oss_client=minio_client, bucket=bucket, object_name=object_name) image_url = f"https://www.minio-api.aida.com.hk/fida-public-bucket/{path}"
copy_result = minio_client.copy_object("fida-public-bucket", path, CopySource(bucket, object_name))
image_url = f"https://www.minio-api.aida.com.hk/{copy_result.bucket_name}/{copy_result.object_name}"
content.append({"type": "image_url", "image_url": {"url": image_url}}) content.append({"type": "image_url", "image_url": {"url": image_url}})
files["input_image"].append(path) files["input_image"].append(path)
# 用户引用图片 # 处理引用图片
if request.quote_image_path: if request.quote_image_path:
input_image_content += "\n【附件引用图片路径】\n" input_image_content += "\n【附件引用图片路径】\n"
input_image_content += f"- 引用图片: {request.quote_image_path}\n" input_image_content += f"- 引用图片: {request.quote_image_path}\n"
bucket, obj = request.quote_image_path.split("/", 1)
bucket, object_name = request.quote_image_path.split('/', 1) minio_client.copy_object("fida-public-bucket", request.quote_image_path, CopySource(bucket, obj))
# image_url = get_presigned_url(oss_client=minio_client, bucket=bucket, object_name=object_name) image_url = f"https://www.minio-api.aida.com.hk/fida-public-bucket/{request.quote_image_path}"
copy_result = minio_client.copy_object("fida-public-bucket", request.quote_image_path, CopySource(bucket, object_name))
image_url = f"https://www.minio-api.aida.com.hk/{copy_result.bucket_name}/{copy_result.object_name}"
content.append({"type": "image_url", "image_url": {"url": image_url}}) content.append({"type": "image_url", "image_url": {"url": image_url}})
files["quote_image"] = request.quote_image_path files["quote_image"] = request.quote_image_path
# 追加文本内容
if input_image_content: if input_image_content:
content[0]['text'] += input_image_content content[0]["text"] += input_image_content
final_messages = {
"messages": [ message_list = [{"role": "user", "content": content}]
{ final_messages = {"messages": message_list, "files": files}
"role": "user",
"content": content
},
],
"files": files
}
logger.info(final_messages) logger.info(final_messages)
config_content_type = f"- type: {request.config_params.type}\n" if request.config_params.type else ""
config_content_region = f"- region: {request.config_params.region}\n" if request.config_params.region else ""
config_content_style = f"- style: {request.config_params.style}\n" if request.config_params.style else ""
async for stream in main_agent.astream( async for stream in main_agent.astream(
final_messages, final_messages,
config=current_config, config=current_config,
stream_mode=["updates", "messages", "custom"], stream_mode=["updates", "messages", "custom"],
subgraphs=True, subgraphs=True,
context=Context(use_report=request.use_report, language=request.language), context=Context(use_report=request.use_report,
language=request.language,
type=config_content_type,
region=config_content_region,
style=config_content_style,
),
): ):
_, mode, chunks = stream _, mode, chunks = stream
if is_first: if is_first:

View File

@@ -3,9 +3,9 @@ from typing import Callable
from dataclasses import dataclass from dataclasses import dataclass
from deepagents import create_deep_agent from deepagents import create_deep_agent
from deepagents.backends import FilesystemBackend from deepagents.backends import FilesystemBackend, CompositeBackend, StateBackend
from langchain.agents.middleware import SummarizationMiddleware, ToolRetryMiddleware, wrap_model_call, ModelRequest, ModelResponse, wrap_tool_call, dynamic_prompt from langchain.agents.middleware import SummarizationMiddleware, ToolRetryMiddleware, wrap_model_call, ModelRequest, ModelResponse, wrap_tool_call, dynamic_prompt
from langchain_core.messages import ToolMessage from langchain_core.messages import ToolMessage, SystemMessage
from langgraph.checkpoint.mongodb import MongoDBSaver from langgraph.checkpoint.mongodb import MongoDBSaver
from langgraph.checkpoint.serde.jsonplus import JsonPlusSerializer from langgraph.checkpoint.serde.jsonplus import JsonPlusSerializer
from langgraph.constants import END from langgraph.constants import END
@@ -14,7 +14,8 @@ from langgraph.store.memory import InMemoryStore
from langgraph.types import Command from langgraph.types import Command
from pymongo import MongoClient from pymongo import MongoClient
from src.core.config import MONGO_URI from src.core.config import MONGO_URI, settings
# from src.server.deep_agent.agents.agent_backed import create_minio_backend
from src.server.deep_agent.agents.researcher import build_researcher_subagent from src.server.deep_agent.agents.researcher import build_researcher_subagent
from src.server.deep_agent.agents.user_profile import user_profile_subagent from src.server.deep_agent.agents.user_profile import user_profile_subagent
from src.server.deep_agent.init_llm import build_main_llm from src.server.deep_agent.init_llm import build_main_llm
@@ -32,10 +33,22 @@ checkpointer = MongoDBSaver(
) )
# minio_backend = create_minio_backend(
# endpoint=settings.MINIO_URL,
# access_key=settings.MINIO_ACCESS,
# secret_key=settings.MINIO_SECRET,
# bucket=settings.MINIO_DEEP_AGENT_BUCKET,
# secure=settings.MINIO_SECURE
# )
@dataclass @dataclass
class Context: class Context:
use_report: bool = False use_report: bool = False
language: str = "en" language: str = "en"
type: str = None
region: str = None
style: str = None
@wrap_tool_call @wrap_tool_call
@@ -105,7 +118,16 @@ def user_role_prompt(request: ModelRequest) -> str:
"To ensure everything works properly, I need you to confirm that the button has been successfully activated. You can refresh the page, click the button again, and then tell me the specific report content. I'll handle it immediately." "To ensure everything works properly, I need you to confirm that the button has been successfully activated. You can refresh the page, click the button again, and then tell me the specific report content. I'll handle it immediately."
- Only when the backend use_report is truly set to True can you call the research-subagent. - Only when the backend use_report is truly set to True can you call the research-subagent.
""" """
final_prompt = SYSTEM_PROMPT_MAPPING[f'SYSTEM_BASE_PROMPT_en'] + report_status + SYSTEM_PROMPT_MAPPING[f"SYSTEM_RULES_PROMPT_en"]
backend_prompt = (
f"Furniture design background settings selected by the current user:\n" +
f"{request.runtime.context.type}" +
f"{request.runtime.context.region}" +
f"{request.runtime.context.style}" +
f"CRITICAL: Do NOT ask the user for these settings again. Use the provided settings to generate your responses."
)
final_prompt = backend_prompt + SYSTEM_PROMPT_MAPPING[f'SYSTEM_BASE_PROMPT_en'] + report_status + SYSTEM_PROMPT_MAPPING[f"SYSTEM_RULES_PROMPT_en"]
logger.info( logger.info(
f"Dynamic prompt generated | use_report={use_report} | " f"Dynamic prompt generated | use_report={use_report} | "
@@ -136,6 +158,13 @@ def build_main_agent(workspace_dir, enable_thinking):
initial_delay=1.0, initial_delay=1.0,
), ),
] ]
# backend = CompositeBackend(
# default=StateBackend(),
# routes={
# "/": minio_backend, # ← 改成你实际的 MinIO 实例
# # "/memories/": memories_backend,
# }
# )
backend = FilesystemBackend( backend = FilesystemBackend(
root_dir=workspace_dir, root_dir=workspace_dir,
virtual_mode=True, # 重要:關掉虛擬模式 → 真的寫硬碟 virtual_mode=True, # 重要:關掉虛擬模式 → 真的寫硬碟

View File

@@ -12,7 +12,25 @@ def build_researcher_subagent(workspace_dir):
report_generator = create_report_generator_tool(workspace_dir) report_generator = create_report_generator_tool(workspace_dir)
research_subagent = { research_subagent = {
"name": "research_subagent", "name": "research_subagent",
"description": "通过网络搜索对家具设计开展深度研究并整合结论", "description": """
A specialized sub-agent for generating furniture design research reports.
Use this sub-agent when the user requests:
- Reports, research, analysis, or summaries
- Insights into furniture styles, design trends, materials, or case studies
- Structured outputs such as markdown reports
This sub-agent will:
- Retrieve user profile (style, room type, etc.)
- Generate research keywords
- Perform web search and content crawling
- Extract structured insights
- Produce a complete research report
Do NOT use this sub-agent for:
- User profile collection (handled by user_profile_subagent)
- Image generation or editing tasks
""",
"system_prompt": build_researcher_prompt(), "system_prompt": build_researcher_prompt(),
"tools": [ "tools": [
query_report_profile, query_report_profile,

View File

@@ -3,7 +3,24 @@ from src.server.deep_agent.tools.user_persona_tool import query_report_profile,
user_profile_subagent = { user_profile_subagent = {
"name": "user_profile_subagent", "name": "user_profile_subagent",
"description": "收集用户报告画像并存储到MongoDB", "description": """
A sub-agent responsible for collecting and maintaining user profile information.
Use this sub-agent when the user:
- Provides or modifies design preferences (e.g., style, room type, budget)
- Shares personal requirements related to furniture design or reports
- Responds to questions asking for missing profile information
This sub-agent will:
- Extract structured profile information from the conversation
- Update existing profile only when the user explicitly provides new or modified data
- Check whether the profile is complete and guide the user if information is missing
Do NOT use this sub-agent for:
- Generating research reports
- Performing analysis or research tasks
- Image generation or editing
""",
"system_prompt": build_user_persona_prompt(), "system_prompt": build_user_persona_prompt(),
"tools": [ "tools": [
query_report_profile, query_report_profile,

View File

@@ -306,71 +306,89 @@ def build_painter_prompt():
def build_researcher_prompt(): def build_researcher_prompt():
prompt = """ prompt = """
你是一名专业的家具设计研究员。你的任务是: You are a professional furniture design researcher.
【0】获取用户画像 Your primary goal:
- 首先调用 get_user_profile 工具,获取当前用户画像信息(如风格、房间类型等)。 - Generate a high-quality, structured furniture design research report based on the user's request and user profile.
- 根据用户画像,生成五个与用户需求和偏好高度相关的研究词条。 - The report should be clear, insightful, and written in well-structured Markdown format.
- It should include design trends, materials, color directions, representative cases, and relevant references.
【1】关键词拆解 You are allowed to:
1. 将研究主题结合用户画像拆解为可搜索的查询关键词 - Retrieve user profile information (e.g., style, room type, preferences)
2. 将关键词组合成五个待搜索的词条 - Generate research keywords
- Search for relevant topics and sources
- Crawl and read web content
- Extract structured insights
- Generate the final report
【2】搜索与爬取 Tool usage guidelines:
3. 使用 topic_research 工具搜索这五个词条获取相关、权威的网址 - If necessary, first retrieve the user profile to better understand preferences.
4. 使用 crawl4ai_batch 批量爬取网址(仅可调用一次,禁止重复调用) - Use meaningful and relevant keywords for research.
- When crawling web content, try to process multiple sources efficiently (avoid repeated calls).
- Focus on extracting key insights such as trends, materials, colors, and case studies.
- Use the report_generator tool to produce the final report.
【3】结构化处理与报告 Important rules:
5. 使用 structured_retrieval 对爬取内容进行结构化提取(重点:设计趋势、材质创新、颜色应用、代表案例、品牌参考) - Your objective is to complete a high-quality report, not to strictly follow a fixed sequence of steps.
6. 使用 report_generator 基于提取内容生成完整 Markdown 报告 - You may adapt your approach depending on the situation.
- Avoid calling the same tool repeatedly (especially crawl tools).
- If some data is missing, proceed with available information and clearly mention any limitations.
- Once the report is generated, consider the task complete and stop further actions.
【严格工具调用规则】: Language rules:
- 调用顺序必须严格get_user_profile → topic_research → crawl4ai_batch仅一次 → structured_retrieval → report_generator。 - Always respond in the same language as the user.
- 不得跳回前面步骤或重复任何工具。 - Do not mix languages in your response.
- 如果爬取结果为空或极少,直接说明: - Keep the output consistent and natural.
“由于部分来源暂时不可访问,本报告基于有限可用信息生成,可能不够全面。如需更完整资料,请提供具体网址或调整需求。”
- 一旦生成 report_generator 的输出,就视为任务完成,直接结束,不要再思考或调用其他工具。
- crawl4ai_batch 最多只能调用一次,即使部分网址失败,也禁止再次调用 crawl4ai_batch 或 topic_research。
重要语言规则(必须遵守):
- 请使用和用户原始查询相同的语言进行思考和输出。
- 用户用中文提问,你的所有输出内容都应该用中文。
- 用户用英文提问,你的所有输出内容都应该用英文。
- 保持自然流畅,不要刻意混杂语言。
- 你的输出将由 Supervisor 进行最终合成,请提供清晰、结构化的内容。
现在开始严格执行以上规则。
""" """
return prompt return prompt
def build_user_persona_prompt(): def build_user_persona_prompt():
prompt = """ prompt = """
你是用户画像收集助手。 You are a user profile collection assistant.
你的任务是从用户对话中理解并提取报告画像信息,包括但不限于: Your goal:
- style装修风格 - Extract and maintain structured user profile information from the conversation.
- room_type房间类型 - The profile is used for generating furniture design reports.
工作流程: Profile fields may include:
- style (design style or aesthetic preference)
- room_type (type of room or space)
- budget (optional)
- other relevant design preferences
1. 先调用 query_report_profile 查询当前画像 What you should do:
2. 从用户输入中理解是否包含新的画像信息 - Understand the user's input and identify any profile-related information.
3. 如果有新的信息,合并旧画像并调用 update_report_profile 更新 - If new information is found, update the profile accordingly.
4. 调用 check_profile_complete 判断是否完整 - If no new information is provided, keep the existing profile unchanged.
5. 如果缺少字段,引导用户补充 - Ensure previously stored information is preserved unless the user explicitly modifies it.
6. 如果完整,回复:
"画像收集完成,即将为你生成报告!" Tool usage guidelines:
- Use query_report_profile when you need to know the current profile.
- Use update_report_profile only when new or updated information is detected.
- Use check_profile_complete to determine if required fields are sufficient for report generation.
注意: Behavior rules:
- 不要编造信息 - Do NOT generate reports.
- 不要覆盖已有字段,除非用户明确修改 - Do NOT guess or fabricate missing information.
- 只负责画像收集,不生成报告 - Only extract information that is clearly stated or strongly implied by the user.
重要语言规则(必须遵守): - Be concise and structured in your output.
- 请使用和用户原始查询相同的语言进行思考和输出。
- 用户用中文提问,你的所有输出内容都应该用中文。 When profile is incomplete:
- 用户用英文提问,你的所有输出内容都应该用英文。 - Ask the user for the missing information in a natural way.
- 保持自然流畅,不要刻意混杂语言。
- 你的输出将由 Supervisor 进行最终合成,请提供清晰、结构化的内容。 When profile is complete:
- Respond with a clear signal that profile collection is done, for example:
"Profile is complete. Ready for report generation."
Language rules:
- Always respond in the same language as the user.
- Do not mix languages.
- Keep the output consistent and natural.
Strict Language Enforcement:
- You MUST use only one language in the entire response.
- The language must match the user's input.
- Mixing multiple languages is strictly prohibited.
""" """
return prompt return prompt