Skip to content

Documentation navigation

Testing & Local Development

Test agents and tools locally without making live model calls.

#Testing and local development

Keep most SDK-facing tests offline: test tool bodies as normal Python, inspect compiled tool metadata, and replace only the remote invocation boundary when testing application behavior.

#Test tool functions directly

Decorators preserve the original callable, so dependencies do not change how direct unit tests invoke it.

Python
from maivn import Agent, depends_on_private_data

agent = Agent(name='support', api_key='test-key')


@depends_on_private_data(data_key='account', arg_name='account')
@agent.toolify(name='lookup_account')
def lookup_account(account: dict[str, object], question: str) -> str:
    return f"{account['name']}: {question}"


def test_lookup_account() -> None:
    assert lookup_account({'name': 'Ada'}, 'status') == 'Ada: status'

#Inspect compiled tool metadata

Agent.compile_tools() returns the actual public tool metadata sent to the runtime. It is the deterministic seam for names, descriptions, schemas, dependency elision, and final-tool selection.

Python
def test_private_dependency_is_not_model_visible() -> None:
    compiled = agent.compile_tools()[0]
    assert compiled.name == 'lookup_account'
    assert compiled.input_schema == {
        'type': 'object',
        'properties': {'question': {'type': 'string'}},
        'additionalProperties': False,
        'required': ['question'],
    }

Call agent.validate_tool_configuration() to check duplicate registered tool names. The swarm validator additionally checks final-output ownership. list_tools() returns current registered metadata. The SDK does not expose a full serialized-request inspection method.

#Test application behavior with InvokeResponse

InvokeResponse is exported from maivn. Construct it with its current required canonical fields when replacing a remote call:

Python
from unittest.mock import patch

from maivn import Agent, InvokeResponse
from maivn.messages import HumanMessage
from maivn_contracts.messages import Message


def summarize_ticket(agent: Agent, ticket: str) -> str:
    return agent.invoke([HumanMessage(content=ticket)]).response.strip()


def test_summarize_ticket() -> None:
    agent = Agent(name='support', api_key='test-key')
    final_message = Message(
        message_id='msg-test',
        role='assistant',
        content='  Customer wants a refund.  ',
        ts='2026-08-08T12:00:00Z',
    )
    fake = InvokeResponse(
        final_message=final_message,
        session_id='session-test',
        root_event_id='event-root',
        event_positions=[1],
        response='  Customer wants a refund.  ',
    )

    with patch.object(agent, 'invoke', return_value=fake):
        result = summarize_ticket(agent, 'I want my money back')

    assert result == 'Customer wants a refund.'

This test asserts application output, not the mock's existence. Prefer an httpx.MockTransport integration test when request serialization or stream/tool-runtime behavior is the contract under test.

#Test stream consumers

agent.stream() yields maivn.StreamEvent. Construct the canonical envelope and optional compatibility projection your consumer reads:

Python
from maivn import StreamEvent

events = [
    StreamEvent(
        position=1,
        event_type='session_progress',
        data={'phase': 'planning'},
        compat_name='enrichment',
        compat_payload={'phase': 'planning'},
    ),
    StreamEvent(
        position=2,
        event_type='session_completed',
        data={'response': 'Done.'},
        compat_name='final',
        compat_payload={'response': 'Done.'},
    ),
]

assert [event.name for event in events] == ['enrichment', 'final']

Use maivn.events.RawSSEEvent only when testing a consumer that starts from raw name/payload pairs, and pass those through normalize_stream(...) when that matches production.

#Fast local checks

  • Use a non-empty dummy key for construction-only tests; do not use a real credential.
  • Keep live-provider tests explicitly opt-in and separate from unit tests.
  • Assert ValueError, TypeError, or Pydantic validation failures for deterministic authoring mistakes.
  • Catch MaivnHTTPError or MaivnSDKError only when those public exception contracts are the behavior under test.

See Tools, Errors, and Frontend Events.