Skip to main content

A framework for building AI agents in Python

Project description

lemurian

lemurian is a framework for building AI agents in Python.

Architecture

Layer Components Role
Orchestration Swarm Registers agents, creates handoff tool, manages handoff loop, tracks active agent
Execution Runner Mediates all transcript access: builds messages for provider, injects system prompt, appends responses and tool results, detects handoffs
Interface Context, Tools, Provider Tools use Context to access state/session/agent. Provider receives pre-built messages and schemas.
Data Session, State Session holds the transcript (ground truth). State holds typed application data.

Tool — A @tool-decorated Python function exposed to an LLM. Schema is generated from the function signature. Receives a Context for accessing state, session, and agent.

Agent — Declarative Pydantic model bundling a system prompt, tools, model name, and provider. Agents don't run themselves — the Runner executes them.

Runner — The agent loop. Builds messages for the provider, dispatches tool calls, serializes results, detects handoffs. The only component that reads or writes the transcript.

Session — A single conversation transcript shared across all agents in a Swarm.

State — Typed Pydantic model for application data. Subclass it to add fields. Mutated in-place through context.state.

Context — Passed automatically to any tool declaring a context parameter. Holds references to session, state, and agent.

Swarm — Multi-agent orchestrator. Creates a dynamic handoff tool with an enum of available agents. Fresh-context handoffs: each agent sees only the handoff message onward.

Provider — Abstraction over LLM APIs. Implementations for OpenAI, OpenRouter, local vLLM, and Modal vLLM.

Defining Tools

Use the @tool decorator on any function. The schema is generated from the type hints and docstring:

from lemurian.tools import tool

@tool
def lookup_customer(email: str):
    """Find a customer by their email address."""
    return {"customer_id": "CUST-123", "name": "Jane Doe"}

You can override the name and description:

@tool(name="search", description="Search the knowledge base")
def kb_search(query: str, limit: int = 10):
    ...

Async functions work the same way:

@tool
async def fetch_data(url: str):
    """Fetch data from a URL."""
    ...

To access state, session, or the current agent, add a context parameter. It is automatically excluded from the schema and injected at call time:

from lemurian.context import Context

@tool
def update_counter(context: Context, amount: int):
    """Increment the counter in state."""
    context.state.counter += amount
    return context.state.counter

Single Agent

import asyncio
from lemurian.tools import tool
from lemurian.agent import Agent
from lemurian.runner import Runner
from lemurian.session import Session
from lemurian.state import State
from lemurian.message import Message, MessageRole
from lemurian.provider import OpenAIProvider

@tool
def greet(name: str):
    """Greet someone by name."""
    return f"Hello, {name}!"

agent = Agent(
    name="greeter",
    system_prompt="You are a friendly greeter. Use the greet tool when asked.",
    tools=[greet],
    model="gpt-4o-mini",
    provider=OpenAIProvider(),
)

async def main():
    session = Session(session_id="demo")
    session.transcript.append(
        Message(role=MessageRole.USER, content="Say hi to Alice")
    )
    result = await Runner().run(agent, session, State())
    print(result.last_message.content)

asyncio.run(main())

Multi-Agent Swarm

import asyncio
from lemurian.tools import tool
from lemurian.agent import Agent
from lemurian.swarm import Swarm
from lemurian.state import State
from lemurian.provider import OpenAIProvider

@tool
def check_invoice(invoice_id: str):
    """Check the status of an invoice."""
    return {"amount": 49.99, "status": "paid"}

provider = OpenAIProvider()

triage = Agent(
    name="triage",
    description="Routes customer requests to the right agent",
    system_prompt="Route customer requests to the appropriate agent.",
    model="gpt-4o-mini",
    provider=provider,
)

billing = Agent(
    name="billing",
    description="Handles billing and invoice questions",
    system_prompt="Help customers with billing inquiries.",
    tools=[check_invoice],
    model="gpt-4o-mini",
    provider=provider,
)

async def main():
    swarm = Swarm(agents=[triage, billing])
    result = await swarm.run("I have a question about my invoice", agent="triage")
    print(f"[{result.active_agent}] {result.last_message.content}")

asyncio.run(main())

The Swarm automatically creates a handoff tool for each agent with an enum of available targets. When triage calls handoff(agent_name="billing", message="..."), the Swarm switches context to the billing agent.

Model Providers

OpenAI / OpenRouter

from lemurian.provider import OpenAIProvider, OpenRouter

provider = OpenAIProvider()    # uses OPENAI_API_KEY env var
provider = OpenRouter()        # uses OPENROUTER_API_KEY env var

Local vLLM

Serve a model with vLLM:

uv run --extra local vllm serve "Qwen/Qwen3-8B" \
    --enable-auto-tool-choice \
    --tool-call-parser hermes \
    --reasoning-parser qwen3
from lemurian.provider import VLLMProvider

provider = VLLMProvider(url="localhost", port=8000)

Refer to the vLLM docs to pair the appropriate tool call parser with your model.

Modal vLLM (Remote/Serverless)

Deploy vLLM to Modal for serverless GPU inference:

uv sync --group modal
modal setup
modal deploy src/scripts/modal_vllm.py
from lemurian.provider import ModalVLLMProvider

provider = ModalVLLMProvider(
    endpoint_url="https://your-workspace--lemurian-vllm-vllmserver-serve.modal.run"
)

Customize deployments with environment variables:

Variable Default Description
MODEL_ID Qwen/Qwen3-8B HuggingFace model ID
GPU_TYPE A100 Modal GPU type (A10G, A100, H100)
GPU_COUNT 1 Number of GPUs for tensor parallelism
MAX_MODEL_LEN 8192 Maximum sequence length

Pre-download model weights for faster cold starts:

modal run src/scripts/modal_vllm.py --download

Testing

uv run pytest
uv run pytest --cov=lemurian --cov-report=term-missing

Development

lemurian is work in progress for agent-based experimentation. Feel free to suggest issues or modifications.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

lemurian-1.0.0.tar.gz (213.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

lemurian-1.0.0-py3-none-any.whl (19.5 kB view details)

Uploaded Python 3

File details

Details for the file lemurian-1.0.0.tar.gz.

File metadata

  • Download URL: lemurian-1.0.0.tar.gz
  • Upload date:
  • Size: 213.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for lemurian-1.0.0.tar.gz
Algorithm Hash digest
SHA256 f64681ff80437c2b98e02dcb767d9f627ca27095a930b4184fd64f7706edcb7c
MD5 8bb53857a7c23020143dc1b6c1377ddb
BLAKE2b-256 331dfe22f97d3d42a20efd54055c6c08ef89bc61a17d3be41bc937a2049ac112

See more details on using hashes here.

Provenance

The following attestation bundles were made for lemurian-1.0.0.tar.gz:

Publisher: publish.yml on mmargenot/lemurian

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file lemurian-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: lemurian-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 19.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for lemurian-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 11fb69abeb50dc96e6edec661f468d65ba1c8dbfcf70e4e6e6f3ddf99c6f63b4
MD5 3999d35815e6d57525157fa7765eff73
BLAKE2b-256 1317842d679a885ceaa35245479027abf2d593f4d0b5885be8eeba9b664d425e

See more details on using hashes here.

Provenance

The following attestation bundles were made for lemurian-1.0.0-py3-none-any.whl:

Publisher: publish.yml on mmargenot/lemurian

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page