feat: implement Phase 2 two-tier architecture with Steward
Add comprehensive two-tier architecture where Steward analyzes requests and Tatlock executes with scoped tools. Includes full infrastructure for request preprocessing, tool tracking, benchmarking, and streaming. **Added:** - Steward agent for request analysis and capability recommendation - Household Registry for centralized capability management - Request preprocessing pipeline (Steward → Tatlock flow) - Tool usage tracking and benchmarking system - Streaming transparency (Steward reasoning visible in streams) - Structured logging with operation timing - Redis benchmark storage with 30-day expiry - Benchmark analysis CLI tools **Infrastructure:** - src/agents/steward/ - Steward agent implementation - src/agents/tatlock_core/ - Tatlock capability domain - src/core/preprocessing.py - Request preprocessing pipeline - src/core/tool_tracking.py - Tool call tracking - src/core/benchmarks.py - Benchmark recording system - src/core/household_registry.py - Capability registry - src/core/startup.py - Application startup coordination - src/core/logging_config.py - Structured logging setup **Integration:** - Responses API uses Steward for Tatlock requests - Chat Completions wraps Responses API for OpenAI compatibility - Streaming coordinator supports Steward + Tatlock flow - Tool scoping per request based on Steward recommendations **Testing:** - Integration tests for Steward-Tatlock flow - Benchmark and registry unit tests - Steward streaming tests See PHASE2_PLAN.md and PHASE2_COMPLETE.md for detailed documentation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
+43
-24
@@ -9,7 +9,6 @@ Main responsibilities:
|
||||
- Router registration
|
||||
- Lifecycle management
|
||||
"""
|
||||
import logging
|
||||
from contextlib import asynccontextmanager
|
||||
from typing import AsyncGenerator
|
||||
|
||||
@@ -21,35 +20,42 @@ from fastapi.responses import JSONResponse
|
||||
from src.chat.router import router as chat_router
|
||||
from src.core.config import config
|
||||
from src.core.exceptions import AppException
|
||||
from src.core.logging_config import get_logger
|
||||
from src.core.router import router as core_router
|
||||
from src.core.startup import initialize_application
|
||||
from src.models.router import router as models_router
|
||||
from src.responses.router import router as responses_router
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=config.LOG_LEVEL,
|
||||
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
# Get structured logger
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
|
||||
"""
|
||||
Application lifespan manager.
|
||||
|
||||
|
||||
Handles startup and shutdown logic.
|
||||
"""
|
||||
# Startup
|
||||
logger.info(f"Starting {config.APP_NAME} v{config.APP_VERSION}")
|
||||
logger.info(f"Environment: {config.ENVIRONMENT.value}")
|
||||
logger.info(f"Ollama host: {config.OLLAMA_HOST}")
|
||||
logger.info(f"Default model: {config.OLLAMA_DEFAULT_MODEL}")
|
||||
|
||||
logger.info(
|
||||
"application_starting",
|
||||
app_name=config.APP_NAME,
|
||||
version=config.APP_VERSION,
|
||||
environment=config.ENVIRONMENT.value,
|
||||
ollama_host=str(config.OLLAMA_HOST),
|
||||
ollama_model=config.OLLAMA_DEFAULT_MODEL,
|
||||
redis_url=config.redis_url,
|
||||
log_format=config.log_format,
|
||||
)
|
||||
|
||||
# Initialize application (register household members, etc.)
|
||||
initialize_application()
|
||||
|
||||
yield
|
||||
|
||||
|
||||
# Shutdown
|
||||
logger.info("Shutting down application")
|
||||
logger.info("application_shutdown")
|
||||
|
||||
|
||||
def create_application() -> FastAPI:
|
||||
@@ -102,10 +108,14 @@ def register_exception_handlers(application: FastAPI) -> None:
|
||||
) -> JSONResponse:
|
||||
"""Handle custom application exceptions."""
|
||||
logger.error(
|
||||
f"Application error: {exc.message}",
|
||||
extra={"details": exc.details}
|
||||
"application_exception",
|
||||
error_message=exc.message,
|
||||
error_type=exc.__class__.__name__,
|
||||
status_code=exc.status_code,
|
||||
details=exc.details,
|
||||
path=request.url.path,
|
||||
)
|
||||
|
||||
|
||||
return JSONResponse(
|
||||
status_code=exc.status_code,
|
||||
content={
|
||||
@@ -116,15 +126,19 @@ def register_exception_handlers(application: FastAPI) -> None:
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@application.exception_handler(RequestValidationError)
|
||||
async def validation_exception_handler(
|
||||
request: Request,
|
||||
exc: RequestValidationError,
|
||||
) -> JSONResponse:
|
||||
"""Handle Pydantic validation errors."""
|
||||
logger.error(f"Validation error: {exc.errors()}")
|
||||
|
||||
logger.error(
|
||||
"validation_error",
|
||||
errors=exc.errors(),
|
||||
path=request.url.path,
|
||||
)
|
||||
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
||||
content={
|
||||
@@ -135,15 +149,20 @@ def register_exception_handlers(application: FastAPI) -> None:
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@application.exception_handler(Exception)
|
||||
async def general_exception_handler(
|
||||
request: Request,
|
||||
exc: Exception,
|
||||
) -> JSONResponse:
|
||||
"""Handle unexpected exceptions."""
|
||||
logger.exception("Unexpected error")
|
||||
|
||||
logger.exception(
|
||||
"unexpected_error",
|
||||
error_type=type(exc).__name__,
|
||||
error_message=str(exc),
|
||||
path=request.url.path,
|
||||
)
|
||||
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
content={
|
||||
|
||||
Reference in New Issue
Block a user