mirror of
https://github.com/furyhawk/agent_alpha.git
synced 2026-07-21 02:25:34 +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.config import Settings
|
||||||
from backend.core.database import close_engine, close_valkey, init_db
|
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.admin import router as admin_router
|
||||||
from backend.routes.auth import router as auth_router
|
from backend.routes.auth import router as auth_router
|
||||||
from backend.routes.chat import router as chat_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.
|
"""Builds and configures the FastAPI application with DI wiring.
|
||||||
|
|
||||||
Accepts a ``Settings`` instance so tests can supply custom config.
|
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:
|
def __init__(self, settings: Settings | None = None) -> None:
|
||||||
@@ -110,9 +115,12 @@ class AppBuilder:
|
|||||||
# Create database tables if they don't exist.
|
# Create database tables if they don't exist.
|
||||||
await init_db()
|
await init_db()
|
||||||
|
|
||||||
# Initialise the agent service on startup.
|
# Build agent with full dependency injection via the factory.
|
||||||
agent_service = await get_agent_service()
|
memory_repo = MemoryRepository()
|
||||||
agent_service.initialize()
|
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
|
yield
|
||||||
|
|
||||||
|
|||||||
+24
-38
@@ -27,41 +27,28 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import time
|
import time
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from pydantic_ai import Agent
|
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.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
|
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.
|
class AgentService:
|
||||||
Accepts a ``Settings`` instance for testability (dependency injection).
|
"""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:
|
def __init__(self, agent: Agent[Any, str], memory_repo: MemoryRepository) -> None:
|
||||||
self._settings = settings
|
self._agent = agent
|
||||||
self._agent: Agent[any, str] | None = None
|
self._memory_repo = memory_repo
|
||||||
|
|
||||||
# ── Lifecycle ──────────────────────────────────────────────────────────
|
# ── 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:
|
async def shutdown(self) -> None:
|
||||||
"""Release agent resources."""
|
"""Release agent resources."""
|
||||||
self._agent = None
|
self._agent = None
|
||||||
@@ -70,17 +57,13 @@ class AgentService:
|
|||||||
|
|
||||||
async def ask(self, prompt: str, session_id: str | None = None) -> AskResult:
|
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."""
|
"""Send a prompt to the agent and return the text output with usage stats."""
|
||||||
self.initialize()
|
di = DeepAgentDeps(backend=self._memory_repo.backend)
|
||||||
assert self._agent is not None
|
|
||||||
|
|
||||||
# Dependency injection using the repository's backend
|
|
||||||
memory_repo = MemoryRepository()
|
|
||||||
deps = DeepAgentDeps(backend=memory_repo.backend)
|
|
||||||
|
|
||||||
t0 = time.monotonic()
|
t0 = time.monotonic()
|
||||||
result = await self._agent.run(prompt, deps=deps)
|
result = await self._agent.run(prompt, deps=di)
|
||||||
elapsed = time.monotonic() - t0
|
elapsed = time.monotonic() - t0
|
||||||
usage = result.usage
|
usage = result.usage
|
||||||
|
|
||||||
return AskResult(
|
return AskResult(
|
||||||
output=result.output,
|
output=result.output,
|
||||||
input_tokens=usage.input_tokens,
|
input_tokens=usage.input_tokens,
|
||||||
@@ -89,6 +72,7 @@ class AgentService:
|
|||||||
elapsed_seconds=round(elapsed, 2),
|
elapsed_seconds=round(elapsed, 2),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class AskResult:
|
class AskResult:
|
||||||
"""Result of a single agent ``ask()`` call with token usage information."""
|
"""Result of a single agent ``ask()`` call with token usage information."""
|
||||||
@@ -108,20 +92,22 @@ class AskResult:
|
|||||||
elapsed_seconds: float = 0.0
|
elapsed_seconds: float = 0.0
|
||||||
"""Wall-clock time in seconds the agent took to respond."""
|
"""Wall-clock time in seconds the agent took to respond."""
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Singleton — lazy-initialised on the first call to ``ask()``.
|
# Singleton — wired once at startup by the lifespan hook in ``app.py``.
|
||||||
# Keeps the existing module-level ``ask`` facade so downstream imports in
|
# Routes access it through ``get_service()``.
|
||||||
# ``chat.py`` continue to work unchanged.
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
from backend.core.config import settings as _settings # noqa: E402
|
_service: AgentService | None = None
|
||||||
|
|
||||||
_service = AgentService(_settings)
|
|
||||||
|
|
||||||
def get_service() -> AgentService:
|
def get_service() -> AgentService:
|
||||||
"""Return the singleton AgentService instance."""
|
"""Return the singleton AgentService instance."""
|
||||||
|
assert _service is not None, "AgentService has not been initialised — did the lifespan run?"
|
||||||
return _service
|
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."""
|
def set_service(service: AgentService) -> None:
|
||||||
return await _service.ask(prompt, session_id=session_id)
|
"""Set the module-level singleton (called by the lifespan hook)."""
|
||||||
|
global _service
|
||||||
|
_service = service
|
||||||
|
|||||||
+5
-2
@@ -12,8 +12,11 @@ server {
|
|||||||
|
|
||||||
# ── API proxy ─────────────────────────────────────────────────
|
# ── API proxy ─────────────────────────────────────────────────
|
||||||
# Container runtime (Docker/Podman) resolves backend:8000 via
|
# Container runtime (Docker/Podman) resolves backend:8000 via
|
||||||
# its built-in DNS. nginx caches the resolved IP at startup, so
|
# its built-in DNS (127.0.0.11). Without a resolver, nginx caches
|
||||||
# restarting the backend container needs a frontend restart too.
|
# 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/ {
|
location /api/ {
|
||||||
proxy_pass http://backend:8000;
|
proxy_pass http://backend:8000;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
|
|||||||
Reference in New Issue
Block a user