Files
lucidia-core/duet/generator.py
Alexa Louise 6afdb4b148 Initial extraction from blackroad-prism-console
Lucidia Core - AI reasoning engines for specialized domains:
- Physicist (867 lines) - energy modeling, force calculations
- Mathematician (760 lines) - symbolic computation, proofs
- Geologist (654 lines) - terrain modeling, stratigraphy
- Engineer (599 lines) - structural analysis, optimization
- Painter (583 lines) - visual generation, graphics
- Chemist (569 lines) - molecular analysis, reactions
- Analyst (505 lines) - pattern recognition, insights
- Plus: architect, researcher, mediator, speaker, poet, navigator

Features:
- FastAPI wrapper with REST endpoints for each agent
- CLI with `lucidia list`, `lucidia run`, `lucidia api`
- Codex YAML configurations for agent personalities
- Quantum engine extensions

12,512 lines of Python across 91 files.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-30 08:00:53 -06:00

95 lines
3.4 KiB
Python

"""Generator interfaces for Lucidia's reasoning duet."""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Any, Callable, Dict, List, Optional, Protocol
@dataclass
class ProposeInput:
"""Minimal information required to form a proposal."""
goal: str
context: Dict[str, Any]
constraints: Optional[List[str]] = None
@dataclass
class Proposal:
"""Structured generator response."""
summary: str
rationale: List[str]
plan: List[str]
raw: Dict[str, Any] = field(default_factory=dict)
def to_payload(self) -> Dict[str, Any]:
"""Convert to a serialisable payload."""
return {
"summary": self.summary,
"rationale": list(self.rationale),
"plan": list(self.plan),
"raw": dict(self.raw),
}
class GeneratorBackend(Protocol):
"""Callable signature for lightweight local LLMs."""
def __call__(self, prompt: str) -> Dict[str, Any]: # pragma: no cover - protocol definition
...
class LocalGenerator:
"""Wraps a lightweight local LLM backend with prompt scaffolding."""
def __init__(self, model_name: str, backend: GeneratorBackend, prompt_builder: Callable[[ProposeInput], str]) -> None:
self.model_name = model_name
self._backend = backend
self._prompt_builder = prompt_builder
def build_prompt(self, payload: ProposeInput) -> str:
"""Build the structured system prompt."""
return self._prompt_builder(payload)
def propose(self, payload: ProposeInput) -> Proposal:
"""Invoke the backend and normalise output."""
prompt = self.build_prompt(payload)
response = self._backend(prompt)
summary = response.get("summary") or response.get("SUMMARY")
rationale = response.get("rationale") or response.get("RATIONALE")
plan = response.get("plan") or response.get("PLAN")
if not isinstance(summary, str):
raise ValueError("Generator response must include a 'summary' string.")
if not isinstance(rationale, list) or not all(isinstance(item, str) for item in rationale):
raise ValueError("Generator response must include a 'rationale' list of strings.")
if not isinstance(plan, list) or not all(isinstance(item, str) for item in plan):
raise ValueError("Generator response must include a 'plan' list of strings.")
raw = {key: value for key, value in response.items() if key not in {"summary", "SUMMARY", "rationale", "RATIONALE", "plan", "PLAN"}}
return Proposal(summary=summary.strip(), rationale=[item.strip() for item in rationale], plan=[item.strip() for item in plan], raw=raw)
def default_prompt_builder(payload: ProposeInput) -> str:
"""Default prompt template aligned with duet requirements."""
constraints = "\n".join(f"- {c}" for c in (payload.constraints or []))
context_repr = "\n".join(f"{key}: {value}" for key, value in payload.context.items())
return (
"SYSTEM: You propose; Codex Ψ′ will validate. No filler. Provide:\n"
"1) SUMMARY (≤120 words), 2) RATIONALE (bullet steps), 3) PLAN (3 actions).\n"
"CONSTRAINTS:\n"
f"{constraints}\n"
"TASK: {goal}\n"
"CONTEXT:\n{context}\n"
).format(goal=payload.goal, context=context_repr)
__all__ = ["Proposal", "ProposeInput", "LocalGenerator", "default_prompt_builder"]