chore (docs): Add README and coderabbit nitpicks
This commit is contained in:
parent
a078783800
commit
ad38546ce0
75
README.md
75
README.md
|
|
@ -21,6 +21,11 @@ Read the user documentation [here](https://docs.honcho.dev)
|
|||
|
||||
- [Project Structure](#project-structure)
|
||||
- [Usage](#usage)
|
||||
- [Configuration](#configuration)
|
||||
- [Using config.toml](#using-configtoml)
|
||||
- [Using Environment Variables](#using-environment-variables)
|
||||
- [Configuration Priority](#configuration-priority)
|
||||
- [Example](#example)
|
||||
- [Architecture](#architecture)
|
||||
- [Storage](#storage)
|
||||
- [Insights](#insights)
|
||||
|
|
@ -55,6 +60,76 @@ Additionally, Honcho can be self-hosted for testing and evaluation purposes. See
|
|||
[Contributing](./CONTRIBUTING.md) for more details on how to setup a local
|
||||
version of Honcho.
|
||||
|
||||
## Configuration
|
||||
|
||||
Honcho uses a flexible configuration system that supports both TOML files and environment variables. Configuration values are loaded in the following priority order (highest to lowest):
|
||||
|
||||
1. Environment variables
|
||||
2. `.env` file (for local development)
|
||||
3. `config.toml` file
|
||||
4. Default values
|
||||
|
||||
### Using config.toml
|
||||
|
||||
Copy the example configuration file to get started:
|
||||
|
||||
```bash
|
||||
cp config.toml.example config.toml
|
||||
```
|
||||
|
||||
Then modify the values as needed. The TOML file is organized into sections:
|
||||
|
||||
- `[app]` - Application-level settings (log level, host, port)
|
||||
- `[db]` - Database connection and pool settings
|
||||
- `[auth]` - Authentication configuration
|
||||
- `[llm]` - LLM provider and model settings
|
||||
- `[agent]` - Agent behavior settings
|
||||
- `[deriver]` - Background worker settings
|
||||
- `[history]` - Message history settings
|
||||
|
||||
### Using Environment Variables
|
||||
|
||||
All configuration values can be overridden using environment variables. The environment variable names follow this pattern:
|
||||
|
||||
- `{SECTION}_{KEY}` for nested settings
|
||||
- Just `{KEY}` for app-level settings
|
||||
|
||||
Examples:
|
||||
- `DB_CONNECTION_URI` - Database connection string
|
||||
- `AUTH_JWT_SECRET` - JWT secret key
|
||||
- `LLM_DIALECTIC_MODEL` - Dialectic LLM model
|
||||
- `LOG_LEVEL` - Application log level
|
||||
|
||||
### Configuration Priority
|
||||
|
||||
When a configuration value is set in multiple places, Honcho uses this priority:
|
||||
|
||||
1. **Environment variables** - Always take precedence
|
||||
2. **.env file** - Loaded for local development
|
||||
3. **config.toml** - Base configuration
|
||||
4. **Default values** - Built-in defaults
|
||||
|
||||
This allows you to:
|
||||
- Use `config.toml` for base configuration
|
||||
- Override specific values with environment variables in production
|
||||
- Use `.env` files for local development without modifying config.toml
|
||||
|
||||
### Example
|
||||
|
||||
If you have this in `config.toml`:
|
||||
```toml
|
||||
[db]
|
||||
CONNECTION_URI = "postgresql://localhost/honcho_dev"
|
||||
POOL_SIZE = 10
|
||||
```
|
||||
|
||||
You can override just the connection URI in production:
|
||||
```bash
|
||||
export DB_CONNECTION_URI="postgresql://prod-server/honcho_prod"
|
||||
```
|
||||
|
||||
The application will use the production connection URI while keeping the pool size from config.toml.
|
||||
|
||||
## Architecture
|
||||
|
||||
The functionality of Honcho can be split into two different services: Storage
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, Optional, Tuple
|
||||
from typing import Any, Optional
|
||||
|
||||
import tomllib
|
||||
from dotenv import load_dotenv
|
||||
|
|
@ -14,15 +15,15 @@ from pydantic_settings import (
|
|||
load_dotenv()
|
||||
|
||||
|
||||
def load_toml_config(config_path: str = "config.toml") -> Dict[str, Any]:
|
||||
def load_toml_config(config_path: str = "config.toml") -> dict[str, Any]:
|
||||
"""Load configuration from TOML file if it exists."""
|
||||
config_file = Path(config_path)
|
||||
if config_file.exists():
|
||||
try:
|
||||
with open(config_file, "rb") as f:
|
||||
return tomllib.load(f)
|
||||
except Exception as e:
|
||||
print(f"Warning: Failed to load {config_path}: {e}")
|
||||
except (tomllib.TOMLDecodeError, OSError) as exc:
|
||||
logging.warning("Failed to load %s: %s", config_path, exc)
|
||||
return {}
|
||||
return {}
|
||||
|
||||
|
|
@ -37,28 +38,26 @@ class TomlConfigSettingsSource(PydanticBaseSettingsSource):
|
|||
def __init__(self, settings_cls: type[BaseSettings]):
|
||||
super().__init__(settings_cls)
|
||||
|
||||
def get_field_value(
|
||||
self, field_name: str, field_info: Any
|
||||
) -> Tuple[Any, str, bool]:
|
||||
SECTION_MAP: dict[str, str] = {
|
||||
"DB": "db",
|
||||
"AUTH": "auth",
|
||||
"SENTRY": "sentry",
|
||||
"OPENTELEMETRY": "opentelemetry",
|
||||
"LLM": "llm",
|
||||
"AGENT": "agent",
|
||||
"DERIVER": "deriver",
|
||||
"HISTORY": "history",
|
||||
"": "app", # For AppSettings with no prefix
|
||||
}
|
||||
|
||||
def get_field_value(self, field_name: str) -> tuple[Any, str, bool]:
|
||||
# Get the env_prefix from the model config
|
||||
prefix = self.settings_cls.model_config.get("env_prefix", "")
|
||||
if prefix.endswith("_"):
|
||||
prefix = prefix[:-1]
|
||||
|
||||
# Map prefixes to TOML sections
|
||||
section_map = {
|
||||
"DB": "db",
|
||||
"AUTH": "auth",
|
||||
"SENTRY": "sentry",
|
||||
"OPENTELEMETRY": "opentelemetry",
|
||||
"LLM": "llm",
|
||||
"AGENT": "agent",
|
||||
"DERIVER": "deriver",
|
||||
"HISTORY": "history",
|
||||
"": "app", # For AppSettings with no prefix
|
||||
}
|
||||
|
||||
section = section_map.get(prefix, prefix.lower())
|
||||
section = self.SECTION_MAP.get(prefix, prefix.lower())
|
||||
toml_data = TOML_CONFIG.get(section, {})
|
||||
|
||||
# Try different case variations
|
||||
|
|
@ -70,34 +69,17 @@ class TomlConfigSettingsSource(PydanticBaseSettingsSource):
|
|||
|
||||
return field_value, field_name, False
|
||||
|
||||
def __call__(self) -> Dict[str, Any]:
|
||||
def __call__(self) -> dict[str, Any]:
|
||||
# Get the env_prefix from the model config
|
||||
prefix = self.settings_cls.model_config.get("env_prefix", "")
|
||||
if prefix.endswith("_"):
|
||||
prefix = prefix[:-1]
|
||||
|
||||
# Map prefixes to TOML sections
|
||||
section_map = {
|
||||
"DB": "db",
|
||||
"AUTH": "auth",
|
||||
"SENTRY": "sentry",
|
||||
"OPENTELEMETRY": "opentelemetry",
|
||||
"LLM": "llm",
|
||||
"AGENT": "agent",
|
||||
"DERIVER": "deriver",
|
||||
"HISTORY": "history",
|
||||
"": "app", # For AppSettings with no prefix
|
||||
}
|
||||
|
||||
section = section_map.get(prefix, prefix.lower())
|
||||
section = self.SECTION_MAP.get(prefix, prefix.lower())
|
||||
toml_data = TOML_CONFIG.get(section, {})
|
||||
|
||||
# Convert keys to uppercase to match field names
|
||||
result = {}
|
||||
for key, value in toml_data.items():
|
||||
result[key.upper()] = value
|
||||
|
||||
return result
|
||||
return {key.upper(): value for key, value in toml_data.items()}
|
||||
|
||||
|
||||
class TomlSettings(BaseSettings):
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ connect_args = {"prepare_threshold": None}
|
|||
|
||||
# Context variable to store request context
|
||||
request_context: contextvars.ContextVar[Optional[str]] = contextvars.ContextVar(
|
||||
"request_context", default=None
|
||||
"request_context",
|
||||
default=None,
|
||||
)
|
||||
|
||||
engine = create_async_engine(
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import asyncio
|
||||
import os
|
||||
import signal
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from logging import getLogger
|
||||
|
||||
import sentry_sdk
|
||||
|
|
@ -12,10 +12,11 @@ from sqlalchemy.exc import IntegrityError
|
|||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.sql import func
|
||||
|
||||
from src.config import settings
|
||||
|
||||
from .. import models
|
||||
from ..dependencies import tracked_db
|
||||
from .consumer import process_item
|
||||
from src.config import settings
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
|
|
@ -116,7 +117,7 @@ class QueueManager:
|
|||
"""Get available sessions that aren't being processed"""
|
||||
# Clean up stale sessions
|
||||
stale_delta = timedelta(minutes=settings.DERIVER.STALE_SESSION_TIMEOUT_MINUTES)
|
||||
five_minutes_ago = datetime.utcnow() - stale_delta
|
||||
five_minutes_ago = datetime.now(timezone.utc) - stale_delta
|
||||
await db.execute(
|
||||
delete(models.ActiveQueueSession).where(
|
||||
models.ActiveQueueSession.last_updated < five_minutes_ago
|
||||
|
|
@ -191,13 +192,17 @@ class QueueManager:
|
|||
)
|
||||
else:
|
||||
self.queue_empty_flag.set()
|
||||
await asyncio.sleep(settings.DERIVER.POLLING_SLEEP_INTERVAL_SECONDS)
|
||||
await asyncio.sleep(
|
||||
settings.DERIVER.POLLING_SLEEP_INTERVAL_SECONDS
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Error in polling loop: {str(e)}", exc_info=True)
|
||||
logger.exception("Error in polling loop: %s", str(e))
|
||||
if settings.SENTRY.ENABLED:
|
||||
sentry_sdk.capture_exception(e)
|
||||
# Note: rollback is handled by tracked_db dependency
|
||||
await asyncio.sleep(settings.DERIVER.POLLING_SLEEP_INTERVAL_SECONDS)
|
||||
await asyncio.sleep(
|
||||
settings.DERIVER.POLLING_SLEEP_INTERVAL_SECONDS
|
||||
)
|
||||
finally:
|
||||
logger.info("Polling loop stopped")
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue