feat: 引入deepseek结构化输出
This commit is contained in:
@@ -16,6 +16,7 @@ from app.agents.chat.prompts_profile import (
|
||||
get_profile_followup_prompt,
|
||||
get_profile_greeting_prompt,
|
||||
)
|
||||
from app.features.memoir.memoir_images.json_payload import extract_json_payload
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
@@ -58,9 +59,13 @@ class ProfileAgent:
|
||||
prompt = get_profile_extraction_prompt(
|
||||
user_message, missing_fields, recent_dialogue=recent_dialogue or None
|
||||
)
|
||||
response = await self.llm.ainvoke(prompt)
|
||||
json_llm = self.llm.bind(
|
||||
model_kwargs={"response_format": {"type": "json_object"}},
|
||||
max_tokens=512,
|
||||
)
|
||||
response = await json_llm.ainvoke(prompt)
|
||||
content = response.content.strip()
|
||||
parsed = json.loads(content)
|
||||
parsed = json.loads(extract_json_payload(content))
|
||||
result = {}
|
||||
if "birth_year" in parsed and parsed["birth_year"] is not None:
|
||||
raw = parsed["birth_year"]
|
||||
|
||||
@@ -52,7 +52,11 @@ class ExtractionAgent:
|
||||
for k, v in (stage_slots or {}).items()
|
||||
},
|
||||
)
|
||||
response = llm.invoke(prompt)
|
||||
json_llm = llm.bind(
|
||||
model_kwargs={"response_format": {"type": "json_object"}},
|
||||
max_tokens=1024,
|
||||
)
|
||||
response = json_llm.invoke(prompt)
|
||||
parsed = json.loads(extract_json_payload(response.content))
|
||||
detected_stage = parsed.get("detected_stage", detected_stage)
|
||||
raw_slots = parsed.get("slots", {}) or {}
|
||||
|
||||
@@ -15,6 +15,7 @@ from app.agents.memoir.prompts import (
|
||||
get_text_rewrite_prompt,
|
||||
inject_image_placeholder_template,
|
||||
)
|
||||
from app.features.memoir.memoir_images.json_payload import extract_json_payload
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
@@ -64,17 +65,14 @@ class MemoryAgent:
|
||||
prompt = get_text_rewrite_prompt(
|
||||
segments_text, chapter_category, existing_content or ""
|
||||
)
|
||||
response = await self.llm.ainvoke(prompt)
|
||||
json_llm = self.llm.bind(
|
||||
model_kwargs={"response_format": {"type": "json_object"}},
|
||||
max_tokens=4096,
|
||||
)
|
||||
response = await json_llm.ainvoke(prompt)
|
||||
content = response.content if hasattr(response, "content") else str(response)
|
||||
content = content.strip()
|
||||
if content.startswith("```json"):
|
||||
content = content[7:]
|
||||
if content.startswith("```"):
|
||||
content = content[3:]
|
||||
if content.endswith("```"):
|
||||
content = content[:-3]
|
||||
content = content.strip()
|
||||
result = json.loads(content)
|
||||
result = json.loads(extract_json_payload(content))
|
||||
result["content"] = inject_image_placeholder_template(
|
||||
result.get("content") or ""
|
||||
)
|
||||
|
||||
@@ -69,7 +69,11 @@ class NarrativeAgent:
|
||||
user_profile=user_profile,
|
||||
birth_year=birth_year,
|
||||
)
|
||||
response = llm.invoke(prompt)
|
||||
json_llm = llm.bind(
|
||||
model_kwargs={"response_format": {"type": "json_object"}},
|
||||
max_tokens=4096,
|
||||
)
|
||||
response = json_llm.invoke(prompt)
|
||||
return (response.content or "").strip()
|
||||
except Exception as e:
|
||||
logger.warning("NarrativeAgent 生成叙事失败: %s", e)
|
||||
|
||||
@@ -13,6 +13,7 @@ from app.core.logging import get_logger
|
||||
from app.core.task_tracker import task_tracker
|
||||
|
||||
from app.agents.state_schema import MemoirStateSchema
|
||||
from app.features.memoir.memoir_images.json_payload import extract_json_payload
|
||||
from app.agents.memoir.prompts import (
|
||||
get_creative_title_prompt,
|
||||
get_narrative_json_prompt,
|
||||
@@ -82,9 +83,13 @@ class ContentAnalyzer:
|
||||
current_stage=current_state.current_stage,
|
||||
stage_slots=current_state.slots.get(detected_stage, {}),
|
||||
)
|
||||
response = await self.llm.ainvoke(prompt)
|
||||
json_llm = self.llm.bind(
|
||||
model_kwargs={"response_format": {"type": "json_object"}},
|
||||
max_tokens=1024,
|
||||
)
|
||||
response = await json_llm.ainvoke(prompt)
|
||||
content = response.content.strip()
|
||||
parsed = json.loads(content)
|
||||
parsed = json.loads(extract_json_payload(content))
|
||||
detected_stage = parsed.get("detected_stage", detected_stage)
|
||||
extracted_slots = parsed.get("slots", {}) or {}
|
||||
emotion = parsed.get("emotion", emotion)
|
||||
@@ -147,7 +152,11 @@ class MemoirGenerator:
|
||||
new_content=new_content,
|
||||
existing_content=existing_content,
|
||||
)
|
||||
response = await self.llm.ainvoke(prompt)
|
||||
json_llm = self.llm.bind(
|
||||
model_kwargs={"response_format": {"type": "json_object"}},
|
||||
max_tokens=4096,
|
||||
)
|
||||
response = await json_llm.ainvoke(prompt)
|
||||
return response.content.strip()
|
||||
except Exception as e:
|
||||
logger.error("生成叙事失败: %s", e)
|
||||
|
||||
@@ -57,7 +57,11 @@ class MemoirImagePromptService:
|
||||
if self.llm:
|
||||
raw_response = None
|
||||
try:
|
||||
response = self.llm.invoke(
|
||||
json_llm = self.llm.bind(
|
||||
model_kwargs={"response_format": {"type": "json_object"}},
|
||||
max_tokens=512,
|
||||
)
|
||||
response = json_llm.invoke(
|
||||
"Return JSON only with keys prompt, style, size. "
|
||||
"Convert the memoir scene into an image-generation prompt.\n"
|
||||
+ json.dumps(llm_input, ensure_ascii=False)
|
||||
@@ -113,7 +117,11 @@ class MemoirImagePromptService:
|
||||
|
||||
if self.llm:
|
||||
try:
|
||||
response = self.llm.invoke(
|
||||
json_llm = self.llm.bind(
|
||||
model_kwargs={"response_format": {"type": "json_object"}},
|
||||
max_tokens=512,
|
||||
)
|
||||
response = json_llm.invoke(
|
||||
"Return JSON only with keys prompt, style, size. "
|
||||
"Create an image-generation prompt for a memoir chapter COVER. "
|
||||
"Emphasize: hero composition, evocative scene, chapter cover aesthetic.\n"
|
||||
|
||||
@@ -798,7 +798,11 @@ def generate_chapter_content(self, user_id: str, stage: str, new_content: str):
|
||||
new_content=new_content,
|
||||
existing_content=existing_content,
|
||||
)
|
||||
response = llm.invoke(prompt)
|
||||
json_llm = llm.bind(
|
||||
model_kwargs={"response_format": {"type": "json_object"}},
|
||||
max_tokens=4096,
|
||||
)
|
||||
response = json_llm.invoke(prompt)
|
||||
new_narrative = response.content.strip()
|
||||
if _is_json_narrative(new_narrative):
|
||||
narrative = new_narrative
|
||||
|
||||
@@ -57,6 +57,7 @@ from app.agents.memoir.prompts import (
|
||||
inject_image_placeholder_template,
|
||||
STAGE_TO_ORDER,
|
||||
)
|
||||
from app.features.memoir.memoir_images.json_payload import extract_json_payload
|
||||
from app.features.memoir.memoir_images.parser import split_narrative_to_sections
|
||||
from app.core.logging import get_logger, setup_logging
|
||||
|
||||
@@ -200,8 +201,12 @@ def extract_slots_with_llm(llm, text: str, current_stage: str, stage_slots: dict
|
||||
current_stage=current_stage,
|
||||
stage_slots=stage_slots,
|
||||
)
|
||||
response = llm.invoke(prompt)
|
||||
parsed = json.loads(response.content.strip())
|
||||
json_llm = llm.bind(
|
||||
model_kwargs={"response_format": {"type": "json_object"}},
|
||||
max_tokens=1024,
|
||||
)
|
||||
response = json_llm.invoke(prompt)
|
||||
parsed = json.loads(extract_json_payload(response.content.strip()))
|
||||
return parsed.get("detected_stage", current_stage), parsed.get("slots", {}) or {}
|
||||
except Exception as e:
|
||||
logger.warning(f"LLM slot 提取失败: {e}")
|
||||
|
||||
@@ -45,10 +45,12 @@ class MemoirImagePromptingTest(unittest.TestCase):
|
||||
liblib_template_uuid="tpl-uuid",
|
||||
)
|
||||
llm = Mock()
|
||||
llm.invoke.return_value.content = (
|
||||
bound_llm = Mock()
|
||||
bound_llm.invoke.return_value.content = (
|
||||
'{"prompt":"A grandmother in a quiet courtyard, summer cicadas, soft watercolor",'
|
||||
'"style":"watercolor","size":"1024x1024"}'
|
||||
)
|
||||
llm.bind.return_value = bound_llm
|
||||
service = MemoirImagePromptService(llm=llm, settings=settings)
|
||||
|
||||
result = service.build_prompt(
|
||||
@@ -74,13 +76,15 @@ class MemoirImagePromptingTest(unittest.TestCase):
|
||||
liblib_template_uuid="tpl-uuid",
|
||||
)
|
||||
llm = Mock()
|
||||
llm.invoke.return_value.content = """```json
|
||||
bound_llm = Mock()
|
||||
bound_llm.invoke.return_value.content = """```json
|
||||
{
|
||||
"prompt": "A middle-aged teacher stands on the empty stage, realistic, cinematic lighting",
|
||||
"style": "realistic",
|
||||
"size": "1280x720"
|
||||
}
|
||||
```"""
|
||||
llm.bind.return_value = bound_llm
|
||||
service = MemoirImagePromptService(llm=llm, settings=settings)
|
||||
|
||||
result = service.build_prompt(
|
||||
@@ -112,7 +116,9 @@ class MemoirImagePromptingTest(unittest.TestCase):
|
||||
liblib_template_uuid="tpl-uuid",
|
||||
)
|
||||
llm = Mock()
|
||||
llm.invoke.return_value.content = "not-json"
|
||||
bound_llm = Mock()
|
||||
bound_llm.invoke.return_value.content = "not-json"
|
||||
llm.bind.return_value = bound_llm
|
||||
service = MemoirImagePromptService(llm=llm, settings=settings)
|
||||
|
||||
result = service.build_prompt(
|
||||
|
||||
@@ -58,7 +58,8 @@ class ProcessMemoirSegmentsImageEnqueueTest(unittest.TestCase):
|
||||
get_state_mock.return_value = SimpleNamespace(current_stage="childhood", slots={})
|
||||
update_slot_mock.return_value = SimpleNamespace(current_stage="childhood", slots={})
|
||||
llm = Mock()
|
||||
llm.invoke.side_effect = [
|
||||
bound_llm = Mock()
|
||||
bound_llm.invoke.side_effect = [
|
||||
SimpleNamespace(
|
||||
content="""```json
|
||||
{
|
||||
@@ -69,8 +70,12 @@ class ProcessMemoirSegmentsImageEnqueueTest(unittest.TestCase):
|
||||
}
|
||||
```"""
|
||||
),
|
||||
SimpleNamespace(content='{"paragraphs":[{"content":"新的章节正文","image_description":"南方小镇的青石板路"}]}'),
|
||||
]
|
||||
llm.bind.return_value = bound_llm
|
||||
llm.invoke.side_effect = [
|
||||
SimpleNamespace(content="childhood"),
|
||||
SimpleNamespace(content="童年的门前"),
|
||||
SimpleNamespace(content="新的章节正文\n\n{{IMAGE:南方小镇的青石板路}}"),
|
||||
]
|
||||
get_llm_mock.return_value = llm
|
||||
|
||||
|
||||
Reference in New Issue
Block a user