This commit is contained in:
Dennis Soong 2026-09-03 09:04:06 -04:00 committed by GitHub
commit 11782099bc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 97 additions and 2 deletions

View File

@ -98,12 +98,23 @@ class SummaryType(Enum):
LONG = "honcho_chat_summary_long"
def _custom_instructions_section(custom_instructions: str | None) -> str:
if not custom_instructions or not custom_instructions.strip():
return ""
return c(f"""
CUSTOM INSTRUCTIONS:
{custom_instructions.strip()}
""")
def short_summary_prompt(
formatted_messages: str,
output_words: int,
previous_summary_text: str,
custom_instructions: str | None = None,
) -> str:
"""Generate the short summary prompt."""
custom_instructions_section = _custom_instructions_section(custom_instructions)
return c(f"""
You are a system that summarizes parts of a conversation to create a concise and accurate summary. Focus on capturing:
@ -118,6 +129,8 @@ Provide a concise, factual summary that captures the essence of the conversation
Return only the summary without any explanation or meta-commentary.
{custom_instructions_section}
<previous_summary>
{previous_summary_text}
</previous_summary>
@ -134,8 +147,10 @@ def long_summary_prompt(
formatted_messages: str,
output_words: int,
previous_summary_text: str,
custom_instructions: str | None = None,
) -> str:
"""Generate the long summary prompt."""
custom_instructions_section = _custom_instructions_section(custom_instructions)
return c(f"""
You are a system that creates thorough, comprehensive summaries of conversations. Focus on capturing:
@ -152,6 +167,8 @@ Provide a thorough and detailed summary that captures the essence of the convers
Return only the summary without any explanation or meta-commentary.
{custom_instructions_section}
<previous_summary>
{previous_summary_text}
</previous_summary>
@ -202,6 +219,7 @@ async def create_short_summary(
input_tokens: int,
previous_summary: str | None = None,
*,
custom_instructions: str | None = None,
workspace_name: str | None = None,
) -> HonchoLLMCallResponse[str]:
# input_tokens indicates how many tokens the message list + previous summary take up
@ -217,7 +235,10 @@ async def create_short_summary(
previous_summary_text = "There is no previous summary -- the messages are the beginning of the conversation."
prompt = short_summary_prompt(
formatted_messages, output_words, previous_summary_text
formatted_messages,
output_words,
previous_summary_text,
custom_instructions,
)
# Mint a root span id.
@ -243,6 +264,7 @@ async def create_long_summary(
formatted_messages: str,
previous_summary: str | None = None,
*,
custom_instructions: str | None = None,
workspace_name: str | None = None,
) -> HonchoLLMCallResponse[str]:
# the word/token ratio is roughly 4:3 so we multiply by 0.75.
@ -255,7 +277,10 @@ async def create_long_summary(
previous_summary_text = "There is no previous summary -- the messages are the beginning of the conversation."
prompt = long_summary_prompt(
formatted_messages, output_words, previous_summary_text
formatted_messages,
output_words,
previous_summary_text,
custom_instructions,
)
# Mint a root span id.
@ -466,6 +491,7 @@ async def _create_and_save_summary(
last_message_id=last_message_id,
last_message_content_preview=last_message_content_preview,
message_count=message_count,
custom_instructions=configuration.reasoning.custom_instructions,
workspace_name=workspace_name,
)
@ -562,6 +588,7 @@ async def _create_summary(
last_message_content_preview: str,
message_count: int,
*,
custom_instructions: str | None = None,
workspace_name: str | None = None,
) -> tuple[Summary, bool, int, int]:
"""
@ -594,12 +621,14 @@ async def _create_summary(
formatted_messages,
input_tokens,
previous_summary_text,
custom_instructions=custom_instructions,
workspace_name=workspace_name,
)
else:
response = await create_long_summary(
formatted_messages,
previous_summary_text,
custom_instructions=custom_instructions,
workspace_name=workspace_name,
)

View File

@ -85,6 +85,8 @@ _RUNTIME_MOCK_TEST_BLOCKLIST_PREFIXES = (
"tests/llm/",
# LLM transport tests mock providers directly and don't need database/runtime setup.
"tests/utils/test_length_finish_reason.py",
# Summary unit tests patch LLM calls directly and should not require database setup.
"tests/utils/test_summarizer.py",
"tests/utils/test_clients.py",
# Session-scope SQL shape — asserts on compiled statements, never executes one.
"tests/crud/test_session_scope_clauses.py",

View File

@ -18,6 +18,8 @@ from src.utils.summarizer import (
_create_summary, # pyright: ignore[reportPrivateUsage]
create_long_summary,
create_short_summary,
long_summary_prompt,
short_summary_prompt,
)
# Common test arguments for _create_summary
@ -222,6 +224,68 @@ class TestCreateSummary:
assert summary["token_count"] == 0
class TestSummaryCustomInstructions:
def test_short_summary_prompt_includes_custom_instructions(self):
prompt = short_summary_prompt(
formatted_messages=_FORMATTED_MESSAGES,
output_words=50,
previous_summary_text="previous context",
custom_instructions="Always write the summary in German.",
)
assert "CUSTOM INSTRUCTIONS:" in prompt
assert "Always write the summary in German." in prompt
assert "previous context" in prompt
assert _FORMATTED_MESSAGES in prompt
def test_long_summary_prompt_omits_blank_custom_instructions(self):
prompt = long_summary_prompt(
formatted_messages=_FORMATTED_MESSAGES,
output_words=250,
previous_summary_text="previous context",
custom_instructions=" \n\t ",
)
assert "CUSTOM INSTRUCTIONS:" not in prompt
assert "previous context" in prompt
assert _FORMATTED_MESSAGES in prompt
@pytest.mark.asyncio
async def test_create_summary_passes_custom_instructions_to_short_summary(self):
mock_response = HonchoLLMCallResponse(
content="Benutzer bevorzugt kurze Antworten.",
input_tokens=100,
output_tokens=8,
finish_reasons=["STOP"],
)
with patch(
"src.utils.summarizer.create_short_summary",
new_callable=AsyncMock,
return_value=mock_response,
) as mock_short:
summary, is_fallback, _, _ = await _create_summary(
formatted_messages=_FORMATTED_MESSAGES,
previous_summary_text=None,
summary_type=SummaryType.SHORT,
input_tokens=_INPUT_TOKENS,
message_public_id=_MESSAGE_PUBLIC_ID,
last_message_id=_LAST_MESSAGE_ID,
last_message_content_preview=_LAST_MESSAGE_CONTENT_PREVIEW,
message_count=_MESSAGE_COUNT,
custom_instructions="Always write summaries in German.",
)
assert is_fallback is False
assert "Benutzer" in summary["content"]
await_args = mock_short.await_args
if await_args is None:
raise AssertionError("Expected short summary call")
assert await_args.kwargs["custom_instructions"] == (
"Always write summaries in German."
)
@pytest.mark.asyncio
class TestSummaryCallerMigration:
async def test_create_short_summary_uses_model_config(self):