fix(editor): preserve slide edit agent chat history

This commit is contained in:
sligter
2026-07-06 15:57:33 +08:00
parent ddd82c800a
commit e051138cca
2 changed files with 102 additions and 1 deletions
@@ -15,6 +15,10 @@ from pydantic import BaseModel
AgentEditMode = Literal["slide", "element"]
_MAX_CONVERSATION_HISTORY_MESSAGES = 10
_MAX_CONVERSATION_HISTORY_TOTAL_CHARS = 6000
_MAX_CONVERSATION_HISTORY_MESSAGE_CHARS = 1200
class SlideEditAgentRequest(BaseModel):
projectId: str
@@ -1008,6 +1012,7 @@ class SlideEditAgentService:
"selected_element_id": request.selectedElementId,
"selected_element_html": request.selectedElementHtml,
"user_request": request.userRequest,
"conversation_history": self._conversation_history_context(request),
"current_html": runner.current_html,
"vision": self._vision_context(request),
"available_tools": self._tool_schemas(runner),
@@ -1016,10 +1021,59 @@ class SlideEditAgentService:
}
return (
"Use a ReAct loop to edit this PPT slide. Choose exactly one action. "
"Use final when the draft is ready. Return strict JSON only.\n\n"
"Use conversation_history for continuity, but execute the latest "
"user_request. Use final when the draft is ready. Return strict JSON only.\n\n"
+ json.dumps(context, ensure_ascii=False, indent=2)
)
def _conversation_history_context(
self, request: SlideEditAgentRequest
) -> List[Dict[str, str]]:
cleaned: List[Dict[str, str]] = []
total_chars = 0
for item in reversed(request.chatHistory or []):
if len(cleaned) >= _MAX_CONVERSATION_HISTORY_MESSAGES:
break
if not isinstance(item, dict):
continue
role = str(item.get("role") or "").strip().lower()
if role not in {"user", "assistant"}:
continue
content = str(item.get("content") or "").strip()
if not content:
continue
remaining_chars = _MAX_CONVERSATION_HISTORY_TOTAL_CHARS - total_chars
if remaining_chars <= 0:
break
max_chars = min(
remaining_chars,
_MAX_CONVERSATION_HISTORY_MESSAGE_CHARS,
)
content = self._truncate_history_content(content, max_chars)
if not content:
continue
cleaned.append({"role": role, "content": content})
total_chars += len(content)
cleaned.reverse()
return cleaned
@staticmethod
def _truncate_history_content(content: str, max_chars: int) -> str:
if max_chars <= 0:
return ""
if len(content) <= max_chars:
return content
if max_chars <= 3:
return content[:max_chars]
return content[: max_chars - 3].rstrip() + "..."
def _vision_context(self, request: SlideEditAgentRequest) -> Dict[str, Any]:
attachments: List[Dict[str, Any]] = []
if request.slideScreenshot:
+47
View File
@@ -381,6 +381,53 @@ def test_tool_runner_build_proposal_strips_agent_ids():
assert proposal.validation.valid is True
def _agent_prompt_context(service: SlideEditAgentService, request: SlideEditAgentRequest):
runner = SlideEditToolRunner(SlideEditAgentContext.from_request(request))
prompt = service._build_prompt(request, runner, scratchpad=[], max_iterations=6)
return json.loads(prompt.split("\n\n", 1)[1])
def test_slide_edit_agent_prompt_includes_sanitized_conversation_history():
service = SlideEditAgentService()
request = _tool_request(
chatHistory=[
{"role": "system", "content": "Do not include this."},
{"role": "user", "content": "Make the heading shorter first."},
{"role": "assistant", "content": "I shortened the heading."},
{"role": "tool", "content": "Internal tool output."},
{"role": "assistant", "content": " "},
]
)
context = _agent_prompt_context(service, request)
assert context["conversation_history"] == [
{"role": "user", "content": "Make the heading shorter first."},
{"role": "assistant", "content": "I shortened the heading."},
]
assert context["user_request"] == "Make the title shorter"
def test_slide_edit_agent_prompt_limits_conversation_history_to_recent_messages():
service = SlideEditAgentService()
history = [
{"role": "user", "content": f"message {index}"}
for index in range(14)
]
history.append({"role": "assistant", "content": "x" * 1500})
request = _tool_request(chatHistory=history)
context = _agent_prompt_context(service, request)
conversation_history = context["conversation_history"]
assert len(conversation_history) == 10
assert conversation_history[0] == {"role": "user", "content": "message 5"}
assert conversation_history[-2] == {"role": "user", "content": "message 13"}
assert conversation_history[-1]["role"] == "assistant"
assert conversation_history[-1]["content"].endswith("...")
assert len(conversation_history[-1]["content"]) <= 1200
class _FakePPTService:
def __init__(self, responses):
self.responses = list(responses)