Files
beige/backend/tests/test_fission_pipeline.py
yangqianqian d85dcd401b 第11环裂变重写:对齐上线版 split.js 一次LLM出N套完整笔记包
架构从"扇出N个GenerationTask各跑完整管道"改为"一次LLM调用直接出N套
完整笔记包(N=1~5)",落 FissionNote 表 + 独立展示页。

后端:
- 018迁移:fission_notes 表(文案JSON+score+passed+imagePlan+images+status)
- fission_prompt:FISSION_SYSTEM+三档参考度(low/mid/high)+normalize_tags+品类兜底
- fission_pipeline:一次LLM出N套→各评分(@80合格线)→排序→落库,不达标标
  needs_optimization 非丢弃;apiports 503 回落 codeproxy gpt-5.5 强档兜底
- fission_images:每套串行调现有生图接口(零改动image_gen/storyboard)
- tasks.py:run_fission_pipeline Celery task,删旧扇出注入
- api/v1/fission:进度聚合FissionNote + GET /fission/{id}/notes(剥内部字段)

前端:FissionProgress对齐状态机 + N套独立展示页 + FissionNoteCard

测试:test_fission_engine(19)+test_fission_pipeline(5) 全过;104 全量回归绿

实测task5(fanout=2,mid)端到端跑通:一次出2套→seq0=85过/seq1=79标优化→
生图codeproxy/edits→1024×1536去AI化→task completed→notes端点返完整数据

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 11:17:37 +08:00

83 lines
3.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
裂变编排层 _score_notes 测试(合格线@80 + 违禁词硬拦截逻辑)
mock llm_score_copy验证 _passed = passed AND not _block 的安全关键规则。
排序/降级落库逻辑(needs_optimization=0 if passed else 1)在 execute_fission_pipeline
本文件只锁评分写回的 _score/_block/_passed 三字段语义。
"""
import sys
import os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
import unittest.mock as mock
from app.services.fission_pipeline import _score_notes
SOURCE = {"title": "", "content": "源正文"}
def _patch_scorer(monkeypatch, results):
"""按调用顺序返回 results 里的评分 dict。"""
calls = {"i": 0}
async def fake(client, copy, source, banned=None, pass_score=80):
r = results[calls["i"] % len(results)]
calls["i"] += 1
return r
monkeypatch.setattr("app.services.ai_engine.llm_scorer.llm_score_copy", fake)
def test_score_high_no_banned_passes(monkeypatch):
_patch_scorer(monkeypatch, [
{"score": 88, "passed": True, "banned_words_found": []},
])
notes = [{"title": "a", "content": "c", "tags": []}]
_score_notes(mock.MagicMock(), notes, SOURCE, [])
assert notes[0]["_score"] == 88
assert notes[0]["_block"] is False
assert notes[0]["_passed"] is True
def test_score_low_marks_not_passed(monkeypatch):
# 低于合格线 → 不达标,但分数照写(落库时标 needs_optimization 不丢弃)
_patch_scorer(monkeypatch, [
{"score": 62, "passed": False, "banned_words_found": []},
])
notes = [{"title": "a", "content": "c", "tags": []}]
_score_notes(mock.MagicMock(), notes, SOURCE, [])
assert notes[0]["_score"] == 62
assert notes[0]["_passed"] is False
def test_score_banned_word_hard_blocks_even_if_passed(monkeypatch):
# 违禁词硬拦截:即便分数达标 passed=True命中违禁词也强制 _passed=False
_patch_scorer(monkeypatch, [
{"score": 95, "passed": True, "banned_words_found": ["美白"]},
])
notes = [{"title": "美白神器", "content": "c", "tags": []}]
_score_notes(mock.MagicMock(), notes, SOURCE, ["美白"])
assert notes[0]["_block"] is True
assert notes[0]["_passed"] is False
def test_score_exception_records_zero(monkeypatch):
# 评分接口抛异常 → 记0分不达标不让整批裂变崩
async def boom(*a, **k):
raise RuntimeError("中转站429")
monkeypatch.setattr("app.services.ai_engine.llm_scorer.llm_score_copy", boom)
notes = [{"title": "a", "content": "c", "tags": []}]
_score_notes(mock.MagicMock(), notes, SOURCE, [])
assert notes[0]["_score"] == 0
assert notes[0]["_passed"] is False
def test_score_multiple_notes_each_scored(monkeypatch):
_patch_scorer(monkeypatch, [
{"score": 90, "passed": True, "banned_words_found": []},
{"score": 50, "passed": False, "banned_words_found": []},
])
notes = [{"title": "a", "content": "c", "tags": []},
{"title": "b", "content": "d", "tags": []}]
_score_notes(mock.MagicMock(), notes, SOURCE, [])
assert notes[0]["_passed"] is True
assert notes[1]["_passed"] is False