代码修改
This commit is contained in:
1 parent
45eb46c3f3
commit
8e7fc54548
4 files changed
+108
-12
No files matched your search
@@ -368,8 +368,8 @@ export const HighFidelityReport: React.FC<HighFidelityReportProps> = ({
|
||||
)}
|
||||
</section>
|
||||
|
||||
{/* Section 3: Generated files per seat */}
|
||||
<section className="bg-white border border-slate-200 rounded-2xl p-5 shadow-[0_2px_8px_rgba(0,0,0,0.02)]">
|
||||
{/* Section 3: Generated files per seat —— 暂时隐藏(无真实产出文件链路) */}
|
||||
<section className="hidden bg-white border border-slate-200 rounded-2xl p-5 shadow-[0_2px_8px_rgba(0,0,0,0.02)]">
|
||||
<h4 className="text-xs font-black text-slate-900 uppercase tracking-widest mb-3 flex items-center gap-2">
|
||||
<Download className="w-4 h-4 text-primary" />
|
||||
生成的报告文件
|
||||
|
||||
@@ -3794,8 +3794,16 @@ export default function RoundtablePlanningPage() {
|
||||
|
||||
{/* ========================================================
|
||||
RIGHT COLUMN (Current Intent Summary, Timeline/Logs)
|
||||
第三步时所有 panel 都已隐藏 → 整个 right-sidebar 也不渲染,
|
||||
让中间方案展示区自适应铺满空间。
|
||||
======================================================== */}
|
||||
<section id="right-sidebar" className="w-[300px] shrink-0 flex flex-col gap-4 overflow-y-auto pl-1">
|
||||
<section
|
||||
id="right-sidebar"
|
||||
className={cn(
|
||||
"w-[300px] shrink-0 flex flex-col gap-4 overflow-y-auto pl-1",
|
||||
currentStep === 3 && "hidden",
|
||||
)}
|
||||
>
|
||||
|
||||
{currentStep === 3 ? (
|
||||
<>
|
||||
|
||||
@@ -305,8 +305,10 @@ RECOMMENDER_AGENT_SOUL = """# 智能体推荐专员
|
||||
- `reason` 必须**具体**,不要"很合适"、"很强"这类空话
|
||||
|
||||
## 输出风格
|
||||
- 思路部分简短:用一句话概括你为什么挑这些(例如"按情报→设计→风险→执行的研讨节奏挑选了 4 位")
|
||||
- 推荐理由要**具体到任务**,例如"负责检索同类合规改造案例,直接对应本次等保 2.0 的对标需求"
|
||||
- 思路部分简短:用一句话概括你**针对本次任务**为什么挑这些智能体,**必须呼应任务目标本身**;
|
||||
不要套用任何固定职能链(如"情报→设计→风险→执行"),那只在合规改造等少数任务里贴合,
|
||||
在广告/产品/营销/科研等任务里完全不适用。
|
||||
- 推荐理由要**具体到本次任务**,例如"负责设计落地页文案,直接对应本次品牌曝光目标"
|
||||
- 适度用 emoji:📋 表示开始汇总
|
||||
|
||||
## 边界
|
||||
@@ -317,7 +319,11 @@ RECOMMENDER_AGENT_SOUL = """# 智能体推荐专员
|
||||
|
||||
## 示例
|
||||
|
||||
**用户输入**(由后端拼装):
|
||||
> 以下两个示例覆盖**不同任务类型**,目的是教你「思路要呼应任务本身」,**不要**把任何一个示例的具体职能链当模板。
|
||||
|
||||
### 示例 A:合规改造任务(职能型候选池)
|
||||
|
||||
**用户输入**:
|
||||
```
|
||||
任务目标:对生产系统进行安全改造,满足等保 2.0 合规要求
|
||||
约束:预算 ≤ 500 万元;工期 ≤ 6 个月;必须满足等保 2.0
|
||||
@@ -334,7 +340,7 @@ RECOMMENDER_AGENT_SOUL = """# 智能体推荐专员
|
||||
```
|
||||
|
||||
**你的输出**:
|
||||
按"情报→设计→风险→执行"的研讨节奏,挑选 5 位最贴合本次等保 2.0 改造任务的智能体,并加入成本测算覆盖预算硬约束。📋
|
||||
本次任务核心是等保 2.0 合规改造,需要先看同类案例、再产出方案、最后审查合规风险并落地排期,因此挑选 5 位智能体覆盖这条链路并加入成本测算保障预算硬约束。📋
|
||||
|
||||
[RECOMMEND_READY]
|
||||
```json
|
||||
@@ -348,6 +354,40 @@ RECOMMENDER_AGENT_SOUL = """# 智能体推荐专员
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 示例 B:广告/营销任务(用户自建领域候选池)
|
||||
|
||||
**用户输入**:
|
||||
```
|
||||
任务目标:为某新能源汽车品牌设计一套季度品牌曝光广告投放方案
|
||||
约束:预算 ≤ 200 万元;周期 1 个月内上线
|
||||
关键假设:目标人群覆盖一二线城市;以社媒为主、信息流为辅
|
||||
|
||||
候选智能体池:
|
||||
- ad-design (广告设计智能体):产出广告创意脚本、海报设计稿、文案 hook
|
||||
- channel-planner (渠道规划师):按预算和受众设计跨平台投放组合
|
||||
- audience-insight (受众洞察):画像、关键词、情绪点研究
|
||||
- creative-copy (创意文案):产出落地页与社媒文案变体
|
||||
- roundtable-risk-review (风险审查):合规红线检查与收敛建议
|
||||
```
|
||||
|
||||
**你的输出**:
|
||||
本次是品牌曝光广告投放,核心要先做受众洞察、再产出创意与文案、最后规划渠道投放组合,因此挑选 4 位直接相关的智能体;合规审查在广告场景里也保留一个位置避免文案踩雷。📋
|
||||
|
||||
[RECOMMEND_READY]
|
||||
```json
|
||||
{
|
||||
"picks": [
|
||||
{"agent_id": "audience-insight", "reason": "新能源汽车一二线人群画像与情绪点研究,决定后续创意切入角度"},
|
||||
{"agent_id": "ad-design", "reason": "产出社媒主推海报与脚本,直接对应品牌曝光交付物"},
|
||||
{"agent_id": "creative-copy", "reason": "为社媒/信息流产出多版本文案 hook,配合 A/B 测试"},
|
||||
{"agent_id": "channel-planner", "reason": "200 万预算内规划社媒+信息流的组合与节奏"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
> **关键观察**:示例 A 选了 `roundtable-intelligence / solution-design / execution-plan` 等"职能型"智能体,示例 B **完全没用任何 `roundtable-*` 内置**,而是从用户自建的领域智能体里挑。
|
||||
> 这是因为:**候选池里的智能体能不能匹配任务,只看 description 和任务的实际语义,不看 id 是不是 `roundtable-` 开头**。如果候选池里有"广告设计智能体"且任务是广告设计,**你必须挑它**,而不是套用示例 A 的职能链。
|
||||
"""
|
||||
|
||||
|
||||
|
||||
@@ -112,7 +112,7 @@ def _build_prompt(intent: dict[str, Any], candidates: list[dict[str, Any]]) -> s
|
||||
|
||||
def _validate_picks(
|
||||
raw_picks: Any,
|
||||
candidate_ids: set[str],
|
||||
candidates: list[dict[str, Any]],
|
||||
) -> list[dict[str, str]]:
|
||||
"""Filter the model's picks against the candidate pool and clamp to 2-6.
|
||||
|
||||
@@ -121,18 +121,50 @@ def _validate_picks(
|
||||
model over-picked. Returns ``[]`` if validation leaves us with fewer than
|
||||
``_MIN_PICKS`` valid picks — the caller treats that as "fall back to
|
||||
defaults".
|
||||
|
||||
容错策略(2026-XX 修复 "用户自建 agent 被推荐丢失" 的问题):
|
||||
模型有时把候选池里的 ``name`` 当成 ``agent_id`` 回填(尤其当 id 是 UUID
|
||||
形式时, LLM 倾向用人类可读名),或者顺便也回了 ``name`` 字段。本函数:
|
||||
1. 先按 agent_id 命中候选池(严格匹配,原行为);
|
||||
2. 否则按 ``agent_id`` 的字面值反查 name → 找回真实 id;
|
||||
3. 否则按 ``name`` 字段反查 name → 找回真实 id。
|
||||
只要能定位到候选池里**唯一**一个 agent,就算 pick 成功。
|
||||
"""
|
||||
if not isinstance(raw_picks, list):
|
||||
return []
|
||||
|
||||
id_set: set[str] = set()
|
||||
name_to_id: dict[str, str] = {}
|
||||
for c in candidates:
|
||||
aid = str(c.get("agent_id") or "").strip()
|
||||
if not aid:
|
||||
continue
|
||||
id_set.add(aid)
|
||||
nm = str(c.get("name") or "").strip()
|
||||
if nm:
|
||||
# 大小写不敏感反查 —— 模型可能用空格 / 不同大小写 echo 中文名。
|
||||
name_to_id.setdefault(nm.lower(), aid)
|
||||
|
||||
picked: list[dict[str, str]] = []
|
||||
seen: set[str] = set()
|
||||
for item in raw_picks:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
aid = str(item.get("agent_id") or "").strip()
|
||||
raw_id = str(item.get("agent_id") or "").strip()
|
||||
raw_name = str(item.get("name") or "").strip()
|
||||
reason = str(item.get("reason") or "").strip()
|
||||
if not aid or aid not in candidate_ids or aid in seen:
|
||||
|
||||
aid = ""
|
||||
if raw_id and raw_id in id_set:
|
||||
aid = raw_id
|
||||
elif raw_id and raw_id.lower() in name_to_id:
|
||||
# 模型把"广告设计智能体"这种 name 当成 agent_id 回填了
|
||||
aid = name_to_id[raw_id.lower()]
|
||||
elif raw_name and raw_name.lower() in name_to_id:
|
||||
# 模型 agent_id 字段乱了,但 name 字段对得上
|
||||
aid = name_to_id[raw_name.lower()]
|
||||
|
||||
if not aid or aid in seen:
|
||||
continue
|
||||
picked.append({"agent_id": aid, "reason": reason})
|
||||
seen.add(aid)
|
||||
@@ -245,14 +277,30 @@ async def stream_recommend(request: Request, payload: dict = Body(...)) -> Strea
|
||||
# 只扫描「最后一条非空 AIMessage」:推荐 agent 单轮输出,无多轮 READY 问题。
|
||||
picks: list[dict[str, str]] = []
|
||||
match = _RECOMMEND_READY_PATTERN.search(last_ai_content)
|
||||
# 诊断:打印模型输出末尾(含 RECOMMEND_READY 块)。grep "recommend stream debug"
|
||||
# 即可看到 LLM 实际推荐了什么、_validate_picks 解析前/后的差异。
|
||||
logger.info(
|
||||
"recommend stream debug | last_ai_tail=%r | match=%s | candidate_count=%d",
|
||||
last_ai_content[-600:] if last_ai_content else "",
|
||||
bool(match),
|
||||
len(candidates),
|
||||
)
|
||||
if match:
|
||||
try:
|
||||
parsed = json.loads(match.group(1))
|
||||
except json.JSONDecodeError as exc:
|
||||
logger.warning("recommend picks JSON parse failed: %s", exc)
|
||||
logger.warning("recommend picks JSON parse failed: %s | raw=%r", exc, match.group(1)[:300])
|
||||
parsed = {}
|
||||
if isinstance(parsed, dict):
|
||||
picks = _validate_picks(parsed.get("picks"), candidate_ids)
|
||||
raw_picks = parsed.get("picks")
|
||||
# 传完整 candidates 列表(含 name) —— _validate_picks 容错按 name 反查 id,
|
||||
# 修复 LLM 把"广告设计智能体"这种 name 当 agent_id 回填导致全丢的问题。
|
||||
picks = _validate_picks(raw_picks, candidates)
|
||||
logger.info(
|
||||
"recommend stream debug | raw_picks=%r | validated_picks=%r",
|
||||
raw_picks,
|
||||
picks,
|
||||
)
|
||||
|
||||
# asking ≠ 模型在追问用户,而是「网关未能产出合法 picks」,由前端 fallback。
|
||||
status_value = "done" if picks else "asking"
|
||||
|
||||
Reference in new issue
Block a user