feat: deepen direct benchmark and analysis actions
This commit is contained in:
17
CHANGELOG.md
17
CHANGELOG.md
@@ -4,12 +4,23 @@
|
||||
|
||||
## 2026-04-05
|
||||
|
||||
### 主 Agent 抖音相似搜索与对标关系 live 修复
|
||||
|
||||
- 修复 `search-similar-accounts` / `save-benchmark-link` 在抖音 live 数据上错误按 `project_id` 查询账号导致的 500。
|
||||
- `OneLiner` 现在会按抖音真实表结构解析目标账号,和国内平台 `content_sources` 路径分开处理。
|
||||
- 新增抖音专用治理回归,锁住“查相似账号 -> 存对标关系”这条真实执行链。
|
||||
|
||||
### OneLiner 对话里的直接执行建议保留完整上下文
|
||||
|
||||
- OneLiner 助手消息里的 `suggested_actions` 现在不再只是渲染成一个裸 `data-action` 标签。
|
||||
- 前端会把每条建议对应的 `executor_key / platform / payload / session_id` 一起带上,所以“直接分析账号 / 直接同步跟踪池 / 直接创建 AI 视频”这类建议从对话里点下去时,会真正走当前 live 执行器。
|
||||
- 这让 OneLiner 对话、运行卡、结果卡三条链的“直接执行”行为终于统一,不会再出现运行卡能跑、对话建议却丢上下文的断层。
|
||||
|
||||
### 主页导入和高分分析的落点改成真正直达
|
||||
|
||||
- `直接导入主页` 现在不再把人扔回 `找对标` 总览,而是直接落到新建同步任务的详情页,方便立即看同步进度。
|
||||
- `直接分析高分作品` 现在会直接回到当前对象,而不是回到整个 `找对标` 首页,让高分拆解结论和相似账号建议更容易接着看。
|
||||
|
||||
### 主 Agent 可直接执行分析账号、加入跟踪、创建 Agent
|
||||
|
||||
- `OneLiner / 主 Agent` 的动作执行器现在新增了三条真实动作:
|
||||
@@ -428,3 +439,9 @@
|
||||
|
||||
- 依赖健康卡片在“未配置地址”时,管理员可以直接点 `去管理员配置台` 继续配置。
|
||||
- 探测地址缺失文案改成“等待配置探测地址”,不再让人误以为系统异常。
|
||||
|
||||
### 主 Agent 可直接查相似与存对标
|
||||
|
||||
- `OneLiner / 主 Agent` 现在新增了 `直接查相似账号` 和 `直接存对标关系` 两条真实执行动作,不再只停留在“建议后跳回找对标”。
|
||||
- `直接查相似账号` 会调用当前平台的相似搜索接口,返回真实候选数量,并在有候选账号时直接落到该账号详情。
|
||||
- `直接存对标关系` 会优先复用最近一次相似搜索的候选,把它直接写入当前平台的对标关系,并把结果回写到找对标工作区。
|
||||
|
||||
@@ -356,6 +356,26 @@ ACTION_REGISTRY_DEFAULTS: dict[str, dict[str, Any]] = {
|
||||
"requires_platform": True,
|
||||
"config": {},
|
||||
},
|
||||
"search-similar-accounts": {
|
||||
"label": "直接查相似账号",
|
||||
"description": "基于当前平台账号直接生成一批相似候选,并沉淀到当前项目。",
|
||||
"category": "analysis",
|
||||
"handler_key": "search-similar-accounts",
|
||||
"status": "enabled",
|
||||
"admin_only": False,
|
||||
"requires_platform": True,
|
||||
"config": {"max_candidates": 8},
|
||||
},
|
||||
"save-benchmark-link": {
|
||||
"label": "直接存对标关系",
|
||||
"description": "把当前相似候选直接加入对标关系,便于后续持续跟踪和拆解。",
|
||||
"category": "analysis",
|
||||
"handler_key": "save-benchmark-link",
|
||||
"status": "enabled",
|
||||
"admin_only": False,
|
||||
"requires_platform": True,
|
||||
"config": {"relation_type": "benchmark"},
|
||||
},
|
||||
"create-assistant": {
|
||||
"label": "直接创建 Agent",
|
||||
"description": "根据当前项目和平台上下文,直接创建可继续编辑的 Agent。",
|
||||
@@ -458,6 +478,7 @@ ACTION_USAGE_KEYS: dict[str, str] = {
|
||||
"analyze-account": "analysis",
|
||||
"track-account": "content_source_sync",
|
||||
"refresh-tracking": "content_source_sync",
|
||||
"search-similar-accounts": "analysis",
|
||||
"create-ai-video": "ai_video",
|
||||
"create-real-cut": "real_cut",
|
||||
"save-live-recorder-source": "live_recorder",
|
||||
@@ -3235,11 +3256,11 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None:
|
||||
return legacy.db.fetch_one(
|
||||
"""
|
||||
SELECT * FROM douyin_accounts
|
||||
WHERE user_id = ? AND project_id = ?
|
||||
WHERE user_id = ?
|
||||
ORDER BY updated_at DESC, created_at DESC
|
||||
LIMIT 1
|
||||
""",
|
||||
(account["id"], project_id),
|
||||
(account["id"],),
|
||||
)
|
||||
|
||||
def _resolve_platform_target_account(
|
||||
@@ -3254,8 +3275,8 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None:
|
||||
if normalized_platform == "douyin":
|
||||
if normalized_requested:
|
||||
return legacy.db.fetch_one(
|
||||
"SELECT * FROM douyin_accounts WHERE id = ? AND user_id = ? AND project_id = ?",
|
||||
(normalized_requested, account["id"], project_id),
|
||||
"SELECT * FROM douyin_accounts WHERE id = ? AND user_id = ?",
|
||||
(normalized_requested, account["id"]),
|
||||
)
|
||||
return _latest_douyin_account(account, project_id=project_id)
|
||||
if normalized_requested:
|
||||
@@ -3268,6 +3289,51 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None:
|
||||
)
|
||||
return _latest_platform_account(account, project_id=project_id, platform=normalized_platform)
|
||||
|
||||
def _latest_similarity_candidate(
|
||||
account: dict[str, Any],
|
||||
*,
|
||||
platform: str,
|
||||
source_account_id: str,
|
||||
) -> dict[str, Any] | None:
|
||||
normalized_platform = _safe_platform(platform, fallback="douyin")
|
||||
normalized_source_id = str(source_account_id or "").strip()
|
||||
if not normalized_source_id:
|
||||
return None
|
||||
table_prefix = "douyin" if normalized_platform == "douyin" else normalized_platform
|
||||
search_row = legacy.db.fetch_one(
|
||||
f"""
|
||||
SELECT * FROM {table_prefix}_similarity_searches
|
||||
WHERE user_id = ? AND source_account_id = ?
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 1
|
||||
""",
|
||||
(account["id"], normalized_source_id),
|
||||
)
|
||||
if not search_row:
|
||||
return None
|
||||
candidate_row = legacy.db.fetch_one(
|
||||
f"""
|
||||
SELECT * FROM {table_prefix}_similarity_candidates
|
||||
WHERE search_id = ?
|
||||
ORDER BY rank_index ASC
|
||||
LIMIT 1
|
||||
""",
|
||||
(search_row["id"],),
|
||||
)
|
||||
if not candidate_row:
|
||||
return {"search_id": search_row["id"], "candidate": {}}
|
||||
candidate_payload = _parse_json(candidate_row.get("raw_output_json") or "{}", {})
|
||||
candidate_payload.setdefault("candidate_account_id", candidate_row.get("candidate_account_id", ""))
|
||||
candidate_payload.setdefault("candidate_profile_url", candidate_row.get("candidate_profile_url", ""))
|
||||
candidate_payload.setdefault("candidate_nickname", candidate_row.get("candidate_nickname", ""))
|
||||
candidate_payload.setdefault("rationale_text", candidate_row.get("rationale_text", ""))
|
||||
candidate_payload.setdefault("agent_score", candidate_row.get("agent_score", 0))
|
||||
candidate_payload.setdefault("heuristic_score", candidate_row.get("heuristic_score", 0))
|
||||
return {
|
||||
"search_id": search_row["id"],
|
||||
"candidate": candidate_payload,
|
||||
}
|
||||
|
||||
async def _call_local_api(
|
||||
account: dict[str, Any],
|
||||
*,
|
||||
@@ -4549,6 +4615,18 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None:
|
||||
},
|
||||
}
|
||||
)
|
||||
secondary_actions.append(
|
||||
{
|
||||
"key": "run-oneliner-action",
|
||||
"label": "直接查相似账号",
|
||||
"kind": "api_action",
|
||||
"executor_key": "search-similar-accounts",
|
||||
"platform": plan.get("platform", ""),
|
||||
"payload": {
|
||||
"target_account_id": latest_platform_account["id"],
|
||||
},
|
||||
}
|
||||
)
|
||||
if plan.get("platform") and latest_platform_account and plan.get("intent_key") in {"track_account", "custom"}:
|
||||
secondary_actions.append(
|
||||
{
|
||||
@@ -4563,6 +4641,37 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None:
|
||||
},
|
||||
}
|
||||
)
|
||||
latest_similarity = (
|
||||
_latest_similarity_candidate(
|
||||
account,
|
||||
platform=plan.get("platform", ""),
|
||||
source_account_id=latest_platform_account["id"],
|
||||
)
|
||||
if plan.get("platform") and latest_platform_account
|
||||
else None
|
||||
)
|
||||
latest_similarity_candidate = (latest_similarity or {}).get("candidate") or {}
|
||||
if (
|
||||
plan.get("platform")
|
||||
and latest_platform_account
|
||||
and latest_similarity_candidate
|
||||
and plan.get("intent_key") in {"analyze_top_videos", "analyze_account", "custom", "track_account"}
|
||||
):
|
||||
secondary_actions.append(
|
||||
{
|
||||
"key": "run-oneliner-action",
|
||||
"label": "直接存对标关系",
|
||||
"kind": "api_action",
|
||||
"executor_key": "save-benchmark-link",
|
||||
"platform": plan.get("platform", ""),
|
||||
"payload": {
|
||||
"source_account_id": latest_platform_account["id"],
|
||||
"target_account_id": latest_similarity_candidate.get("candidate_account_id") or "",
|
||||
"target_profile_url": latest_similarity_candidate.get("candidate_profile_url") or "",
|
||||
"search_id": (latest_similarity or {}).get("search_id") or "",
|
||||
},
|
||||
}
|
||||
)
|
||||
if plan.get("platform") and plan.get("intent_key") in {"track_account", "custom"}:
|
||||
secondary_actions.append(
|
||||
{
|
||||
@@ -5732,10 +5841,10 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None:
|
||||
"existing_source_id": (existing_source or {}).get("id", ""),
|
||||
},
|
||||
"recommended_action": _recommended_action(
|
||||
"goto-discovery",
|
||||
label="去找对标",
|
||||
summary=f"继续查看 {legacy.platform_label(inferred_platform)} 主页导入后的账号分析和候选对标。",
|
||||
screen="discovery",
|
||||
"open-job-detail",
|
||||
label="看任务详情",
|
||||
summary=f"继续查看 {legacy.platform_label(inferred_platform)} 主页导入任务的同步进度和后续分析结果。",
|
||||
screen="production",
|
||||
platform=inferred_platform,
|
||||
job_id=sync_job.get("id", ""),
|
||||
),
|
||||
@@ -5918,6 +6027,162 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None:
|
||||
),
|
||||
}
|
||||
|
||||
async def _run_search_similar_accounts() -> dict[str, Any]:
|
||||
if not normalized_platform:
|
||||
raise HTTPException(status_code=400, detail="Platform is required for similarity search")
|
||||
source_account = _resolve_platform_target_account(
|
||||
account,
|
||||
project_id=project["id"],
|
||||
platform=normalized_platform,
|
||||
requested_account_id=str(
|
||||
requested_payload.get("target_account_id")
|
||||
or requested_payload.get("targetAccountId")
|
||||
or requested_payload.get("source_account_id")
|
||||
or requested_payload.get("sourceAccountId")
|
||||
or ""
|
||||
),
|
||||
)
|
||||
if not source_account:
|
||||
raise HTTPException(status_code=404, detail="No platform account available for similarity search")
|
||||
search_payload = await _call_local_api(
|
||||
account,
|
||||
method="POST",
|
||||
path=f"/v2/{normalized_platform}/similar-searches",
|
||||
json_body={
|
||||
"source_account_id": source_account["id"],
|
||||
"candidate_urls": list(requested_payload.get("candidate_urls") or requested_payload.get("candidateUrls") or []),
|
||||
"seed_linked_accounts": bool(requested_payload.get("seed_linked_accounts", requested_payload.get("seedLinkedAccounts", True))),
|
||||
"search_public_pages": bool(requested_payload.get("search_public_pages", requested_payload.get("searchPublicPages", True))),
|
||||
"model_profile_id": str(requested_payload.get("model_profile_id") or requested_payload.get("modelProfileId") or ""),
|
||||
"max_candidates": max(1, min(int(requested_payload.get("max_candidates") or requested_payload.get("maxCandidates") or 8), 20)),
|
||||
"extra_requirements": str(requested_payload.get("extra_requirements") or requested_payload.get("extraRequirements") or latest_user_message or ""),
|
||||
},
|
||||
)
|
||||
search_id = str(search_payload.get("search_id") or search_payload.get("id") or "").strip()
|
||||
detail = await _call_local_api(
|
||||
account,
|
||||
method="GET",
|
||||
path=f"/v2/{normalized_platform}/similar-searches/{search_id}",
|
||||
) if search_id else {"candidates": []}
|
||||
candidates = list(detail.get("candidates") or [])
|
||||
top_candidate = candidates[0] if candidates else {}
|
||||
top_candidate_account_id = str(top_candidate.get("candidate_account_id") or "").strip()
|
||||
recommended_action = (
|
||||
_recommended_action(
|
||||
"select-account",
|
||||
label="打开当前候选",
|
||||
summary=f"继续查看 {legacy.platform_label(normalized_platform)} 相似候选的详情、拆解和对标关系。",
|
||||
screen="discovery",
|
||||
account_id=top_candidate_account_id,
|
||||
search_id=search_id,
|
||||
)
|
||||
if top_candidate_account_id
|
||||
else _recommended_action(
|
||||
"goto-discovery",
|
||||
label="回到找对标",
|
||||
summary=f"继续查看 {legacy.platform_label(normalized_platform)} 相似候选列表和高分样本。",
|
||||
screen="discovery",
|
||||
search_id=search_id,
|
||||
)
|
||||
)
|
||||
return {
|
||||
"title": "OneLiner 已查到相似账号",
|
||||
"summary": f"已为 {legacy.platform_label(normalized_platform)} 当前账号生成 {len(candidates)} 个相似候选。",
|
||||
"payload": {
|
||||
"platform": normalized_platform,
|
||||
"source_account_id": source_account["id"],
|
||||
"search": {
|
||||
"id": search_id,
|
||||
"candidate_count": len(candidates),
|
||||
"top_candidate_account_id": top_candidate_account_id,
|
||||
},
|
||||
"detail": detail,
|
||||
},
|
||||
"recommended_action": recommended_action,
|
||||
}
|
||||
|
||||
async def _run_save_benchmark_link() -> dict[str, Any]:
|
||||
if not normalized_platform:
|
||||
raise HTTPException(status_code=400, detail="Platform is required for benchmark linking")
|
||||
source_account = _resolve_platform_target_account(
|
||||
account,
|
||||
project_id=project["id"],
|
||||
platform=normalized_platform,
|
||||
requested_account_id=str(
|
||||
requested_payload.get("source_account_id")
|
||||
or requested_payload.get("sourceAccountId")
|
||||
or requested_payload.get("target_account_id")
|
||||
or requested_payload.get("targetAccountId")
|
||||
or ""
|
||||
),
|
||||
)
|
||||
if not source_account:
|
||||
raise HTTPException(status_code=404, detail="No platform account available for benchmark linking")
|
||||
latest_similarity = _latest_similarity_candidate(
|
||||
account,
|
||||
platform=normalized_platform,
|
||||
source_account_id=source_account["id"],
|
||||
) or {}
|
||||
candidate_payload = latest_similarity.get("candidate") or {}
|
||||
target_account_id = str(
|
||||
requested_payload.get("target_account_id")
|
||||
or requested_payload.get("targetAccountId")
|
||||
or candidate_payload.get("candidate_account_id")
|
||||
or ""
|
||||
).strip()
|
||||
target_profile_url = str(
|
||||
requested_payload.get("target_profile_url")
|
||||
or requested_payload.get("targetProfileUrl")
|
||||
or ("" if target_account_id else candidate_payload.get("candidate_profile_url"))
|
||||
or ""
|
||||
).strip()
|
||||
if not target_account_id and not target_profile_url:
|
||||
raise HTTPException(status_code=404, detail="No benchmark candidate available to save")
|
||||
created = await _call_local_api(
|
||||
account,
|
||||
method="POST",
|
||||
path=f"/v2/{normalized_platform}/accounts/{source_account['id']}/benchmark-links",
|
||||
json_body={
|
||||
"target_account_ids": [target_account_id] if target_account_id else [],
|
||||
"target_profile_urls": [] if target_account_id else [target_profile_url],
|
||||
"relation_type": str(requested_payload.get("relation_type") or requested_payload.get("relationType") or "benchmark"),
|
||||
"note": str(requested_payload.get("note") or candidate_payload.get("rationale_text") or "由 OneLiner 直接加入对标库。"),
|
||||
"search_id": str(requested_payload.get("search_id") or requested_payload.get("searchId") or latest_similarity.get("search_id") or ""),
|
||||
},
|
||||
)
|
||||
links = list(created.get("links") or [])
|
||||
latest_link = links[0] if links else {}
|
||||
recommended_action = (
|
||||
_recommended_action(
|
||||
"select-account",
|
||||
label="打开当前对标",
|
||||
summary=f"继续查看 {legacy.platform_label(normalized_platform)} 当前对标账号的详情、关系和高分样本。",
|
||||
screen="discovery",
|
||||
account_id=target_account_id,
|
||||
)
|
||||
if target_account_id
|
||||
else _recommended_action(
|
||||
"goto-discovery",
|
||||
label="回到找对标",
|
||||
summary=f"继续查看 {legacy.platform_label(normalized_platform)} 对标关系和相似候选。",
|
||||
screen="discovery",
|
||||
)
|
||||
)
|
||||
return {
|
||||
"title": "OneLiner 已存对标关系",
|
||||
"summary": f"已把当前候选加入 {legacy.platform_label(normalized_platform)} 对标关系。",
|
||||
"payload": {
|
||||
"platform": normalized_platform,
|
||||
"source_account_id": source_account["id"],
|
||||
"target_account_id": target_account_id,
|
||||
"target_profile_url": target_profile_url,
|
||||
"search_id": str(latest_similarity.get("search_id") or ""),
|
||||
"link": latest_link,
|
||||
"links": links,
|
||||
},
|
||||
"recommended_action": recommended_action,
|
||||
}
|
||||
|
||||
async def _run_analyze_top_videos() -> dict[str, Any]:
|
||||
if not normalized_platform:
|
||||
raise HTTPException(status_code=400, detail="Platform is required for top video analysis")
|
||||
@@ -6012,10 +6277,11 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None:
|
||||
"memory": memory,
|
||||
},
|
||||
"recommended_action": _recommended_action(
|
||||
"goto-discovery",
|
||||
label="去找对标",
|
||||
summary=f"继续查看 {legacy.platform_label(normalized_platform)} 高分作品拆解和相似账号。",
|
||||
"select-account",
|
||||
label="打开当前对象",
|
||||
summary=f"继续查看 {legacy.platform_label(normalized_platform)} 当前对象的高分作品拆解和相似账号。",
|
||||
screen="discovery",
|
||||
account_id=account_payload.get("id", ""),
|
||||
platform=normalized_platform,
|
||||
),
|
||||
}
|
||||
@@ -6224,6 +6490,8 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None:
|
||||
"track-account": _run_track_account,
|
||||
"refresh-tracking": _run_refresh_tracking,
|
||||
"mark-tracking-read": _run_mark_tracking_read,
|
||||
"search-similar-accounts": _run_search_similar_accounts,
|
||||
"save-benchmark-link": _run_save_benchmark_link,
|
||||
"analyze-top-videos": _run_analyze_top_videos,
|
||||
"create-assistant": _run_create_assistant,
|
||||
"create-ai-video": _run_create_ai_video,
|
||||
|
||||
@@ -156,16 +156,7 @@ class MainAgentGovernanceTests(unittest.TestCase):
|
||||
result_json: str = '{"summary":"done"}',
|
||||
) -> str:
|
||||
now = self.db_module.utc_now()
|
||||
knowledge_base_id = "kb_member_default"
|
||||
existing_kb = self.core.db.fetch_one("SELECT id FROM knowledge_bases WHERE id = ?", (knowledge_base_id,))
|
||||
if not existing_kb:
|
||||
self.core.db.execute(
|
||||
"""
|
||||
INSERT INTO knowledge_bases (id, user_id, project_id, name, description, sync_status, created_at, updated_at)
|
||||
VALUES (?, ?, ?, 'Default KB', '', 'ready', ?, ?)
|
||||
""",
|
||||
(knowledge_base_id, self.ctx["member_id"], self.ctx["project_id"], now, now),
|
||||
)
|
||||
knowledge_base_id = self._ensure_default_knowledge_base(now)
|
||||
self.core.db.execute(
|
||||
"""
|
||||
INSERT INTO jobs (
|
||||
@@ -191,6 +182,42 @@ class MainAgentGovernanceTests(unittest.TestCase):
|
||||
)
|
||||
return job_id
|
||||
|
||||
def _ensure_default_knowledge_base(self, now: str | None = None) -> str:
|
||||
knowledge_base_id = "kb_member_default"
|
||||
existing_kb = self.core.db.fetch_one("SELECT id FROM knowledge_bases WHERE id = ?", (knowledge_base_id,))
|
||||
if existing_kb:
|
||||
return knowledge_base_id
|
||||
ts = now or self.db_module.utc_now()
|
||||
self.core.db.execute(
|
||||
"""
|
||||
INSERT INTO knowledge_bases (id, user_id, project_id, name, description, sync_status, created_at, updated_at)
|
||||
VALUES (?, ?, ?, 'Default KB', '', 'ready', ?, ?)
|
||||
""",
|
||||
(knowledge_base_id, self.ctx["member_id"], self.ctx["project_id"], ts, ts),
|
||||
)
|
||||
return knowledge_base_id
|
||||
|
||||
def _insert_douyin_account(
|
||||
self,
|
||||
*,
|
||||
account_id: str,
|
||||
profile_url: str,
|
||||
nickname: str,
|
||||
) -> str:
|
||||
now = self.db_module.utc_now()
|
||||
self.core.db.execute(
|
||||
"""
|
||||
INSERT INTO douyin_accounts (
|
||||
id, user_id, profile_url, canonical_profile_url, sec_uid, douyin_uid, douyin_id,
|
||||
nickname, signature, avatar_url, tags_json, profile_stats_json, raw_profile_json,
|
||||
source_mode, sync_status, last_public_sync_at, last_creator_sync_at, last_analysis_at,
|
||||
created_at, updated_at
|
||||
) VALUES (?, ?, ?, ?, '', '', '', ?, '', '', '[]', '{}', '{}', 'public', 'ready', NULL, NULL, NULL, ?, ?)
|
||||
""",
|
||||
(account_id, self.ctx["member_id"], profile_url, profile_url, nickname, now, now),
|
||||
)
|
||||
return account_id
|
||||
|
||||
def _insert_assistant(
|
||||
self,
|
||||
*,
|
||||
@@ -802,6 +829,8 @@ class MainAgentGovernanceTests(unittest.TestCase):
|
||||
self.assertIn("refresh-tracking", action_keys)
|
||||
self.assertIn("mark-tracking-read", action_keys)
|
||||
self.assertIn("create-assistant", action_keys)
|
||||
self.assertIn("search-similar-accounts", action_keys)
|
||||
self.assertIn("save-benchmark-link", action_keys)
|
||||
|
||||
save_registry = self.client.put(
|
||||
"/v2/oneliner/action-registry/generate-copy",
|
||||
@@ -974,6 +1003,91 @@ class MainAgentGovernanceTests(unittest.TestCase):
|
||||
self.assertEqual(copy_payload["recommended_action"]["screen"], "playbook")
|
||||
self.assertEqual(copy_payload["recommended_action"]["platform"], "douyin")
|
||||
|
||||
with patch.object(
|
||||
self.core,
|
||||
"create_content_source_sync_job",
|
||||
new=AsyncMock(return_value={"id": "job_sync_import", "title": "Import Sync Job"}),
|
||||
):
|
||||
import_response = self.client.post(
|
||||
"/v2/oneliner/actions/execute",
|
||||
headers=self.ctx["member_headers"],
|
||||
json={
|
||||
"action_key": "import-homepage",
|
||||
"project_id": self.ctx["project_id"],
|
||||
"platform": "douyin",
|
||||
"payload": {"source_url": "https://www.douyin.com/user/test-homepage"},
|
||||
},
|
||||
)
|
||||
self.assertEqual(import_response.status_code, 200, import_response.text)
|
||||
import_payload = import_response.json()
|
||||
self.assertEqual(import_payload["recommended_action"]["action"], "open-job-detail")
|
||||
self.assertEqual(import_payload["recommended_action"]["screen"], "production")
|
||||
self.assertEqual(import_payload["recommended_action"]["job_id"], "job_sync_import")
|
||||
|
||||
source_id = self._insert_content_source_account(
|
||||
platform="kuaishou",
|
||||
title="高分拆解账号",
|
||||
source_url="https://www.kuaishou.com/profile/top-video-account",
|
||||
)
|
||||
video_source = self.core.create_content_source(
|
||||
account_id=self.ctx["member_id"],
|
||||
project_id=self.ctx["project_id"],
|
||||
source_kind="video_link",
|
||||
platform="kuaishou",
|
||||
source_url="https://www.kuaishou.com/video/top-video-1",
|
||||
title="高分作品 1",
|
||||
metadata={
|
||||
"origin_content_source_id": source_id,
|
||||
"source_account_url": "https://www.kuaishou.com/profile/top-video-account",
|
||||
},
|
||||
)
|
||||
now = self.db_module.utc_now()
|
||||
knowledge_base_id = self._ensure_default_knowledge_base(now)
|
||||
self.core.db.execute(
|
||||
"""
|
||||
INSERT INTO jobs (
|
||||
id, user_id, project_id, parent_job_id, assistant_id, knowledge_base_id, content_source_id,
|
||||
source_type, line_type, workflow_key, orchestrator, provider_name, provider_task_id,
|
||||
source_url, title, language, status, transcript_text, style_summary, upload_status,
|
||||
error, artifacts_json, result_json, analysis_model_profile_id, created_at, updated_at
|
||||
) VALUES (?, ?, ?, '', NULL, ?, ?, ?, ?, ?, 'n8n', 'collector', '', '', ?, 'auto', 'completed', '', '', 'completed', '', '{}', ?, '', ?, ?)
|
||||
""",
|
||||
(
|
||||
"job_video_1",
|
||||
self.ctx["member_id"],
|
||||
self.ctx["project_id"],
|
||||
knowledge_base_id,
|
||||
video_source["id"],
|
||||
"video_link",
|
||||
"analysis",
|
||||
"analysis_pipeline",
|
||||
"高分作品 1",
|
||||
'{"performance_score":91,"summary":"高分作品摘要"}',
|
||||
now,
|
||||
now,
|
||||
),
|
||||
)
|
||||
with patch.object(self.core, "call_model", new=AsyncMock(return_value='{"summary":"保留开头 3 秒抓人结构"}')):
|
||||
analyze_top_response = self.client.post(
|
||||
"/v2/oneliner/actions/execute",
|
||||
headers=self.ctx["member_headers"],
|
||||
json={
|
||||
"action_key": "analyze-top-videos",
|
||||
"project_id": self.ctx["project_id"],
|
||||
"platform": "kuaishou",
|
||||
"payload": {
|
||||
"target_account_id": source_id,
|
||||
"top_video_count": 1,
|
||||
"min_score": 0,
|
||||
},
|
||||
},
|
||||
)
|
||||
self.assertEqual(analyze_top_response.status_code, 200, analyze_top_response.text)
|
||||
analyze_top_payload = analyze_top_response.json()
|
||||
self.assertEqual(analyze_top_payload["recommended_action"]["action"], "select-account")
|
||||
self.assertEqual(analyze_top_payload["recommended_action"]["screen"], "discovery")
|
||||
self.assertEqual(analyze_top_payload["recommended_action"]["account_id"], source_id)
|
||||
|
||||
def test_create_ai_video_action_passes_provider_and_model_through_oneliner(self) -> None:
|
||||
self._insert_completed_job(job_id="job_ai_video_source", title="AI Video Source Job")
|
||||
self._insert_assistant()
|
||||
@@ -1040,6 +1154,11 @@ class MainAgentGovernanceTests(unittest.TestCase):
|
||||
title="快手测试账号",
|
||||
source_url="https://www.kuaishou.com/profile/test-account",
|
||||
)
|
||||
candidate_id = self._insert_content_source_account(
|
||||
platform="kuaishou",
|
||||
title="快手候选账号",
|
||||
source_url="https://www.kuaishou.com/profile/candidate-account",
|
||||
)
|
||||
captured_model_calls: list[dict[str, Any]] = []
|
||||
|
||||
async def fake_call_model(profile: dict[str, Any], *, system_prompt: str, user_prompt: str, temperature: float = 0.3, **_: Any) -> str:
|
||||
@@ -1161,6 +1280,108 @@ class MainAgentGovernanceTests(unittest.TestCase):
|
||||
self.assertTrue(create_payload["recommended_action"]["assistant_id"])
|
||||
self.assertEqual(create_payload["payload"]["assistant"]["name"], "快手增长 Agent")
|
||||
|
||||
similar_response = self.client.post(
|
||||
"/v2/oneliner/actions/execute",
|
||||
headers=self.ctx["member_headers"],
|
||||
json={
|
||||
"action_key": "search-similar-accounts",
|
||||
"project_id": self.ctx["project_id"],
|
||||
"platform": "kuaishou",
|
||||
"payload": {
|
||||
"target_account_id": source_id,
|
||||
"max_candidates": 3,
|
||||
"extra_requirements": "优先找商业化和知识付费方向相近的账号",
|
||||
},
|
||||
},
|
||||
)
|
||||
self.assertEqual(similar_response.status_code, 200, similar_response.text)
|
||||
similar_payload = similar_response.json()
|
||||
self.assertEqual(similar_payload["recommended_action"]["action"], "select-account")
|
||||
self.assertEqual(similar_payload["recommended_action"]["screen"], "discovery")
|
||||
self.assertTrue(similar_payload["recommended_action"]["account_id"])
|
||||
self.assertNotEqual(similar_payload["recommended_action"]["account_id"], source_id)
|
||||
self.assertEqual(similar_payload["payload"]["platform"], "kuaishou")
|
||||
self.assertEqual(similar_payload["payload"]["source_account_id"], source_id)
|
||||
self.assertGreaterEqual(int(similar_payload["payload"]["search"]["candidate_count"] or 0), 1)
|
||||
|
||||
save_benchmark_response = self.client.post(
|
||||
"/v2/oneliner/actions/execute",
|
||||
headers=self.ctx["member_headers"],
|
||||
json={
|
||||
"action_key": "save-benchmark-link",
|
||||
"project_id": self.ctx["project_id"],
|
||||
"platform": "kuaishou",
|
||||
"payload": {
|
||||
"source_account_id": source_id,
|
||||
"note": "由主 Agent 直接加入对标库",
|
||||
},
|
||||
},
|
||||
)
|
||||
self.assertEqual(save_benchmark_response.status_code, 200, save_benchmark_response.text)
|
||||
save_benchmark_payload = save_benchmark_response.json()
|
||||
self.assertEqual(save_benchmark_payload["recommended_action"]["action"], "select-account")
|
||||
self.assertEqual(save_benchmark_payload["recommended_action"]["screen"], "discovery")
|
||||
self.assertTrue(save_benchmark_payload["recommended_action"]["account_id"])
|
||||
self.assertNotEqual(save_benchmark_payload["recommended_action"]["account_id"], source_id)
|
||||
self.assertEqual(save_benchmark_payload["payload"]["platform"], "kuaishou")
|
||||
self.assertEqual(save_benchmark_payload["payload"]["source_account_id"], source_id)
|
||||
self.assertTrue(save_benchmark_payload["payload"]["link"]["id"])
|
||||
|
||||
def test_direct_oneliner_similarity_and_benchmark_actions_execute_real_douyin_flows(self) -> None:
|
||||
source_id = self._insert_douyin_account(
|
||||
account_id="dyacct_source",
|
||||
profile_url="https://www.douyin.com/user/source-account",
|
||||
nickname="源账号",
|
||||
)
|
||||
candidate_id = self._insert_douyin_account(
|
||||
account_id="dyacct_candidate",
|
||||
profile_url="https://www.douyin.com/user/candidate-account",
|
||||
nickname="候选账号",
|
||||
)
|
||||
|
||||
similar_response = self.client.post(
|
||||
"/v2/oneliner/actions/execute",
|
||||
headers=self.ctx["member_headers"],
|
||||
json={
|
||||
"action_key": "search-similar-accounts",
|
||||
"project_id": self.ctx["project_id"],
|
||||
"platform": "douyin",
|
||||
"payload": {
|
||||
"target_account_id": source_id,
|
||||
"max_candidates": 3,
|
||||
},
|
||||
},
|
||||
)
|
||||
self.assertEqual(similar_response.status_code, 200, similar_response.text)
|
||||
similar_payload = similar_response.json()
|
||||
self.assertEqual(similar_payload["recommended_action"]["action"], "select-account")
|
||||
self.assertEqual(similar_payload["recommended_action"]["screen"], "discovery")
|
||||
self.assertEqual(similar_payload["payload"]["platform"], "douyin")
|
||||
self.assertEqual(similar_payload["payload"]["source_account_id"], source_id)
|
||||
self.assertEqual(similar_payload["payload"]["search"]["top_candidate_account_id"], candidate_id)
|
||||
|
||||
save_benchmark_response = self.client.post(
|
||||
"/v2/oneliner/actions/execute",
|
||||
headers=self.ctx["member_headers"],
|
||||
json={
|
||||
"action_key": "save-benchmark-link",
|
||||
"project_id": self.ctx["project_id"],
|
||||
"platform": "douyin",
|
||||
"payload": {
|
||||
"source_account_id": source_id,
|
||||
"note": "由主 Agent 直接加入对标库",
|
||||
},
|
||||
},
|
||||
)
|
||||
self.assertEqual(save_benchmark_response.status_code, 200, save_benchmark_response.text)
|
||||
save_benchmark_payload = save_benchmark_response.json()
|
||||
self.assertEqual(save_benchmark_payload["recommended_action"]["action"], "select-account")
|
||||
self.assertEqual(save_benchmark_payload["recommended_action"]["screen"], "discovery")
|
||||
self.assertEqual(save_benchmark_payload["payload"]["platform"], "douyin")
|
||||
self.assertEqual(save_benchmark_payload["payload"]["source_account_id"], source_id)
|
||||
self.assertEqual(save_benchmark_payload["payload"]["target_account_id"], candidate_id)
|
||||
self.assertTrue(save_benchmark_payload["payload"]["link"]["relation_id"])
|
||||
|
||||
def test_platform_agent_routes_are_live(self) -> None:
|
||||
save_profile = self.client.put(
|
||||
"/v2/platform-agents/douyin/profile",
|
||||
|
||||
Reference in New Issue
Block a user