mirror of
https://github.com/furyhawk/agent_alpha.git
synced 2026-07-20 10:15:33 +00:00
feat: refactor agent initialization and enhance API proxy configuration in nginx
This commit is contained in:
+13
-5
@@ -11,7 +11,10 @@ from uvicorn.logging import DefaultFormatter
|
||||
|
||||
from backend.core.config import Settings
|
||||
from backend.core.database import close_engine, close_valkey, init_db
|
||||
from backend.core.dependencies import get_agent_service
|
||||
from backend.core.agent import AgentService, set_service
|
||||
from backend.repositories.memory_repository import MemoryRepository
|
||||
from backend.services.agent_factory import build_agent
|
||||
from backend.services.rag_service import RagService
|
||||
from backend.routes.admin import router as admin_router
|
||||
from backend.routes.auth import router as auth_router
|
||||
from backend.routes.chat import router as chat_router
|
||||
@@ -60,7 +63,9 @@ class AppBuilder:
|
||||
"""Builds and configures the FastAPI application with DI wiring.
|
||||
|
||||
Accepts a ``Settings`` instance so tests can supply custom config.
|
||||
The ``AgentService`` is lazily initialised inside the lifespan.
|
||||
The ``AgentService`` is built inside the lifespan hook, which wires
|
||||
``MemoryRepository``, ``RagService``, and the agent factory together
|
||||
and registers the result as a module-level singleton.
|
||||
"""
|
||||
|
||||
def __init__(self, settings: Settings | None = None) -> None:
|
||||
@@ -110,9 +115,12 @@ class AppBuilder:
|
||||
# Create database tables if they don't exist.
|
||||
await init_db()
|
||||
|
||||
# Initialise the agent service on startup.
|
||||
agent_service = await get_agent_service()
|
||||
agent_service.initialize()
|
||||
# Build agent with full dependency injection via the factory.
|
||||
memory_repo = MemoryRepository()
|
||||
rag_service = RagService()
|
||||
agent = build_agent(self._settings, memory_repo, rag_service)
|
||||
agent_service = AgentService(agent=agent, memory_repo=memory_repo)
|
||||
set_service(agent_service)
|
||||
|
||||
yield
|
||||
|
||||
|
||||
+24
-38
@@ -27,41 +27,28 @@ from __future__ import annotations
|
||||
|
||||
import time
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from pydantic_ai import Agent
|
||||
from pydantic_ai.tools import Tool
|
||||
from pydantic_ai_harness import CodeMode
|
||||
|
||||
from backend.core.config import Settings
|
||||
from backend.repositories.memory_repository import MemoryRepository
|
||||
from backend.services.agent_factory import build_agent
|
||||
from backend.services.rag_service import RagService
|
||||
from pydantic_deep.deps import DeepAgentDeps
|
||||
|
||||
class AgentService:
|
||||
"""Encapsulates agent construction, execution, and teardown.
|
||||
|
||||
Uses lazy initialization so the agent is not built at import time.
|
||||
Accepts a ``Settings`` instance for testability (dependency injection).
|
||||
class AgentService:
|
||||
"""Thin orchestrator for agent execution.
|
||||
|
||||
Receives a pre-built ``Agent`` and its ``MemoryRepository`` via
|
||||
constructor injection so the lifespan (or tests) control wiring.
|
||||
No lazy initialisation — the agent is ready to run immediately.
|
||||
"""
|
||||
|
||||
def __init__(self, settings: Settings) -> None:
|
||||
self._settings = settings
|
||||
self._agent: Agent[any, str] | None = None
|
||||
def __init__(self, agent: Agent[Any, str], memory_repo: MemoryRepository) -> None:
|
||||
self._agent = agent
|
||||
self._memory_repo = memory_repo
|
||||
|
||||
# ── Lifecycle ──────────────────────────────────────────────────────────
|
||||
|
||||
def initialize(self) -> None:
|
||||
"""Build the agent. Idempotent — safe to call multiple times."""
|
||||
if self._agent is not None:
|
||||
return
|
||||
|
||||
memory_repo = MemoryRepository()
|
||||
rag_service = RagService()
|
||||
|
||||
# The factory handles all complex initialization logic
|
||||
self._agent = build_agent(self._settings, memory_repo, rag_service)
|
||||
|
||||
async def shutdown(self) -> None:
|
||||
"""Release agent resources."""
|
||||
self._agent = None
|
||||
@@ -70,17 +57,13 @@ class AgentService:
|
||||
|
||||
async def ask(self, prompt: str, session_id: str | None = None) -> AskResult:
|
||||
"""Send a prompt to the agent and return the text output with usage stats."""
|
||||
self.initialize()
|
||||
assert self._agent is not None
|
||||
|
||||
# Dependency injection using the repository's backend
|
||||
memory_repo = MemoryRepository()
|
||||
deps = DeepAgentDeps(backend=memory_repo.backend)
|
||||
di = DeepAgentDeps(backend=self._memory_repo.backend)
|
||||
|
||||
t0 = time.monotonic()
|
||||
result = await self._agent.run(prompt, deps=deps)
|
||||
result = await self._agent.run(prompt, deps=di)
|
||||
elapsed = time.monotonic() - t0
|
||||
usage = result.usage
|
||||
|
||||
return AskResult(
|
||||
output=result.output,
|
||||
input_tokens=usage.input_tokens,
|
||||
@@ -89,6 +72,7 @@ class AgentService:
|
||||
elapsed_seconds=round(elapsed, 2),
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class AskResult:
|
||||
"""Result of a single agent ``ask()`` call with token usage information."""
|
||||
@@ -108,20 +92,22 @@ class AskResult:
|
||||
elapsed_seconds: float = 0.0
|
||||
"""Wall-clock time in seconds the agent took to respond."""
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Singleton — lazy-initialised on the first call to ``ask()``.
|
||||
# Keeps the existing module-level ``ask`` facade so downstream imports in
|
||||
# ``chat.py`` continue to work unchanged.
|
||||
# Singleton — wired once at startup by the lifespan hook in ``app.py``.
|
||||
# Routes access it through ``get_service()``.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
from backend.core.config import settings as _settings # noqa: E402
|
||||
_service: AgentService | None = None
|
||||
|
||||
_service = AgentService(_settings)
|
||||
|
||||
def get_service() -> AgentService:
|
||||
"""Return the singleton AgentService instance."""
|
||||
assert _service is not None, "AgentService has not been initialised — did the lifespan run?"
|
||||
return _service
|
||||
|
||||
async def ask(prompt: str, session_id: str | None = None) -> AskResult:
|
||||
"""Send a prompt to the agent and return the text output with usage stats."""
|
||||
return await _service.ask(prompt, session_id=session_id)
|
||||
|
||||
def set_service(service: AgentService) -> None:
|
||||
"""Set the module-level singleton (called by the lifespan hook)."""
|
||||
global _service
|
||||
_service = service
|
||||
|
||||
+5
-2
@@ -12,8 +12,11 @@ server {
|
||||
|
||||
# ── API proxy ─────────────────────────────────────────────────
|
||||
# Container runtime (Docker/Podman) resolves backend:8000 via
|
||||
# its built-in DNS. nginx caches the resolved IP at startup, so
|
||||
# restarting the backend container needs a frontend restart too.
|
||||
# its built-in DNS (127.0.0.11). Without a resolver, nginx caches
|
||||
# the resolved IP at startup and returns 502 when the backend
|
||||
# container is recreated. The resolver + valid=5s forces periodic
|
||||
# re-resolution so the frontend survives backend restarts.
|
||||
resolver 127.0.0.11 valid=5s;
|
||||
location /api/ {
|
||||
proxy_pass http://backend:8000;
|
||||
proxy_set_header Host $host;
|
||||
|
||||
Reference in New Issue
Block a user