From 4c75c6f4f442e9bd0f9894488a70ffc6d0c75410 Mon Sep 17 00:00:00 2001 From: yangshilin <2157598560@qq.com> Date: Thu, 19 Mar 2026 11:27:43 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=BC=95=E5=85=A5deepseek=E7=BB=93?= =?UTF-8?q?=E6=9E=84=E5=8C=96=E8=BE=93=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/app/agents/chat/profile_agent.py | 9 +++++++-- api/app/agents/memoir/extraction_agent.py | 6 +++++- api/app/agents/memoir/memory_agent.py | 16 +++++++--------- api/app/agents/memoir/narrative_agent.py | 6 +++++- api/app/agents/memoir/processor.py | 15 ++++++++++++--- .../features/memoir/memoir_images/prompting.py | 12 ++++++++++-- api/app/tasks/memoir_tasks.py | 6 +++++- api/scripts/reprocess_user_memoir.py | 9 +++++++-- api/tests/test_memoir_image_prompting.py | 12 +++++++++--- ...test_process_memoir_segments_image_enqueue.py | 9 +++++++-- 10 files changed, 74 insertions(+), 26 deletions(-) diff --git a/api/app/agents/chat/profile_agent.py b/api/app/agents/chat/profile_agent.py index 91337a9..ca85575 100644 --- a/api/app/agents/chat/profile_agent.py +++ b/api/app/agents/chat/profile_agent.py @@ -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"] diff --git a/api/app/agents/memoir/extraction_agent.py b/api/app/agents/memoir/extraction_agent.py index f6e9136..027b9e8 100644 --- a/api/app/agents/memoir/extraction_agent.py +++ b/api/app/agents/memoir/extraction_agent.py @@ -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 {} diff --git a/api/app/agents/memoir/memory_agent.py b/api/app/agents/memoir/memory_agent.py index 242f3a7..fc82924 100644 --- a/api/app/agents/memoir/memory_agent.py +++ b/api/app/agents/memoir/memory_agent.py @@ -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 "" ) diff --git a/api/app/agents/memoir/narrative_agent.py b/api/app/agents/memoir/narrative_agent.py index 0f68cf0..fd82ff1 100644 --- a/api/app/agents/memoir/narrative_agent.py +++ b/api/app/agents/memoir/narrative_agent.py @@ -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) diff --git a/api/app/agents/memoir/processor.py b/api/app/agents/memoir/processor.py index f81e751..56b9155 100644 --- a/api/app/agents/memoir/processor.py +++ b/api/app/agents/memoir/processor.py @@ -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) diff --git a/api/app/features/memoir/memoir_images/prompting.py b/api/app/features/memoir/memoir_images/prompting.py index ee2b539..892fc06 100644 --- a/api/app/features/memoir/memoir_images/prompting.py +++ b/api/app/features/memoir/memoir_images/prompting.py @@ -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" diff --git a/api/app/tasks/memoir_tasks.py b/api/app/tasks/memoir_tasks.py index ef3b0bb..c70e895 100644 --- a/api/app/tasks/memoir_tasks.py +++ b/api/app/tasks/memoir_tasks.py @@ -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 diff --git a/api/scripts/reprocess_user_memoir.py b/api/scripts/reprocess_user_memoir.py index 2b1ce90..6a459be 100644 --- a/api/scripts/reprocess_user_memoir.py +++ b/api/scripts/reprocess_user_memoir.py @@ -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}") diff --git a/api/tests/test_memoir_image_prompting.py b/api/tests/test_memoir_image_prompting.py index 7c20e82..b2e7141 100644 --- a/api/tests/test_memoir_image_prompting.py +++ b/api/tests/test_memoir_image_prompting.py @@ -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( diff --git a/api/tests/test_process_memoir_segments_image_enqueue.py b/api/tests/test_process_memoir_segments_image_enqueue.py index f07136a..8d101b0 100644 --- a/api/tests/test_process_memoir_segments_image_enqueue.py +++ b/api/tests/test_process_memoir_segments_image_enqueue.py @@ -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