From b6178cc73921f73b9cdcd0d5ee272056b9b6aa9f Mon Sep 17 00:00:00 2001 From: Oxygen <1391083091@qq.com> Date: Wed, 10 Jun 2026 23:26:25 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20address=20review=20feedback=20=E2=80=94?= =?UTF-8?q?=20add=20custom=5Finstructions=20to=20server=20config;=20merge?= =?UTF-8?q?=20estimate=20helpers;=20revert=20uv.lock?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/schemas/configuration.py | 15 +++++++++++ src/utils/summarizer.py | 52 +++++++----------------------------- uv.lock | 4 +++ 3 files changed, 28 insertions(+), 43 deletions(-) diff --git a/src/schemas/configuration.py b/src/schemas/configuration.py index b8291cab..22822e6f 100644 --- a/src/schemas/configuration.py +++ b/src/schemas/configuration.py @@ -61,6 +61,15 @@ class SummaryConfiguration(BaseModel): ge=20, description="Number of messages per long summary. Must be positive, greater than or equal to 20, and greater than messages_per_short_summary.", ) + custom_instructions: str | None = Field( + default=None, + description="Optional custom instructions for session summaries. Rejected if they exceed the summarizer custom-instruction token cap.", + ) + + @field_validator("custom_instructions") + @classmethod + def validate_custom_instructions(cls, value: str | None) -> str | None: + return _validate_custom_instructions_budget(value) @model_validator(mode="after") def validate_summary_thresholds(self) -> Self: @@ -172,6 +181,12 @@ class ResolvedSummaryConfiguration(BaseModel): enabled: bool messages_per_short_summary: int messages_per_long_summary: int + custom_instructions: str | None = None + + @field_validator("custom_instructions") + @classmethod + def validate_custom_instructions(cls, value: str | None) -> str | None: + return _validate_custom_instructions_budget(value) class ResolvedDreamConfiguration(BaseModel): diff --git a/src/utils/summarizer.py b/src/utils/summarizer.py index bba6d612..da684ab3 100644 --- a/src/utils/summarizer.py +++ b/src/utils/summarizer.py @@ -14,6 +14,7 @@ from src.cache.client import cache as cache_client from src.config import ConfiguredModelSettings, settings from src.crud.session import session_cache_key from src.dependencies import tracked_db +# TODO: move _custom_instructions_section to shared utility from src.deriver.prompts import _custom_instructions_section from src.exceptions import ResourceNotFoundException from src.llm import HonchoLLMCallResponse, honcho_llm_call @@ -173,42 +174,24 @@ Hard limit: {output_words} words maximum. If needed, drop lower-priority detail @cache -def estimate_short_summary_prompt_tokens() -> int: - """Estimate tokens for the short summary prompt (without messages/previous_summary or custom instructions).""" +def estimate_short_summary_prompt_tokens(custom_instructions: str | None = None) -> int: + """Estimate tokens for the short summary prompt, optionally including custom instructions.""" try: return estimate_tokens( short_summary_prompt( formatted_messages="", output_words=0, previous_summary_text="", - custom_instructions=None, + custom_instructions=custom_instructions, ) ) except Exception: - # Return a rough estimate if estimation fails return 200 -def estimate_short_summary_prompt_tokens_with_custom_instructions( - custom_instructions: str | None, -) -> int: - """Estimate short summary prompt tokens, including custom instructions if present.""" - if custom_instructions is None: - return estimate_short_summary_prompt_tokens() - - return estimate_tokens( - short_summary_prompt( - formatted_messages="", - output_words=0, - previous_summary_text="", - custom_instructions=custom_instructions, - ) - ) - - @cache -def estimate_long_summary_prompt_tokens() -> int: - """Estimate tokens for the long summary prompt (without messages/previous_summary or custom instructions).""" +def estimate_long_summary_prompt_tokens(custom_instructions: str | None = None) -> int: + """Estimate tokens for the long summary prompt, optionally including custom instructions.""" try: return estimate_tokens( long_summary_prompt( @@ -219,26 +202,9 @@ def estimate_long_summary_prompt_tokens() -> int: ) ) except Exception: - # Return a rough estimate if estimation fails return 200 -def estimate_long_summary_prompt_tokens_with_custom_instructions( - custom_instructions: str | None, -) -> int: - """Estimate long summary prompt tokens, including custom instructions if present.""" - if custom_instructions is None: - return estimate_long_summary_prompt_tokens() - - return estimate_tokens( - long_summary_prompt( - formatted_messages="", - output_words=0, - previous_summary_text="", - custom_instructions=custom_instructions, - ) - ) - @conditional_observe(name="Create Short Summary") async def create_short_summary( @@ -521,7 +487,7 @@ async def _create_and_save_summary( # This is separate from reasoning custom_instructions — workspace # operators may want summaries in a different style than deriver output. custom_instructions: str | None = None - if configuration.summary and configuration.summary.custom_instructions: + if configuration.summary and configuration.summary.custom_instructions is not None: custom_instructions = configuration.summary.custom_instructions ( @@ -546,11 +512,11 @@ async def _create_and_save_summary( # save-summary path and the telemetry emit below can use it # without basedpyright tripping on a possibly-unbound name. if summary_type == SummaryType.SHORT: - prompt_tokens = estimate_short_summary_prompt_tokens_with_custom_instructions( + prompt_tokens = estimate_short_summary_prompt_tokens( custom_instructions ) else: - prompt_tokens = estimate_long_summary_prompt_tokens_with_custom_instructions( + prompt_tokens = estimate_long_summary_prompt_tokens( custom_instructions ) diff --git a/uv.lock b/uv.lock index 2c504207..25279af9 100644 --- a/uv.lock +++ b/uv.lock @@ -7,6 +7,10 @@ resolution-markers = [ "python_full_version < '3.13'", ] +[options] +exclude-newer = "2026-05-28T15:27:36.945866Z" +exclude-newer-span = "P5D" + [manifest] members = [ "honcho",