feat: 生成图片提示词agent结构封装
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Agent 模块(按功能拆分:chat / memoir)
|
||||
Agent 模块(按功能拆分:chat / memoir / image_prompt)
|
||||
"""
|
||||
from app.agents.chat import (
|
||||
ChatOrchestrator,
|
||||
@@ -7,6 +7,7 @@ from app.agents.chat import (
|
||||
InterviewAgent,
|
||||
ProfileAgent,
|
||||
)
|
||||
from app.agents.image_prompt import ImagePromptOrchestrator, PromptGenerationAgent
|
||||
from app.agents.memoir import BackgroundTaskRunner, MemoryAgent
|
||||
|
||||
__all__ = [
|
||||
@@ -16,4 +17,6 @@ __all__ = [
|
||||
"ProfileAgent",
|
||||
"InterviewAgent",
|
||||
"BackgroundTaskRunner",
|
||||
"ImagePromptOrchestrator",
|
||||
"PromptGenerationAgent",
|
||||
]
|
||||
|
||||
8
api/app/agents/image_prompt/__init__.py
Normal file
8
api/app/agents/image_prompt/__init__.py
Normal file
@@ -0,0 +1,8 @@
|
||||
"""图片提示词模块:ImagePromptOrchestrator + PromptGenerationAgent"""
|
||||
from app.agents.image_prompt.orchestrator import ImagePromptOrchestrator
|
||||
from app.agents.image_prompt.prompt_agent import PromptGenerationAgent
|
||||
|
||||
__all__ = [
|
||||
"ImagePromptOrchestrator",
|
||||
"PromptGenerationAgent",
|
||||
]
|
||||
57
api/app/agents/image_prompt/orchestrator.py
Normal file
57
api/app/agents/image_prompt/orchestrator.py
Normal file
@@ -0,0 +1,57 @@
|
||||
"""
|
||||
ImagePromptOrchestrator:图片提示词生成编排器。
|
||||
根据调用方(封面/正文)选择 build_prompt 或 build_cover_prompt;
|
||||
统一异常处理和回退;内部委托 PromptGenerationAgent。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Optional
|
||||
|
||||
from app.features.memoir.memoir_images.settings import MemoirImageSettings
|
||||
|
||||
from app.agents.image_prompt.prompt_agent import PromptGenerationAgent
|
||||
|
||||
|
||||
class ImagePromptOrchestrator:
|
||||
"""
|
||||
图片提示词编排器。
|
||||
区分封面 vs 正文配图,统一调用 PromptGenerationAgent;
|
||||
异常与回退由 PromptGenerationAgent(底层 MemoirImagePromptService)处理。
|
||||
"""
|
||||
|
||||
def __init__(self, llm: Optional[Any], settings: MemoirImageSettings) -> None:
|
||||
self._agent = PromptGenerationAgent(llm=llm, settings=settings)
|
||||
|
||||
def build_prompt(
|
||||
self,
|
||||
chapter_title: str,
|
||||
chapter_category: str,
|
||||
description: str,
|
||||
context_excerpt: str,
|
||||
) -> dict[str, str]:
|
||||
"""
|
||||
生成正文配图的 prompt。
|
||||
委托 PromptGenerationAgent,已含 LLM 调用失败时的 fallback 逻辑。
|
||||
"""
|
||||
return self._agent.build_prompt(
|
||||
chapter_title=chapter_title,
|
||||
chapter_category=chapter_category,
|
||||
description=description,
|
||||
context_excerpt=context_excerpt,
|
||||
)
|
||||
|
||||
def build_cover_prompt(
|
||||
self,
|
||||
chapter_title: str,
|
||||
chapter_category: str,
|
||||
context_excerpt: str,
|
||||
) -> dict[str, str]:
|
||||
"""
|
||||
生成章节封面的 prompt。
|
||||
委托 PromptGenerationAgent,已含 LLM 调用失败时的 fallback 逻辑。
|
||||
"""
|
||||
return self._agent.build_cover_prompt(
|
||||
chapter_title=chapter_title,
|
||||
chapter_category=chapter_category,
|
||||
context_excerpt=context_excerpt,
|
||||
)
|
||||
50
api/app/agents/image_prompt/prompt_agent.py
Normal file
50
api/app/agents/image_prompt/prompt_agent.py
Normal file
@@ -0,0 +1,50 @@
|
||||
"""
|
||||
PromptGenerationAgent:生成回忆录配图的 image-generation prompt。
|
||||
接收 chapter_title、chapter_category、description、context_excerpt,
|
||||
调用 LLM 或 fallback 生成 {prompt, style, size}。
|
||||
底层委托 MemoirImagePromptService,保持对外接口兼容。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Optional
|
||||
|
||||
from app.features.memoir.memoir_images.prompting import MemoirImagePromptService
|
||||
from app.features.memoir.memoir_images.settings import MemoirImageSettings
|
||||
|
||||
|
||||
class PromptGenerationAgent:
|
||||
"""
|
||||
图片提示词生成 Specialist Agent。
|
||||
封装 MemoirImagePromptService,提供 build_prompt / build_cover_prompt 接口。
|
||||
"""
|
||||
|
||||
def __init__(self, llm: Optional[Any], settings: MemoirImageSettings) -> None:
|
||||
self._service = MemoirImagePromptService(llm=llm, settings=settings)
|
||||
|
||||
def build_prompt(
|
||||
self,
|
||||
chapter_title: str,
|
||||
chapter_category: str,
|
||||
description: str,
|
||||
context_excerpt: str,
|
||||
) -> dict[str, str]:
|
||||
"""生成正文配图的 image-generation prompt。"""
|
||||
return self._service.build_prompt(
|
||||
chapter_title=chapter_title,
|
||||
chapter_category=chapter_category,
|
||||
description=description,
|
||||
context_excerpt=context_excerpt,
|
||||
)
|
||||
|
||||
def build_cover_prompt(
|
||||
self,
|
||||
chapter_title: str,
|
||||
chapter_category: str,
|
||||
context_excerpt: str,
|
||||
) -> dict[str, str]:
|
||||
"""生成章节封面图的 image-generation prompt。"""
|
||||
return self._service.build_cover_prompt(
|
||||
chapter_title=chapter_title,
|
||||
chapter_category=chapter_category,
|
||||
context_excerpt=context_excerpt,
|
||||
)
|
||||
@@ -42,6 +42,7 @@ from app.features.memoir.memoir_images.parser import (
|
||||
)
|
||||
import hashlib
|
||||
from app.core.dependencies import get_image_generator
|
||||
from app.agents.image_prompt import ImagePromptOrchestrator
|
||||
from app.features.memoir.memoir_images.prompting import MemoirImagePromptService
|
||||
from app.features.memoir.memoir_images.schema import (
|
||||
completed_image_assets,
|
||||
@@ -883,7 +884,7 @@ def generate_chapter_images(self, chapter_id: str):
|
||||
logger.info("章节补图跳过: chapter=%s, reason=locked", chapter_id)
|
||||
return {"status": "locked"}
|
||||
|
||||
prompt_service = MemoirImagePromptService(_get_llm(), settings)
|
||||
prompt_orchestrator = ImagePromptOrchestrator(_get_llm(), settings)
|
||||
image_generator = get_image_generator()
|
||||
storage = TencentCosStorageService.from_env()
|
||||
logger.info(
|
||||
@@ -922,7 +923,7 @@ def generate_chapter_images(self, chapter_id: str):
|
||||
sections_ordered = sorted(sections, key=lambda s: getattr(s, "order_index", 0))
|
||||
first_content = (sections_ordered[0].content or "").strip() if sections_ordered else ""
|
||||
context_excerpt = " ".join(first_content.split("\n")[:5])[:200]
|
||||
prompt_data = prompt_service.build_cover_prompt(
|
||||
prompt_data = prompt_orchestrator.build_cover_prompt(
|
||||
chapter_title=chapter.title,
|
||||
chapter_category=chapter.category or "",
|
||||
context_excerpt=context_excerpt,
|
||||
@@ -985,7 +986,7 @@ def generate_chapter_images(self, chapter_id: str):
|
||||
try:
|
||||
context_lines = (section.content or "").strip().split("\n")[:5]
|
||||
context_excerpt = " ".join(context_lines)[:200]
|
||||
prompt_data = prompt_service.build_prompt(
|
||||
prompt_data = prompt_orchestrator.build_prompt(
|
||||
chapter_title=chapter.title,
|
||||
chapter_category=chapter.category or "",
|
||||
description=current_item.get("description", ""),
|
||||
|
||||
@@ -63,7 +63,7 @@ class GenerateChapterImagesPersistenceTest(unittest.TestCase):
|
||||
@patch("app.tasks.memoir_tasks.get_sync_db")
|
||||
@patch("app.tasks.memoir_tasks.TencentCosStorageService")
|
||||
@patch("app.tasks.memoir_tasks.get_image_generator")
|
||||
@patch("app.tasks.memoir_tasks.MemoirImagePromptService")
|
||||
@patch("app.tasks.memoir_tasks.ImagePromptOrchestrator")
|
||||
@patch("app.tasks.memoir_tasks._release_chapter_image_lock")
|
||||
@patch("app.tasks.memoir_tasks._acquire_chapter_image_lock", return_value=True)
|
||||
def test_successful_generation_persists_completed_status(
|
||||
|
||||
@@ -92,7 +92,7 @@ class GenerateChapterImagesTaskTest(unittest.TestCase):
|
||||
@patch("app.tasks.memoir_tasks.get_sync_db")
|
||||
@patch("app.tasks.memoir_tasks.TencentCosStorageService")
|
||||
@patch("app.tasks.memoir_tasks.get_image_generator")
|
||||
@patch("app.tasks.memoir_tasks.MemoirImagePromptService")
|
||||
@patch("app.tasks.memoir_tasks.ImagePromptOrchestrator")
|
||||
def test_generate_chapter_images_skips_when_lock_is_already_held(
|
||||
self,
|
||||
prompt_service_cls,
|
||||
@@ -120,7 +120,7 @@ class GenerateChapterImagesTaskTest(unittest.TestCase):
|
||||
@patch("app.tasks.memoir_tasks.get_sync_db")
|
||||
@patch("app.tasks.memoir_tasks.TencentCosStorageService")
|
||||
@patch("app.tasks.memoir_tasks.get_image_generator")
|
||||
@patch("app.tasks.memoir_tasks.MemoirImagePromptService")
|
||||
@patch("app.tasks.memoir_tasks.ImagePromptOrchestrator")
|
||||
@patch("app.tasks.memoir_tasks._release_chapter_image_lock")
|
||||
@patch("app.tasks.memoir_tasks._acquire_chapter_image_lock", return_value=True)
|
||||
def test_generate_chapter_images_retries_when_any_item_generation_fails(
|
||||
@@ -162,7 +162,7 @@ class GenerateChapterImagesTaskTest(unittest.TestCase):
|
||||
@patch("app.tasks.memoir_tasks.get_sync_db")
|
||||
@patch("app.tasks.memoir_tasks.TencentCosStorageService")
|
||||
@patch("app.tasks.memoir_tasks.get_image_generator")
|
||||
@patch("app.tasks.memoir_tasks.MemoirImagePromptService")
|
||||
@patch("app.tasks.memoir_tasks.ImagePromptOrchestrator")
|
||||
@patch("app.tasks.memoir_tasks._release_chapter_image_lock")
|
||||
@patch("app.tasks.memoir_tasks._acquire_chapter_image_lock", return_value=True)
|
||||
def test_generate_chapter_images_marks_successful_item_completed(
|
||||
@@ -203,7 +203,7 @@ class GenerateChapterImagesTaskTest(unittest.TestCase):
|
||||
@patch("app.tasks.memoir_tasks.get_sync_db")
|
||||
@patch("app.tasks.memoir_tasks.TencentCosStorageService")
|
||||
@patch("app.tasks.memoir_tasks.get_image_generator")
|
||||
@patch("app.tasks.memoir_tasks.MemoirImagePromptService")
|
||||
@patch("app.tasks.memoir_tasks.ImagePromptOrchestrator")
|
||||
@patch("app.tasks.memoir_tasks.MemoirImageSettings.from_env")
|
||||
def test_generate_chapter_images_returns_disabled_when_feature_flag_is_off(
|
||||
self,
|
||||
@@ -242,7 +242,7 @@ class GenerateChapterImagesTaskTest(unittest.TestCase):
|
||||
@patch("app.tasks.memoir_tasks.get_sync_db")
|
||||
@patch("app.tasks.memoir_tasks.TencentCosStorageService")
|
||||
@patch("app.tasks.memoir_tasks.get_image_generator")
|
||||
@patch("app.tasks.memoir_tasks.MemoirImagePromptService")
|
||||
@patch("app.tasks.memoir_tasks.ImagePromptOrchestrator")
|
||||
@patch("app.tasks.memoir_tasks._release_chapter_image_lock")
|
||||
@patch("app.tasks.memoir_tasks._acquire_chapter_image_lock", return_value=True)
|
||||
def test_generate_chapter_images_converts_non_png_payload_before_upload(
|
||||
@@ -287,7 +287,7 @@ class GenerateChapterImagesTaskTest(unittest.TestCase):
|
||||
@patch("app.tasks.memoir_tasks.get_sync_db")
|
||||
@patch("app.tasks.memoir_tasks.TencentCosStorageService")
|
||||
@patch("app.tasks.memoir_tasks.get_image_generator")
|
||||
@patch("app.tasks.memoir_tasks.MemoirImagePromptService")
|
||||
@patch("app.tasks.memoir_tasks.ImagePromptOrchestrator")
|
||||
@patch("app.tasks.memoir_tasks._release_chapter_image_lock")
|
||||
@patch("app.tasks.memoir_tasks._acquire_chapter_image_lock", return_value=True)
|
||||
def test_generate_chapter_images_fails_without_retry_on_permanent_cos_error(
|
||||
@@ -330,7 +330,7 @@ class GenerateChapterImagesTaskTest(unittest.TestCase):
|
||||
@patch("app.tasks.memoir_tasks.get_sync_db")
|
||||
@patch("app.tasks.memoir_tasks.TencentCosStorageService")
|
||||
@patch("app.tasks.memoir_tasks.get_image_generator")
|
||||
@patch("app.tasks.memoir_tasks.MemoirImagePromptService")
|
||||
@patch("app.tasks.memoir_tasks.ImagePromptOrchestrator")
|
||||
@patch("app.tasks.memoir_tasks._release_chapter_image_lock")
|
||||
@patch("app.tasks.memoir_tasks._acquire_chapter_image_lock", return_value=True)
|
||||
def test_generate_chapter_images_skips_completed_items_for_idempotency(
|
||||
|
||||
Reference in New Issue
Block a user