fix: Make deriver use get context internally (#184)

* fix: Make deriver use get context internally

* chore: Code Rabbit nits
This commit is contained in:
Vineeth Voruganti 2025-08-11 13:10:07 -04:00 committed by GitHub
parent 04b1f64d2e
commit 13645d2e05
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 196 additions and 67 deletions

3
.gitignore vendored
View File

@ -181,3 +181,6 @@ timing_logs.csv
config.toml
.aider*
CRUSH.md
.crush/

View File

@ -1,3 +1,7 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
# Honcho Overview
## What is Honcho?
@ -100,28 +104,64 @@ All API routes follow the pattern: `/v1/{resource}/{id}/{action}`
```
src/
├── main.py # FastAPI app setup with middleware and exception handlers
├── models.py # SQLAlchemy ORM models with proper type annotations
├── schemas.py # Pydantic validation schemas for API
├── crud.py # Database operations
├── dependencies.py # Dependency injection (DB sessions)
├── exceptions.py # Custom exception types
├── security.py # JWT authentication
├── agent.py # Dialectic API implementation
├── routers/ # API endpoints
├── main.py # FastAPI app setup with middleware and exception handlers
├── models.py # SQLAlchemy ORM models with proper type annotations
├── schemas.py # Pydantic validation schemas for API
├── config.py # Configuration management
├── db.py # Database connection and session management
├── dependencies.py # Dependency injection (DB sessions)
├── exceptions.py # Custom exception types
├── security.py # JWT authentication
├── embedding_client.py # Embedding service client
├── crud/ # Database operations
│ ├── __init__.py
│ ├── collection.py # Collection CRUD operations
│ ├── deriver.py # Deriver-related CRUD operations
│ ├── document.py # Document CRUD operations
│ ├── message.py # Message CRUD operations
│ ├── peer.py # Peer CRUD operations
│ ├── representation.py # Representation CRUD operations
│ ├── session.py # Session CRUD operations
│ ├── webhook.py # Webhook CRUD operations
│ └── workspace.py # Workspace CRUD operations
├── dialectic/ # Dialectic API implementation
│ ├── __init__.py
│ ├── chat.py # Chat functionality
│ ├── prompts.py # Prompt templates
│ └── utils.py # Dialectic utilities
├── routers/ # API endpoints
│ ├── workspaces.py
│ ├── peers.py
│ ├── sessions.py
│ ├── messages.py
│ └── keys.py
├── deriver/ # Background processing system
│ ├── consumer.py # Message processing logic
│ ├── queue.py # Queue management
│ └── tom/ # Theory of Mind implementations
└── utils/ # Utilities
├── history.py # Session history management
├── cache.py # Caching utilities
└── model_client.py # LLM client abstraction
│ ├── keys.py
│ └── webhooks.py # Webhook endpoints
├── deriver/ # Background processing system
│ ├── __init__.py
│ ├── __main__.py # Deriver entry point
│ ├── consumer.py # Message consumer
│ ├── deriver.py # Main deriver logic
│ ├── enqueue.py # Queue operations
│ ├── prompts.py # Deriver prompts
│ ├── queue_manager.py # Queue management
│ ├── queue_payload.py # Queue payload schemas
│ └── utils.py # Deriver utilities
├── utils/ # Utilities
│ ├── __init__.py
│ ├── clients.py # LLM client abstraction
│ ├── embedding_store.py # Vector storage management
│ ├── files.py # File handling utilities
│ ├── filter.py # Query filtering utilities
│ ├── formatting.py # Message formatting utilities
│ ├── logging.py # Logging configuration
│ ├── search.py # Search functionality
│ ├── shared_models.py # Shared data models
│ ├── summarizer.py # Session summarization
│ └── types.py # Type definitions
└── webhooks/ # Webhook system
├── events.py # Webhook event definitions
├── webhook_delivery.py # Webhook delivery logic
└── README.md # Webhook documentation
```
- Tests in pytest with fixtures in tests/conftest.py

View File

@ -194,6 +194,11 @@ class DeriverSettings(HonchoSettings):
# Thinking budget tokens are only applied when using Anthropic as provider
THINKING_BUDGET_TOKENS: Annotated[int, Field(default=1024, gt=0, le=5000)] = 1024
# Context token limit for get_context method
CONTEXT_TOKEN_LIMIT: Annotated[int, Field(default=30_000, gt=1000, le=100_000)] = (
30_000
)
class DialecticSettings(HonchoSettings):
model_config = SettingsConfigDict(env_prefix="DIALECTIC_", extra="ignore") # pyright: ignore

View File

@ -169,12 +169,14 @@ class Deriver:
# created_at is now always a datetime object from Pydantic validation
message_dt_obj = created_at
formatted_history = await summarizer.get_summarized_history(
# Use get_session_context_formatted with configurable token limit
formatted_history = await summarizer.get_session_context_formatted(
db,
workspace_name,
session_name,
token_limit=settings.DERIVER.CONTEXT_TOKEN_LIMIT,
cutoff=message_id,
summary_type=summarizer.SummaryType.SHORT,
include_summary=True,
)
# instantiate embedding store from collection

View File

@ -388,56 +388,13 @@ async def get_session_context(
"""
token_limit = tokens or config.settings.GET_CONTEXT_MAX_TOKENS
summary_content = ""
messages_tokens = token_limit
messages_start_id = 0
if summary:
summary_tokens_limit = token_limit * 0.4
latest_short_summary, latest_long_summary = await summarizer.get_both_summaries(
db,
workspace_name=workspace_id,
session_name=session_id,
)
long_len = latest_long_summary["token_count"] if latest_long_summary else 0
short_len = latest_short_summary["token_count"] if latest_short_summary else 0
# The goal is to return the longest summary that fits within the token limit
# Sometimes (rarely) the short summary can be longer than the long summary,
# so we need to check for that and return the longer one.
if (
latest_long_summary
and long_len <= summary_tokens_limit
and long_len > short_len
):
summary_content = latest_long_summary["content"]
messages_tokens = token_limit - latest_long_summary["token_count"]
messages_start_id = latest_long_summary["message_id"]
elif (
latest_short_summary and short_len <= summary_tokens_limit and short_len > 0
):
summary_content = latest_short_summary["content"]
messages_tokens = token_limit - latest_short_summary["token_count"]
messages_start_id = latest_short_summary["message_id"]
else:
logger.info(
"No summary available for get_context call with token limit %s, returning empty string. long_summary_len: %s, short_summary_len: %s",
token_limit,
long_len,
short_len,
)
summary_content = ""
# Get the recent messages after summary to return verbatim
messages = await crud.get_messages_id_range(
# Use the shared get_session_context function from summarizer
summary_content, messages = await summarizer.get_session_context(
db,
workspace_name=workspace_id,
session_name=session_id,
start_id=messages_start_id,
token_limit=messages_tokens,
token_limit=token_limit,
include_summary=summary,
)
return schemas.SessionContext(

View File

@ -45,6 +45,8 @@ __all__ = [
"get_summary",
"get_both_summaries",
"get_summarized_history",
"get_session_context",
"get_session_context_formatted",
"SummaryType",
"Summary",
]
@ -554,6 +556,126 @@ async def get_both_summaries(
return summaries.get(SummaryType.SHORT.value), summaries.get(SummaryType.LONG.value)
async def get_session_context(
db: AsyncSession,
workspace_name: str,
session_name: str,
token_limit: int,
*,
cutoff: int | None = None,
include_summary: bool = True,
) -> tuple[str, list[models.Message]]:
"""
Get session context similar to the API endpoint but for internal use.
Args:
db: Database session
workspace_name: The workspace name
session_name: The session name
token_limit: Maximum tokens for the context
cutoff: Optional message ID to stop at (exclusive)
include_summary: Whether to include summary if available
Returns:
Tuple of (summary_content, messages) where summary_content is the summary text (or empty string)
and messages is the list of message objects
"""
summary_content = ""
messages_tokens = token_limit
messages_start_id = 0
if include_summary:
# Allocate 40% of tokens to summary, 60% to messages
summary_tokens_limit = int(token_limit * 0.4)
latest_short_summary, latest_long_summary = await get_both_summaries(
db, workspace_name, session_name
)
long_len = latest_long_summary["token_count"] if latest_long_summary else 0
short_len = latest_short_summary["token_count"] if latest_short_summary else 0
# Return the longest summary that fits within the token limit
if (
latest_long_summary
and long_len <= summary_tokens_limit
and long_len > short_len
):
summary_content = latest_long_summary["content"]
messages_tokens = token_limit - latest_long_summary["token_count"]
messages_start_id = latest_long_summary["message_id"]
elif (
latest_short_summary and short_len <= summary_tokens_limit and short_len > 0
):
summary_content = latest_short_summary["content"]
messages_tokens = token_limit - latest_short_summary["token_count"]
messages_start_id = latest_short_summary["message_id"]
else:
logger.warning(
"No summary available for get_context call with token limit %s, returning empty string. long_summary_len: %s, short_summary_len: %s",
token_limit,
long_len,
short_len,
)
# Get recent messages after summary
messages = await crud.get_messages_id_range(
db,
workspace_name,
session_name,
start_id=messages_start_id,
end_id=cutoff,
token_limit=messages_tokens,
)
return summary_content, messages
async def get_session_context_formatted(
db: AsyncSession,
workspace_name: str,
session_name: str,
token_limit: int,
*,
cutoff: int | None = None,
include_summary: bool = True,
) -> str:
"""
Get formatted session context as a string for internal use (e.g., deriver).
This is a convenience wrapper around get_session_context that formats
the output as a string.
"""
summary_content, messages = await get_session_context(
db,
workspace_name,
session_name,
token_limit,
cutoff=cutoff,
include_summary=include_summary,
)
# Format the messages
messages_text = _format_messages(messages)
if summary_content and messages_text:
return f"""<summary>
{summary_content}
</summary>
<recent_messages>
{messages_text}
</recent_messages>"""
elif summary_content:
return f"""<summary>
{summary_content}
</summary>"""
elif messages_text:
return messages_text
else:
return ""
def _format_messages(messages: list[models.Message]) -> str:
"""
Format a list of messages into a string by concatenating their content and