Spaces:
Sleeping
Sleeping
| """Abstract base for LexGuard agents. | |
| Concrete agents define: | |
| - `name`: matches `AgentName` Literal in schemas | |
| - `prompt_file`: filename (without .md) under `app/prompts/` | |
| - `run(...)`: the agent-specific call producing structured output | |
| The base class owns: | |
| - prompt loading + system-block assembly with caching | |
| - LLM invocation | |
| - event-bus emission of `AgentMessage` for streaming | |
| """ | |
| from __future__ import annotations | |
| import logging | |
| import time | |
| from abc import ABC | |
| from typing import Any, ClassVar | |
| from app.prompts import SHARED_PREAMBLE, load_prompt | |
| from app.schemas import AgentMessage, AgentName | |
| from app.services import llm | |
| logger = logging.getLogger(__name__) | |
| class BaseAgent(ABC): | |
| name: ClassVar[AgentName] | |
| prompt_file: ClassVar[str] | |
| def __init__(self, *, document: str | None = None, emit=None) -> None: | |
| self._document = document | |
| self._emit = emit | |
| def role_instructions(self) -> str: | |
| return load_prompt(self.prompt_file) | |
| def system_blocks(self) -> list[dict[str, Any]]: | |
| return llm.build_system_blocks( | |
| shared_preamble=SHARED_PREAMBLE, | |
| document=self._document, | |
| role_instructions=self.role_instructions, | |
| ) | |
| async def _call( | |
| self, | |
| user_prompt: str, | |
| *, | |
| max_tokens: int = 2048, | |
| temperature: float = 0.2, | |
| clause_id: str | None = None, | |
| log_label: str = "", | |
| ) -> llm.LLMResponse: | |
| started = time.perf_counter() | |
| response = await llm.complete( | |
| system_blocks=self.system_blocks(), | |
| user_prompt=user_prompt, | |
| max_tokens=max_tokens, | |
| temperature=temperature, | |
| ) | |
| elapsed = int((time.perf_counter() - started) * 1000) | |
| logger.info( | |
| "agent=%s %s clause_id=%s elapsed_ms=%d cache_read=%d", | |
| self.name, | |
| log_label, | |
| clause_id, | |
| elapsed, | |
| response.usage.cache_read_input_tokens, | |
| ) | |
| return response | |
| def _emit_message(self, content: str, clause_id: str | None = None) -> None: | |
| if self._emit is None: | |
| return | |
| self._emit( | |
| AgentMessage( | |
| agent=self.name, | |
| clause_id=clause_id, | |
| content=content, | |
| timestamp=time.time(), | |
| ) | |
| ) | |