Getting started

Praval has two entry paths. Start with direct Agent calls when one agent is enough. Add Reef and Spores when independent specialists need to collaborate.

Install

python -m pip install praval

Praval 0.8.4 supports Python 3.10 through 3.14. Optional capabilities are installed separately:

python -m pip install "praval[memory]"
python -m pip install "praval[storage]"
python -m pip install "praval[mcp]"
python -m pip install "praval[observability]"
python -m pip install "praval[eval-ragas]"

For a hosted provider, set the corresponding environment variable before running an example:

export OPENAI_API_KEY="..."
# or ANTHROPIC_API_KEY, COHERE_API_KEY, GEMINI_API_KEY, or OPENROUTER_API_KEY

Passing provider and model explicitly makes an example reproducible. If they are omitted, Praval resolves its configured defaults and available credentials.

First path: call a model through Agent

from praval import Agent

with Agent(
    "assistant",
    provider="openai",
    model="gpt-5.4-mini",
    system_message="Be concise.",
) as assistant:
    response = assistant.generate("Explain what a Praval Spore carries.")
    print(response.content)
    print(response.provider, response.model, response.usage)

Agent.generate() returns ModelResponse. Its content, provider, model, usage, tool calls, and raw provider data are available without changing the application contract for each provider.

The compatibility method returns only a string:

from praval import Agent

with Agent("assistant", provider="openai", model="gpt-5.4-mini") as assistant:
    text = assistant.chat("Explain Reef in one sentence.")

Prefer generate() for new code. Use agenerate(), stream(), and astream() for asynchronous work and normalized streaming events.

Structured output

import json

from praval import Agent

with Agent("assistant", provider="openai", model="gpt-5.4-mini") as assistant:
    response = assistant.generate(
        "Return one fact about Reef as JSON.",
        response_schema={
            "schema": {
                "type": "object",
                "properties": {"fact": {"type": "string"}},
                "required": ["fact"],
            },
            "validate_locally": True,
        },
    )
    fact = json.loads(response.content)["fact"]

The runtime checks that the selected provider profile supports structured output and then sends the schema as a provider constraint. Local validation is off by default; the example enables it. Invalid final JSON or a schema mismatch raises ProviderInvalidResponseError, including on streaming final responses. See Structured Outputs for the full contract.

Second path: collaborate through Reef

A Spore contains routing fields and a structured knowledge dictionary. The conventional knowledge["type"] value lets decorated agents decide which messages to handle.

from praval import agent, broadcast, get_reef, start_agents


@agent("researcher", provider="ollama", responds_to=["research_request"])
def researcher(spore):
    topic = spore.knowledge["topic"]
    broadcast(
        {
            "type": "research_complete",
            "topic": topic,
            "finding": f"Evidence collected for {topic}",
        }
    )


@agent("editor", provider="ollama", responds_to=["research_complete"])
def editor(spore):
    print(spore.knowledge["finding"])


start_agents(
    researcher,
    editor,
    initial_data={"type": "research_request", "topic": "agent systems"},
)
reef = get_reef()
reef.wait_for_completion(timeout=30)
reef.shutdown()

This example uses the Ollama preset only to construct credential-free message handlers; it does not call the local model server. A decorated handler may call chat() or achat() when it actually needs model output.

wait_for_completion() waits for submitted Reef work. Always shut down Reef and other resources explicitly in scripts and tests.

When to use each layer

Need

Start with

One conversational or tool-using agent

Agent

Provider-neutral metadata and usage

Agent.generate()

Streaming deltas and final events

Agent.stream() / Agent.astream()

Multiple reactive specialists

@agent, Reef, and Spores

Deterministic application cleanup

PravalApp

External MCP tools

praval.mcp.MCPClient with async Agent calls

PravalApp owns agents and a Reef for cleanup. In this release it does not isolate provider registries or redirect agent Reef helpers; see Application lifecycle.

Local models

Praval connects to an already-running OpenAI-compatible server:

from praval import Agent

local = Agent("local", provider="ollama", model="llama3")
try:
    print(local.chat("Say hello."))
finally:
    local.close()

The presets are ollama, vllm, lmstudio, and llama-cpp. Text and streaming are enabled by default. Tools, structured output, reasoning, and multimodal input require verified capabilities for the server and model you are using. Ollama can discover these with config={"provider_options": {"discover_model": True}}; see Local LLMs.

Learn by inspecting execution

The Jupyter course explains Agent, Reef, Spore, tools, memory, HITL, MCP, and voice flows with visible runtime state. Start with examples/notebooks/course/00_architecture.ipynb.

For a credential-free runtime check:

python examples/model_runtime_fake_provider.py

Next, read Core concepts and API layers, Model Runtime, and the generated API Reference.

Building with a coding agent

Give a coding agent the installed version (praval --version), the matching versioned manual at https://pravalagents.com/docs/v0.8.4/, and the task’s provider/model requirements. praval doctor --json reports installation and credential presence without printing keys. The published manual includes its plain-text sources under _sources/, such as _sources/guide/model-runtime.md.txt, for tools that cannot render HTML.

In 0.8.4, doctor checks the listed provider credentials but does not include OpenRouter. Check that OPENROUTER_API_KEY is set without printing its value when using that adapter.

For generated application code:

  • Use the documented public imports and generated API signatures. Prefer generate() when the caller needs usage, metadata or tool results.

  • Distinguish constructor config from per-call options; see Model Runtime. Unknown call keywords warn and are ignored in 0.8.4.

  • Verify model capabilities through Providers and Local LLMs. A registry profile describes Praval’s assumptions, not account availability.

  • Register callable tools through Agent.tool() or add_tool_spec(), and use async agent APIs for MCP tools. Keep the client open through approval/resume.

  • Bound retries, output and tool rounds, handle typed failures, and close owned agents, clients and Reef resources. Check tool results before claiming success.

  • Exercise behavior with fake provider/HTTP clients before paid model checks. Compilation alone does not establish that a tool ran or an artifact is correct.

Design the first agent so it can be evaluated

Use a stable agent name and versioned model/prompt configuration, return a structured terminal outcome, bound retries and tool rounds, and keep providers and tools replaceable in tests. One completed agent call should produce one immutable observation with aggregated model/tool facts. Do not create one evaluation subject per log line, model attempt, or tool call.

When quality becomes a release decision, begin with deterministic assertions and metrics. Add a separate evaluator model or least-privilege evaluator agent only for semantic judgment. The complete pattern and anti-pattern guide is Recommended agent and evaluator-agent patterns; the credential-free first suite is Install and five-minute quickstart.