229 lines
10 KiB
Python
229 lines
10 KiB
Python
"""E2E stub server: deterministic LLM payloads per workflow stage.
|
|
|
|
Injects a dispatcher into main_mod.get_client so the real FastAPI app,
|
|
pydantic validation, evidence sanitization and the frontend run unchanged.
|
|
Run on the same port the real server uses (index.html fetches /api/* same-origin).
|
|
"""
|
|
import uvicorn
|
|
import main as main_mod
|
|
|
|
ANALYZE = {
|
|
"essay_summary": "文章以一支静止的铅笔开头,描述作者遇到一个无法立即解决的问题,并从中学会了耐心,最后回到书桌前继续面对。",
|
|
"prompt_alignment": {"prompt_intent": "", "current_alignment": "", "optional_opportunity": ""},
|
|
"paragraphs": [
|
|
{
|
|
"id": "p1",
|
|
"original_text": "The pencil in my hand was still. I wanted to find the answer.",
|
|
"natural_meaning_zh": "我手里的铅笔静止不动,我想要找到答案。",
|
|
"semantic_anchor": "铅笔静止暗示思考停滞,内心渴望答案。",
|
|
"optional_content_opportunity": "",
|
|
},
|
|
{
|
|
"id": "p2",
|
|
"original_text": "I met a problem that I could not solve right away.",
|
|
"natural_meaning_zh": "我遇到了一个无法立刻解决的问题。",
|
|
"semantic_anchor": "明确点出问题的存在,且无法立即解决。",
|
|
"optional_content_opportunity": "",
|
|
},
|
|
{
|
|
"id": "p3",
|
|
"original_text": "I learned to be patient.",
|
|
"natural_meaning_zh": "我学会了耐心。",
|
|
"semantic_anchor": "核心教训是耐心,但未说明如何学到。",
|
|
"optional_content_opportunity": "",
|
|
},
|
|
{
|
|
"id": "p4",
|
|
"original_text": "Now, I sit at my desk again.",
|
|
"natural_meaning_zh": "现在,我再次坐在书桌前。",
|
|
"semantic_anchor": "回到起点,暗示重新面对问题,但未说明结果。",
|
|
"optional_content_opportunity": "",
|
|
},
|
|
],
|
|
}
|
|
|
|
DIAGNOSE = {
|
|
"overall_diagnosis": "文章极短,但每一段都呈现典型的生成式写作模式:抽象陈述、缺乏具体细节、结构过于简化和对称。",
|
|
"patterns": [
|
|
{
|
|
"pattern_id": "P02",
|
|
"name": "Explicit Lesson",
|
|
"category": "growth",
|
|
"affected_paragraphs": ["p3"],
|
|
"evidence": ["I learned to be patient."],
|
|
"why_ai_like": "总结式结尾是生成模型最顺手的收束方式。",
|
|
"human_impact": "直接说教,读者看不到具体如何学会。",
|
|
"transformation_rule": "Lesson -> Change in Judgment",
|
|
},
|
|
{
|
|
"pattern_id": "P08",
|
|
"name": "Circular Ending",
|
|
"category": "structure",
|
|
"affected_paragraphs": ["p4"],
|
|
"evidence": ["Now, I sit at my desk again."],
|
|
"why_ai_like": "首尾呼应结构容易讨巧。",
|
|
"human_impact": "只剩结构对称,没有新信息。",
|
|
"transformation_rule": "Close on a Concrete Action",
|
|
},
|
|
],
|
|
"paragraph_briefs": [
|
|
{
|
|
"paragraph_id": "p1",
|
|
"confirmed_meaning": "铅笔静止暗示思考停滞,内心渴望答案。",
|
|
"rewrite_goal": "把静止的铅笔转化为具体的场景与内心活动。",
|
|
"ai_focus": "抽象陈述;缺乏具体细节",
|
|
"must_preserve": [],
|
|
"annotations": [
|
|
{
|
|
"pattern_id": "P01",
|
|
"category": "rhetoric",
|
|
"kind_label": "修辞包装",
|
|
"title": "象征开头",
|
|
"evidence": ["The pencil in my hand was still."],
|
|
"observation": "用静止铅笔暗示停滞,但没给任何具体场景。",
|
|
"rewrite_action": "补一个真实瞬间:笔停在纸上的位置、当时我在做什么。",
|
|
"why_ai_like": "象征开头是生成模型高频起笔方式。",
|
|
}
|
|
],
|
|
"context_hint": "上一段已完成 0 个反射,这一段只需建立画面。",
|
|
"scaffold": "The pencil stayed in my hand because ______.",
|
|
"reference_snippet": "The pencil had stopped halfway across the page.",
|
|
},
|
|
{
|
|
"paragraph_id": "p2",
|
|
"confirmed_meaning": "明确点出问题的存在,且无法立即解决。",
|
|
"rewrite_goal": "把问题具体化,写出具体内容和无法解决的原因。",
|
|
"ai_focus": "抽象陈述",
|
|
"must_preserve": [],
|
|
"annotations": [
|
|
{
|
|
"pattern_id": "P01",
|
|
"category": "rhetoric",
|
|
"kind_label": "修辞包装",
|
|
"title": "泛化问题",
|
|
"evidence": ["a problem that I could not solve"],
|
|
"observation": "问题本身是空洞的泛指。",
|
|
"rewrite_action": "写出问题具体是什么。",
|
|
"why_ai_like": "泛化名词让生成模型无需真实素材。",
|
|
}
|
|
],
|
|
"context_hint": "上一段已建立画面,这一段写具体困境。",
|
|
"scaffold": "The problem was ______.",
|
|
"reference_snippet": "",
|
|
},
|
|
{
|
|
"paragraph_id": "p3",
|
|
"confirmed_meaning": "核心教训是耐心,但未说明如何学到。",
|
|
"rewrite_goal": "把耐心转化为具体行为,并保留 secret code。",
|
|
"ai_focus": "总结式说教",
|
|
"must_preserve": ["secret code"],
|
|
"annotations": [
|
|
{
|
|
"pattern_id": "P02",
|
|
"category": "growth",
|
|
"kind_label": "抽象总结",
|
|
"title": "直接点题",
|
|
"evidence": ["I learned to be patient."],
|
|
"observation": "用一句总结构束教训。",
|
|
"rewrite_action": "写出耐心在行为上如何体现,保留 secret code。",
|
|
"why_ai_like": "教训总结是生成模型收束习惯。",
|
|
}
|
|
],
|
|
"context_hint": "上一段已写具体困境,这一段写困境中的一次具体选择。",
|
|
"scaffold": "Each day I ______ instead of ______.",
|
|
"reference_snippet": "",
|
|
},
|
|
{
|
|
"paragraph_id": "p4",
|
|
"confirmed_meaning": "回到起点,暗示重新面对问题,但未说明结果。",
|
|
"rewrite_goal": "把回到起点转化为具体行动,暗示改变但不总结。",
|
|
"ai_focus": "结构对称",
|
|
"must_preserve": [],
|
|
"annotations": [
|
|
{
|
|
"pattern_id": "P08",
|
|
"category": "structure",
|
|
"kind_label": "结构路标",
|
|
"title": "首尾呼应",
|
|
"evidence": ["Now, I sit at my desk again."],
|
|
"observation": "用呼应结构收尾,没有新内容。",
|
|
"rewrite_action": "写一个具体的下一步动作。",
|
|
"why_ai_like": "首尾对称让文章看起来完整。",
|
|
}
|
|
],
|
|
"context_hint": "上一段已写具体选择,这一段以行动收尾,不写总结。",
|
|
"scaffold": "This time, I ______.",
|
|
"reference_snippet": "",
|
|
},
|
|
],
|
|
"optional_suggestion": "若有真实素材,可补当时问题发生的具体场景。",
|
|
}
|
|
|
|
RECHECK_PASS = {
|
|
"status": "pass",
|
|
"checked_rewrite_version": "rv_1",
|
|
"global_checks": {
|
|
"semantic_preservation": "pass",
|
|
"voice_consistency": "pass",
|
|
"pattern_reduction": "pass",
|
|
"new_pattern": "pass",
|
|
"coherence": "pass",
|
|
"reference_copying": "pass",
|
|
"word_limit": "pass",
|
|
},
|
|
"revision_targets": [],
|
|
}
|
|
|
|
SCAFFOLD = {"paragraph_id": "p1", "scaffold": "The pencil stayed in my hand because ______."}
|
|
REFERENCE = {"paragraph_id": "p1", "starter": "The pencil had stopped halfway across the page.", "reference_snippet": "The pencil had stopped halfway across the page."}
|
|
|
|
RECHECK_REVISION = {
|
|
"status": "revision_required",
|
|
"checked_rewrite_version": "rv_1",
|
|
"global_checks": {
|
|
"semantic_preservation": "pass",
|
|
"voice_consistency": "pass",
|
|
"pattern_reduction": "pass",
|
|
"new_pattern": "pass",
|
|
"coherence": "pass",
|
|
"reference_copying": "pass",
|
|
"word_limit": "pass",
|
|
},
|
|
"revision_targets": [
|
|
{
|
|
"paragraph_id": "p3",
|
|
"blocking_issue": "新版本仍是 Explicit Lesson:直接总结学到了什么。",
|
|
"evidence": ["gradually came to realize"],
|
|
"single_revision_goal": "不要总结我学到了什么,写判断怎么变化。",
|
|
}
|
|
],
|
|
}
|
|
|
|
|
|
class StubCompleter:
|
|
def complete_json(self, system, user, validate=None):
|
|
# NOTE: system prompts share a _TASK_CHAIN prefix (contains 全文复检 etc.)
|
|
# so dispatch must use stage-unique keywords, most specific first.
|
|
if "给我一个写作起点" in system:
|
|
return SCAFFOLD
|
|
if "Level 2" in system:
|
|
return REFERENCE
|
|
if "Human Voice Diagnosis" in system:
|
|
return DIAGNOSE
|
|
if "最终阶段" in system:
|
|
# mockFillFail injects revision-case wording; detect it to serve the
|
|
# revision_required path, otherwise the golden-path pass response.
|
|
# NOTE: cannot use "gradually came to realize" — previous_revision_target
|
|
# is embedded in the prompt (with its evidence quoting that phrase), so it
|
|
# matches on every resubmission even when the current text is fixed.
|
|
if "uncertainty is an important part of growth" in user:
|
|
return RECHECK_REVISION
|
|
return RECHECK_PASS
|
|
return ANALYZE
|
|
|
|
|
|
main_mod.get_client = lambda: StubCompleter()
|
|
|
|
if __name__ == "__main__":
|
|
uvicorn.run(main_mod.app, host="127.0.0.1", port=8000, log_level="warning")
|