修复Supervisor无法触发的情况

This commit is contained in:
zcr
2026-03-06 16:15:25 +08:00
parent 5106875618
commit d66a870207
8 changed files with 113 additions and 200 deletions

View File

@@ -7,10 +7,9 @@ from langchain_core.runnables import RunnableConfig
from langchain_qwq import ChatQwen
from src.core.config import settings
from src.server.agent.prompt import SYSTEM_PROMPT
from src.server.agent.prompt import SYSTEM_PROMPT, visualizer_prompt, designer_prompt
from src.server.agent.state import AgentState
from src.server.agent.tools.generate_furniture_sketch import generate_furniture
from src.server.agent.config_loader import get_agent_prompt
from src.server.agent.tools.crawl_tool import crawl4ai_batch
from src.server.agent.tools.report_generator_tool import report_generator
from src.server.agent.tools.research_tool import topic_research
@@ -46,7 +45,7 @@ research_agent = create_deep_agent(
# 辅助函数:根据配置动态获取 LLM
def get_model(config: RunnableConfig):
def get_model(config: RunnableConfig, streaming=False):
temp = config["configurable"].get("llm_temperature", 0.5)
return ChatQwen(
enable_thinking=False,
@@ -55,7 +54,9 @@ def get_model(config: RunnableConfig):
timeout=None,
max_retries=2,
temperature=temp,
api_key=settings.QWEN_API_KEY)
api_key=settings.QWEN_API_KEY,
streaming=streaming
)
# --- 1. Designer Agent (设计顾问) ---
@@ -64,11 +65,8 @@ async def designer_node(state: AgentState, config: RunnableConfig):
model = get_model(config) # 获取带动态温度的模型
messages = state["messages"]
system_text = get_agent_prompt("designer")
system_prompt = SystemMessage(content=system_text)
system_prompt = SystemMessage(content=designer_prompt)
should_suggest = len(state["messages"]) % 5 == 0
# 改为异步调用 ainvoke
response = await model.ainvoke([system_prompt] + messages)
return {"messages": [response], "require_suggestion": should_suggest}
@@ -100,11 +98,7 @@ async def researcher_node(
"next": "Supervisor"
}
return
full_content = ""
current_step = "正在启动深度报告生成..."
# 初始提示
yield {
"messages": [AIMessage(
content="正在启动深度报告生成...",
@@ -115,7 +109,6 @@ async def researcher_node(
}
)]
}
async for chunk in research_agent.astream(
{"messages": messages[-12:]},
config=config
@@ -128,68 +121,17 @@ async def researcher_node(
# "status": "thinking"
}
else:
# 其他類型的 chunk
yield chunk
#
# async def researcher_node(
# state: AgentState,
# config: RunnableConfig
# ) -> Dict[str, Any]:
# """
# 薄節點:只判斷是否要跑深度報告,並準備初始訊息
# 真正的 report 生成與 streaming 交給外層或子圖處理
# """
# use_report = config["configurable"].get("use_report", False)
#
# if not use_report:
# return {
# "messages": [AIMessage(
# content="深度報告功能未啟用,請通過前端按鈕觸發。",
# name="Researcher"
# )],
# "next": "Supervisor"
# }
#
# # 發送初始訊息,讓前端馬上看到「正在啟動」
# # initial_msg = AIMessage(
# # content="正在啟動深度報告生成...",
# # name="Researcher",
# # additional_kwargs={
# # "current_step": "正在啟動深度報告生成...",
# # "streaming": True
# # }
# # )
#
# # 方式一:最簡單,直接把 research_agent 當作下一個要執行的東西
# # (假設 research_agent 已 compile 好,且支援 astream
# # return {
# # "messages": state["messages"] + [initial_msg],
# # # 可以選擇加一個自訂 key 標記
# # "report_in_progress": True,
# # # next 留空或回 Supervisor由 conditional edges 決定
# # }
#
# # 方式二:如果你想更明確(推薦用 Send未來好擴充
# return Send(
# "research_sub_agent", # 你要在 graph.add_node("research_sub_agent", research_agent)
# {
# "messages": state["messages"][-12:],
# "configurable": config["configurable"]
# }
# )
# --- 3. Visualizer Agent (视觉专家) ---
async def visualizer_node(state: AgentState, config: RunnableConfig):
"""负责将自然语言转化为绘图 Prompt 并调用绘图工具"""
model = get_model(config)
model = get_model(config, streaming=False)
tools = [generate_furniture]
llm_with_tools = model.bind_tools(tools)
messages = state["messages"]
system_text = get_agent_prompt("visualizer")
system_prompt = SystemMessage(content=system_text)
system_prompt = SystemMessage(content=visualizer_prompt)
response = await llm_with_tools.ainvoke([system_prompt] + messages)
if response.tool_calls:

View File

@@ -1,32 +0,0 @@
"""加载项目根目录下的 config.yaml 并提供 agent prompt 访问接口。"""
import os
from functools import lru_cache
from typing import Any, Dict, Optional
import yaml
def _project_root() -> str:
return os.path.abspath(os.path.join(os.path.dirname(__file__), "../..", ".."))
@lru_cache(maxsize=1)
def load_config() -> Dict[str, Any]:
path = os.path.join(_project_root(), "config.yaml")
if not os.path.exists(path):
return {}
with open(path, "r", encoding="utf-8") as f:
return yaml.safe_load(f) or {}
def get_agent_prompt(agent_name: str) -> Optional[str]:
cfg = load_config()
agents = cfg.get("agents", {})
entry = agents.get(agent_name, {})
prompt = entry.get("prompt_template") or entry.get("prompt")
return prompt
def get_model_config() -> Dict[str, Any]:
cfg = load_config()
return cfg.get("model", {})

View File

@@ -1,3 +1,4 @@
import random
from typing import Literal
from langchain_core.messages import AIMessage
from langchain_core.runnables import RunnableConfig
@@ -29,49 +30,47 @@ llm_supervisor = ChatQwen(
def supervisor_node(state: AgentState, config: RunnableConfig):
use_report = config["configurable"].get("use_report", False)
configurable = config["configurable"]
use_report = configurable.get("use_report", False)
suggest_frequency = configurable.get("require_suggestion", 0.6) # 0.0~1.0
messages = state["messages"]
if not messages:
return {"next": "Suggester"}
last_message = messages[-1]
# --- 拦截逻辑修改 ---
# 如果专家已经回复完了AIMessage 且无工具调用),则交给 Suggester 生成按钮
if isinstance(last_message, AIMessage) and not last_message.tool_calls:
should_go_to_suggester = state.get("require_suggestion", False)
# 如果符合建议条件
if should_go_to_suggester:
return {"next": "Suggester"}
else:
return {"next": "FINISH"}
# ── system prompt 保持不变 ──
system_prompt = f"""你是家具设计主管,负责分配任务。
当前设定:
- 是否需要市场研究报告:{'' if use_report else ''}
严格遵守以下规则:
- 如果 **不需要** 市场研究报告use_report = False**绝对不能** 选择 Researcher
- 只有在 **明确需要** 市场报告、竞争分析、材质趋势、价格区间等外部资讯时,才选择 Researcher且 **必须** use_report = True
- 常见分配:
- 纯设计、风格、尺寸、材质建议 → Designer
- 需要生成图片、渲染 → Visualizer
- 需要产生建议按钮 → Suggester
- 需要市场报告 → Researcher但只有 use_report=True 时才允许)
- 对话已完整、无需继续 → FINISH
用户最后说了什么?请根据实际需求决定下一步。
"""
当前设定:
- 是否需要市场研究报告:{'' if use_report else ''}
严格遵守以下规则:
- 如果 **不需要** 市场研究报告use_report = False**绝对不能** 选择 Researcher
- 只有在 **明确需要** 市场报告、竞争分析、材质趋势、价格区间等外部资讯时,才选择 Researcher且 **必须** use_report = True
- 常见分配:
- 纯设计、风格、尺寸、材质建议 → Designer
- 需要生成图片、渲染 → Visualizer
- 需要产生建议按钮 → Suggester
- 需要市场报告 → Researcher但只有 use_report=True 时才允许)
- 对话已完整、无需继续 → FINISH
用户最后说了什么?请根据实际需求决定下一步。
"""
chain = llm_supervisor.with_structured_output(RouteResponse)
decision = chain.invoke([{"role": "system", "content": system_prompt}] + messages)
next_node = decision.next
next_node = decision.next # 防空默认 FINISH
# 安全阀:禁止非法选择 Researcher
if next_node == "Researcher" and not use_report:
print("警告LLM 違規選擇 Researcher制改為 FINISH 或 Suggester")
print("警告LLM 违规选择了 Researcher制改 Suggester 或 FINISH")
next_node = "Suggester" if state.get("require_suggestion", False) else "FINISH"
# 核心改动:只有 LLM 决定 FINISH 时,才掷骰子看是否插入 Suggester
if next_node == "FINISH":
# 满足概率条件 → 插入 Suggester
if suggest_frequency > 0 and random.random() < suggest_frequency:
next_node = "Suggester"
return {"next": next_node}
@@ -82,7 +81,7 @@ workflow.add_node("Supervisor", supervisor_node)
workflow.add_node("Designer", designer_node)
workflow.add_node("Researcher", researcher_node)
workflow.add_node("Visualizer", visualizer_node)
workflow.add_node("Suggester", suggester_node) # 新增节点
workflow.add_node("Suggester", suggester_node)
workflow.add_edge(START, "Supervisor")
# 修改条件边映射

View File

@@ -64,3 +64,56 @@ After structured_retrieval summary received:
Current status: Phase 0
"""
designer_prompt = """
你是家具设计团队的主管(Supervisor)。
请根据用户的意图,选择最合适的专家:
- Designer: 设计建议、参数细化、闲聊、问候。
- Visualizer: 绘图、看草图。
- Researcher: 市场报告、趋势。
只需输出专家名称。
"""
visualizer_prompt = """
你是专业的家具工业设计视觉专家,专长将文字描述转化为**清晰、结构明确的家具线稿line drawing / technical sketch**。
你的任务流程(必须严格遵守):
1. 先基于全部对话上下文,在内心生成一个详细的英文 Stable Diffusion Prompt不要提前输出
2. 最后只调用 generate_furniture 工具,把刚才生成的 prompt 作为参数传入。
3. 工具调用完成后,自然结束,不要额外说话或解释。
Prompt 生成要求(仅供内部参考,必须全部做到):
- 全程使用英文
- 核心目标:生成专业的**单品家具线稿**technical line drawing / clean sketch / industrial design sketch
- 必须包含以下关键词(尽量靠前放置):
line drawing, technical sketch, clean line art, black and white, wireframe sketch, contour lines only, no shading, no color, no rendering, no texture, no shadow, no gradient
- 必须包含:
highly detailed linework, sharp precise lines, professional industrial design sketch, clear construction lines, accurate proportions, furniture design blueprint style
- 视角描述isometric view 或 three-quarter view 或 front + side + top combination view根据家具特性选择最适合展示结构的视角
- 背景与整体风格:
pure white background, studio style, no people, no text, no logo, no dimension lines unless requested, no color fills
- 负面提示强制包含negative prompt 部分要写强):
blurry, low quality, sketchy messy lines, thick marker, pencil shading, colored pencil, watercolor, rendering, photorealistic, 3d render, realistic lighting, shadow, gradient, depth of field, bokeh, cartoon, anime, deformed, extra limbs, bad anatomy
- 品质修饰词(放在结尾):
masterpiece, best quality, ultra detailed linework, professional CAD sketch style, clean and precise
现在开始执行:
- 先在内心完整构建符合以上全部要求的英文 prompt
- 然后在回复中先单独输出这个完整的 prompt让用户能检查
- 最后立即调用工具generate_furniture参数 prompt = 你刚才输出的完整内容
- 不要做其他任何说明或聊天
"""
designer_prompt = """
你是一位资深的家具设计师,经验丰富、审美一流、沟通温暖且高效。
你的核心目标:快速理解用户想法,并用最合适的方式推进设计。
你可以:
1. 用户描述模糊时,可以自然地询问或给出建议,但**绝不强迫补充**尺寸、材质、人体工学等细节,除非用户自己关心或需要明确这些参数。
2. 如果用户提到想看图、想出效果图、想画草图、想渲染等,**直接同意并推动**
- 用一句话确认或赞美用户的想法
- 主动说“我这就帮你把当前设计转给视觉专家生成效果图/草图”
- 然后让 Visualizer 节点去处理(不需要你先写一大段细节描述)
3. 回复时像和懂设计的客户聊天一样:专业、亲切、有创意,偶尔带点热情或幽默,但始终围绕家具设计。
永远不要用“必须”“请先描述清楚”“按照流程”等强硬的流程化语言。
"""