Skip to main content

miiflow-agent

A lightweight, unified Python SDK for LLM providers with built-in agentic patterns

PyPI version Python versions License


miiflow-agent gives you a unified API across LLM providers, with built-in support for ReAct agents, tool calling, and streaming — all in ~39K lines of focused code.

from miiflow_agent import LLMClient, Message

# Same interface for any provider
client = LLMClient.create("openai", model="gpt-5.6-luna")
response = client.chat([Message.user("Hello!")])

# Switch providers with one line
client = LLMClient.create("anthropic", model="claude-sonnet-5")

Demo of an Agentic Run

https://github.com/user-attachments/assets/0b5c870a-f9b2-4d55-a829-9d7c000be907

Why miiflow-agent?

miiflow-agent LangChain LiteLLM
Codebase size ~39K lines ~500K lines ~50K lines
Dependencies 9 core 50+ 20+
Built-in agents ReAct + sub-agent hand-off Requires setup None
Tool system @tool decorator Chains None
Learning curve Hours Weeks Hours
Type safety Full generics Partial Basic

The LangChain Problem

LangChain is powerful but complex. For production apps, you often fight its abstractions more than use them. miiflow-agent gives you what you actually need:

  • Unified provider interface — swap OpenAI → Claude → Gemini with one line
  • Agentic patterns built-in — a single ReAct loop with emergent planning and multi-agent hand-off, not bolted on
  • Simple tool system — decorate any function with @tool
  • Real streaming — event-based, not just token callbacks
  • Type-safe — full generics, proper error types

The LiteLLM Gap

LiteLLM unifies provider APIs but stops there. miiflow-agent adds:

  • ReAct agents with multi-hop reasoning
  • Sub-agent hand-off for complex multi-step tasks
  • Tool calling with automatic schema generation
  • Context injection (Pydantic AI compatible)

Installation

pip install miiflow-agent

# With optional providers
pip install miiflow-agent[groq,google]

# Tracing support (Phoenix / Arize AX)
pip install miiflow-agent[observability]

# Everything
pip install miiflow-agent[all]

Requires Python 3.10+ (the 1.15.0 release dropped 3.9, forced by the openai 2.x floor — stay on 1.14.0 if you're pinned to 3.9).

Quick Start

Basic Chat

from miiflow_agent import LLMClient, Message

client = LLMClient.create("openai", model="gpt-5.6-luna")
response = client.chat([
    Message.system("You are a helpful assistant."),
    Message.user("What is Python?")
])
print(response.message.content)

client.chat() is a sync convenience that calls asyncio.run() internally — inside an already-running event loop (Jupyter, an async app), use await client.achat(...) instead.

Streaming

async for chunk in client.astream_chat([Message.user("Tell me a story")]):
    print(chunk.delta, end="", flush=True)

ReAct Agent with Tools

from miiflow_agent import Agent, AgentType, LLMClient, tool

@tool("calculate", "Evaluate mathematical expressions")
def calculate(expression: str) -> str:
    return str(eval(expression))

@tool("search", "Search for information")
def search(query: str) -> str:
    return f"Results for '{query}': ..."

# Create agent
agent = Agent(
    LLMClient.create("openai", model="gpt-5.6-sol"),
    agent_type=AgentType.REACT,
    max_iterations=10
)
agent.add_tool(calculate)
agent.add_tool(search)

# Run with automatic reasoning
result = await agent.run("What is 25 * 4 + the population of France?")
print(result.data)  # Agent reasons, calls tools, synthesizes answer

Context Injection (Pydantic AI Style)

from dataclasses import dataclass
from miiflow_agent import Agent, RunContext, tool

@dataclass
class UserContext:
    user_id: str
    permissions: list[str]

@tool("get_user_data")
def get_user_data(ctx: RunContext[UserContext], field: str) -> str:
    """Fetch data for the current user."""
    if "read" not in ctx.deps.permissions:
        return "Permission denied"
    return f"User {ctx.deps.user_id} data for {field}"

agent: Agent[UserContext, str] = Agent(client, agent_type=AgentType.REACT)
agent.add_tool(get_user_data)

result = await agent.run(
    "What's my account status?",
    deps=UserContext(user_id="alice", permissions=["read"])
)

Agent is generic over its deps — annotate the variable (Agent[UserContext, str]) and pass the runtime value via run(deps=...).

Architecture

┌─────────────────────────────────────────────────────────────────┐
│                         Your Application                         │
└─────────────────────────────┬───────────────────────────────────┘
                              │
┌─────────────────────────────▼───────────────────────────────────┐
│                          LLMClient                               │
│  • Unified interface for all providers                          │
│  • Automatic tool schema generation                             │
│  • Metrics collection & observability                           │
└─────────────────────────────┬───────────────────────────────────┘
                              │
        ┌─────────────────────┼─────────────────────┐
        │                     │                     │
        ▼                     ▼                     ▼
┌───────────────┐   ┌───────────────┐   ┌───────────────┐
│    Agent      │   │   Provider    │   │    Tools      │
│               │   │   Clients     │   │               │
│ • SINGLE_HOP  │   │               │   │ • @tool       │
│ • REACT       │   │ • OpenAI      │   │ • FunctionTool│
│ • SubAgents   │   │ • Anthropic   │   │ • HTTPTool    │
│               │   │ • Gemini      │   │ • Registry    │
│ ┌───────────┐ │   │ • More...     │   │               │
│ │Orchestrator│ │   │               │   │ ┌───────────┐ │
│ │ • plan    │ │   │               │   │ │ Schemas   │ │
│ │ • handoff │ │   │               │   │ │ • Auto-gen│ │
│ └───────────┘ │   │               │   │ │ • Validate│ │
└───────────────┘   │               │   │ └───────────┘ │
                    └───────────────┘   └───────────────┘
                              │
                              ▼
                    ┌───────────────┐
                    │   Message     │
                    │   Unified     │
                    │   Format      │
                    │               │
                    │ • Text        │
                    │ • Images      │
                    │ • Tool calls  │
                    └───────────────┘

Supported Providers

Provider Streaming Tool Calling Vision Status
OpenAI Stable
Anthropic Stable
Google Gemini Stable
Groq - Beta
Amazon Bedrock Beta
Mistral - Beta
OpenRouter Beta
Ollama - Beta
xAI - Beta

Stable providers are production-tested with full feature support. Beta providers are functional but may have edge cases.

Provider keys for LLMClient.create(): openai, anthropic, gemini (not google — that's only the pip extra's name), groq, bedrock, mistral, openrouter, ollama, xai.

Agentic Patterns

miiflow-agent runs a single, unified ReAct loop. Each turn the model emits exactly one of: tool calls (the loop continues) or a text answer (the loop exits). Planning and parallel multi-agent execution are emergent behaviors inside this one loop — there are no separate "plan & execute" or "multi-agent" orchestrators to choose between. Complex tasks are handled by letting the model plan over multiple turns and dispatch work to sub-agents as normal tool calls.

Migrating from a pre-1.8 release? The standalone PlanAndExecuteOrchestrator, MultiAgentOrchestrator, and the AgentType.PLAN_AND_EXECUTE / PARALLEL_PLAN / MULTI_AGENT enum values have been removed. Express the same outcomes with a single Agent plus sub_agents=[SubAgent(...)] — see Sub-agent hand-off below. AgentType.SINGLE_HOP (the default) and AgentType.REACT are the only modes.

ReAct (Reasoning + Acting)

The agent thinks step-by-step, deciding when to use tools, and plans across turns for multi-step tasks:

agent = Agent(client, agent_type=AgentType.REACT)

# Agent internally:
# Thought: I need to search for this information
# Action: search("topic")
# Observation: Results...
# Thought: Now I can answer
# Final Answer: ...

Sub-agent hand-off

For complex work that benefits from specialization, give the agent one or more sub-agents. The parent's ReAct loop decides when to hand off, and the dispatch happens as an ordinary tool call — no separate orchestrator. Sub-agents are wired in through AgentConfig; the framework synthesizes a dispatch_assistant-shaped tool that routes calls through the dispatch lifecycle (depth, cycle, budget, and event bubbling):

from miiflow_agent import Agent
from miiflow_agent.core.config import AgentConfig

# `sub_agents` holds objects implementing the SubAgent protocol — each one a
# parent-side "edge" to a specialist child, carrying per-edge policy such as
# `when_to_use`, `handoff_schema`, and `clarification_policy`.
lead = Agent(config=AgentConfig(
    client=client,
    agent_type=AgentType.REACT,  # AgentConfig defaults to SINGLE_HOP
    system_prompt="Coordinate research and writing.",
    sub_agents=[researcher_subagent, writer_subagent],
))

result = await lead.run(
    "Research Python web frameworks, compare them, and write a summary"
)
# The ReAct loop plans, dispatches to each specialist, and synthesizes the result.

When constructing with config=, pass everything through AgentConfig — mixing config= with sibling kwargs like system_prompt= raises a ValueError. See miiflow_agent/core/subagent.py for the SubAgent protocol and its per-edge policy fields, and examples/subagents.py for reusable sub-agent config templates and a registry-based dispatch example.

Event Streaming

Stream real-time events during agent execution:

from miiflow_agent import Agent, AgentType, RunContext
from miiflow_agent.core.react import ReActEventType

agent = Agent(client, agent_type=AgentType.REACT)
context = RunContext(deps=None)

async for event in agent.stream("What is 2+2?", context):
    match event.event_type:
        case ReActEventType.THINKING_CHUNK:
            print(event.data.get("delta", ""), end="")
        case ReActEventType.ACTION_PLANNED:
            print(f"\nCalling: {event.data['action']}")
        case ReActEventType.OBSERVATION:
            print(f"Result: {event.data['observation']}")
        case ReActEventType.FINAL_ANSWER:
            print(f"\nAnswer: {event.data['answer']}")

Observability

Built-in OpenInference tracing for Phoenix and Arize AX. Requires the observability extra:

pip install "miiflow-agent[observability]"
# Local / self-hosted Phoenix
from miiflow_agent.core.observability import ObservabilityConfig, enable_phoenix_tracing

enable_phoenix_tracing(ObservabilityConfig.for_local())  # or ObservabilityConfig.from_env()

# Arize AX — credentials are the switch, no flag needed:
#   export ARIZE_SPACE_ID=...  ARIZE_API_KEY=...  [ARIZE_PROJECT_NAME=my-app]
from miiflow_agent.core.observability import setup_opentelemetry_tracing
setup_opentelemetry_tracing()

# All LLM calls are now traced. Wrap your own units of work in an agent span:
from miiflow_agent.core.observability import agent_span

with agent_span("my-run", input_value=prompt, session_id=thread_id):
    result = await agent.run(prompt)

The legacy setup_tracing(phoenix_endpoint=...) helper still exists but is gated on the PHOENIX_ENABLED=true env var — pass force=True to bypass the gate.

Error Handling

Comprehensive error hierarchy:

from miiflow_agent import (
    MiiflowLLMError,    # Base
    ProviderError,      # Provider-specific
    RateLimitError,     # Rate limited
    AuthenticationError, # Invalid API key
    TimeoutError,       # Request timeout
    ToolError,          # Tool execution failed
)

try:
    response = client.chat(messages)
except RateLimitError as e:
    print(f"Rate limited, retry after {e.retry_after}s")
except AuthenticationError:
    print("Check your API key")
except ProviderError as e:
    print(f"{e.provider} error: {e.message}")

RateLimitError.retry_after is populated automatically from provider Retry-After headers. ModelError and ParsingError are also exported.

Documentation

Contributing

We welcome contributions! Here's how to get started:

# Clone and install
git clone https://github.com/Miiflow/miiflow-agent.git
cd miiflow-agent
pip install -e ".[all]"

# Run tests
pytest tests/

# Format code
black miiflow_agent/ tests/
isort miiflow_agent/ tests/

Ways to Contribute

  • Report bugs — Open an issue with reproduction steps
  • Request features — Describe your use case
  • Add providers — See CONTRIBUTING.md for the provider guide
  • Improve docs — Fix typos, add examples
  • Write tests — Increase coverage

See CONTRIBUTING.md for detailed guidelines.

License

MIT License - see LICENSE for details.

Download files

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

Source Distribution

miiflow_agent-1.17.0.tar.gz (442.1 kB view details)

Uploaded Source

Built Distribution

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

miiflow_agent-1.17.0-py3-none-any.whl (518.2 kB view details)

Uploaded Python 3

File details

Details for the file miiflow_agent-1.17.0.tar.gz.

File metadata

  • Download URL: miiflow_agent-1.17.0.tar.gz
  • Upload date:
  • Size: 442.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.4.2 CPython/3.11.16 Linux/6.17.0-1022-azure

File hashes

Hashes for miiflow_agent-1.17.0.tar.gz
Algorithm Hash digest
SHA256 7492badca9a6c0ce5b3c2bf35ce58f79db58456256292b1a3f77c0613d3220bb
MD5 1bd8a70c29363ba012ce9aca3dbe2c43
BLAKE2b-256 d2b8d55eae6d0f110825ba1716aebf0920cbe434b2c62a3f2e47b052269112eb

See more details on using hashes here.

File details

Details for the file miiflow_agent-1.17.0-py3-none-any.whl.

File metadata

  • Download URL: miiflow_agent-1.17.0-py3-none-any.whl
  • Upload date:
  • Size: 518.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.4.2 CPython/3.11.16 Linux/6.17.0-1022-azure

File hashes

Hashes for miiflow_agent-1.17.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a3852b893385759b311a87e61badc400dff01f85862ae24efeefd67ff1adfa9b
MD5 e40684f187802e113a9010fa4d08c321
BLAKE2b-256 639bfdd39224b1d69ab703abb877ef16ac59ed6fb19dec9cb2918a830797b8a0

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

1.17.0 This release

2 files

1.16.0

2 files

1.13.0

2 files

1.12.0

2 files

1.11.0

2 files

1.5.0

2 files

1.4.0

2 files

1.3.0

2 files

1.2.0

2 files

1.1.2

2 files

1.1.1

2 files

1.0.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page