| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633 |
- from fastapi.testclient import TestClient
- from content_agent import api
- from content_agent.flow_ledger_service import _FLOW_LEDGER_CACHE
- from content_agent.run_service import RunService
- def test_first_round_detection_robust_to_old_labeling():
- from content_agent.flow_ledger_service import _is_first_round_content
- # 旧 run:标签带回视频被误标 search_query_direct;只看 previous_discovery_step 会误判成首轮。
- old = {"search_query_id": "tag_q", "previous_discovery_step": "search_query_direct"}
- assert _is_first_round_content(old) is True
- # 传入扩展 query 集合后(query→内容关联在旧 run 里仍正确),正确识别为游走带回。
- assert _is_first_round_content(old, {"tag_q"}) is False
- # 真首轮内容不受影响。
- assert _is_first_round_content({"search_query_id": "q1", "previous_discovery_step": "seed_term"}, {"tag_q"}) is True
- def test_flow_ledger_api_returns_business_v4_ledger(tmp_path, monkeypatch):
- _FLOW_LEDGER_CACHE.clear()
- service = RunService(runtime_root=tmp_path / "runtime" / "v1")
- run_id = "run_flow_ledger"
- policy_run_id = "policy_flow_ledger"
- service.runtime.prepare_run(run_id)
- service.runtime.write_json(
- run_id,
- "source_context.json",
- {
- "run_id": run_id,
- "demand_content_id": "demand_123",
- "name": "睡前拉伸需求",
- "suggestion": "用户希望看到睡前放松内容",
- "reason": "来自 itemset 123",
- "merge_leve2": "PG Pattern",
- "score": 0.8,
- "dt": "20260615",
- "ext_data": {
- "desc": "用户希望看到睡前放松内容",
- "reason": "来自 itemset 123",
- "type": "pattern",
- "evidence_pack": {
- "source_post_id": "post_001",
- "pattern_execution_id": 581,
- "itemset_ids": [1608101],
- "seed_terms": ["睡前拉伸"],
- "absolute_support": 33,
- "category_bindings": [
- {
- "category_id": 76006,
- "category_path": "/理念/观念/个人观念/人生观",
- "category_level": 4,
- "category_name": "人生观",
- "itemset_item_id": 6055,
- }
- ],
- "element_bindings": [
- {
- "itemset_id": 1608101,
- "category_id": 76006,
- "sample_elements": [
- {
- "post_id": "post_001",
- "point_text": "睡前肩颈放松",
- "point_type": "目的点",
- }
- ],
- }
- ],
- }
- },
- "raw_demand_content": {
- "id": "demand_123",
- "name": "睡前拉伸需求",
- "suggestion": "用户希望看到睡前放松内容",
- "reason": "来自 itemset 123",
- "merge_leve2": "PG Pattern",
- "score": 0.8,
- "dt": "20260615",
- "ext_data": {
- "desc": "用户希望看到睡前放松内容",
- "reason": "来自 itemset 123",
- "type": "pattern",
- },
- },
- },
- )
- service.runtime.write_json(
- run_id,
- "pattern_seed_pack.json",
- {
- "run_id": run_id,
- "policy_run_id": policy_run_id,
- "pattern_execution_id": 581,
- "source_post_id": "post_001",
- "element_bindings": [],
- },
- )
- service.runtime.append_jsonl(
- run_id,
- "search_queries.jsonl",
- [
- {
- "run_id": run_id,
- "policy_run_id": policy_run_id,
- "search_query_id": "q_001",
- "search_query": "睡前拉伸",
- "search_query_generation_method": "seed_term",
- "pattern_seed_ref": {"query_source_text": "睡前拉伸", "source_post_id": "seed_001"},
- "query_source_terms": ["睡前拉伸"],
- "raw_payload": {"query_source_refs": [{"query_source_type": "seed_term"}]},
- },
- {
- "run_id": run_id,
- "policy_run_id": policy_run_id,
- "search_query_id": "q_002",
- "search_query": "肩颈放松",
- "search_query_generation_method": "tag_query",
- "raw_payload": {"parent_search_query_id": "q_001", "hashtag": "肩颈放松"},
- },
- {
- "run_id": run_id,
- "policy_run_id": policy_run_id,
- "search_query_id": "q_003",
- "search_query": "情绪放松练习",
- "search_query_generation_method": "piaoquan_topic_point",
- "pattern_seed_ref": {"query_source_text": "情绪放松练习", "source_post_id": "post_001"},
- "raw_payload": {
- "query_source_refs": [
- {
- "query_source_type": "piaoquan_point_text",
- "source_ref": {
- "post_id": "post_888",
- "point_id": "point_456",
- "point_type": "灵感点",
- "point_text": "情绪放松练习",
- },
- }
- ]
- },
- },
- {
- "run_id": run_id,
- "policy_run_id": policy_run_id,
- "search_query_id": "q_004",
- "search_query": "人生观",
- "search_query_generation_method": "category_leaf_element",
- "pattern_seed_ref": {
- "query_source_text": "人生观",
- "source_post_id": "post_001",
- "pattern_execution_id": 581,
- },
- "raw_payload": {
- "query_source_refs": [
- {
- "query_source_type": "category_terminal_element",
- "source_ref": {
- "category_id": 76006,
- "element_name": "人生观",
- "itemset_item_id": 6055,
- },
- }
- ]
- },
- },
- ],
- )
- service.runtime.append_jsonl(
- run_id,
- "discovered_content_items.jsonl",
- [
- {
- "run_id": run_id,
- "policy_run_id": policy_run_id,
- "content_discovery_id": "content_001",
- "platform_content_id": "douyin_001",
- "search_query_id": "q_001",
- "platform": "douyin",
- "description": "睡前 5 分钟肩颈拉伸",
- "author_display_name": "健康教练",
- "tags": ["肩颈", "拉伸"],
- "statistics": {"digg_count": 100, "comment_count": 8, "share_count": 3},
- "raw_payload": {},
- },
- {
- "run_id": run_id,
- "policy_run_id": policy_run_id,
- "content_discovery_id": "content_002",
- "platform_content_id": "douyin_002",
- "search_query_id": "q_002",
- "previous_discovery_step": "hashtag_to_query",
- "platform": "douyin",
- "description": "扩展搜到的视频",
- "statistics": {},
- "raw_payload": {},
- },
- {
- "run_id": run_id,
- "policy_run_id": policy_run_id,
- "content_discovery_id": "content_003",
- "platform_content_id": "douyin_003",
- "search_query_id": "q_003",
- "platform": "douyin",
- "description": "情绪放松练习视频",
- "author_display_name": "心理教练",
- "statistics": {},
- "raw_payload": {},
- },
- {
- "run_id": run_id,
- "policy_run_id": policy_run_id,
- "content_discovery_id": "content_tech",
- "platform_content_id": "douyin_tech",
- "search_query_id": "q_003",
- "platform": "douyin",
- "description": "技术重试视频",
- "author_display_name": "技术作者",
- "statistics": {},
- "raw_payload": {},
- },
- {
- "run_id": run_id,
- "policy_run_id": policy_run_id,
- "content_discovery_id": "content_no_valid",
- "platform_content_id": "shipinhao_no_valid",
- "search_query_id": "q_003",
- "platform": "shipinhao",
- "description": "无可用正片 URL",
- "author_display_name": "技术作者",
- "statistics": {},
- "raw_payload": {},
- },
- ],
- )
- service.runtime.append_jsonl(
- run_id,
- "content_media_records.jsonl",
- [
- {
- "run_id": run_id,
- "policy_run_id": policy_run_id,
- "platform": "douyin",
- "platform_content_id": "douyin_001",
- "play_url": "https://example.com/raw.mp4",
- "oss_url": "https://oss.example.com/video.mp4",
- "local_path": None,
- "content_media_status": "oss_uploaded",
- "raw_payload": {"oss_object_key": "video.mp4"},
- },
- {
- "run_id": run_id,
- "policy_run_id": policy_run_id,
- "platform": "douyin",
- "platform_content_id": "douyin_tech",
- "play_url": "https://example.com/raw-tech.mp4",
- "oss_url": "https://oss.example.com/tech.mp4",
- "local_path": None,
- "content_media_status": "oss_uploaded",
- "raw_payload": {
- "oss_archive_attempt_count": 1,
- "oss_timing_metrics": {"oss_upload_duration_ms": 1234},
- "oss_payload_mode": "no_referer",
- "oss_response_summary": {
- "status": 10000,
- "msg": "bad",
- "oss_object_present": False,
- "oss_object_has_cdn_url": False,
- },
- "selected_video_url_path": "$.search.video_url_list[0].video_url",
- "selected_video_url_host": "findermp.video.qq.com",
- "selected_video_url_probe_status": "verified",
- "selected_video_url_content_type": "video/mp4",
- "selected_video_url_content_range": "bytes 0-99/100",
- "video_url_candidate_counts": {"video": 1, "image": 1},
- "video_url_reject_reasons": {"image_path": 1},
- },
- },
- {
- "run_id": run_id,
- "policy_run_id": policy_run_id,
- "platform": "shipinhao",
- "platform_content_id": "shipinhao_no_valid",
- "play_url": None,
- "oss_url": None,
- "local_path": None,
- "content_media_status": "unavailable",
- "failure_reason": "no_valid_play_url",
- "raw_payload": {
- "failure_reason": "no_valid_play_url",
- "no_valid_play_url": True,
- "selected_video_url_path": "$.search.video_url_list[0].video_url",
- "selected_video_url_host": "findermp.video.qq.com",
- "selected_video_url_probe_status": "failed_fallback",
- "video_url_candidate_counts": {"video": 1, "image": 1},
- "video_url_reject_reasons": {"image_path": 1, "probe_failed": 1},
- },
- },
- ],
- )
- service.runtime.append_jsonl(
- run_id,
- "pattern_recall_evidence.jsonl",
- [
- {
- "run_id": run_id,
- "policy_run_id": policy_run_id,
- "recall_evidence_id": "evidence_001",
- "platform_content_id": "douyin_001",
- "decode_status": "ok",
- "raw_payload": {},
- },
- {
- "run_id": run_id,
- "policy_run_id": policy_run_id,
- "recall_evidence_id": "evidence_tech",
- "platform_content_id": "douyin_tech",
- "recall_status": "failed",
- "evidence_summary": {
- "schema_version": "v4_gemini_query_relevance.v1",
- "failure_type": "gemini_client_timeout",
- "exception_type": "WriteTimeout",
- "error_message": "The write operation timed out",
- "retry_count": 2,
- "final_status": "failed",
- "response_body_summary": {
- "http_status_code": 502,
- "content_type": "application/json",
- "json_top_level_keys": ["error"],
- "text_excerpt": "upstream overloaded",
- },
- "timing_metrics": {
- "video_fetch": {
- "gemini_video_source": "oss_url",
- "download_duration_ms": 1500,
- "ffmpeg_duration_ms": 2600,
- "total_duration_ms": 4100,
- "downloaded_bytes": 10485760,
- "compressed_bytes": 1048576,
- },
- "gemini_request": {
- "total_duration_ms": 180000,
- "attempts": [
- {
- "attempt": 1,
- "status": "failed",
- "duration_ms": 90000,
- "failure_type": "gemini_client_timeout",
- "exception_type": "WriteTimeout",
- "http_status_code": None,
- "response_body_summary": {
- "http_status_code": 502,
- "text_excerpt": "upstream overloaded",
- },
- }
- ],
- },
- },
- },
- "raw_payload": {},
- },
- {
- "run_id": run_id,
- "policy_run_id": policy_run_id,
- "recall_evidence_id": "evidence_no_valid",
- "platform_content_id": "shipinhao_no_valid",
- "recall_status": "failed",
- "evidence_summary": {
- "schema_version": "v4_gemini_query_relevance.v1",
- "failure_type": "no_valid_play_url",
- "final_status": "failed",
- "retry_count": 1,
- "timing_metrics": {"video_fetch": {"skipped": "no_valid_play_url"}},
- },
- "raw_payload": {},
- },
- ],
- )
- service.runtime.append_jsonl(
- run_id,
- "rule_decisions.jsonl",
- [
- {
- "run_id": run_id,
- "policy_run_id": policy_run_id,
- "decision_id": "decision_001",
- "search_query_id": "q_001",
- "decision_target_id": "content_001",
- "decision_action": "ADD_TO_CONTENT_POOL",
- "decision_reason_code": "v4_query_and_platform_pass",
- "score": 82,
- "scorecard": {
- "total_score": 82,
- "query_relevance_score": 90,
- "platform_performance_score": 74,
- "platform_performance_components": [
- {"field": "statistics.digg_count", "value": 100, "weight": 0.4, "normalized_score": 80},
- {"field": "statistics.comment_count", "value": 8, "weight": 0.2, "normalized_score": 70},
- ],
- },
- "decision_replay_data": {
- "allow_walk": True,
- "walk_gate_snapshot": {
- "score": 82,
- "query_relevance_score": 90,
- "platform_performance_score": 74,
- },
- },
- "raw_payload": {},
- },
- {
- "run_id": run_id,
- "policy_run_id": policy_run_id,
- "decision_id": "decision_003",
- "search_query_id": "q_003",
- "decision_target_id": "content_003",
- "decision_action": "KEEP_CONTENT_FOR_REVIEW",
- "decision_reason_code": "v4_score_review_needed",
- "score": 62,
- "scorecard": {
- "total_score": 62,
- "query_relevance_score": 95,
- "platform_performance_score": 29,
- },
- "decision_replay_data": {"allow_walk": False},
- "raw_payload": {},
- },
- {
- "run_id": run_id,
- "policy_run_id": policy_run_id,
- "decision_id": "decision_tech",
- "search_query_id": "q_003",
- "decision_target_id": "content_tech",
- "decision_action": "TECHNICAL_RETRY_REQUIRED",
- "decision_reason_code": "v4_technical_retry_needed",
- "score": None,
- "scorecard": {
- "failure_type": "gemini_client_timeout",
- "exception_type": "WriteTimeout",
- "retry_count": 2,
- "final_status": "failed",
- "total_score": None,
- },
- "decision_replay_data": {"allow_walk": False},
- "raw_payload": {},
- },
- {
- "run_id": run_id,
- "policy_run_id": policy_run_id,
- "decision_id": "decision_no_valid",
- "search_query_id": "q_003",
- "decision_target_id": "content_no_valid",
- "decision_action": "TECHNICAL_RETRY_REQUIRED",
- "decision_reason_code": "v4_technical_retry_needed",
- "score": None,
- "scorecard": {
- "failure_type": "no_valid_play_url",
- "final_status": "failed",
- "total_score": None,
- },
- "decision_replay_data": {"allow_walk": False},
- "raw_payload": {},
- },
- ],
- )
- service.runtime.append_jsonl(
- run_id,
- "walk_actions.jsonl",
- [
- {
- "run_id": run_id,
- "policy_run_id": policy_run_id,
- "walk_action_id": "walk_001",
- "edge_id": "hashtag_to_query",
- "hashtag": "拉伸",
- "depth": 1,
- "from_node_type": "Content",
- "from_node_id": "douyin_001",
- "to_node_id": "q_002",
- "walk_status": "success",
- "reason_code": "v4_query_and_platform_pass",
- "walk_gate_snapshot": {
- "score": 82,
- "query_relevance_score": 90,
- "platform_performance_score": 74,
- },
- "raw_payload": {},
- },
- ],
- )
- service.runtime.write_json(
- run_id,
- "final_output.json",
- {
- "run_id": run_id,
- "policy_run_id": policy_run_id,
- "content_assets": [{"decision_id": "decision_001", "platform_content_id": "douyin_001"}],
- "review_records": [{"decision_id": "decision_003", "platform_content_id": "douyin_003"}],
- "reject_records": [],
- "summary": {},
- },
- )
- monkeypatch.setattr(api, "service", service)
- client = TestClient(api.app)
- ledger = client.get(f"/runs/{run_id}/flow-ledger").json()
- assert ledger["data_origin_label"] == "本地调试缓存"
- assert ledger["demand_summary"]["id"] == "demand_123"
- assert ledger["demand_summary"]["name"] == "睡前拉伸需求"
- assert ledger["demand_summary"]["description"] == "用户希望看到睡前放松内容"
- assert ledger["demand_summary"]["itemset_ids"] == ["1608101"]
- assert ledger["demand_summary"]["category_paths"] == ["/理念/观念/个人观念/人生观"]
- assert ledger["demand_summary"]["extracted_points"] == ["睡前肩颈放松(目的点 / 帖子 post_001)"]
- assert len(ledger["rows"]) == 3
- assert ledger["summary"]["first_round_query_count"] == 3
- assert ledger["summary"]["extension_query_count"] == 1
- # M9 游走指标(run 级):键齐全且为数值。
- for key in ("walk_video_total", "walk_pooled_count", "walk_action_total", "walk_action_success", "walk_max_depth"):
- assert isinstance(ledger["summary"][key], int)
- assert ledger["summary"]["walk_action_total"] >= 1 # fixture 有 1 个 hashtag_to_query success
- row = next(item for item in ledger["rows"] if item["id"] == "q_001")
- assert row["query"]["method_label"] == "从需求单的 pattern 词来"
- assert row["source"]["details"] == ["需求单号:demand_123", "种子词:睡前拉伸", "来源帖子:seed_001"]
- assert row["first_round_video_total"] == 1
- assert row["video_stats"]["oss_uploaded_count"] == 1
- assert row["rule_summary"]["pool_count"] == 1
- assert row["rule_summary"]["score_summary"]["items"][0]["label"] == "总分"
- assert row["rule_summary"]["score_summary"]["items"][0]["score_label"] == "平均 82 分"
- assert row["rule_summary"]["score_summary"]["items"][1]["label"] == "是否契合需求"
- assert row["rule_summary"]["score_summary"]["items"][1]["score_label"] == "平均 90 分"
- assert row["first_round_videos"][0]["decision"]["label"] == "入池"
- assert row["first_round_videos"][0]["media_status_label"] == "已保存"
- assert row["first_round_videos"][0]["platform_url"] == "https://www.douyin.com/video/douyin_001"
- # 这条视频真正向外走过 1 次(walk_001 hashtag success),首页据此显示「看扩展过程」。
- assert row["first_round_videos"][0]["walk_out_count"] == 1
- assert row["first_round_videos"][0]["decision"]["score_items"][1]["label"] == "是否契合需求"
- assert row["first_round_videos"][0]["decision"]["score_items"][3]["label"] == "点赞表现"
- piaoquan = next(item for item in ledger["rows"] if item["id"] == "q_003")
- assert piaoquan["source"]["label"] == "票圈帖子具体的点"
- assert "帖子 ID:post_888" in piaoquan["source"]["details"]
- assert "内容点 ID:point_456" in piaoquan["source"]["details"]
- assert piaoquan["asset_summary"]["pool_count"] == 0
- assert piaoquan["asset_summary"]["review_count"] == 1
- assert piaoquan["asset_summary"]["headline"] == "待复看 1 条"
- category = next(item for item in ledger["rows"] if item["id"] == "q_004")
- assert "分类树:Pattern 执行 581" in category["source"]["details"]
- assert "分类路径:/理念/观念/个人观念/人生观" in category["source"]["details"]
- assert "所在层级:第 4 层" in category["source"]["details"]
- videos = client.get(f"/runs/{run_id}/flow-ledger/queries/q_001/videos").json()
- assert videos["summary"]["total"] == 1
- assert videos["videos"][0]["oss_url"] == "https://oss.example.com/video.mp4"
- tech_videos = client.get(f"/runs/{run_id}/flow-ledger/queries/q_003/videos").json()
- tech_video = next(item for item in tech_videos["videos"] if item["platform_content_id"] == "douyin_tech")
- assert tech_video["decision"]["label"] == "技术问题"
- assert tech_video["technical_retry_detail"]["stage_label"] == "OpenRouter/Gemini"
- assert tech_video["technical_retry_detail"]["failure_type"] == "gemini_client_timeout"
- assert tech_video["technical_retry_detail"]["timings"]["download_seconds"] == 1.5
- assert tech_video["technical_retry_detail"]["timings"]["ffmpeg_seconds"] == 2.6
- assert tech_video["technical_retry_detail"]["timings"]["gemini_seconds"] == 180.0
- assert tech_video["technical_retry_detail"]["sizes"]["downloaded_mb"] == 10.0
- assert tech_video["technical_retry_detail"]["attempts"][0]["exception_type"] == "WriteTimeout"
- assert tech_video["technical_retry_detail"]["attempts"][0]["response_body_summary"]["http_status_code"] == 502
- assert tech_video["technical_retry_detail"]["openrouter"]["response_summary"]["text_excerpt"] == "upstream overloaded"
- assert tech_video["technical_retry_detail"]["oss"]["payload_mode"] == "no_referer"
- assert tech_video["technical_retry_detail"]["oss"]["response_summary"]["status"] == 10000
- assert tech_video["technical_retry_detail"]["video_url"]["selected_host"] == "findermp.video.qq.com"
- assert tech_video["technical_retry_detail"]["video_url"]["probe_status"] == "verified"
- assert tech_video["technical_retry_detail"]["video_url"]["candidate_counts"] == {"video": 1, "image": 1}
- no_valid_video = next(item for item in tech_videos["videos"] if item["platform_content_id"] == "shipinhao_no_valid")
- assert no_valid_video["technical_retry_detail"]["failure_type"] == "no_valid_play_url"
- assert no_valid_video["technical_retry_detail"]["failure_label"] == "未找到可用正片 URL"
- assert no_valid_video["technical_retry_detail"]["brief_reason"] == "平台结果未找到可用正片 URL"
- assert no_valid_video["technical_retry_detail"]["video_url"]["probe_status"] == "failed_fallback"
- walk = client.get(f"/runs/{run_id}/flow-ledger/queries/q_001/walk").json()
- assert walk["summary"]["total_actions"] == 1
- assert walk["summary"]["extension_query_count"] == 1
- assert walk["lanes"]["tag_query"][0]["edge_label"] == "从视频标签继续游走"
- assert walk["lanes"]["tag_query"][0]["source_video"]["title"] == "睡前 5 分钟肩颈拉伸"
- assert walk["lanes"]["tag_query"][0]["trigger_label"] == "标签:#拉伸"
- assert walk["lanes"]["tag_query"][0]["target_query"]["text"] == "肩颈放松"
- assert walk["lanes"]["tag_query"][0]["target_videos"][0]["title"] == "扩展搜到的视频"
- assert "起点:从视频《睡前 5 分钟肩颈拉伸》触发" in walk["lanes"]["tag_query"][0]["detail_lines"]
- assert "结果:已生成标签搜索词《肩颈放松》,带回 1 条视频。" in walk["lanes"]["tag_query"][0]["detail_lines"]
- assert walk["lanes"]["tag_query"][0]["score_items"][0]["score_label"] == "82 分"
- # M8:query_next_page 边已删,原翻页 lane 断言随之移除。
- # M9+ 全 run 游走图:不按 query 过滤,附游走预算总览。
- run_walk = client.get(f"/runs/{run_id}/flow-ledger/walk").json()
- assert run_walk["summary"]["total_actions"] >= 1
- assert run_walk["summary"]["tag_used"] == 1 # walk_001 hashtag success
- assert run_walk["summary"]["tag_cap"] == 20
- assert run_walk["summary"]["author_used"] == 0
- assert run_walk["summary"]["max_depth_reached"] == 1
- assert run_walk["summary"]["max_depth_cap"] == 5
- assert run_walk["summary"]["walk_video_total"] == 1 # douyin_002 是游走带回
- assert "q_001" in run_walk["summary"]["query_labels"]
- assert any(a["source_video"]["title"] == "睡前 5 分钟肩颈拉伸" for a in run_walk["lanes"]["tag_query"])
- # M9+ 单视频子树:从 douyin_001 出发,只保留它走出去的那一支并递归。
- video_walk = client.get(f"/runs/{run_id}/flow-ledger/videos/douyin_001/walk").json()
- assert video_walk["root_video"]["id"] == "douyin_001"
- assert video_walk["summary"]["total_actions"] >= 1
- assert video_walk["actions"][0]["edge_id"] == "hashtag_to_query"
- assert video_walk["actions"][0]["target_videos"][0]["title"] == "扩展搜到的视频"
- # 没向外走过的视频(douyin_003):子树为空。
- empty_walk = client.get(f"/runs/{run_id}/flow-ledger/videos/douyin_003/walk").json()
- assert empty_walk["root_video"]["id"] == "douyin_003"
- assert empty_walk["summary"]["total_actions"] == 0
- detail = client.get(f"/runs/{run_id}/flow-ledger/videos/douyin_001").json()
- assert detail["video"]["playable_url"] == "https://oss.example.com/video.mp4"
- assert detail["decision"]["label"] == "入池"
- service.runtime.append_jsonl(
- run_id,
- "search_queries.jsonl",
- [
- {
- "run_id": run_id,
- "policy_run_id": policy_run_id,
- "search_query_id": "q_005",
- "search_query": "新搜索词",
- "search_query_generation_method": "seed_term",
- "raw_payload": {},
- }
- ],
- )
- cached = client.get(f"/runs/{run_id}/flow-ledger").json()
- assert cached["summary"]["first_round_query_count"] == 3
- debug_fresh = client.get(f"/runs/{run_id}/flow-ledger?debug=true").json()
- assert debug_fresh["summary"]["first_round_query_count"] == 4
|