代码修改
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>
|
||||||
|
|
||||||
{/* Section 3: Generated files per seat */}
|
{/* 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 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">
|
<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" />
|
<Download className="w-4 h-4 text-primary" />
|
||||||
生成的报告文件
|
生成的报告文件
|
||||||
|
|||||||
@@ -3794,8 +3794,16 @@ export default function RoundtablePlanningPage() {
|
|||||||
|
|
||||||
{/* ========================================================
|
{/* ========================================================
|
||||||
RIGHT COLUMN (Current Intent Summary, Timeline/Logs)
|
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 ? (
|
{currentStep === 3 ? (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -305,8 +305,10 @@ RECOMMENDER_AGENT_SOUL = """# 智能体推荐专员
|
|||||||
- `reason` 必须**具体**,不要"很合适"、"很强"这类空话
|
- `reason` 必须**具体**,不要"很合适"、"很强"这类空话
|
||||||
|
|
||||||
## 输出风格
|
## 输出风格
|
||||||
- 思路部分简短:用一句话概括你为什么挑这些(例如"按情报→设计→风险→执行的研讨节奏挑选了 4 位")
|
- 思路部分简短:用一句话概括你**针对本次任务**为什么挑这些智能体,**必须呼应任务目标本身**;
|
||||||
- 推荐理由要**具体到任务**,例如"负责检索同类合规改造案例,直接对应本次等保 2.0 的对标需求"
|
不要套用任何固定职能链(如"情报→设计→风险→执行"),那只在合规改造等少数任务里贴合,
|
||||||
|
在广告/产品/营销/科研等任务里完全不适用。
|
||||||
|
- 推荐理由要**具体到本次任务**,例如"负责设计落地页文案,直接对应本次品牌曝光目标"
|
||||||
- 适度用 emoji:📋 表示开始汇总
|
- 适度用 emoji:📋 表示开始汇总
|
||||||
|
|
||||||
## 边界
|
## 边界
|
||||||
@@ -317,7 +319,11 @@ RECOMMENDER_AGENT_SOUL = """# 智能体推荐专员
|
|||||||
|
|
||||||
## 示例
|
## 示例
|
||||||
|
|
||||||
**用户输入**(由后端拼装):
|
> 以下两个示例覆盖**不同任务类型**,目的是教你「思路要呼应任务本身」,**不要**把任何一个示例的具体职能链当模板。
|
||||||
|
|
||||||
|
### 示例 A:合规改造任务(职能型候选池)
|
||||||
|
|
||||||
|
**用户输入**:
|
||||||
```
|
```
|
||||||
任务目标:对生产系统进行安全改造,满足等保 2.0 合规要求
|
任务目标:对生产系统进行安全改造,满足等保 2.0 合规要求
|
||||||
约束:预算 ≤ 500 万元;工期 ≤ 6 个月;必须满足等保 2.0
|
约束:预算 ≤ 500 万元;工期 ≤ 6 个月;必须满足等保 2.0
|
||||||
@@ -334,7 +340,7 @@ RECOMMENDER_AGENT_SOUL = """# 智能体推荐专员
|
|||||||
```
|
```
|
||||||
|
|
||||||
**你的输出**:
|
**你的输出**:
|
||||||
按"情报→设计→风险→执行"的研讨节奏,挑选 5 位最贴合本次等保 2.0 改造任务的智能体,并加入成本测算覆盖预算硬约束。📋
|
本次任务核心是等保 2.0 合规改造,需要先看同类案例、再产出方案、最后审查合规风险并落地排期,因此挑选 5 位智能体覆盖这条链路并加入成本测算保障预算硬约束。📋
|
||||||
|
|
||||||
[RECOMMEND_READY]
|
[RECOMMEND_READY]
|
||||||
```json
|
```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(
|
def _validate_picks(
|
||||||
raw_picks: Any,
|
raw_picks: Any,
|
||||||
candidate_ids: set[str],
|
candidates: list[dict[str, Any]],
|
||||||
) -> list[dict[str, str]]:
|
) -> list[dict[str, str]]:
|
||||||
"""Filter the model's picks against the candidate pool and clamp to 2-6.
|
"""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
|
model over-picked. Returns ``[]`` if validation leaves us with fewer than
|
||||||
``_MIN_PICKS`` valid picks — the caller treats that as "fall back to
|
``_MIN_PICKS`` valid picks — the caller treats that as "fall back to
|
||||||
defaults".
|
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):
|
if not isinstance(raw_picks, list):
|
||||||
return []
|
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]] = []
|
picked: list[dict[str, str]] = []
|
||||||
seen: set[str] = set()
|
seen: set[str] = set()
|
||||||
for item in raw_picks:
|
for item in raw_picks:
|
||||||
if not isinstance(item, dict):
|
if not isinstance(item, dict):
|
||||||
continue
|
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()
|
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
|
continue
|
||||||
picked.append({"agent_id": aid, "reason": reason})
|
picked.append({"agent_id": aid, "reason": reason})
|
||||||
seen.add(aid)
|
seen.add(aid)
|
||||||
@@ -245,14 +277,30 @@ async def stream_recommend(request: Request, payload: dict = Body(...)) -> Strea
|
|||||||
# 只扫描「最后一条非空 AIMessage」:推荐 agent 单轮输出,无多轮 READY 问题。
|
# 只扫描「最后一条非空 AIMessage」:推荐 agent 单轮输出,无多轮 READY 问题。
|
||||||
picks: list[dict[str, str]] = []
|
picks: list[dict[str, str]] = []
|
||||||
match = _RECOMMEND_READY_PATTERN.search(last_ai_content)
|
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:
|
if match:
|
||||||
try:
|
try:
|
||||||
parsed = json.loads(match.group(1))
|
parsed = json.loads(match.group(1))
|
||||||
except json.JSONDecodeError as exc:
|
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 = {}
|
parsed = {}
|
||||||
if isinstance(parsed, dict):
|
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。
|
# asking ≠ 模型在追问用户,而是「网关未能产出合法 picks」,由前端 fallback。
|
||||||
status_value = "done" if picks else "asking"
|
status_value = "done" if picks else "asking"
|
||||||
|
|||||||
Reference in new issue
Block a user