Skip to content

simulatecraft.brains.llm

simulatecraft.brains.llm

LLMBrain: pydantic-ai-driven reasoning behind the standard Brain interface.

This is the ONLY module in the package that imports pydantic_ai. Its API moves fast, so every pydantic-ai-specific call lives here; pin the exact version in pyproject.toml and upgrade in this single file.

Model string formats accepted by LLMBrain / resolve_model()

  • "groq:openai/gpt-oss-120b" ← Groq free tier, default ⚡
  • "groq:openai/gpt-oss-20b" ← Groq free tier, faster/smaller
  • "groq:qwen/qwen3.6-27b" ← Groq free tier, strong reasoning
  • "openrouter:meta-llama/llama-3.1-8b-instruct:free" ← OpenRouter free tier
  • "openrouter:anthropic/claude-sonnet-4.6" ← OpenRouter paid
  • "anthropic:claude-sonnet-4-5" ← direct Anthropic key
  • "openai:gpt-4o-mini" ← direct OpenAI key
  • "openai-compatible:oc/mimo-v2.5-free" ← any OpenAI-compatible gateway
  • "oc/mimo-v2.5-free" ← same, when OPENAI_BASE_URL is set
  • "google-gla:gemini-2.0-flash" ← direct Google key
  • "test" ← offline TestModel, no key needed

Auto-selection order (resolve_model): GROQ_API_KEY → OPENROUTER_API_KEY → "test"

OpenAI-compatible gateways (9Router, LiteLLM, vLLM, …)

export OPENAI_BASE_URL=http://localhost:20128/v1
export OPENAI_API_KEY=<dashboard-key>
export SIMULATECRAFT_MODEL=oc/mimo-v2.5-free

BrainDeps

Bases: BaseModel

Dependency-injected context handed to the pydantic-ai agent each run.

LLMBrain

LLMBrain(action_types: list[type[Action]], *, persona: str, model: str | Any = 'test', config: LLMBrainConfig | None = None, instructions: str | None = None, memory: MemoryStream | None = None, retriever: Retriever | None = None, planner: Planner | None = None, skills: SkillRegistry | None = None, summarizer: Any = None)

Bases: Brain[Observation]

Decides via an LLM with validated structured output (no manual parsing).

  • Available actions are exposed through pydantic-ai's output_type as a discriminated union of your Action subclasses, so the model's choice arrives as an already-validated Action instance.
  • Schema failures are handled by pydantic-ai's native retry mechanism.
  • Provider switching is just the model string ("anthropic:...", "openai:...", "google-gla:...", "google-gla:gemini-...").
Source code in src/simulatecraft/brains/llm.py
def __init__(
    self,
    action_types: list[type[Action]],
    *,
    persona: str,
    model: str | Any = "test",
    config: LLMBrainConfig | None = None,
    instructions: str | None = None,
    memory: MemoryStream | None = None,
    retriever: Retriever | None = None,
    planner: Planner | None = None,
    skills: SkillRegistry | None = None,
    summarizer: Any = None,
) -> None:
    self.config = config or LLMBrainConfig(model=model if isinstance(model, str) else "test")
    self.action_types = action_types
    self.persona = persona
    self.memory = memory if memory is not None else MemoryStream()
    self.retriever = retriever
    self.planner = planner
    self.skills = skills
    self._inbox: list[str] = []

    # Resolve model string → pydantic-ai model object.
    # "openrouter:<name>" builds an OpenRouterModel; everything else passes through.
    resolved_model = _build_pydantic_ai_model(model)

    output_type = _discriminated_union(action_types)
    self.agent: PydanticAgent[BrainDeps, Any] = PydanticAgent(
        resolved_model,
        output_type=output_type,
        deps_type=BrainDeps,
        instructions=instructions or DEFAULT_INSTRUCTIONS,
        retries=self.config.retries,
    )

    self.reflection: ReflectionEngine | None = None
    if summarizer is not None:
        self.reflection = ReflectionEngine(
            summarizer, every_n_records=max(5, self.config.reflect_every // 2)
        )
    self._observations_since_reflect = 0
    self._skill_queue: list[Action] = []

resolve_model

resolve_model(env_var: str = 'SIMULATECRAFT_MODEL') -> str

Read the model string from the environment, auto-selecting a free provider.

Priority
  1. SIMULATECRAFT_MODEL env var — any format accepted: openrouter:meta-llama/llama-3.1-8b-instruct:free groq:openai/gpt-oss-120b anthropic:claude-sonnet-4-5 openai:gpt-4o-mini test
  2. GROQ_API_KEY present → groq:openai/gpt-oss-120b (Groq is free-tier, very fast — best default for agent tick loops)
  3. OPENROUTER_API_KEY present → openrouter:meta-llama/llama-3.1-8b-instruct:free
  4. No keys at all → "test" (offline TestModel, zero network calls)
Source code in src/simulatecraft/brains/llm.py
def resolve_model(env_var: str = "SIMULATECRAFT_MODEL") -> str:
    """Read the model string from the environment, auto-selecting a free provider.

    Priority
    --------
    1. ``SIMULATECRAFT_MODEL`` env var  — any format accepted:
         openrouter:meta-llama/llama-3.1-8b-instruct:free
         groq:openai/gpt-oss-120b
         anthropic:claude-sonnet-4-5
         openai:gpt-4o-mini
         test
    2. ``GROQ_API_KEY`` present  →  ``groq:openai/gpt-oss-120b``
       (Groq is free-tier, very fast — best default for agent tick loops)
    3. ``OPENROUTER_API_KEY`` present  →  ``openrouter:meta-llama/llama-3.1-8b-instruct:free``
    4. No keys at all  →  ``"test"`` (offline TestModel, zero network calls)
    """
    from dotenv import load_dotenv

    load_dotenv()

    model = os.getenv(env_var, "").strip()
    if model:
        return model

    if os.getenv("GROQ_API_KEY", "").strip():
        groq_model = "groq:openai/gpt-oss-120b"
        log.info("No %s set; using Groq free-tier model: %s", env_var, groq_model)
        return groq_model

    if os.getenv("OPENROUTER_API_KEY", "").strip():
        free_model = "openrouter:meta-llama/llama-3.1-8b-instruct:free"
        log.info("No %s set; using free OpenRouter model: %s", env_var, free_model)
        return free_model

    log.warning(
        "No %s, GROQ_API_KEY, or OPENROUTER_API_KEY set. Using offline TestModel — "
        "agents will produce canned responses.\n"
        "  Free options:\n"
        "    Groq (fast):       export GROQ_API_KEY=gsk_...   (console.groq.com/keys)\n"
        "    OpenRouter (many): export OPENROUTER_API_KEY=sk-or-... (openrouter.ai/keys)",
        env_var,
    )
    return "test"