feat: 增加内容生成配置管理

This commit is contained in:
2026-08-03 17:23:42 +08:00
parent 3f8ff30bbc
commit 8ee4d65b7b
17 changed files with 1348 additions and 43 deletions

View File

@@ -0,0 +1,116 @@
from __future__ import annotations
import json
from types import SimpleNamespace
import pytest
from fastapi import HTTPException
from sqlalchemy import create_engine
from sqlalchemy.orm import Session
from sqlalchemy.pool import StaticPool
from app.models import Base
from app.models.ai_config import ContentGenerationConfig
from app.services.content_generation_config_service import (
SAMPLE_VALUES,
ContentGenerationConfigService,
)
from app.services.tracked_generation_service import TrackedGenerationService
def _db() -> Session:
engine = create_engine("sqlite:///:memory:", connect_args={"check_same_thread": False}, poolclass=StaticPool)
Base.metadata.create_all(engine)
return Session(engine)
def test_template_preview_keeps_locked_notice_and_rejects_unknown_variables():
content = ContentGenerationConfigService.preview(
"help_card",
"问题:{{issue}}\n摘要:{{summary}}",
)
assert "问题:我第一次参加带练" in content
assert "不会自动发送给老师" in content
assert "不代表已经转人工处理" in content
with pytest.raises(HTTPException) as error:
ContentGenerationConfigService.preview(
"help_card",
"问题:{{issue}}\n摘要:{{summary}}\n未知:{{unknown}}",
)
assert error.value.status_code == 400
assert "未知变量" in str(error.value.detail)
with pytest.raises(HTTPException) as malformed:
ContentGenerationConfigService.preview(
"help_card",
"问题:{{issue}}\n摘要:{{summary}}\n错误变量:{{bad-name}}",
)
assert malformed.value.status_code == 400
def test_config_versions_save_reset_and_restore_without_overwriting_history():
with _db() as db:
first = ContentGenerationConfigService.save(
db,
config_type="help_card",
template_content="自定义一:{{issue}}\n{{summary}}",
instruction_content="只整理明确内容",
updated_by=1,
)
db.commit()
first_id = first.id
reset = ContentGenerationConfigService.reset(db, config_type="help_card", updated_by=1)
db.commit()
restored = ContentGenerationConfigService.restore(
db,
config_type="help_card",
source_config_id=first_id,
updated_by=2,
)
db.commit()
assert db.query(ContentGenerationConfig).count() == 3
assert restored.id != first_id
assert restored.source_config_id == first_id
assert restored.change_type == "restore"
assert restored.template_content == "自定义一:{{issue}}\n{{summary}}"
assert reset.change_type == "reset"
assert ContentGenerationConfigService.current(db, "help_card").id == restored.id
def test_ai_generation_uses_configured_instruction_and_only_accepts_allowed_fields(monkeypatch):
captured: dict[str, str] = {}
def fake_generate(db, *, prompt, scenario, user_id):
captured["prompt"] = prompt
return SimpleNamespace(
answer=json.dumps(
{
"issue": "整理后的问题",
"summary": "整理后的摘要",
"current_focus": "整理后的关注",
"unknown": "不能进入卡片",
},
ensure_ascii=False,
)
)
monkeypatch.setattr(TrackedGenerationService, "generate", fake_generate)
with _db() as db:
generated, used_fallback = ContentGenerationConfigService.generate_values(
db,
config_type="help_card",
instruction_content="优先保留用户原话",
values=dict(SAMPLE_VALUES),
user_id=1,
)
assert used_fallback is False
assert generated["issue"] == "整理后的问题"
assert generated["summary"] == "整理后的摘要"
assert "unknown" not in generated
assert "优先保留用户原话" in captured["prompt"]
assert "不得分析人格" in captured["prompt"]

View File

@@ -9,6 +9,7 @@ from sqlalchemy.orm import Session
from sqlalchemy.pool import StaticPool
from app.models import Base
from app.models.ai_config import ContentGenerationConfig
from app.models.chat import ChatMessage, ChatSession, TopicSession
from app.models.entitlement import EntitlementPlan
from app.models.growth import TeacherHelpCard
@@ -43,6 +44,15 @@ def test_generate_help_card_from_topic_summary_and_mark_copied():
started_at=_now(),
)
db.add_all([user, plan, session, topic])
db.add(
ContentGenerationConfig(
config_type="help_card",
template_content="【给老师的求助卡·自定义模板】\n问题:{{issue}}\n摘要:{{summary}}",
instruction_content="只整理用户明确表达的内容",
change_type="save",
updated_by=1,
)
)
db.add_all(
[
ChatMessage(id=1, session_id=1, topic_session_id=1, user_id=1, role="user", content="我练习时身体抗拒。", created_at=_now()),
@@ -54,6 +64,7 @@ def test_generate_help_card_from_topic_summary_and_mark_copied():
card = HelpCardService.generate_for_session(db, user=user, session=session)
assert "给老师的求助卡" in card.content
assert "自定义模板" in card.content
assert "不会自动发送给老师" in card.content
assert "阴影人格练习步骤是否正确" in card.content
assert "情绪 / 身体感受" not in card.content

View File

@@ -9,6 +9,7 @@ from sqlalchemy.orm import Session
from sqlalchemy.pool import StaticPool
from app.models import Base
from app.models.ai_config import ContentGenerationConfig
from app.models.chat import ChatMessage, ChatSession, TopicSession
from app.models.entitlement import EntitlementPlan
from app.models.growth import ShareDraft
@@ -43,6 +44,15 @@ def test_generate_share_draft_from_topic_summary_and_mark_copied():
started_at=_now(),
)
db.add_all([user, plan, session, topic])
db.add(
ContentGenerationConfig(
config_type="share_draft",
template_content="【实修分享稿草稿·自定义模板】\n主题:{{issue}}\n回顾:{{summary}}",
instruction_content="只整理当下明确谈到的内容",
change_type="save",
updated_by=1,
)
)
db.add_all(
[
ChatMessage(id=1, session_id=1, topic_session_id=1, user_id=1, role="user", content="我不敢表达。", created_at=_now()),
@@ -54,6 +64,7 @@ def test_generate_share_draft_from_topic_summary_and_mark_copied():
draft = ShareDraftService.generate_for_session(db, user=user, session=session)
assert "实修分享稿草稿" in draft.content
assert "自定义模板" in draft.content
assert "系统不会自动发送到任何群" in draft.content
assert "不代表结论" in draft.content
assert "情绪和身体反应" not in draft.content