docs: add Next.js documentation site with @hanzo/mdx

- Fumadocs-based docs with @hanzo/mdx and @hanzo/ui integration
- Comprehensive SDK documentation for all packages:
  - hanzo-agent SDK (agents, tools, guardrails, tracing)
  - hanzo-mcp tools (filesystem, shell, browser, memory, etc.)
  - Client, models, embeddings, chat APIs
- Tailwind 4 + Next.js 16 + React 19 stack
- Static export for GitHub Pages deployment
This commit is contained in:
2025-12-29 20:16:38 -08:00
parent 4d0db161a8
commit b1de477422
61 changed files with 12429 additions and 0 deletions
+27
View File
@@ -0,0 +1,27 @@
# Dependencies
node_modules/
# Next.js
.next/
out/
# Fumadocs generated
.source/
# Build
dist/
# Misc
.DS_Store
*.pem
*.log
# Local env
.env*.local
# Vercel
.vercel
# TypeScript
*.tsbuildinfo
next-env.d.ts
+227
View File
@@ -0,0 +1,227 @@
# Zen Coder Training Documentation
This document describes the training infrastructure and methodology for the Zen Coder model family.
## Overview
Zen Coder is a code-specialized LLM trained on the **Zen Agentic Dataset** - a curated collection of:
- Git commit history and diffs from 1,452+ repositories
- Claude Code debug sessions (agentic interactions)
- Code review and documentation examples
- Multi-language programming samples
## Dataset Statistics
| Metric | Value |
|--------|-------|
| Total Tokens | ~8.47B |
| Training Samples | 1.44M |
| Validation Samples | 75K |
| Prepared Data Size | 4 GB |
| Source Repositories | 1,452+ |
### Data Sources
1. **Git History** (~12GB)
- Commit messages and diffs
- Full source files at each commit
- Author metadata
2. **Claude Debug Sessions** (~12GB)
- Real agentic coding interactions
- Tool usage patterns
- Multi-turn problem solving
3. **Claude Full Conversations** (~2GB)
- Extended coding sessions
- Architecture discussions
- Code review examples
4. **Additional Sources** (~4GB)
- Internal development logs
- Documentation examples
- Test cases
## Training Configuration
### Base Model
- **Model**: `Qwen/Qwen3-4B-Instruct-2507`
- **Parameters**: 4B
- **Architecture**: Qwen3 transformer
### LoRA Configuration
```yaml
fine_tune_type: lora
num_layers: -1 # ALL layers
batch_size: 1
grad_accumulation: 8 # Effective batch = 8
learning_rate: 1e-5
optimizer: adamw
max_seq_length: 4096
mask_prompt: true # Train on completions only
grad_checkpoint: true # Memory optimization
```
### Training Parameters
| Parameter | Value |
|-----------|-------|
| Total Iterations | 50,000 |
| Checkpoint Every | 1,000 iters |
| Eval Every | 500 iters |
| Estimated Time | ~3 days |
## Data Preparation
### Chunking Strategy
Long sequences are chunked to fit within the 4096 token context:
```python
MAX_CHARS_PER_CHUNK = 12000 # ~3K tokens per chunk
def chunk_messages(messages, max_chars):
"""Split long message sequences into manageable chunks."""
# Split on paragraph boundaries when possible
# Each chunk gets system prompt prepended
# Maintains conversation context
```
### Format Conversion
All data is converted to the `messages` format:
```json
{
"messages": [
{"role": "system", "content": "You are Zen Coder..."},
{"role": "user", "content": "Explain this code..."},
{"role": "assistant", "content": "This code..."}
]
}
```
### Supported Input Formats
The pipeline handles multiple source formats:
1. **Messages format** - Direct passthrough
2. **Conversations format** - OpenAI-style with `from`/`value`
3. **Prompt/Completion** - Simple pairs
4. **Git content** - Commits, diffs, files
5. **Debug sessions** - Claude Code format
## Training Infrastructure
### Hardware
- Apple Silicon (M-series)
- MLX framework for Apple GPU acceleration
- 64GB unified memory
### Software Stack
- `mlx-lm` - MLX language model training
- `tiktoken` - Token counting (cl100k_base)
- Custom data preparation pipeline
## Checkpoints
Checkpoints are saved every 1,000 iterations:
```
adapters/
├── 0001000_adapters.safetensors (63MB)
├── 0002000_adapters.safetensors
├── ...
├── 0050000_adapters.safetensors
└── adapters.safetensors (latest)
```
## Usage
### Training
```bash
cd /path/to/zen-coder/training
# Start training
python train_full.py
# Resume from checkpoint
python train_full.py --resume
# Check status
python train_full.py --status
```
### Monitoring
```bash
# Watch training progress
tail -f full_training.log
# Check status JSON
cat training_status.json
```
### Inference with Adapter
```python
from mlx_lm import load, generate
model, tokenizer = load(
"Qwen/Qwen3-4B-Instruct-2507",
adapter_path="./adapters"
)
response = generate(
model, tokenizer,
prompt="Explain this Python code:\n\ndef fibonacci(n):\n ...",
max_tokens=500
)
```
## HuggingFace
- **Private dataset**: `zenlm/zen-agentic-dataset`
- **Public card**: `hanzoai/zen-agentic-dataset`
- **Model**: `zenlm/zen-coder-4b-instruct` (after training)
## Best Practices Applied
1. **Train on completions only** (`--mask-prompt`)
- Loss computed only on assistant responses
- User/system tokens masked
2. **LoRA on ALL layers** (`--num-layers -1`)
- Better adaptation than attention-only
- Moderate rank for regularization
3. **Gradient accumulation**
- Effective batch size 8 with batch=1
- Memory efficient
4. **Gradient checkpointing**
- Reduces memory footprint
- Enables longer sequences
5. **Data chunking**
- Samples split to fit context
- Preserves conversation structure
## Training Progress
Track training metrics:
| Iteration | Train Loss | Notes |
|-----------|------------|-------|
| 1 | 2.19 | Initial |
| 1,000 | 1.5x | First checkpoint |
| 10,000 | 1.3x | Steady improvement |
| 50,000 | TBD | Final |
## References
- [MLX-LM Documentation](https://github.com/ml-explore/mlx-examples/tree/main/llms)
- [LoRA Paper](https://arxiv.org/abs/2106.09685)
- [Qwen3 Technical Report](https://qwenlm.github.io/blog/qwen3/)
+46
View File
@@ -0,0 +1,46 @@
import { source } from '@/lib/source';
import {
DocsPage,
DocsBody,
DocsTitle,
DocsDescription,
} from 'fumadocs-ui/page';
import { notFound } from 'next/navigation';
import defaultMdxComponents from 'fumadocs-ui/mdx';
export default async function Page(props: {
params: Promise<{ slug?: string[] }>;
}) {
const params = await props.params;
const page = source.getPage(params.slug);
if (!page) notFound();
const { body: MDX, toc } = await page.data.load();
return (
<DocsPage toc={toc}>
<DocsTitle>{page.data.title}</DocsTitle>
<DocsDescription>{page.data.description}</DocsDescription>
<DocsBody>
<MDX components={{ ...defaultMdxComponents }} />
</DocsBody>
</DocsPage>
);
}
export async function generateStaticParams() {
return source.generateParams();
}
export async function generateMetadata(props: {
params: Promise<{ slug?: string[] }>;
}) {
const params = await props.params;
const page = source.getPage(params.slug);
if (!page) notFound();
return {
title: page.data.title,
description: page.data.description,
};
}
+39
View File
@@ -0,0 +1,39 @@
import { DocsLayout } from 'fumadocs-ui/layouts/docs';
import type { ReactNode } from 'react';
import { source } from '@/lib/source';
function HanzoLogo() {
return (
<svg viewBox="0 0 67 67" width="24" height="24" xmlns="http://www.w3.org/2000/svg">
<path d="M22.21 67V44.6369H0V67H22.21Z" fill="currentColor"/>
<path d="M0 44.6369L22.21 46.8285V44.6369H0Z" fill="currentColor" opacity="0.7"/>
<path d="M66.7038 22.3184H22.2534L0.0878906 44.6367H44.4634L66.7038 22.3184Z" fill="currentColor"/>
<path d="M22.21 0H0V22.3184H22.21V0Z" fill="currentColor"/>
<path d="M66.7198 0H44.5098V22.3184H66.7198V0Z" fill="currentColor"/>
<path d="M66.6753 22.3185L44.5098 20.0822V22.3185H66.6753Z" fill="currentColor" opacity="0.7"/>
<path d="M66.7198 67V44.6369H44.5098V67H66.7198Z" fill="currentColor"/>
</svg>
);
}
export default function Layout({ children }: { children: ReactNode }) {
return (
<DocsLayout
tree={source.pageTree}
nav={{
title: (
<div className="flex items-center gap-2">
<HanzoLogo />
<span className="font-semibold">Hanzo Python SDK</span>
</div>
),
url: '/docs',
}}
sidebar={{
defaultOpenLevel: 1,
}}
>
{children}
</DocsLayout>
);
}
+1
View File
@@ -0,0 +1 @@
@import 'tailwindcss';
+37
View File
@@ -0,0 +1,37 @@
import 'fumadocs-ui/style.css';
import './global.css';
import { RootProvider } from 'fumadocs-ui/provider/next';
import type { ReactNode } from 'react';
import type { Metadata } from 'next';
export const metadata: Metadata = {
title: {
default: 'Hanzo Python SDK',
template: '%s | Hanzo Python SDK',
},
description: 'The official Python SDK for Hanzo AI - 100+ LLM providers through a single OpenAI-compatible API',
icons: {
icon: [
{ url: '/python-sdk/favicon.svg', type: 'image/svg+xml' },
{ url: '/python-sdk/favicon.png', type: 'image/png', sizes: '32x32' },
],
apple: '/python-sdk/apple-touch-icon.png',
},
};
export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="en" suppressHydrationWarning className="dark">
<body>
<RootProvider
theme={{
defaultTheme: 'dark',
attribute: 'class',
}}
>
{children}
</RootProvider>
</body>
</html>
);
}
+31
View File
@@ -0,0 +1,31 @@
import Link from 'next/link';
export default function HomePage() {
return (
<main className="flex min-h-screen flex-col items-center justify-center p-8">
<div className="max-w-2xl text-center">
<h1 className="mb-4 text-4xl font-bold">Hanzo Python SDK</h1>
<p className="mb-8 text-lg text-fd-muted-foreground">
The official Python SDK for Hanzo AI - Unified access to 100+ LLM providers
through a single OpenAI-compatible API.
</p>
<div className="flex gap-4 justify-center">
<Link
href="/docs"
className="rounded-lg bg-fd-primary px-6 py-3 font-medium text-fd-primary-foreground transition-colors hover:bg-fd-primary/90"
>
Get Started
</Link>
<a
href="https://github.com/hanzoai/python-sdk"
className="rounded-lg border border-fd-border px-6 py-3 font-medium transition-colors hover:bg-fd-accent"
target="_blank"
rel="noopener noreferrer"
>
GitHub
</a>
</div>
</div>
</main>
);
}
+135
View File
@@ -0,0 +1,135 @@
---
title: Agents
description: LLMs equipped with instructions and tools
---
Agents are the core building block in your apps. An agent is a large language model (LLM), configured with instructions and tools.
## Basic configuration
The most common properties of an agent you'll configure are:
- `instructions`: also known as a developer message or system prompt.
- `model`: which LLM to use, and optional `model_settings` to configure model tuning parameters like temperature, top_p, etc.
- `tools`: Tools that the agent can use to achieve its tasks.
```python
from hanzo_agent import Agent, ModelSettings, function_tool
@function_tool
def get_weather(city: str) -> str:
return f"The weather in {city} is sunny"
agent = Agent(
name="Haiku agent",
instructions="Always respond in haiku form",
model="o3-mini",
tools=[get_weather],
)
```
## Context
Agents are generic on their `context` type. Context is a dependency-injection tool: it's an object you create and pass to `Runner.run()`, that is passed to every agent, tool, handoff etc, and it serves as a grab bag of dependencies and state for the agent run. You can provide any Python object as the context.
```python
@dataclass
class UserContext:
uid: str
is_pro_user: bool
async def fetch_purchases() -> list[Purchase]:
return ...
agent = Agent[UserContext](
...,
)
```
## Output types
By default, agents produce plain text (i.e. `str`) outputs. If you want the agent to produce a particular type of output, you can use the `output_type` parameter. A common choice is to use [Pydantic](https://docs.pydantic.dev/) objects, but we support any type that can be wrapped in a Pydantic [TypeAdapter](https://docs.pydantic.dev/latest/api/type_adapter/) - dataclasses, lists, TypedDict, etc.
```python
from pydantic import BaseModel
from hanzo_agent import Agent
class CalendarEvent(BaseModel):
name: str
date: str
participants: list[str]
agent = Agent(
name="Calendar extractor",
instructions="Extract calendar events from text",
output_type=CalendarEvent,
)
```
!!! note
When you pass an `output_type`, that tells the model to use [structured outputs](https://platform.openai.com/docs/guides/structured-outputs) instead of regular plain text responses.
## Handoffs
Handoffs are sub-agents that the agent can delegate to. You provide a list of handoffs, and the agent can choose to delegate to them if relevant. This is a powerful pattern that allows orchestrating modular, specialized agents that excel at a single task. Read more in the [handoffs](handoffs.md) documentation.
```python
from hanzo_agent import Agent
booking_agent = Agent(...)
refund_agent = Agent(...)
triage_agent = Agent(
name="Triage agent",
instructions=(
"Help the user with their questions."
"If they ask about booking, handoff to the booking agent."
"If they ask about refunds, handoff to the refund agent."
),
handoffs=[booking_agent, refund_agent],
)
```
## Dynamic instructions
In most cases, you can provide instructions when you create the agent. However, you can also provide dynamic instructions via a function. The function will receive the agent and context, and must return the prompt. Both regular and `async` functions are accepted.
```python
def dynamic_instructions(
context: RunContextWrapper[UserContext], agent: Agent[UserContext]
) -> str:
return f"The user's name is {context.context.name}. Help them with their questions."
agent = Agent[UserContext](
name="Triage agent",
instructions=dynamic_instructions,
)
```
## Lifecycle events (hooks)
Sometimes, you want to observe the lifecycle of an agent. For example, you may want to log events, or pre-fetch data when certain events occur. You can hook into the agent lifecycle with the `hooks` property. Subclass the [`AgentHooks`][agents.lifecycle.AgentHooks] class, and override the methods you're interested in.
## Guardrails
Guardrails allow you to run checks/validations on user input, in parallel to the agent running. For example, you could screen the user's input for relevance. Read more in the [guardrails](guardrails.md) documentation.
## Cloning/copying agents
By using the `clone()` method on an agent, you can duplicate an Agent, and optionally change any properties you like.
```python
pirate_agent = Agent(
name="Pirate",
instructions="Write like a pirate",
model="o3-mini",
)
robot_agent = pirate_agent.clone(
name="Robot",
instructions="Write like a robot",
)
```
+97
View File
@@ -0,0 +1,97 @@
---
title: Configuration
description: Configure the Agent SDK for your needs
---
## API keys and clients
By default, the SDK looks for the `OPENAI_API_KEY` environment variable for LLM requests and tracing, as soon as it is imported. If you are unable to set that environment variable before your app starts, you can use the [set_default_openai_key()][agents.set_default_openai_key] function to set the key.
```python
from hanzo_agent import set_default_openai_key
set_default_openai_key("sk-...")
```
Alternatively, you can also configure an Hanzo AI client to be used. By default, the SDK creates an `AsyncHanzo AI` instance, using the API key from the environment variable or the default key set above. You can change this by using the [set_default_openai_client()][agents.set_default_openai_client] function.
```python
from openai import AsyncHanzo AI
from hanzo_agent import set_default_openai_client
custom_client = AsyncHanzo AI(base_url="...", api_key="...")
set_default_openai_client(custom_client)
```
Finally, you can also customize the Hanzo AI API that is used. By default, we use the Hanzo AI Responses API. You can override this to use the Chat Completions API by using the [set_default_openai_api()][agents.set_default_openai_api] function.
```python
from hanzo_agent import set_default_openai_api
set_default_openai_api("chat_completions")
```
## Tracing
Tracing is enabled by default. It uses the Hanzo AI API keys from the section above by default (i.e. the environment variable or the default key you set). You can specifically set the API key used for tracing by using the [`set_tracing_export_api_key`][agents.set_tracing_export_api_key] function.
```python
from hanzo_agent import set_tracing_export_api_key
set_tracing_export_api_key("sk-...")
```
You can also disable tracing entirely by using the [`set_tracing_disabled()`][agents.set_tracing_disabled] function.
```python
from hanzo_agent import set_tracing_disabled
set_tracing_disabled(True)
```
## Debug logging
The SDK has two Python loggers without any handlers set. By default, this means that warnings and errors are sent to `stdout`, but other logs are suppressed.
To enable verbose logging, use the [`enable_verbose_stdout_logging()`][agents.enable_verbose_stdout_logging] function.
```python
from hanzo_agent import enable_verbose_stdout_logging
enable_verbose_stdout_logging()
```
Alternatively, you can customize the logs by adding handlers, filters, formatters, etc. You can read more in the [Python logging guide](https://docs.python.org/3/howto/logging.html).
```python
import logging
logger = logging.getLogger("openai.agents") # or openai.agents.tracing for the Tracing logger
# To make all logs show up
logger.setLevel(logging.DEBUG)
# To make info and above show up
logger.setLevel(logging.INFO)
# To make warning and above show up
logger.setLevel(logging.WARNING)
# etc
# You can customize this as needed, but this will output to `stderr` by default
logger.addHandler(logging.StreamHandler())
```
### Sensitive data in logs
Certain logs may contain sensitive data (for example, user data). If you want to disable this data from being logged, set the following environment variables.
To disable logging LLM inputs and outputs:
```bash
export OPENAI_AGENTS_DONT_LOG_MODEL_DATA=1
```
To disable logging tool inputs and outputs:
```bash
export OPENAI_AGENTS_DONT_LOG_TOOL_DATA=1
```
+80
View File
@@ -0,0 +1,80 @@
---
title: Context Management
description: Manage context and state across agent interactions
---
Context is an overloaded term. There are two main classes of context you might care about:
1. Context available locally to your code: this is data and dependencies you might need when tool functions run, during callbacks like `on_handoff`, in lifecycle hooks, etc.
2. Context available to LLMs: this is data the LLM sees when generating a response.
## Local context
This is represented via the [`RunContextWrapper`][agents.run_context.RunContextWrapper] class and the [`context`][agents.run_context.RunContextWrapper.context] property within it. The way this works is:
1. You create any Python object you want. A common pattern is to use a dataclass or a Pydantic object.
2. You pass that object to the various run methods (e.g. `Runner.run(..., **context=whatever**))`.
3. All your tool calls, lifecycle hooks etc will be passed a wrapper object, `RunContextWrapper[T]`, where `T` represents your context object type which you can access via `wrapper.context`.
The **most important** thing to be aware of: every agent, tool function, lifecycle etc for a given agent run must use the same _type_ of context.
You can use the context for things like:
- Contextual data for your run (e.g. things like a username/uid or other information about the user)
- Dependencies (e.g. logger objects, data fetchers, etc)
- Helper functions
!!! danger "Note"
The context object is **not** sent to the LLM. It is purely a local object that you can read from, write to and call methods on it.
```python
import asyncio
from dataclasses import dataclass
from hanzo_agent import Agent, RunContextWrapper, Runner, function_tool
@dataclass
class UserInfo: # (1)!
name: str
uid: int
@function_tool
async def fetch_user_age(wrapper: RunContextWrapper[UserInfo]) -> str: # (2)!
return f"User {wrapper.context.name} is 47 years old"
async def main():
user_info = UserInfo(name="John", uid=123) # (3)!
agent = Agent[UserInfo]( # (4)!
name="Assistant",
tools=[fetch_user_age],
)
result = await Runner.run(
starting_agent=agent,
input="What is the age of the user?",
context=user_info,
)
print(result.final_output) # (5)!
# The user John is 47 years old.
if __name__ == "__main__":
asyncio.run(main())
```
1. This is the context object. We've used a dataclass here, but you can use any type.
2. This is a tool. You can see it takes a `RunContextWrapper[UserInfo]`. The tool implementation reads from the context.
3. We mark the agent with the generic `UserInfo`, so that the typechecker can catch errors (for example, if we tried to pass a tool that took a different context type).
4. The context is passed to the `run` function.
5. The agent correctly calls the tool and gets the age.
## Agent/LLM context
When an LLM is called, the **only** data it can see is from the conversation history. This means that if you want to make some new data available to the LLM, you must do it in a way that makes it available in that history. There are a few ways to do this:
1. You can add it to the Agent `instructions`. This is also known as a "system prompt" or "developer message". System prompts can be static strings, or they can be dynamic functions that receive the context and output a string. This is a common tactic for information that is always useful (for example, the user's name or the current date).
2. Add it to the `input` when calling the `Runner.run` functions. This is similar to the `instructions` tactic, but allows you to have messages that are lower in the [chain of command](https://cdn.openai.com/spec/model-spec-2024-05-08.html#follow-the-chain-of-command).
3. Expose it via function tools. This is useful for _on-demand_ context - the LLM decides when it needs some data, and can call the tool to fetch that data.
4. Use retrieval or web search. These are special tools that are able to fetch relevant data from files or databases (retrieval), or from the web (web search). This is useful for "grounding" the response in relevant contextual data.
+157
View File
@@ -0,0 +1,157 @@
---
title: Guardrails
description: Validate inputs and outputs for safety
---
Guardrails run _in parallel_ to your agents, enabling you to do checks and validations of user input. For example, imagine you have an agent that uses a very smart (and hence slow/expensive) model to help with customer requests. You wouldn't want malicious users to ask the model to help them with their math homework. So, you can run a guardrail with a fast/cheap model. If the guardrail detects malicious usage, it can immediately raise an error, which stops the expensive model from running and saves you time/money.
There are two kinds of guardrails:
1. Input guardrails run on the initial user input
2. Output guardrails run on the final agent output
## Input guardrails
Input guardrails run in 3 steps:
1. First, the guardrail receives the same input passed to the agent.
2. Next, the guardrail function runs to produce a [`GuardrailFunctionOutput`][agents.guardrail.GuardrailFunctionOutput], which is then wrapped in an [`InputGuardrailResult`][agents.guardrail.InputGuardrailResult]
3. Finally, we check if [`.tripwire_triggered`][agents.guardrail.GuardrailFunctionOutput.tripwire_triggered] is true. If true, an [`InputGuardrailTripwireTriggered`][agents.exceptions.InputGuardrailTripwireTriggered] exception is raised, so you can appropriately respond to the user or handle the exception.
!!! Note
Input guardrails are intended to run on user input, so an agent's guardrails only run if the agent is the *first* agent. You might wonder, why is the `guardrails` property on the agent instead of passed to `Runner.run`? It's because guardrails tend to be related to the actual Agent - you'd run different guardrails for different agents, so colocating the code is useful for readability.
## Output guardrails
Output guardrails run in 3 steps:
1. First, the guardrail receives the same input passed to the agent.
2. Next, the guardrail function runs to produce a [`GuardrailFunctionOutput`][agents.guardrail.GuardrailFunctionOutput], which is then wrapped in an [`OutputGuardrailResult`][agents.guardrail.OutputGuardrailResult]
3. Finally, we check if [`.tripwire_triggered`][agents.guardrail.GuardrailFunctionOutput.tripwire_triggered] is true. If true, an [`OutputGuardrailTripwireTriggered`][agents.exceptions.OutputGuardrailTripwireTriggered] exception is raised, so you can appropriately respond to the user or handle the exception.
!!! Note
Output guardrails are intended to run on the final agent input, so an agent's guardrails only run if the agent is the *last* agent. Similar to the input guardrails, we do this because guardrails tend to be related to the actual Agent - you'd run different guardrails for different agents, so colocating the code is useful for readability.
## Tripwires
If the input or output fails the guardrail, the Guardrail can signal this with a tripwire. As soon as we see a guardrail that has triggered the tripwires, we immediately raise a `{Input,Output}GuardrailTripwireTriggered` exception and halt the Agent execution.
## Implementing a guardrail
You need to provide a function that receives input, and returns a [`GuardrailFunctionOutput`][agents.guardrail.GuardrailFunctionOutput]. In this example, we'll do this by running an Agent under the hood.
```python
from pydantic import BaseModel
from hanzo_agent import (
Agent,
GuardrailFunctionOutput,
InputGuardrailTripwireTriggered,
RunContextWrapper,
Runner,
TResponseInputItem,
input_guardrail,
)
class MathHomeworkOutput(BaseModel):
is_math_homework: bool
reasoning: str
guardrail_agent = Agent( # (1)!
name="Guardrail check",
instructions="Check if the user is asking you to do their math homework.",
output_type=MathHomeworkOutput,
)
@input_guardrail
async def math_guardrail( # (2)!
ctx: RunContextWrapper[None], agent: Agent, input: str | list[TResponseInputItem]
) -> GuardrailFunctionOutput:
result = await Runner.run(guardrail_agent, input, context=ctx.context)
return GuardrailFunctionOutput(
output_info=result.final_output, # (3)!
tripwire_triggered=result.final_output.is_math_homework,
)
agent = Agent( # (4)!
name="Customer support agent",
instructions="You are a customer support agent. You help customers with their questions.",
input_guardrails=[math_guardrail],
)
async def main():
# This should trip the guardrail
try:
await Runner.run(agent, "Hello, can you help me solve for x: 2x + 3 = 11?")
print("Guardrail didn't trip - this is unexpected")
except InputGuardrailTripwireTriggered:
print("Math homework guardrail tripped")
```
1. We'll use this agent in our guardrail function.
2. This is the guardrail function that receives the agent's input/context, and returns the result.
3. We can include extra information in the guardrail result.
4. This is the actual agent that defines the workflow.
Output guardrails are similar.
```python
from pydantic import BaseModel
from hanzo_agent import (
Agent,
GuardrailFunctionOutput,
OutputGuardrailTripwireTriggered,
RunContextWrapper,
Runner,
output_guardrail,
)
class MessageOutput(BaseModel): # (1)!
response: str
class MathOutput(BaseModel): # (2)!
is_math: bool
reasoning: str
guardrail_agent = Agent(
name="Guardrail check",
instructions="Check if the output includes any math.",
output_type=MathOutput,
)
@output_guardrail
async def math_guardrail( # (3)!
ctx: RunContextWrapper, agent: Agent, output: MessageOutput
) -> GuardrailFunctionOutput:
result = await Runner.run(guardrail_agent, output.response, context=ctx.context)
return GuardrailFunctionOutput(
output_info=result.final_output,
tripwire_triggered=result.final_output.is_math,
)
agent = Agent( # (4)!
name="Customer support agent",
instructions="You are a customer support agent. You help customers with their questions.",
output_guardrails=[math_guardrail],
output_type=MessageOutput,
)
async def main():
# This should trip the guardrail
try:
await Runner.run(agent, "Hello, can you help me solve for x: 2x + 3 = 11?")
print("Guardrail didn't trip - this is unexpected")
except OutputGuardrailTripwireTriggered:
print("Math output guardrail tripped")
```
1. This is the actual agent's output type.
2. This is the guardrail's output type.
3. This is the guardrail function that receives the agent's output, and returns the result.
4. This is the actual agent that defines the workflow.
+116
View File
@@ -0,0 +1,116 @@
---
title: Handoffs
description: Delegate tasks between multiple agents
---
Handoffs allow an agent to delegate tasks to another agent. This is particularly useful in scenarios where different agents specialize in distinct areas. For example, a customer support app might have agents that each specifically handle tasks like order status, refunds, FAQs, etc.
Handoffs are represented as tools to the LLM. So if there's a handoff to an agent named `Refund Agent`, the tool would be called `transfer_to_refund_agent`.
## Creating a handoff
All agents have a [`handoffs`][agents.agent.Agent.handoffs] param, which can either take an `Agent` directly, or a `Handoff` object that customizes the Handoff.
You can create a handoff using the [`handoff()`][agents.handoffs.handoff] function provided by the Agent SDK. This function allows you to specify the agent to hand off to, along with optional overrides and input filters.
### Basic Usage
Here's how you can create a simple handoff:
```python
from hanzo_agent import Agent, handoff
billing_agent = Agent(name="Billing agent")
refund_agent = Agent(name="Refund agent")
# (1)!
triage_agent = Agent(name="Triage agent", handoffs=[billing_agent, handoff(refund_agent)])
```
1. You can use the agent directly (as in `billing_agent`), or you can use the `handoff()` function.
### Customizing handoffs via the `handoff()` function
The [`handoff()`][agents.handoffs.handoff] function lets you customize things.
- `agent`: This is the agent to which things will be handed off.
- `tool_name_override`: By default, the `Handoff.default_tool_name()` function is used, which resolves to `transfer_to_<agent_name>`. You can override this.
- `tool_description_override`: Override the default tool description from `Handoff.default_tool_description()`
- `on_handoff`: A callback function executed when the handoff is invoked. This is useful for things like kicking off some data fetching as soon as you know a handoff is being invoked. This function receives the agent context, and can optionally also receive LLM generated input. The input data is controlled by the `input_type` param.
- `input_type`: The type of input expected by the handoff (optional).
- `input_filter`: This lets you filter the input received by the next agent. See below for more.
```python
from hanzo_agent import Agent, handoff, RunContextWrapper
def on_handoff(ctx: RunContextWrapper[None]):
print("Handoff called")
agent = Agent(name="My agent")
handoff_obj = handoff(
agent=agent,
on_handoff=on_handoff,
tool_name_override="custom_handoff_tool",
tool_description_override="Custom description",
)
```
## Handoff inputs
In certain situations, you want the LLM to provide some data when it calls a handoff. For example, imagine a handoff to an "Escalation agent". You might want a reason to be provided, so you can log it.
```python
from pydantic import BaseModel
from hanzo_agent import Agent, handoff, RunContextWrapper
class EscalationData(BaseModel):
reason: str
async def on_handoff(ctx: RunContextWrapper[None], input_data: EscalationData):
print(f"Escalation agent called with reason: {input_data.reason}")
agent = Agent(name="Escalation agent")
handoff_obj = handoff(
agent=agent,
on_handoff=on_handoff,
input_type=EscalationData,
)
```
## Input filters
When a handoff occurs, it's as though the new agent takes over the conversation, and gets to see the entire previous conversation history. If you want to change this, you can set an [`input_filter`][agents.handoffs.Handoff.input_filter]. An input filter is a function that receives the existing input via a [`HandoffInputData`][agents.handoffs.HandoffInputData], and must return a new `HandoffInputData`.
There are some common patterns (for example removing all tool calls from the history), which are implemented for you in [`agents.extensions.handoff_filters`][]
```python
from hanzo_agent import Agent, handoff
from hanzo_agent.extensions import handoff_filters
agent = Agent(name="FAQ agent")
handoff_obj = handoff(
agent=agent,
input_filter=handoff_filters.remove_all_tools, # (1)!
)
```
1. This will automatically remove all tools from the history when `FAQ agent` is called.
## Recommended prompts
To make sure that LLMs understand handoffs properly, we recommend including information about handoffs in your agents. We have a suggested prefix in [`agents.extensions.handoff_prompt.RECOMMENDED_PROMPT_PREFIX`][], or you can call [`agents.extensions.handoff_prompt.prompt_with_handoff_instructions`][] to automatically add recommended data to your prompts.
```python
from hanzo_agent import Agent
from hanzo_agent.extensions.handoff_prompt import RECOMMENDED_PROMPT_PREFIX
billing_agent = Agent(
name="Billing agent",
instructions=f"""{RECOMMENDED_PROMPT_PREFIX}
<Fill in the rest of your prompt here>.""",
)
```
+55
View File
@@ -0,0 +1,55 @@
---
title: Hanzo AI Agent SDK
description: Build agentic AI apps with a lightweight, production-ready SDK
---
The Hanzo AI Agent SDK enables you to build agentic AI apps in a lightweight, easy-to-use package with very few abstractions. It's a production-ready upgrade of our previous experimentation for agents, Swarm. The Agent SDK has a very small set of primitives:
- **Agents**, which are LLMs equipped with instructions and tools
- **Handoffs**, which allow agents to delegate to other agents for specific tasks
- **Guardrails**, which enable the inputs to agents to be validated
In combination with Python, these primitives are powerful enough to express complex relationships between tools and agents, and allow you to build real-world applications without a steep learning curve. In addition, the SDK comes with built-in **tracing** that lets you visualize and debug your agentic flows, as well as evaluate them and even fine-tune models for your application.
## Why use the Agent SDK
The SDK has two driving design principles:
1. Enough features to be worth using, but few enough primitives to make it quick to learn.
2. Works great out of the box, but you can customize exactly what happens.
Here are the main features of the SDK:
- **Agent loop**: Built-in agent loop that handles calling tools, sending results to the LLM, and looping until the LLM is done.
- **Python-first**: Use built-in language features to orchestrate and chain agents, rather than needing to learn new abstractions.
- **Handoffs**: A powerful feature to coordinate and delegate between multiple agents.
- **Guardrails**: Run input validations and checks in parallel to your agents, breaking early if the checks fail.
- **Function tools**: Turn any Python function into a tool, with automatic schema generation and Pydantic-powered validation.
- **Tracing**: Built-in tracing that lets you visualize, debug and monitor your workflows, as well as use the Hanzo AI suite of evaluation, fine-tuning and distillation tools.
## Installation
```bash
pip install hanzo-agent
```
## Hello world example
```python
from hanzo_agent import Agent, Runner
agent = Agent(name="Assistant", instructions="You are a helpful assistant")
result = Runner.run_sync(agent, "Write a haiku about recursion in programming.")
print(result.final_output)
# Code within the code,
# Functions calling themselves,
# Infinite loop's dance.
```
Set the `HANZO_API_KEY` environment variable:
```bash
export HANZO_API_KEY=your-api-key
```
+19
View File
@@ -0,0 +1,19 @@
{
"title": "Agent SDK",
"pages": [
"index",
"quickstart",
"agents",
"running_agents",
"results",
"streaming",
"tools",
"handoffs",
"guardrails",
"context",
"multi_agent",
"tracing",
"models",
"config"
]
}
+96
View File
@@ -0,0 +1,96 @@
---
title: Models
description: Configure and use different LLM models
---
The Agent SDK comes with out-of-the-box support for Hanzo AI models in two flavors:
- **Recommended**: the [`Hanzo AIResponsesModel`][agents.models.openai_responses.Hanzo AIResponsesModel], which calls Hanzo AI APIs using the new [Responses API](https://platform.openai.com/docs/api-reference/responses).
- The [`Hanzo AIChatCompletionsModel`][agents.models.openai_chatcompletions.Hanzo AIChatCompletionsModel], which calls Hanzo AI APIs using the [Chat Completions API](https://platform.openai.com/docs/api-reference/chat).
## Mixing and matching models
Within a single workflow, you may want to use different models for each agent. For example, you could use a smaller, faster model for triage, while using a larger, more capable model for complex tasks. When configuring an [`Agent`][agents.Agent], you can select a specific model by either:
1. Passing the name of an Hanzo AI model.
2. Passing any model name + a [`ModelProvider`][agents.models.interface.ModelProvider] that can map that name to a Model instance.
3. Directly providing a [`Model`][agents.models.interface.Model] implementation.
!!!note
While our SDK supports both the [`Hanzo AIResponsesModel`][agents.models.openai_responses.Hanzo AIResponsesModel] and the [`Hanzo AIChatCompletionsModel`][agents.models.openai_chatcompletions.Hanzo AIChatCompletionsModel] shapes, we recommend using a single model shape for each workflow because the two shapes support a different set of features and tools. If your workflow requires mixing and matching model shapes, make sure that all the features you're using are available on both.
```python
from hanzo_agent import Agent, Runner, AsyncHanzo AI, Hanzo AIChatCompletionsModel
import asyncio
spanish_agent = Agent(
name="Spanish agent",
instructions="You only speak Spanish.",
model="o3-mini", # (1)!
)
english_agent = Agent(
name="English agent",
instructions="You only speak English",
model=Hanzo AIChatCompletionsModel( # (2)!
model="gpt-4o",
openai_client=AsyncHanzo AI()
),
)
triage_agent = Agent(
name="Triage agent",
instructions="Handoff to the appropriate agent based on the language of the request.",
handoffs=[spanish_agent, english_agent],
model="gpt-3.5-turbo",
)
async def main():
result = await Runner.run(triage_agent, input="Hola, ¿cómo estás?")
print(result.final_output)
```
1. Sets the name of an Hanzo AI model directly.
2. Provides a [`Model`][agents.models.interface.Model] implementation.
## Using other LLM providers
You can use other LLM providers in 3 ways (examples [here](https://github.com/openai/hanzo-agent-python/tree/main/examples/model_providers/)):
1. [`set_default_openai_client`][agents.set_default_openai_client] is useful in cases where you want to globally use an instance of `AsyncHanzo AI` as the LLM client. This is for cases where the LLM provider has an Hanzo AI compatible API endpoint, and you can set the `base_url` and `api_key`. See a configurable example in [examples/model_providers/custom_example_global.py](https://github.com/openai/hanzo-agent-python/tree/main/examples/model_providers/custom_example_global.py).
2. [`ModelProvider`][agents.models.interface.ModelProvider] is at the `Runner.run` level. This lets you say "use a custom model provider for all agents in this run". See a configurable example in [examples/model_providers/custom_example_provider.py](https://github.com/openai/hanzo-agent-python/tree/main/examples/model_providers/custom_example_provider.py).
3. [`Agent.model`][agents.agent.Agent.model] lets you specify the model on a specific Agent instance. This enables you to mix and match different providers for different agents. See a configurable example in [examples/model_providers/custom_example_agent.py](https://github.com/openai/hanzo-agent-python/tree/main/examples/model_providers/custom_example_agent.py).
In cases where you do not have an API key from `platform.openai.com`, we recommend disabling tracing via `set_tracing_disabled()`, or setting up a [different tracing processor](tracing.md).
!!! note
In these examples, we use the Chat Completions API/model, because most LLM providers don't yet support the Responses API. If your LLM provider does support it, we recommend using Responses.
## Common issues with using other LLM providers
### Tracing client error 401
If you get errors related to tracing, this is because traces are uploaded to Hanzo AI servers, and you don't have an Hanzo AI API key. You have three options to resolve this:
1. Disable tracing entirely: [`set_tracing_disabled(True)`][agents.set_tracing_disabled].
2. Set an Hanzo AI key for tracing: [`set_tracing_export_api_key(...)`][agents.set_tracing_export_api_key]. This API key will only be used for uploading traces, and must be from [platform.openai.com](https://platform.openai.com/).
3. Use a non-Hanzo AI trace processor. See the [tracing docs](tracing.md#custom-tracing-processors).
### Responses API support
The SDK uses the Responses API by default, but most other LLM providers don't yet support it. You may see 404s or similar issues as a result. To resolve, you have two options:
1. Call [`set_default_openai_api("chat_completions")`][agents.set_default_openai_api]. This works if you are setting `OPENAI_API_KEY` and `OPENAI_BASE_URL` via environment vars.
2. Use [`Hanzo AIChatCompletionsModel`][agents.models.openai_chatcompletions.Hanzo AIChatCompletionsModel]. There are examples [here](https://github.com/openai/hanzo-agent-python/tree/main/examples/model_providers/).
### Structured outputs support
Some model providers don't have support for [structured outputs](https://platform.openai.com/docs/guides/structured-outputs). This sometimes results in an error that looks something like this:
```
BadRequestError: Error code: 400 - {'error': {'message': "'response_format.type' : value is not one of the allowed values ['text','json_object']", 'type': 'invalid_request_error'}}
```
This is a shortcoming of some model providers - they support JSON outputs, but don't allow you to specify the `json_schema` to use for the output. We are working on a fix for this, but we suggest relying on providers that do have support for JSON schema output, because otherwise your app will often break because of malformed JSON.
+40
View File
@@ -0,0 +1,40 @@
---
title: Multi-Agent Orchestration
description: Coordinate multiple agents working together
---
Orchestration refers to the flow of agents in your app. Which agents run, in what order, and how do they decide what happens next? There are two main ways to orchestrate agents:
1. Allowing the LLM to make decisions: this uses the intelligence of an LLM to plan, reason, and decide on what steps to take based on that.
2. Orchestrating via code: determining the flow of agents via your code.
You can mix and match these patterns. Each has their own tradeoffs, described below.
## Orchestrating via LLM
An agent is an LLM equipped with instructions, tools and handoffs. This means that given an open-ended task, the LLM can autonomously plan how it will tackle the task, using tools to take actions and acquire data, and using handoffs to delegate tasks to sub-agents. For example, a research agent could be equipped with tools like:
- Web search to find information online
- File search and retrieval to search through proprietary data and connections
- Computer use to take actions on a computer
- Code execution to do data analysis
- Handoffs to specialized agents that are great at planning, report writing and more.
This pattern is great when the task is open-ended and you want to rely on the intelligence of an LLM. The most important tactics here are:
1. Invest in good prompts. Make it clear what tools are available, how to use them, and what parameters it must operate within.
2. Monitor your app and iterate on it. See where things go wrong, and iterate on your prompts.
3. Allow the agent to introspect and improve. For example, run it in a loop, and let it critique itself; or, provide error messages and let it improve.
4. Have specialized agents that excel in one task, rather than having a general purpose agent that is expected to be good at anything.
5. Invest in [evals](https://platform.openai.com/docs/guides/evals). This lets you train your agents to improve and get better at tasks.
## Orchestrating via code
While orchestrating via LLM is powerful, orchestrating via code makes tasks more deterministic and predictable, in terms of speed, cost and performance. Common patterns here are:
- Using [structured outputs](https://platform.openai.com/docs/guides/structured-outputs) to generate well formed data that you can inspect with your code. For example, you might ask an agent to classify the task into a few categories, and then pick the next agent based on the category.
- Chaining multiple agents by transforming the output of one into the input of the next. You can decompose a task like writing a blog post into a series of steps - do research, write an outline, write the blog post, critique it, and then improve it.
- Running the agent that performs the task in a `while` loop with an agent that evaluates and provides feedback, until the evaluator says the output passes certain criteria.
- Running multiple agents in parallel, e.g. via Python primitives like `asyncio.gather`. This is useful for speed when you have multiple tasks that don't depend on each other.
We have a number of examples in [`examples/agent_patterns`](https://github.com/openai/hanzo-agent-python/tree/main/examples/agent_patterns).
+192
View File
@@ -0,0 +1,192 @@
---
title: Quickstart
description: Get started with the Agent SDK in minutes
---
## Create a project and virtual environment
You'll only need to do this once.
```bash
mkdir my_project
cd my_project
python -m venv .venv
```
### Activate the virtual environment
Do this every time you start a new terminal session.
```bash
source .venv/bin/activate
```
### Install the Agent SDK
```bash
pip install hanzo-agent # or `uv add hanzo-agent`, etc
```
### Set an Hanzo AI API key
If you don't have one, follow [these instructions](https://platform.openai.com/docs/quickstart#create-and-export-an-api-key) to create an Hanzo AI API key.
```bash
export OPENAI_API_KEY=sk-...
```
## Create your first agent
Agents are defined with instructions, a name, and optional config (such as `model_config`)
```python
from hanzo_agent import Agent
agent = Agent(
name="Math Tutor",
instructions="You provide help with math problems. Explain your reasoning at each step and include examples",
)
```
## Add a few more agents
Additional agents can be defined in the same way. `handoff_descriptions` provide additional context for determining handoff routing
```python
from hanzo_agent import Agent
history_tutor_agent = Agent(
name="History Tutor",
handoff_description="Specialist agent for historical questions",
instructions="You provide assistance with historical queries. Explain important events and context clearly.",
)
math_tutor_agent = Agent(
name="Math Tutor",
handoff_description="Specialist agent for math questions",
instructions="You provide help with math problems. Explain your reasoning at each step and include examples",
)
```
## Define your handoffs
On each agent, you can define an inventory of outgoing handoff options that the agent can choose from to decide how to make progress on their task.
```python
triage_agent = Agent(
name="Triage Agent",
instructions="You determine which agent to use based on the user's homework question",
handoffs=[history_tutor_agent, math_tutor_agent]
)
```
## Run the agent orchestration
Let's check that the workflow runs and the triage agent correctly routes between the two specialist agents.
```python
from hanzo_agent import Runner
async def main():
result = await Runner.run(triage_agent, "What is the capital of France?")
print(result.final_output)
```
## Add a guardrail
You can define custom guardrails to run on the input or output.
```python
from hanzo_agent import GuardrailFunctionOutput, Agent, Runner
from pydantic import BaseModel
class HomeworkOutput(BaseModel):
is_homework: bool
reasoning: str
guardrail_agent = Agent(
name="Guardrail check",
instructions="Check if the user is asking about homework.",
output_type=HomeworkOutput,
)
async def homework_guardrail(ctx, agent, input_data):
result = await Runner.run(guardrail_agent, input_data, context=ctx.context)
final_output = result.final_output_as(HomeworkOutput)
return GuardrailFunctionOutput(
output_info=final_output,
tripwire_triggered=not final_output.is_homework,
)
```
## Put it all together
Let's put it all together and run the entire workflow, using handoffs and the input guardrail.
```python
from hanzo_agent import Agent, InputGuardrail,GuardrailFunctionOutput, Runner
from pydantic import BaseModel
import asyncio
class HomeworkOutput(BaseModel):
is_homework: bool
reasoning: str
guardrail_agent = Agent(
name="Guardrail check",
instructions="Check if the user is asking about homework.",
output_type=HomeworkOutput,
)
math_tutor_agent = Agent(
name="Math Tutor",
handoff_description="Specialist agent for math questions",
instructions="You provide help with math problems. Explain your reasoning at each step and include examples",
)
history_tutor_agent = Agent(
name="History Tutor",
handoff_description="Specialist agent for historical questions",
instructions="You provide assistance with historical queries. Explain important events and context clearly.",
)
async def homework_guardrail(ctx, agent, input_data):
result = await Runner.run(guardrail_agent, input_data, context=ctx.context)
final_output = result.final_output_as(HomeworkOutput)
return GuardrailFunctionOutput(
output_info=final_output,
tripwire_triggered=not final_output.is_homework,
)
triage_agent = Agent(
name="Triage Agent",
instructions="You determine which agent to use based on the user's homework question",
handoffs=[history_tutor_agent, math_tutor_agent],
input_guardrails=[
InputGuardrail(guardrail_function=homework_guardrail),
],
)
async def main():
result = await Runner.run(triage_agent, "who was the first president of the united states?")
print(result.final_output)
result = await Runner.run(triage_agent, "what is life")
print(result.final_output)
if __name__ == "__main__":
asyncio.run(main())
```
## View your traces
To review what happened during your agent run, navigate to the [Trace viewer in the Hanzo AI Dashboard](https://platform.openai.com/traces) to view traces of your agent runs.
## Next steps
Learn how to build more complex agentic flows:
- Learn about how to configure [Agents](agents.md).
- Learn about [running agents](running_agents.md).
- Learn about [tools](tools.md), [guardrails](guardrails.md) and [models](models.md).
+55
View File
@@ -0,0 +1,55 @@
---
title: Results
description: Handle and process agent execution results
---
When you call the `Runner.run` methods, you either get a:
- [`RunResult`][agents.result.RunResult] if you call `run` or `run_sync`
- [`RunResultStreaming`][agents.result.RunResultStreaming] if you call `run_streamed`
Both of these inherit from [`RunResultBase`][agents.result.RunResultBase], which is where most useful information is present.
## Final output
The [`final_output`][agents.result.RunResultBase.final_output] property contains the final output of the last agent that ran. This is either:
- a `str`, if the last agent didn't have an `output_type` defined
- an object of type `last_agent.output_type`, if the agent had an output type defined.
!!! note
`final_output` is of type `Any`. We can't statically type this, because of handoffs. If handoffs occur, that means any Agent might be the last agent, so we don't statically know the set of possible output types.
## Inputs for the next turn
You can use [`result.to_input_list()`][agents.result.RunResultBase.to_input_list] to turn the result into an input list that concatenates the original input you provided, to the items generated during the agent run. This makes it convenient to take the outputs of one agent run and pass them into another run, or to run it in a loop and append new user inputs each time.
## Last agent
The [`last_agent`][agents.result.RunResultBase.last_agent] property contains the last agent that ran. Depending on your application, this is often useful for the next time the user inputs something. For example, if you have a frontline triage agent that hands off to a language-specific agent, you can store the last agent, and re-use it the next time the user messages the agent.
## New items
The [`new_items`][agents.result.RunResultBase.new_items] property contains the new items generated during the run. The items are [`RunItem`][agents.items.RunItem]s. A run item wraps the raw item generated by the LLM.
- [`MessageOutputItem`][agents.items.MessageOutputItem] indicates a message from the LLM. The raw item is the message generated.
- [`HandoffCallItem`][agents.items.HandoffCallItem] indicates that the LLM called the handoff tool. The raw item is the tool call item from the LLM.
- [`HandoffOutputItem`][agents.items.HandoffOutputItem] indicates that a handoff occurred. The raw item is the tool response to the handoff tool call. You can also access the source/target agents from the item.
- [`ToolCallItem`][agents.items.ToolCallItem] indicates that the LLM invoked a tool.
- [`ToolCallOutputItem`][agents.items.ToolCallOutputItem] indicates that a tool was called. The raw item is the tool response. You can also access the tool output from the item.
- [`ReasoningItem`][agents.items.ReasoningItem] indicates a reasoning item from the LLM. The raw item is the reasoning generated.
## Other information
### Guardrail results
The [`input_guardrail_results`][agents.result.RunResultBase.input_guardrail_results] and [`output_guardrail_results`][agents.result.RunResultBase.output_guardrail_results] properties contain the results of the guardrails, if any. Guardrail results can sometimes contain useful information you want to log or store, so we make these available to you.
### Raw responses
The [`raw_responses`][agents.result.RunResultBase.raw_responses] property contains the [`ModelResponse`][agents.items.ModelResponse]s generated by the LLM.
### Original input
The [`input`][agents.result.RunResultBase.input] property contains the original input you provided to the `run` method. In most cases you won't need this, but it's available in case you do.
@@ -0,0 +1,98 @@
---
title: Running Agents
description: Execute agents synchronously or asynchronously
---
You can run agents via the [`Runner`][agents.run.Runner] class. You have 3 options:
1. [`Runner.run()`][agents.run.Runner.run], which runs async and returns a [`RunResult`][agents.result.RunResult].
2. [`Runner.run_sync()`][agents.run.Runner.run_sync], which is a sync method and just runs `.run()` under the hood.
3. [`Runner.run_streamed()`][agents.run.Runner.run_streamed], which runs async and returns a [`RunResultStreaming`][agents.result.RunResultStreaming]. It calls the LLM in streaming mode, and streams those events to you as they are received.
```python
from hanzo_agent import Agent, Runner
async def main():
agent = Agent(name="Assistant", instructions="You are a helpful assistant")
result = await Runner.run(agent, "Write a haiku about recursion in programming.")
print(result.final_output)
# Code within the code,
# Functions calling themselves,
# Infinite loop's dance.
```
Read more in the [results guide](results.md).
## The agent loop
When you use the run method in `Runner`, you pass in a starting agent and input. The input can either be a string (which is considered a user message), or a list of input items, which are the items in the Hanzo AI Responses API.
The runner then runs a loop:
1. We call the LLM for the current agent, with the current input.
2. The LLM produces its output.
1. If the LLM returns a `final_output`, the loop ends and we return the result.
2. If the LLM does a handoff, we update the current agent and input, and re-run the loop.
3. If the LLM produces tool calls, we run those tool calls, append the results, and re-run the loop.
3. If we exceed the `max_turns` passed, we raise a [`MaxTurnsExceeded`][agents.exceptions.MaxTurnsExceeded] exception.
!!! note
The rule for whether the LLM output is considered as a "final output" is that it produces text output with the desired type, and there are no tool calls.
## Streaming
Streaming allows you to additionally receive streaming events as the LLM runs. Once the stream is done, the [`RunResultStreaming`][agents.result.RunResultStreaming] will contain the complete information about the run, including all the new outputs produces. You can call `.stream_events()` for the streaming events. Read more in the [streaming guide](streaming.md).
## Run config
The `run_config` parameter lets you configure some global settings for the agent run:
- [`model`][agents.run.RunConfig.model]: Allows setting a global LLM model to use, irrespective of what `model` each Agent has.
- [`model_provider`][agents.run.RunConfig.model_provider]: A model provider for looking up model names, which defaults to Hanzo AI.
- [`model_settings`][agents.run.RunConfig.model_settings]: Overrides agent-specific settings. For example, you can set a global `temperature` or `top_p`.
- [`input_guardrails`][agents.run.RunConfig.input_guardrails], [`output_guardrails`][agents.run.RunConfig.output_guardrails]: A list of input or output guardrails to include on all runs.
- [`handoff_input_filter`][agents.run.RunConfig.handoff_input_filter]: A global input filter to apply to all handoffs, if the handoff doesn't already have one. The input filter allows you to edit the inputs that are sent to the new agent. See the documentation in [`Handoff.input_filter`][agents.handoffs.Handoff.input_filter] for more details.
- [`tracing_disabled`][agents.run.RunConfig.tracing_disabled]: Allows you to disable [tracing](tracing.md) for the entire run.
- [`trace_include_sensitive_data`][agents.run.RunConfig.trace_include_sensitive_data]: Configures whether traces will include potentially sensitive data, such as LLM and tool call inputs/outputs.
- [`workflow_name`][agents.run.RunConfig.workflow_name], [`trace_id`][agents.run.RunConfig.trace_id], [`group_id`][agents.run.RunConfig.group_id]: Sets the tracing workflow name, trace ID and trace group ID for the run. We recommend at least setting `workflow_name`. The session ID is an optional field that lets you link traces across multiple runs.
- [`trace_metadata`][agents.run.RunConfig.trace_metadata]: Metadata to include on all traces.
## Conversations/chat threads
Calling any of the run methods can result in one or more agents running (and hence one or more LLM calls), but it represents a single logical turn in a chat conversation. For example:
1. User turn: user enter text
2. Runner run: first agent calls LLM, runs tools, does a handoff to a second agent, second agent runs more tools, and then produces an output.
At the end of the agent run, you can choose what to show to the user. For example, you might show the user every new item generated by the agents, or just the final output. Either way, the user might then ask a followup question, in which case you can call the run method again.
You can use the base [`RunResultBase.to_input_list()`][agents.result.RunResultBase.to_input_list] method to get the inputs for the next turn.
```python
async def main():
agent = Agent(name="Assistant", instructions="Reply very concisely.")
with trace(workflow_name="Conversation", group_id=thread_id):
# First turn
result = await Runner.run(agent, "What city is the Golden Gate Bridge in?")
print(result.final_output)
# San Francisco
# Second turn
new_input = result.to_input_list() + [{"role": "user", "content": "What state is it in?"}]
result = await Runner.run(agent, new_input)
print(result.final_output)
# California
```
## Exceptions
The SDK raises exceptions in certain cases. The full list is in [`agents.exceptions`][]. As an overview:
- [`AgentsException`][agents.exceptions.AgentsException] is the base class for all exceptions raised in the SDK.
- [`MaxTurnsExceeded`][agents.exceptions.MaxTurnsExceeded] is raised when the run exceeds the `max_turns` passed to the run methods.
- [`ModelBehaviorError`][agents.exceptions.ModelBehaviorError] is raised when the model produces invalid outputs, e.g. malformed JSON or using non-existent tools.
- [`UserError`][agents.exceptions.UserError] is raised when you (the person writing code using the SDK) make an error using the SDK.
- [`InputGuardrailTripwireTriggered`][agents.exceptions.InputGuardrailTripwireTriggered], [`OutputGuardrailTripwireTriggered`][agents.exceptions.OutputGuardrailTripwireTriggered] is raised when a [guardrail](guardrails.md) is tripped.
+90
View File
@@ -0,0 +1,90 @@
---
title: Streaming
description: Stream agent responses in real-time
---
Streaming lets you subscribe to updates of the agent run as it proceeds. This can be useful for showing the end-user progress updates and partial responses.
To stream, you can call [`Runner.run_streamed()`][agents.run.Runner.run_streamed], which will give you a [`RunResultStreaming`][agents.result.RunResultStreaming]. Calling `result.stream_events()` gives you an async stream of [`StreamEvent`][agents.stream_events.StreamEvent] objects, which are described below.
## Raw response events
[`RawResponsesStreamEvent`][agents.stream_events.RawResponsesStreamEvent] are raw events passed directly from the LLM. They are in Hanzo AI Responses API format, which means each event has a type (like `response.created`, `response.output_text.delta`, etc) and data. These events are useful if you want to stream response messages to the user as soon as they are generated.
For example, this will output the text generated by the LLM token-by-token.
```python
import asyncio
from openai.types.responses import ResponseTextDeltaEvent
from hanzo_agent import Agent, Runner
async def main():
agent = Agent(
name="Joker",
instructions="You are a helpful assistant.",
)
result = Runner.run_streamed(agent, input="Please tell me 5 jokes.")
async for event in result.stream_events():
if event.type == "raw_response_event" and isinstance(event.data, ResponseTextDeltaEvent):
print(event.data.delta, end="", flush=True)
if __name__ == "__main__":
asyncio.run(main())
```
## Run item events and agent events
[`RunItemStreamEvent`][agents.stream_events.RunItemStreamEvent]s are higher level events. They inform you when an item has been fully generated. This allows you to push progress updates at the level of "message generated", "tool ran", etc, instead of each token. Similarly, [`AgentUpdatedStreamEvent`][agents.stream_events.AgentUpdatedStreamEvent] gives you updates when the current agent changes (e.g. as the result of a handoff).
For example, this will ignore raw events and stream updates to the user.
```python
import asyncio
import random
from hanzo_agent import Agent, ItemHelpers, Runner, function_tool
@function_tool
def how_many_jokes() -> int:
return random.randint(1, 10)
async def main():
agent = Agent(
name="Joker",
instructions="First call the `how_many_jokes` tool, then tell that many jokes.",
tools=[how_many_jokes],
)
result = Runner.run_streamed(
agent,
input="Hello",
)
print("=== Run starting ===")
async for event in result.stream_events():
# We'll ignore the raw responses event deltas
if event.type == "raw_response_event":
continue
# When the agent updates, print that
elif event.type == "agent_updated_stream_event":
print(f"Agent updated: {event.new_agent.name}")
continue
# When items are generated, print them
elif event.type == "run_item_stream_event":
if event.item.type == "tool_call_item":
print("-- Tool was called")
elif event.item.type == "tool_call_output_item":
print(f"-- Tool output: {event.item.output}")
elif event.item.type == "message_output_item":
print(f"-- Message output:\n {ItemHelpers.text_message_output(event.item)}")
else:
pass # Ignore other event types
print("=== Run complete ===")
if __name__ == "__main__":
asyncio.run(main())
```
+273
View File
@@ -0,0 +1,273 @@
---
title: Tools
description: Define and use tools with your agents
---
Tools let agents take actions: things like fetching data, running code, calling external APIs, and even using a computer. There are three classes of tools in the Agent SDK:
- Hosted tools: these run on LLM servers alongside the AI models. Hanzo AI offers retrieval, web search and computer use as hosted tools.
- Function calling: these allow you to use any Python function as a tool.
- Agents as tools: this allows you to use an agent as a tool, allowing Agents to call other agents without handing off to them.
## Hosted tools
Hanzo AI offers a few built-in tools when using the [`Hanzo AIResponsesModel`][agents.models.openai_responses.Hanzo AIResponsesModel]:
- The [`WebSearchTool`][agents.tool.WebSearchTool] lets an agent search the web.
- The [`FileSearchTool`][agents.tool.FileSearchTool] allows retrieving information from your Hanzo AI Vector Stores.
- The [`ComputerTool`][agents.tool.ComputerTool] allows automating computer use tasks.
```python
from hanzo_agent import Agent, FileSearchTool, Runner, WebSearchTool
agent = Agent(
name="Assistant",
tools=[
WebSearchTool(),
FileSearchTool(
max_num_results=3,
vector_store_ids=["VECTOR_STORE_ID"],
),
],
)
async def main():
result = await Runner.run(agent, "Which coffee shop should I go to, taking into account my preferences and the weather today in SF?")
print(result.final_output)
```
## Function tools
You can use any Python function as a tool. The Agent SDK will setup the tool automatically:
- The name of the tool will be the name of the Python function (or you can provide a name)
- Tool description will be taken from the docstring of the function (or you can provide a description)
- The schema for the function inputs is automatically created from the function's arguments
- Descriptions for each input are taken from the docstring of the function, unless disabled
We use Python's `inspect` module to extract the function signature, along with [`griffe`](https://mkdocstrings.github.io/griffe/) to parse docstrings and `pydantic` for schema creation.
```python
import json
from typing_extensions import TypedDict, Any
from hanzo_agent import Agent, FunctionTool, RunContextWrapper, function_tool
class Location(TypedDict):
lat: float
long: float
@function_tool # (1)!
async def fetch_weather(location: Location) -> str:
# (2)!
"""Fetch the weather for a given location.
Args:
location: The location to fetch the weather for.
"""
# In real life, we'd fetch the weather from a weather API
return "sunny"
@function_tool(name_override="fetch_data") # (3)!
def read_file(ctx: RunContextWrapper[Any], path: str, directory: str | None = None) -> str:
"""Read the contents of a file.
Args:
path: The path to the file to read.
directory: The directory to read the file from.
"""
# In real life, we'd read the file from the file system
return "<file contents>"
agent = Agent(
name="Assistant",
tools=[fetch_weather, read_file], # (4)!
)
for tool in agent.tools:
if isinstance(tool, FunctionTool):
print(tool.name)
print(tool.description)
print(json.dumps(tool.params_json_schema, indent=2))
print()
```
1. You can use any Python types as arguments to your functions, and the function can be sync or async.
2. Docstrings, if present, are used to capture descriptions and argument descriptions
3. Functions can optionally take the `context` (must be the first argument). You can also set overrides, like the name of the tool, description, which docstring style to use, etc.
4. You can pass the decorated functions to the list of tools.
??? note "Expand to see output"
```
fetch_weather
Fetch the weather for a given location.
{
"$defs": {
"Location": {
"properties": {
"lat": {
"title": "Lat",
"type": "number"
},
"long": {
"title": "Long",
"type": "number"
}
},
"required": [
"lat",
"long"
],
"title": "Location",
"type": "object"
}
},
"properties": {
"location": {
"$ref": "#/$defs/Location",
"description": "The location to fetch the weather for."
}
},
"required": [
"location"
],
"title": "fetch_weather_args",
"type": "object"
}
fetch_data
Read the contents of a file.
{
"properties": {
"path": {
"description": "The path to the file to read.",
"title": "Path",
"type": "string"
},
"directory": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "The directory to read the file from.",
"title": "Directory"
}
},
"required": [
"path"
],
"title": "fetch_data_args",
"type": "object"
}
```
### Custom function tools
Sometimes, you don't want to use a Python function as a tool. You can directly create a [`FunctionTool`][agents.tool.FunctionTool] if you prefer. You'll need to provide:
- `name`
- `description`
- `params_json_schema`, which is the JSON schema for the arguments
- `on_invoke_tool`, which is an async function that receives the context and the arguments as a JSON string, and must return the tool output as a string.
```python
from typing import Any
from pydantic import BaseModel
from hanzo_agent import RunContextWrapper, FunctionTool
def do_some_work(data: str) -> str:
return "done"
class FunctionArgs(BaseModel):
username: str
age: int
async def run_function(ctx: RunContextWrapper[Any], args: str) -> str:
parsed = FunctionArgs.model_validate_json(args)
return do_some_work(data=f"{parsed.username} is {parsed.age} years old")
tool = FunctionTool(
name="process_user",
description="Processes extracted user data",
params_json_schema=FunctionArgs.model_json_schema(),
on_invoke_tool=run_function,
)
```
### Automatic argument and docstring parsing
As mentioned before, we automatically parse the function signature to extract the schema for the tool, and we parse the docstring to extract descriptions for the tool and for individual arguments. Some notes on that:
1. The signature parsing is done via the `inspect` module. We use type annotations to understand the types for the arguments, and dynamically build a Pydantic model to represent the overall schema. It supports most types, including Python primitives, Pydantic models, TypedDicts, and more.
2. We use `griffe` to parse docstrings. Supported docstring formats are `google`, `sphinx` and `numpy`. We attempt to automatically detect the docstring format, but this is best-effort and you can explicitly set it when calling `function_tool`. You can also disable docstring parsing by setting `use_docstring_info` to `False`.
The code for the schema extraction lives in [`agents.function_schema`][].
## Agents as tools
In some workflows, you may want a central agent to orchestrate a network of specialized agents, instead of handing off control. You can do this by modeling agents as tools.
```python
from hanzo_agent import Agent, Runner
import asyncio
spanish_agent = Agent(
name="Spanish agent",
instructions="You translate the user's message to Spanish",
)
french_agent = Agent(
name="French agent",
instructions="You translate the user's message to French",
)
orchestrator_agent = Agent(
name="orchestrator_agent",
instructions=(
"You are a translation agent. You use the tools given to you to translate."
"If asked for multiple translations, you call the relevant tools."
),
tools=[
spanish_agent.as_tool(
tool_name="translate_to_spanish",
tool_description="Translate the user's message to Spanish",
),
french_agent.as_tool(
tool_name="translate_to_french",
tool_description="Translate the user's message to French",
),
],
)
async def main():
result = await Runner.run(orchestrator_agent, input="Say 'Hello, how are you?' in Spanish.")
print(result.final_output)
```
## Handling errors in function tools
When you create a function tool via `@function_tool`, you can pass a `failure_error_function`. This is a function that provides an error response to the LLM in case the tool call crashes.
- By default (i.e. if you don't pass anything), it runs a `default_tool_error_function` which tells the LLM an error occurred.
- If you pass your own error function, it runs that instead, and sends the response to the LLM.
- If you explicitly pass `None`, then any tool call errors will be re-raised for you to handle. This could be a `ModelBehaviorError` if the model produced invalid JSON, or a `UserError` if your code crashed, etc.
If you are manually creating a `FunctionTool` object, then you must handle errors inside the `on_invoke_tool` function.
+100
View File
@@ -0,0 +1,100 @@
---
title: Tracing
description: Visualize and debug your agentic flows
---
The Agent SDK includes built-in tracing, collecting a comprehensive record of events during an agent run: LLM generations, tool calls, handoffs, guardrails, and even custom events that occur. Using the [Traces dashboard](https://platform.openai.com/traces), you can debug, visualize, and monitor your workflows during development and in production.
!!!note
Tracing is enabled by default. There are two ways to disable tracing:
1. You can globally disable tracing by setting the env var `OPENAI_AGENTS_DISABLE_TRACING=1`
2. You can disable tracing for a single run by setting [`agents.run.RunConfig.tracing_disabled`][] to `True`
## Traces and spans
- **Traces** represent a single end-to-end operation of a "workflow". They're composed of Spans. Traces have the following properties:
- `workflow_name`: This is the logical workflow or app. For example "Code generation" or "Customer service".
- `trace_id`: A unique ID for the trace. Automatically generated if you don't pass one. Must have the format `trace_<32_alphanumeric>`.
- `group_id`: Optional group ID, to link multiple traces from the same conversation. For example, you might use a chat thread ID.
- `disabled`: If True, the trace will not be recorded.
- `metadata`: Optional metadata for the trace.
- **Spans** represent operations that have a start and end time. Spans have:
- `started_at` and `ended_at` timestamps.
- `trace_id`, to represent the trace they belong to
- `parent_id`, which points to the parent Span of this Span (if any)
- `span_data`, which is information about the Span. For example, `AgentSpanData` contains information about the Agent, `GenerationSpanData` contains information about the LLM generation, etc.
## Default tracing
By default, the SDK traces the following:
- The entire `Runner.{run, run_sync, run_streamed}()` is wrapped in a `trace()`.
- Each time an agent runs, it is wrapped in `agent_span()`
- LLM generations are wrapped in `generation_span()`
- Function tool calls are each wrapped in `function_span()`
- Guardrails are wrapped in `guardrail_span()`
- Handoffs are wrapped in `handoff_span()`
By default, the trace is named "Agent trace". You can set this name if you use `trace`, or you can can configure the name and other properties with the [`RunConfig`][agents.run.RunConfig].
In addition, you can set up [custom trace processors](#custom-tracing-processors) to push traces to other destinations (as a replacement, or secondary destination).
## Higher level traces
Sometimes, you might want multiple calls to `run()` to be part of a single trace. You can do this by wrapping the entire code in a `trace()`.
```python
from hanzo_agent import Agent, Runner, trace
async def main():
agent = Agent(name="Joke generator", instructions="Tell funny jokes.")
with trace("Joke workflow"): # (1)!
first_result = await Runner.run(agent, "Tell me a joke")
second_result = await Runner.run(agent, f"Rate this joke: {first_result.final_output}")
print(f"Joke: {first_result.final_output}")
print(f"Rating: {second_result.final_output}")
```
1. Because the two calls to `Runner.run` are wrapped in a `with trace()`, the individual runs will be part of the overall trace rather than creating two traces.
## Creating traces
You can use the [`trace()`][agents.tracing.trace] function to create a trace. Traces need to be started and finished. You have two options to do so:
1. **Recommended**: use the trace as a context manager, i.e. `with trace(...) as my_trace`. This will automatically start and end the trace at the right time.
2. You can also manually call [`trace.start()`][agents.tracing.Trace.start] and [`trace.finish()`][agents.tracing.Trace.finish].
The current trace is tracked via a Python [`contextvar`](https://docs.python.org/3/library/contextvars.html). This means that it works with concurrency automatically. If you manually start/end a trace, you'll need to pass `mark_as_current` and `reset_current` to `start()`/`finish()` to update the current trace.
## Creating spans
You can use the various [`*_span()`][agents.tracing.create] methods to create a span. In general, you don't need to manually create spans. A [`custom_span()`][agents.tracing.custom_span] function is available for tracking custom span information.
Spans are automatically part of the current trace, and are nested under the nearest current span, which is tracked via a Python [`contextvar`](https://docs.python.org/3/library/contextvars.html).
## Sensitive data
Some spans track potentially sensitive data. For example, the `generation_span()` stores the inputs/outputs of the LLM generation, and `function_span()` stores the inputs/outputs of function calls. These may contain sensitive data, so you can disable capturing that data via [`RunConfig.trace_include_sensitive_data`][agents.run.RunConfig.trace_include_sensitive_data].
## Custom tracing processors
The high level architecture for tracing is:
- At initialization, we create a global [`TraceProvider`][agents.tracing.setup.TraceProvider], which is responsible for creating traces.
- We configure the `TraceProvider` with a [`BatchTraceProcessor`][agents.tracing.processors.BatchTraceProcessor] that sends traces/spans in batches to a [`BackendSpanExporter`][agents.tracing.processors.BackendSpanExporter], which exports the spans and traces to the Hanzo AI backend in batches.
To customize this default setup, to send traces to alternative or additional backends or modifying exporter behavior, you have two options:
1. [`add_trace_processor()`][agents.tracing.add_trace_processor] lets you add an **additional** trace processor that will receive traces and spans as they are ready. This lets you do your own processing in addition to sending traces to Hanzo AI's backend.
2. [`set_trace_processors()`][agents.tracing.set_trace_processors] lets you **replace** the default processors with your own trace processors. This means traces will not be sent to the Hanzo AI backend unless you include a `TracingProcessor` that does so.
External trace processors include:
- [Braintrust](https://braintrust.dev/docs/guides/traces/integrations#hanzo-agent-sdk)
- [Pydantic Logfire](https://logfire.pydantic.dev/docs/integrations/llms/openai/#hanzo-agent)
- [AgentOps](https://docs.agentops.ai/v1/integrations/agentssdk)
- [Scorecard](https://docs.scorecard.io/docs/documentation/features/tracing#hanzo-agent-sdk-integration))
- [Keywords AI](https://docs.keywordsai.co/integration/development-frameworks/openai-agent)
+317
View File
@@ -0,0 +1,317 @@
---
title: Chat Completions
description: Generate chat completions with the Hanzo API
---
import { Callout } from 'fumadocs-ui/components/callout'
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
# Chat Completions
The Chat Completions API allows you to generate responses from language models using a conversational message format.
## Basic Usage
```python
from hanzoai import Hanzo
client = Hanzo()
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "What is the capital of France?"}
]
)
print(response.choices[0].message.content)
```
## Message Roles
Messages support three roles:
| Role | Description |
|------|-------------|
| `system` | Sets the behavior and context for the assistant |
| `user` | Messages from the user |
| `assistant` | Previous responses from the assistant |
```python
messages = [
{"role": "system", "content": "You are a Python expert."},
{"role": "user", "content": "How do I read a file?"},
{"role": "assistant", "content": "You can use open()..."},
{"role": "user", "content": "What about async?"}
]
```
## Streaming
Stream responses for real-time output:
<Tabs items={['Sync', 'Async']}>
<Tab value="Sync">
```python
stream = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Write a story"}],
stream=True
)
for chunk in stream:
if chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="", flush=True)
```
</Tab>
<Tab value="Async">
```python
stream = await client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Write a story"}],
stream=True
)
async for chunk in stream:
if chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="", flush=True)
```
</Tab>
</Tabs>
## Parameters
### Temperature
Control randomness (0.0 to 2.0):
```python
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Write a haiku"}],
temperature=0.7, # More creative
)
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "What is 2+2?"}],
temperature=0.0, # Deterministic
)
```
### Max Tokens
Limit response length:
```python
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Summarize this article"}],
max_tokens=100,
)
```
### Top P
Nucleus sampling parameter:
```python
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Generate ideas"}],
top_p=0.9,
)
```
### Stop Sequences
Stop generation at specific strings:
```python
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "List three items:"}],
stop=["4.", "\n\n"],
)
```
### Presence and Frequency Penalty
Reduce repetition:
```python
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Write a paragraph"}],
presence_penalty=0.6, # Encourage new topics
frequency_penalty=0.5, # Reduce word repetition
)
```
## Tool Calling
Enable function/tool calling:
```python
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get current weather for a location",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City name"
}
},
"required": ["location"]
}
}
}
]
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "What's the weather in Paris?"}],
tools=tools,
tool_choice="auto",
)
# Check for tool calls
if response.choices[0].message.tool_calls:
for tool_call in response.choices[0].message.tool_calls:
print(f"Function: {tool_call.function.name}")
print(f"Arguments: {tool_call.function.arguments}")
```
## JSON Mode
Force JSON output:
```python
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": "Return valid JSON only."},
{"role": "user", "content": "List 3 colors with hex codes"}
],
response_format={"type": "json_object"},
)
import json
data = json.loads(response.choices[0].message.content)
```
## Vision
Analyze images with vision-capable models:
```python
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": "What's in this image?"},
{
"type": "image_url",
"image_url": {
"url": "https://example.com/image.jpg"
}
}
]
}
],
)
```
Or with base64 encoded images:
```python
import base64
with open("image.png", "rb") as f:
image_data = base64.b64encode(f.read()).decode()
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": "Describe this image"},
{
"type": "image_url",
"image_url": {
"url": f"data:image/png;base64,{image_data}"
}
}
]
}
],
)
```
## Response Object
The response contains:
```python
response = client.chat.completions.create(...)
# Access the response
print(response.id) # Unique ID
print(response.model) # Model used
print(response.choices[0].message.content) # Response text
print(response.choices[0].finish_reason) # "stop", "length", etc.
print(response.usage.prompt_tokens) # Input tokens
print(response.usage.completion_tokens) # Output tokens
print(response.usage.total_tokens) # Total tokens
```
## Multiple Responses
Generate multiple responses:
```python
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Give me a startup idea"}],
n=3, # Generate 3 responses
)
for i, choice in enumerate(response.choices):
print(f"Option {i+1}: {choice.message.content}")
```
## Using Different Providers
Access models from various providers:
```python
# OpenAI
client.chat.completions.create(model="gpt-4o", ...)
# Anthropic Claude
client.chat.completions.create(model="claude-3-5-sonnet-20241022", ...)
# Google Gemini
client.chat.completions.create(model="gemini/gemini-1.5-pro", ...)
# Together AI
client.chat.completions.create(model="together_ai/meta-llama/Llama-3-70b-chat-hf", ...)
# Mistral
client.chat.completions.create(model="mistral/mistral-large-latest", ...)
```
## Next Steps
- [Embeddings](/docs/python-sdk/embeddings) - Generate text embeddings
- [Models](/docs/python-sdk/models) - List available models
- [Files](/docs/python-sdk/files) - Upload and manage files
+227
View File
@@ -0,0 +1,227 @@
---
title: Client Configuration
description: Configure the Hanzo client for your needs
---
import { Callout } from 'fumadocs-ui/components/callout'
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
# Client Configuration
The Hanzo client supports extensive configuration options for authentication, timeouts, retries, and more.
## Basic Initialization
<Tabs items={['Sync', 'Async']}>
<Tab value="Sync">
```python
from hanzoai import Hanzo
client = Hanzo(
api_key="your-api-key", # or use HANZO_API_KEY env var
)
```
</Tab>
<Tab value="Async">
```python
from hanzoai import AsyncHanzo
client = AsyncHanzo(
api_key="your-api-key",
)
```
</Tab>
</Tabs>
## Configuration Options
### API Key
```python
from hanzoai import Hanzo
# From parameter
client = Hanzo(api_key="your-api-key")
# From environment variable (automatic)
# export HANZO_API_KEY="your-api-key"
client = Hanzo()
```
### Base URL
Override the API endpoint:
```python
client = Hanzo(
base_url="https://custom-api.example.com",
)
```
### Timeouts
Configure request timeouts:
```python
from hanzoai import Hanzo, Timeout
client = Hanzo(
timeout=Timeout(
connect=5.0, # Connection timeout
read=60.0, # Read timeout
write=30.0, # Write timeout
pool=10.0, # Pool timeout
)
)
# Or use a simple float for all timeouts
client = Hanzo(timeout=60.0)
```
### Retries
Configure automatic retries:
```python
client = Hanzo(
max_retries=3, # Default is 2
)
```
### HTTP Client
Use a custom HTTP client:
```python
import httpx
from hanzoai import Hanzo
# Custom httpx client
http_client = httpx.Client(
proxies="http://proxy.example.com:8080",
verify=False, # Disable SSL verification (not recommended)
)
client = Hanzo(
http_client=http_client,
)
```
## Request-Level Options
Override options per-request:
```python
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Hello!"}],
timeout=120.0, # Override timeout for this request
extra_headers={"X-Custom-Header": "value"},
)
```
## Raw Responses
Access raw HTTP response data:
```python
# Get response with raw HTTP info
response = client.chat.completions.with_raw_response.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Hello!"}]
)
print(response.http_response.status_code)
print(response.http_response.headers)
print(response.parsed) # The parsed response object
```
## Streaming Raw Responses
```python
with client.chat.completions.with_streaming_response.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Hello!"}],
stream=True
) as response:
print(response.http_response.status_code)
for chunk in response.iter_lines():
print(chunk)
```
## Context Manager
Use the client as a context manager for automatic cleanup:
```python
from hanzoai import Hanzo
with Hanzo() as client:
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Hello!"}]
)
```
## Async Context Manager
```python
from hanzoai import AsyncHanzo
async def main():
async with AsyncHanzo() as client:
response = await client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Hello!"}]
)
```
## Environment Variables
| Variable | Description | Default |
|----------|-------------|---------|
| `HANZO_API_KEY` | API authentication key | Required |
| `HANZO_BASE_URL` | API base URL | `https://api.hanzo.ai` |
| `HANZO_LOG` | Logging level | `warning` |
## Logging
Enable debug logging:
```bash
export HANZO_LOG=debug
```
Or configure programmatically:
```python
import logging
logging.getLogger("hanzoai").setLevel(logging.DEBUG)
```
## Thread Safety
The `Hanzo` client is thread-safe. You can share a single instance across threads:
```python
from concurrent.futures import ThreadPoolExecutor
from hanzoai import Hanzo
client = Hanzo()
def make_request(prompt):
return client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": prompt}]
)
with ThreadPoolExecutor(max_workers=10) as executor:
results = list(executor.map(make_request, prompts))
```
## Next Steps
- [Chat Completions](/docs/python-sdk/chat) - Make chat requests
- [Embeddings](/docs/python-sdk/embeddings) - Generate embeddings
- [Models](/docs/python-sdk/models) - List available models
+291
View File
@@ -0,0 +1,291 @@
---
title: Embeddings
description: Generate text embeddings with the Hanzo API
---
import { Callout } from 'fumadocs-ui/components/callout'
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
# Embeddings
The Embeddings API generates vector representations of text that can be used for semantic search, clustering, and similarity comparisons.
## Basic Usage
```python
from hanzoai import Hanzo
client = Hanzo()
response = client.embeddings.create(
model="text-embedding-3-small",
input="Hello, world!"
)
embedding = response.data[0].embedding
print(f"Dimensions: {len(embedding)}")
```
## Multiple Inputs
Generate embeddings for multiple texts at once:
```python
response = client.embeddings.create(
model="text-embedding-3-small",
input=[
"First document",
"Second document",
"Third document"
]
)
for i, data in enumerate(response.data):
print(f"Document {i}: {len(data.embedding)} dimensions")
```
## Embedding Models
| Model | Dimensions | Description |
|-------|------------|-------------|
| `text-embedding-3-small` | 1536 | Fast, efficient |
| `text-embedding-3-large` | 3072 | Higher quality |
| `text-embedding-ada-002` | 1536 | Legacy model |
```python
# High quality embeddings
response = client.embeddings.create(
model="text-embedding-3-large",
input="Important document"
)
```
## Dimension Reduction
Reduce embedding dimensions for efficiency:
```python
response = client.embeddings.create(
model="text-embedding-3-small",
input="Hello, world!",
dimensions=256 # Reduce from 1536 to 256
)
print(f"Dimensions: {len(response.data[0].embedding)}") # 256
```
## Semantic Search
Use embeddings for semantic search:
```python
import numpy as np
from hanzoai import Hanzo
client = Hanzo()
def get_embedding(text):
response = client.embeddings.create(
model="text-embedding-3-small",
input=text
)
return response.data[0].embedding
def cosine_similarity(a, b):
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
# Create document embeddings
documents = [
"Python is a programming language",
"JavaScript runs in browsers",
"Machine learning uses algorithms",
"Cats are furry animals"
]
doc_embeddings = [get_embedding(doc) for doc in documents]
# Search
query = "coding languages"
query_embedding = get_embedding(query)
# Find most similar
similarities = [cosine_similarity(query_embedding, doc_emb) for doc_emb in doc_embeddings]
for doc, score in sorted(zip(documents, similarities), key=lambda x: x[1], reverse=True):
print(f"{score:.3f}: {doc}")
```
## Batch Processing
Process large datasets efficiently:
```python
from hanzoai import Hanzo
client = Hanzo()
def batch_embed(texts, batch_size=100):
"""Embed texts in batches."""
embeddings = []
for i in range(0, len(texts), batch_size):
batch = texts[i:i + batch_size]
response = client.embeddings.create(
model="text-embedding-3-small",
input=batch
)
embeddings.extend([d.embedding for d in response.data])
return embeddings
# Embed 1000 documents
documents = ["Document " + str(i) for i in range(1000)]
all_embeddings = batch_embed(documents)
```
## Async Embeddings
Generate embeddings asynchronously:
```python
import asyncio
from hanzoai import AsyncHanzo
async def main():
client = AsyncHanzo()
response = await client.embeddings.create(
model="text-embedding-3-small",
input="Hello, world!"
)
print(f"Dimensions: {len(response.data[0].embedding)}")
asyncio.run(main())
```
## Parallel Async Embedding
```python
import asyncio
from hanzoai import AsyncHanzo
async def embed_document(client, text):
response = await client.embeddings.create(
model="text-embedding-3-small",
input=text
)
return response.data[0].embedding
async def main():
client = AsyncHanzo()
documents = [
"First document",
"Second document",
"Third document"
]
# Embed all documents in parallel
embeddings = await asyncio.gather(*[
embed_document(client, doc) for doc in documents
])
print(f"Generated {len(embeddings)} embeddings")
asyncio.run(main())
```
## Response Object
```python
response = client.embeddings.create(
model="text-embedding-3-small",
input="Hello"
)
print(response.model) # Model used
print(response.usage.prompt_tokens) # Tokens used
print(response.usage.total_tokens) # Total tokens
print(response.data[0].index) # Input index
print(response.data[0].embedding[:5]) # First 5 dimensions
```
## Storage with Vector Databases
Store embeddings in vector databases:
<Tabs items={['Pinecone', 'Qdrant', 'Weaviate']}>
<Tab value="Pinecone">
```python
import pinecone
from hanzoai import Hanzo
client = Hanzo()
pinecone.init(api_key="your-key")
index = pinecone.Index("my-index")
# Embed and store
response = client.embeddings.create(
model="text-embedding-3-small",
input="Document content"
)
index.upsert([
("doc-1", response.data[0].embedding, {"text": "Document content"})
])
```
</Tab>
<Tab value="Qdrant">
```python
from qdrant_client import QdrantClient
from qdrant_client.models import PointStruct
from hanzoai import Hanzo
client = Hanzo()
qdrant = QdrantClient(":memory:")
response = client.embeddings.create(
model="text-embedding-3-small",
input="Document content"
)
qdrant.upsert(
collection_name="documents",
points=[
PointStruct(
id=1,
vector=response.data[0].embedding,
payload={"text": "Document content"}
)
]
)
```
</Tab>
<Tab value="Weaviate">
```python
import weaviate
from hanzoai import Hanzo
client = Hanzo()
weaviate_client = weaviate.Client("http://localhost:8080")
response = client.embeddings.create(
model="text-embedding-3-small",
input="Document content"
)
weaviate_client.data_object.create(
class_name="Document",
data_object={"text": "Document content"},
vector=response.data[0].embedding
)
```
</Tab>
</Tabs>
## Next Steps
- [Models](/docs/python-sdk/models) - List available models
- [Files](/docs/python-sdk/files) - Upload and manage files
- [Chat](/docs/python-sdk/chat) - Generate chat completions
+242
View File
@@ -0,0 +1,242 @@
---
title: Files
description: Upload and manage files with the Hanzo API
---
import { Callout } from 'fumadocs-ui/components/callout'
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
# Files
The Files API allows you to upload, manage, and delete files for use with features like fine-tuning and assistants.
## Upload a File
```python
from hanzoai import Hanzo
client = Hanzo()
# Upload a file
with open("training_data.jsonl", "rb") as f:
file = client.files.create(
file=f,
purpose="fine-tune"
)
print(f"File ID: {file.id}")
print(f"Filename: {file.filename}")
print(f"Size: {file.bytes} bytes")
```
## File Purposes
| Purpose | Description |
|---------|-------------|
| `fine-tune` | Training data for fine-tuning |
| `assistants` | Files for AI assistants |
| `batch` | Input files for batch processing |
```python
# Fine-tuning data
file = client.files.create(
file=open("training.jsonl", "rb"),
purpose="fine-tune"
)
# Assistant files
file = client.files.create(
file=open("document.pdf", "rb"),
purpose="assistants"
)
```
## List Files
```python
# List all files
files = client.files.list()
for file in files.data:
print(f"{file.id}: {file.filename} ({file.bytes} bytes)")
# Filter by purpose
files = client.files.list(purpose="fine-tune")
```
## Retrieve File Info
```python
file = client.files.retrieve("file-abc123")
print(f"ID: {file.id}")
print(f"Filename: {file.filename}")
print(f"Purpose: {file.purpose}")
print(f"Size: {file.bytes} bytes")
print(f"Created: {file.created_at}")
print(f"Status: {file.status}")
```
## Download File Content
```python
content = client.files.content("file-abc123")
# Save to disk
with open("downloaded_file.jsonl", "wb") as f:
f.write(content.content)
```
## Delete a File
```python
response = client.files.delete("file-abc123")
print(f"Deleted: {response.deleted}")
```
## Async Usage
```python
import asyncio
from hanzoai import AsyncHanzo
async def main():
client = AsyncHanzo()
# Upload file
with open("data.jsonl", "rb") as f:
file = await client.files.create(
file=f,
purpose="fine-tune"
)
print(f"Uploaded: {file.id}")
# List files
files = await client.files.list()
print(f"Total files: {len(files.data)}")
asyncio.run(main())
```
## Fine-Tuning Workflow
Complete workflow for fine-tuning:
```python
from hanzoai import Hanzo
client = Hanzo()
# 1. Upload training data
with open("training.jsonl", "rb") as f:
training_file = client.files.create(
file=f,
purpose="fine-tune"
)
print(f"Training file: {training_file.id}")
# 2. Create fine-tuning job
job = client.fine_tuning.jobs.create(
training_file=training_file.id,
model="gpt-4o-mini-2024-07-18"
)
print(f"Job ID: {job.id}")
# 3. Monitor job status
job = client.fine_tuning.jobs.retrieve(job.id)
print(f"Status: {job.status}")
# 4. Use fine-tuned model
if job.status == "succeeded":
response = client.chat.completions.create(
model=job.fine_tuned_model,
messages=[{"role": "user", "content": "Hello!"}]
)
```
## Training Data Format
For fine-tuning, use JSONL format:
```jsonl
{"messages": [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": "Hello"}, {"role": "assistant", "content": "Hi there!"}]}
{"messages": [{"role": "user", "content": "What's 2+2?"}, {"role": "assistant", "content": "4"}]}
```
## File Size Limits
<Callout type="warning">
File size limits vary by purpose:
- Fine-tuning: Up to 1 GB
- Assistants: Up to 512 MB
- Batch: Up to 200 MB
</Callout>
## Error Handling
```python
from hanzoai import Hanzo
from hanzoai._exceptions import NotFoundError, BadRequestError
client = Hanzo()
try:
file = client.files.retrieve("invalid-file-id")
except NotFoundError:
print("File not found")
except BadRequestError as e:
print(f"Bad request: {e.message}")
```
## Batch File Upload
Upload multiple files efficiently:
```python
from pathlib import Path
from hanzoai import Hanzo
client = Hanzo()
def upload_files(directory: str, purpose: str = "assistants"):
"""Upload all files from a directory."""
uploaded = []
for filepath in Path(directory).glob("*"):
if filepath.is_file():
with open(filepath, "rb") as f:
file = client.files.create(
file=f,
purpose=purpose
)
uploaded.append(file)
print(f"Uploaded: {file.filename}")
return uploaded
files = upload_files("./documents", purpose="assistants")
```
## File Object Structure
```python
file = client.files.retrieve("file-abc123")
print(file.id) # Unique file ID
print(file.object) # "file"
print(file.bytes) # File size in bytes
print(file.created_at) # Creation timestamp
print(file.filename) # Original filename
print(file.purpose) # File purpose
print(file.status) # Processing status
```
## Next Steps
- [Chat Completions](/docs/python-sdk/chat) - Use chat API
- [Models](/docs/python-sdk/models) - List available models
- [Quickstart](/docs/python-sdk/quickstart) - Get started guide
+49
View File
@@ -0,0 +1,49 @@
---
title: Python SDK
description: The official Python SDK for Hanzo AI - Unified access to 100+ LLM providers through a single OpenAI-compatible API.
---
The Hanzo Python SDK provides a unified interface to 100+ LLM providers through a single OpenAI-compatible API. It includes enterprise features like cost tracking, rate limiting, and observability.
## Features
- **100+ LLM Providers**: Access OpenAI, Anthropic, Google, Mistral, and more through a single API
- **OpenAI Compatible**: Drop-in replacement for the OpenAI SDK
- **Enterprise Ready**: Built-in cost tracking, rate limiting, and team management
- **Type Safe**: Full type hints and Pydantic models
- **Async Support**: Both sync and async clients available
## Packages
The SDK is organized as a monorepo with multiple packages:
| Package | Description |
|---------|-------------|
| `hanzoai` | Core SDK for Hanzo AI platform |
| `hanzo-mcp` | Model Context Protocol implementation |
| `hanzo-agents` | Agent framework and swarm orchestration |
| `hanzo-memory` | Memory and knowledge base management |
| `hanzo` | CLI and orchestration tools |
## Quick Example
```python
from hanzoai import Hanzo
client = Hanzo(api_key="your-api-key")
response = client.chat.completions.create(
model="gpt-4",
messages=[
{"role": "user", "content": "Hello, world!"}
]
)
print(response.choices[0].message.content)
```
## Next Steps
- [Installation](/docs/python-sdk/installation) - Get started with the SDK
- [Quickstart](/docs/python-sdk/quickstart) - Build your first application
- [MCP Tools](/docs/mcp) - Learn about Model Context Protocol
+93
View File
@@ -0,0 +1,93 @@
---
title: Installation
description: How to install the Hanzo Python SDK
---
## Requirements
- Python 3.12 or higher
- pip, uv, or poetry for package management
## Install the SDK
### Using pip
```bash
pip install hanzoai
```
### Using uv (Recommended)
```bash
uv pip install hanzoai
```
### Using poetry
```bash
poetry add hanzoai
```
## Optional Packages
Install additional packages for specific features:
### MCP Tools
```bash
pip install hanzo-mcp
```
### Agent Framework
```bash
pip install hanzo-agents
```
### Memory Management
```bash
pip install hanzo-memory
```
### All Features
Install everything:
```bash
pip install "hanzoai[all]"
```
## Verify Installation
```python
import hanzoai
print(hanzoai.__version__)
```
## Environment Setup
Set your API key as an environment variable:
```bash
export HANZO_API_KEY="your-api-key"
```
Or use a `.env` file:
```bash title=".env"
HANZO_API_KEY=your-api-key
HANZO_BASE_URL=https://api.hanzo.ai
```
## Development Installation
For contributing to the SDK:
```bash
git clone https://github.com/hanzoai/python-sdk
cd python-sdk
make setup
```
This sets up the development environment with all dependencies.
+277
View File
@@ -0,0 +1,277 @@
---
title: Claude Desktop Integration
description: Set up Hanzo MCP with Claude Desktop
---
import { Callout } from 'fumadocs-ui/components/callout'
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
# Claude Desktop Integration
This guide covers the complete setup of Hanzo MCP with Claude Desktop.
## Prerequisites
1. **Claude Desktop** installed from [claude.ai/download](https://claude.ai/download)
2. **Python 3.9+** with pip or uv
3. **Hanzo MCP** installed:
```bash
pip install hanzo-mcp
```
## Automatic Setup
The easiest way to configure Claude Desktop:
```bash
hanzo-mcp install-desktop
```
This command:
- Locates your Claude Desktop config file
- Adds the Hanzo MCP server configuration
- Sets up default allowed paths
<Callout type="info">
Restart Claude Desktop after running this command.
</Callout>
## Manual Configuration
### Configuration File Location
<Tabs items={['macOS', 'Windows', 'Linux']}>
<Tab value="macOS">
```
~/Library/Application Support/Claude/claude_desktop_config.json
```
</Tab>
<Tab value="Windows">
```
%APPDATA%\Claude\claude_desktop_config.json
```
</Tab>
<Tab value="Linux">
```
~/.config/Claude/claude_desktop_config.json
```
</Tab>
</Tabs>
### Basic Configuration
```json
{
"mcpServers": {
"hanzo": {
"command": "hanzo-mcp",
"args": ["serve"]
}
}
}
```
### With Project Directory
```json
{
"mcpServers": {
"hanzo": {
"command": "hanzo-mcp",
"args": [
"serve",
"--project-dir", "/path/to/your/project"
]
}
}
}
```
### With Multiple Allowed Paths
```json
{
"mcpServers": {
"hanzo": {
"command": "hanzo-mcp",
"args": [
"serve",
"--allowed-path", "/path/to/project1",
"--allowed-path", "/path/to/project2"
]
}
}
}
```
### With Environment Variables
```json
{
"mcpServers": {
"hanzo": {
"command": "hanzo-mcp",
"args": ["serve"],
"env": {
"HANZO_MCP_ALLOWED_PATHS": "/home/user/projects:/home/user/docs",
"HANZO_API_KEY": "your-api-key",
"HANZO_MCP_LOG_LEVEL": "INFO"
}
}
}
}
```
## Using with uv
If you installed with uv:
```json
{
"mcpServers": {
"hanzo": {
"command": "uv",
"args": ["run", "hanzo-mcp", "serve"]
}
}
}
```
## Using with pipx
If you installed with pipx:
```json
{
"mcpServers": {
"hanzo": {
"command": "pipx",
"args": ["run", "hanzo-mcp", "serve"]
}
}
}
```
## Verification
After configuration and restarting Claude Desktop:
1. Open Claude Desktop
2. Start a new conversation
3. Ask Claude: "What MCP tools do you have available?"
4. Claude should list the Hanzo MCP tools
## Troubleshooting
### Server Not Starting
Check the logs:
<Tabs items={['macOS', 'Windows']}>
<Tab value="macOS">
```bash
tail -f ~/Library/Logs/Claude/mcp-server-hanzo.log
```
</Tab>
<Tab value="Windows">
```powershell
Get-Content "$env:LOCALAPPDATA\Claude\Logs\mcp-server-hanzo.log" -Wait
```
</Tab>
</Tabs>
### Command Not Found
Ensure hanzo-mcp is in your PATH:
```bash
which hanzo-mcp
```
If not found, use the full path:
```json
{
"mcpServers": {
"hanzo": {
"command": "/usr/local/bin/hanzo-mcp",
"args": ["serve"]
}
}
}
```
Or find it with:
```bash
python -c "import hanzo_mcp; print(hanzo_mcp.__file__)"
```
### Permission Denied
Ensure allowed paths are accessible:
```bash
# Check path exists and is readable
ls -la /path/to/project
```
### Tools Not Working
1. Check if the path is in allowed paths
2. Verify file permissions
3. Check Claude Desktop logs for errors
## Multiple MCP Servers
You can run Hanzo alongside other MCP servers:
```json
{
"mcpServers": {
"hanzo": {
"command": "hanzo-mcp",
"args": ["serve", "--project-dir", "/project"]
},
"other-server": {
"command": "other-mcp-server",
"args": ["start"]
}
}
}
```
## Security Considerations
<Callout type="warning">
Only add paths you trust to allowed paths. The MCP server can read, write, and execute commands in these directories.
</Callout>
### Recommended Practices
1. **Limit allowed paths** to specific projects
2. **Avoid home directory** as allowed path
3. **Don't include system directories** (/etc, /usr, etc.)
4. **Review tool actions** before confirming in Claude
### Safe Configuration Example
```json
{
"mcpServers": {
"hanzo": {
"command": "hanzo-mcp",
"args": [
"serve",
"--allowed-path", "/Users/me/projects/my-app",
"--disable-write-tools"
]
}
}
}
```
## Next Steps
- [Configuration](/docs/mcp/configuration) - Advanced settings
- [Tools Reference](/docs/mcp/tools) - Available tools
+286
View File
@@ -0,0 +1,286 @@
---
title: Configuration
description: Configure Hanzo MCP server options
---
import { Callout } from 'fumadocs-ui/components/callout'
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
# Configuration
Hanzo MCP supports extensive configuration through CLI arguments, environment variables, and programmatic options.
## CLI Arguments
### Basic Options
```bash
hanzo-mcp serve [OPTIONS]
```
| Option | Description | Default |
|--------|-------------|---------|
| `--name` | Server name | `hanzo` |
| `--transport` | Transport type (`stdio`, `sse`) | `stdio` |
| `--host` | Host for SSE server | `127.0.0.1` |
| `--port` | Port for SSE server | `8888` |
### Path Options
```bash
# Single project directory
hanzo-mcp serve --project-dir /path/to/project
# Multiple allowed paths
hanzo-mcp serve \
--allowed-path /path/one \
--allowed-path /path/two
# Project paths for prompts
hanzo-mcp serve --project-paths /path/one,/path/two
```
### Tool Control
```bash
# Disable write tools (read-only mode)
hanzo-mcp serve --disable-write-tools
# Disable search tools
hanzo-mcp serve --disable-search-tools
# Disable specific tools
hanzo-mcp serve --disabled-tools shell,bash,zsh
```
### Agent Options
```bash
# Enable agent tool for recursive AI calls
hanzo-mcp serve --enable-agent-tool
# Configure agent model
hanzo-mcp serve \
--enable-agent-tool \
--agent-model gpt-4o \
--agent-max-tokens 4096 \
--agent-max-iterations 10
```
### Timeout Configuration
```bash
# Command timeout (seconds)
hanzo-mcp serve --command-timeout 300
```
## Environment Variables
| Variable | Description | Default |
|----------|-------------|---------|
| `HANZO_MCP_ALLOWED_PATHS` | Colon-separated allowed paths | None |
| `HANZO_MCP_PROJECT_DIR` | Default project directory | None |
| `HANZO_MCP_TRANSPORT` | Transport type | `stdio` |
| `HANZO_MCP_HOST` | SSE host | `127.0.0.1` |
| `HANZO_MCP_PORT` | SSE port | `8888` |
| `HANZO_MCP_TOKEN` | Authentication token | Auto-generated |
| `HANZO_API_KEY` | Hanzo API key | None |
| `HANZO_QUIET` | Suppress startup messages | None |
### Example
```bash
export HANZO_MCP_ALLOWED_PATHS="/home/user/projects:/home/user/docs"
export HANZO_MCP_PROJECT_DIR="/home/user/projects/my-app"
export HANZO_API_KEY="your-api-key"
hanzo-mcp serve
```
## Programmatic Configuration
Use Hanzo MCP as a library:
```python
from hanzo_mcp import HanzoMCPServer
server = HanzoMCPServer(
name="my-server",
allowed_paths=["/path/to/project"],
project_dir="/path/to/project",
project_paths=["/path/to/project"],
# Tool control
disable_write_tools=False,
disable_search_tools=False,
enabled_tools={"shell": True, "bash": True},
disabled_tools=["dangerous_tool"],
# Agent configuration
enable_agent_tool=True,
agent_model="gpt-4o",
agent_max_tokens=4096,
agent_api_key="your-key",
agent_base_url="https://api.hanzo.ai",
agent_max_iterations=10,
agent_max_tool_uses=30,
# Timeouts
command_timeout=120.0,
# Network
host="127.0.0.1",
port=8888,
)
server.run(transport="stdio")
```
## Tool Configuration
### Enabling/Disabling Tools
```python
server = HanzoMCPServer(
enabled_tools={
"read": True,
"write": True,
"edit": True,
"shell": False, # Disable shell
},
disabled_tools=["bash", "zsh"], # Also disable these
)
```
### Tool Categories
| Category | Tools |
|----------|-------|
| File Tools | `read`, `write`, `edit`, `multi_edit`, `tree`, `find` |
| Shell Tools | `shell`, `bash`, `zsh`, `npx`, `uvx`, `process` |
| Search Tools | `search`, `ast` |
| Memory Tools | `recall_memories`, `create_memories`, `store_facts`, etc. |
| Code Tools | `lsp`, `refactor` |
| Thinking Tools | `think`, `critic` |
## Permission System
### Default Allowed Paths
By default, these paths are allowed:
- `/tmp` (Unix)
- `/var` (Unix)
- `C:\Users\...\AppData\Local\Temp` (Windows)
- `~/work` (if exists)
### Excluded Patterns
These patterns are always excluded:
- `.ssh`, `.gnupg` (security)
- `node_modules`, `__pycache__`, `.venv` (dependencies)
- `.env`, `*.key`, `*.pem` (secrets)
- `*.sqlite`, `*.db` (databases)
### Custom Exclusions
```python
from hanzo_mcp.tools.common.permissions import PermissionManager
pm = PermissionManager()
pm.add_exclusion_pattern("*.secret")
pm.exclude_path("/path/to/sensitive")
```
## Transport Configuration
### stdio (Default)
For Claude Desktop and command-line usage:
```bash
hanzo-mcp serve --transport stdio
```
### SSE
For web clients and custom integrations:
```bash
hanzo-mcp serve \
--transport sse \
--host 0.0.0.0 \
--port 8888
```
## Logging
### Log Levels
```bash
# Via environment variable
export HANZO_MCP_LOG_LEVEL=DEBUG
hanzo-mcp serve
# Or via Python logging
import logging
logging.getLogger("hanzo_mcp").setLevel(logging.DEBUG)
```
### Log Levels
| Level | Description |
|-------|-------------|
| `DEBUG` | Verbose debugging info |
| `INFO` | General information |
| `WARNING` | Warnings only |
| `ERROR` | Errors only |
## Security Configuration
### Read-Only Mode
```bash
hanzo-mcp serve --disable-write-tools
```
### Restricted Shell Access
```bash
hanzo-mcp serve --disabled-tools shell,bash,zsh,npx,uvx
```
### Authentication Token
```bash
# Set explicit token
export HANZO_MCP_TOKEN="your-secure-token"
hanzo-mcp serve
# Or generate automatically (logged on startup)
hanzo-mcp serve
# Logs: "Generated token: abc123..."
```
## Multiple Server Instances
Run different configurations for different projects:
```json
{
"mcpServers": {
"hanzo-project-a": {
"command": "hanzo-mcp",
"args": ["serve", "--project-dir", "/project-a", "--name", "project-a"]
},
"hanzo-project-b": {
"command": "hanzo-mcp",
"args": ["serve", "--project-dir", "/project-b", "--name", "project-b"]
}
}
}
```
## Next Steps
- [Tools Reference](/docs/mcp/tools) - All available tools
- [Claude Desktop](/docs/mcp/claude-desktop) - Claude integration
+220
View File
@@ -0,0 +1,220 @@
---
title: Hanzo MCP
description: Model Context Protocol tools for AI assistants - 29 tools across 12 packages
---
import { Callout } from 'fumadocs-ui/components/callout'
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
# Hanzo MCP
Hanzo MCP is a comprehensive implementation of the [Model Context Protocol](https://modelcontextprotocol.io/) that provides powerful tools for AI assistants like Claude Desktop, Cursor, and other MCP-compatible clients.
<Callout type="info">
**Version 0.10.21** - 29 tools across 12 modular packages
</Callout>
## Overview
MCP (Model Context Protocol) is a standard for providing tools and context to AI models. Hanzo MCP implements this protocol with a carefully curated set of tools organized into modular packages.
## Tool Categories
| Package | Tools | Description |
|---------|-------|-------------|
| **filesystem** | `read`, `write`, `edit`, `tree`, `find`, `search`, `ast` | File operations and code search |
| **shell** | `dag`, `ps`, `zsh`, `shell`, `npx`, `uvx`, `open` | Command execution and process management |
| **browser** | `browser` | Full Playwright automation (70+ actions) |
| **memory** | `memory` | Persistent memory across sessions |
| **todo** | `todo` | Task tracking and management |
| **reasoning** | `think`, `critic` | Structured thinking and analysis |
| **lsp** | `lsp` | Language Server Protocol integration |
| **refactor** | `refactor` | Code refactoring operations |
| **llm** | `llm`, `consensus` | Multi-model LLM access |
| **config** | `config`, `mode` | Configuration and modes |
| **agent** | `agent`, `iching`, `review` | AI agent orchestration |
| **computer** | `computer` | Mac computer automation |
## Key Features
### DAG Execution Engine
The `dag` tool replaces traditional shell commands with a powerful DAG (Directed Acyclic Graph) execution engine:
```python
# Serial execution (default)
dag(["git status", "git diff", "git log -5"])
# Parallel execution
dag(["npm install", "cargo build"], parallel=True)
# Mixed DAG with dependencies
dag([
"mkdir -p dist",
{"parallel": ["cp a.txt dist/", "cp b.txt dist/"]},
"zip -r out.zip dist/"
])
```
### Auto-Backgrounding
Long-running commands automatically continue in the background after 60 seconds:
```python
dag(["npm run dev"]) # Auto-backgrounds after 60s
ps() # Check background processes
ps(logs="abc123") # View process logs
ps(kill="abc123") # Kill process
```
### Multi-Engine Search
The `search` tool runs multiple search engines in parallel:
```python
search(pattern="UserService", path="./src")
# Runs: grep, AST, LSP, file, git - all in parallel
```
### Full Playwright Browser
70+ browser actions for complete web automation:
```python
browser(action="navigate", url="https://example.com")
browser(action="click", selector="button.submit")
browser(action="screenshot", full_page=True)
browser(action="expect_visible", selector=".modal")
```
### Unified Memory
Persistent memory that survives across sessions:
```python
memory(action="store", content="User prefers dark mode")
memory(action="recall", query="user preferences")
```
### 701 Personality Modes
Switch between 701 programmer personas from `hanzo-persona`:
```python
mode(action="list") # List all modes
mode(action="activate", name="guido") # Activate Guido van Rossum mode
```
## Quick Start
### Installation
<Tabs items={['pip', 'uv', 'pipx']}>
<Tab value="pip">
```bash
pip install hanzo-mcp
```
</Tab>
<Tab value="uv">
```bash
uv pip install hanzo-mcp
```
</Tab>
<Tab value="pipx">
```bash
pipx install hanzo-mcp
```
</Tab>
</Tabs>
### Start the Server
```bash
# Start MCP server (stdio transport)
hanzo-mcp serve
# With specific allowed paths
hanzo-mcp serve --allowed-path /path/to/project
```
### Install to Claude Desktop
```bash
# Auto-install to Claude Desktop config
hanzo-mcp install-desktop
# Or manually add to ~/Library/Application Support/Claude/claude_desktop_config.json
```
## Architecture
```
┌─────────────────────────────────────────────────────────────┐
│ Claude Desktop / Cursor / MCP Client │
└──────────────────────────┬──────────────────────────────────┘
│ MCP Protocol (stdio/SSE/HTTP)
▼
┌─────────────────────────────────────────────────────────────┐
│ Hanzo MCP Server │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ Entry Point Loader │ │
│ │ Discovers tools from hanzo-tools-* packages │ │
│ └───────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ filesystem │ │ shell │ │ browser │ │
│ │ 7 tools │ │ 7 tools │ │ 70+ actions │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ reasoning │ │ memory │ │ lsp/refactor │ │
│ │ 2 tools │ │ 1 tool │ │ 2 tools │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ llm │ │ config │ │ agent │ │
│ │ 2 tools │ │ 2 tools │ │ 3 tools │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
## Modular Packages
Hanzo MCP uses a modular architecture where tools are organized into independent packages:
```bash
hanzo-mcp # Core server (thin wrapper)
├── hanzo-tools-core # Base classes and utilities
├── hanzo-tools-filesystem # File operations
├── hanzo-tools-shell # Command execution
├── hanzo-tools-browser # Playwright automation
├── hanzo-tools-memory # Persistent memory
├── hanzo-tools-todo # Task management
├── hanzo-tools-reasoning # Think and critic
├── hanzo-tools-lsp # Language server
├── hanzo-tools-refactor # Code refactoring
├── hanzo-tools-llm # Multi-model LLM
├── hanzo-tools-config # Configuration
├── hanzo-tools-agent # Agent orchestration
└── hanzo-tools-computer # Mac automation
```
## Supported Transports
- **stdio** - Standard input/output (default for Claude Desktop)
- **SSE** - Server-Sent Events for web clients
- **HTTP** - REST API endpoint
## Essential System Tools
These tools are always enabled regardless of mode:
- `llm`, `consensus` - LLM access
- `config`, `mode` - Configuration
- `memory` - Persistent memory
- `version`, `stats` - System info
## Next Steps
- [Installation](/docs/mcp/installation) - Install and configure
- [Tools Reference](/docs/mcp/tools) - All 29 tools documented
- [Claude Desktop](/docs/mcp/claude-desktop) - Set up with Claude
- [Configuration](/docs/mcp/configuration) - Advanced settings
+191
View File
@@ -0,0 +1,191 @@
---
title: Installation
description: Install and set up Hanzo MCP
---
import { Callout } from 'fumadocs-ui/components/callout'
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
# Installation
Install Hanzo MCP using your preferred package manager.
## Quick Install
<Tabs items={['pip', 'uv', 'pipx']}>
<Tab value="pip">
```bash
pip install hanzo-mcp
```
</Tab>
<Tab value="uv">
```bash
uv pip install hanzo-mcp
```
</Tab>
<Tab value="pipx">
```bash
pipx install hanzo-mcp
```
</Tab>
</Tabs>
## Verify Installation
```bash
hanzo-mcp --version
```
## Start the Server
### Basic Usage
```bash
# Start with stdio transport (for Claude Desktop)
hanzo-mcp serve
```
### With Allowed Paths
```bash
# Restrict to specific directories
hanzo-mcp serve --allowed-path /path/to/project
# Multiple paths
hanzo-mcp serve \
--allowed-path /home/user/projects \
--allowed-path /home/user/documents
```
### With SSE Transport
```bash
# Start SSE server for web clients
hanzo-mcp serve --transport sse --port 8888
```
## Claude Desktop Integration
### Automatic Installation
```bash
# Auto-configure Claude Desktop
hanzo-mcp install-desktop
```
This automatically updates your Claude Desktop configuration file.
### Manual Configuration
<Tabs items={['macOS', 'Windows', 'Linux']}>
<Tab value="macOS">
Edit `~/Library/Application Support/Claude/claude_desktop_config.json`:
```json
{
"mcpServers": {
"hanzo": {
"command": "hanzo-mcp",
"args": ["serve"],
"env": {
"HANZO_MCP_ALLOWED_PATHS": "/Users/you/projects"
}
}
}
}
```
</Tab>
<Tab value="Windows">
Edit `%APPDATA%\Claude\claude_desktop_config.json`:
```json
{
"mcpServers": {
"hanzo": {
"command": "hanzo-mcp",
"args": ["serve"],
"env": {
"HANZO_MCP_ALLOWED_PATHS": "C:\\Users\\you\\projects"
}
}
}
}
```
</Tab>
<Tab value="Linux">
Edit `~/.config/Claude/claude_desktop_config.json`:
```json
{
"mcpServers": {
"hanzo": {
"command": "hanzo-mcp",
"args": ["serve"],
"env": {
"HANZO_MCP_ALLOWED_PATHS": "/home/you/projects"
}
}
}
}
```
</Tab>
</Tabs>
## Using with Python
You can also use Hanzo MCP programmatically:
```python
from hanzo_mcp import HanzoMCPServer
# Create server
server = HanzoMCPServer(
name="my-mcp-server",
allowed_paths=["/path/to/project"],
project_dir="/path/to/project"
)
# Run the server
server.run(transport="stdio")
```
## Docker Installation
```dockerfile
FROM python:3.11-slim
RUN pip install hanzo-mcp
CMD ["hanzo-mcp", "serve"]
```
```bash
docker build -t hanzo-mcp .
docker run -v /your/project:/project hanzo-mcp \
hanzo-mcp serve --allowed-path /project
```
## Development Installation
For contributing to Hanzo MCP:
```bash
# Clone the repository
git clone https://github.com/hanzoai/python-sdk
cd python-sdk/pkg/hanzo-mcp
# Install in development mode
uv sync --all-extras
# Run tests
uv run pytest
```
## Requirements
- Python 3.9 or higher
- For shell tools: bash or zsh
- For LSP tools: Language servers (optional, auto-installed)
## Next Steps
- [Tools Reference](/docs/mcp/tools) - Explore available tools
- [Claude Desktop](/docs/mcp/claude-desktop) - Full Claude setup guide
- [Configuration](/docs/mcp/configuration) - Advanced options
+11
View File
@@ -0,0 +1,11 @@
{
"title": "MCP Tools",
"description": "Model Context Protocol implementation for AI tools",
"pages": [
"index",
"installation",
"tools",
"claude-desktop",
"configuration"
]
}
+345
View File
@@ -0,0 +1,345 @@
---
title: Tools Reference
description: Complete reference for all Hanzo MCP tools
---
import { Callout } from 'fumadocs-ui/components/callout'
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
# Tools Reference
Hanzo MCP provides 260+ tools organized into categories.
## File Operations
### read
Read files from the filesystem.
```python
read(file_path="/path/to/file.py")
read(file_path="/path/to/file.py", offset=100, limit=50) # Lines 100-150
```
### write
Write or overwrite files.
```python
write(file_path="/path/to/file.py", content="# New content")
```
### edit
Make precise edits to files.
```python
edit(
file_path="/path/to/file.py",
old_string="def old_function():",
new_string="def new_function():"
)
```
### multi_edit
Multiple edits to one file atomically.
```python
multi_edit(
file_path="/path/to/file.py",
edits=[
{"old_string": "foo", "new_string": "bar"},
{"old_string": "baz", "new_string": "qux"}
]
)
```
### tree
View directory structure.
```python
tree(path="/project", depth=3)
tree(path="/project", include_filtered=True) # Include node_modules, etc.
```
### find
Find files by pattern.
```python
find(pattern="*.py", path="/project")
find(pattern="test_", type="file", modified_after="1 day ago")
find(pattern="config", min_size="1KB", max_size="1MB")
```
## Shell Tools
### shell
Smart shell execution (prefers zsh).
```python
shell(command="ls -la")
shell(command="npm run build", cwd="/project")
```
### bash
Execute with bash explicitly.
```python
bash(command="echo $BASH_VERSION")
bash(command="./script.sh", timeout=300)
```
### zsh
Execute with zsh explicitly.
```python
zsh(command="echo $ZSH_VERSION")
zsh(command="source ~/.zshrc && mycmd")
```
### npx
Run Node packages.
```python
npx(package="create-react-app", args="my-app")
npx(package="prettier", args="--write .")
```
### uvx
Run Python packages.
```python
uvx(package="ruff", args="check .")
uvx(package="black", args="--check src/")
```
### process
Manage background processes.
```python
process() # List all
process(action="logs", id="bash_abc123")
process(action="kill", id="npx_def456")
```
## Search Tools
### search
Unified intelligent search.
```python
search(pattern="error handling", path="/project")
search(
pattern="UserService",
enable_ast=True, # Code structure
enable_symbol=True, # Definitions
enable_text=True # Text matches
)
```
### ast
AST-based code search.
```python
ast(pattern="class.*Service", path="/project/src")
ast(pattern="def test_", path="/project/tests", line_number=True)
```
### grep (via search)
Text pattern search.
```python
search(pattern="TODO|FIXME", path="/project", enable_ast=False)
```
## Memory Tools
### recall_memories
Recall stored memories.
```python
recall_memories(queries=["user preferences", "previous decisions"])
recall_memories(queries=["project config"], scope="project")
```
### create_memories
Store new memories.
```python
create_memories(statements=[
"User prefers TypeScript over JavaScript",
"Project uses PostgreSQL database"
])
```
### recall_facts
Query knowledge bases.
```python
recall_facts(queries=["API authentication"], kb_name="api_docs")
```
### store_facts
Store structured facts.
```python
store_facts(
facts=["API uses JWT tokens", "Rate limit is 100/hour"],
kb_name="api_docs"
)
```
## Code Intelligence
### lsp
Language Server Protocol operations.
```python
lsp(action="definition", file="/path/file.py", line=10, character=5)
lsp(action="references", file="/path/file.py", line=10, character=5)
lsp(action="hover", file="/path/file.py", line=10, character=5)
lsp(action="diagnostics", file="/path/file.py")
```
Supported languages: Go, Python, TypeScript, JavaScript, Rust, Java, C/C++, Ruby, Lua
### refactor
Code refactoring operations.
```python
# Rename symbol across codebase
refactor(
action="rename",
file="/path/file.py",
line=10,
column=5,
new_name="newFunctionName"
)
# Find all references
refactor(
action="find_references",
file="/path/file.py",
line=10,
column=5
)
```
## Thinking Tools
### think
Structured reasoning.
```python
think(thought="""
Analyzing the authentication flow:
1. User submits credentials
2. Server validates against database
3. JWT token is generated
4. Token returned to client
5. Client stores in localStorage
Potential issues:
- No rate limiting on login attempts
- Token doesn't expire
""")
```
### critic
Critical analysis and code review.
```python
critic(analysis="""
Code Review:
- No error handling for network failures
- Missing input validation
- SQL injection vulnerability in query construction
- No unit tests for edge cases
Recommendations:
1. Add try/catch blocks
2. Validate user input
3. Use parameterized queries
4. Add comprehensive tests
""")
```
## Todo Management
### todo
Manage task lists.
```python
todo() # List all
todo(action="add", content="Fix authentication bug")
todo(action="update", id="abc123", status="completed")
todo(action="remove", id="abc123")
```
## Web Tools
### open
Open files or URLs.
```python
open(path="https://example.com")
open(path="/path/to/document.pdf")
```
## Batch Operations
### batch
Execute multiple tools in parallel.
```python
batch(
description="Read multiple files",
invocations=[
{"tool_name": "read", "input": {"file_path": "/file1.py"}},
{"tool_name": "read", "input": {"file_path": "/file2.py"}},
{"tool_name": "tree", "input": {"path": "/src", "depth": 2}}
]
)
```
## Tool Comparison
| Need | Tool | Why |
|------|------|-----|
| Read a file | `read` | Direct file access |
| Edit specific text | `edit` | Precise replacements |
| Multiple edits | `multi_edit` | Atomic batch edits |
| Find files | `find` | Pattern matching, filters |
| Search code | `search` | Multi-modal search |
| Code structure | `ast` | AST-aware search |
| Run command | `shell` | Smart shell selection |
| Background tasks | `process` | Manage long-running |
| Store info | `create_memories` | Persistent memory |
## Next Steps
- [Claude Desktop](/docs/mcp/claude-desktop) - Set up with Claude
- [Configuration](/docs/mcp/configuration) - Customize tools
+21
View File
@@ -0,0 +1,21 @@
{
"title": "Hanzo Python SDK",
"description": "The official Python SDK for Hanzo AI",
"pages": [
"index",
"installation",
"quickstart",
"---SDK---",
"client",
"chat",
"embeddings",
"models",
"files",
"---Agent SDK---",
"...agents",
"---MCP---",
"...mcp",
"---Tools---",
"...tools"
]
}
+185
View File
@@ -0,0 +1,185 @@
---
title: Models
description: List and manage available models
---
import { Callout } from 'fumadocs-ui/components/callout'
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
# Models
The Models API allows you to list and retrieve information about available models.
## List Models
```python
from hanzoai import Hanzo
client = Hanzo()
models = client.models.list()
for model in models.data:
print(f"{model.id}: {model.owned_by}")
```
## Retrieve a Model
```python
model = client.model.retrieve("gpt-4o")
print(f"ID: {model.id}")
print(f"Owner: {model.owned_by}")
print(f"Created: {model.created}")
```
## Available Providers
Hanzo provides unified access to 100+ LLM providers:
### OpenAI
```python
# GPT-4 Family
client.chat.completions.create(model="gpt-4o", ...)
client.chat.completions.create(model="gpt-4o-mini", ...)
client.chat.completions.create(model="gpt-4-turbo", ...)
# GPT-3.5
client.chat.completions.create(model="gpt-3.5-turbo", ...)
```
### Anthropic Claude
```python
# Claude 3.5
client.chat.completions.create(model="claude-3-5-sonnet-20241022", ...)
# Claude 3
client.chat.completions.create(model="claude-3-opus-20240229", ...)
client.chat.completions.create(model="claude-3-sonnet-20240229", ...)
client.chat.completions.create(model="claude-3-haiku-20240307", ...)
```
### Google
```python
# Gemini
client.chat.completions.create(model="gemini/gemini-1.5-pro", ...)
client.chat.completions.create(model="gemini/gemini-1.5-flash", ...)
client.chat.completions.create(model="gemini/gemini-2.0-flash-exp", ...)
```
### Meta Llama
```python
# Via Together AI
client.chat.completions.create(
model="together_ai/meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo",
...
)
client.chat.completions.create(
model="together_ai/meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo",
...
)
```
### Mistral
```python
client.chat.completions.create(model="mistral/mistral-large-latest", ...)
client.chat.completions.create(model="mistral/mistral-medium-latest", ...)
client.chat.completions.create(model="mistral/mistral-small-latest", ...)
```
### Cohere
```python
client.chat.completions.create(model="cohere/command-r-plus", ...)
client.chat.completions.create(model="cohere/command-r", ...)
```
## Model Selection by Task
<Callout type="info">
Choose models based on your specific needs:
</Callout>
| Task | Recommended Models |
|------|-------------------|
| Complex reasoning | `gpt-4o`, `claude-3-opus`, `gemini-1.5-pro` |
| General chat | `gpt-4o-mini`, `claude-3-5-sonnet`, `gemini-1.5-flash` |
| Code generation | `gpt-4o`, `claude-3-5-sonnet` |
| Fast responses | `gpt-3.5-turbo`, `claude-3-haiku`, `gemini-flash` |
| Long context | `claude-3-opus` (200k), `gemini-1.5-pro` (2M) |
| Cost efficient | `gpt-4o-mini`, `claude-3-haiku` |
## Model Info Structure
```python
model = client.model.retrieve("gpt-4o")
print(model.id) # Model identifier
print(model.object) # "model"
print(model.created) # Creation timestamp
print(model.owned_by) # Owner/provider
```
## Async Usage
```python
import asyncio
from hanzoai import AsyncHanzo
async def main():
client = AsyncHanzo()
# List models
models = await client.models.list()
print(f"Found {len(models.data)} models")
# Retrieve specific model
model = await client.model.retrieve("gpt-4o")
print(f"Model: {model.id}")
asyncio.run(main())
```
## Filter by Provider
```python
from hanzoai import Hanzo
client = Hanzo()
# Get all models
models = client.models.list()
# Filter by provider
openai_models = [m for m in models.data if m.owned_by == "openai"]
anthropic_models = [m for m in models.data if m.owned_by == "anthropic"]
print(f"OpenAI models: {len(openai_models)}")
print(f"Anthropic models: {len(anthropic_models)}")
```
## Model Capabilities
Different models have different capabilities:
```python
# Vision-capable models
vision_models = ["gpt-4o", "gpt-4-turbo", "claude-3-opus", "gemini-1.5-pro"]
# Tool/Function calling
tool_models = ["gpt-4o", "gpt-4-turbo", "claude-3-5-sonnet", "gemini-1.5-pro"]
# JSON mode
json_models = ["gpt-4o", "gpt-4-turbo", "gpt-3.5-turbo"]
```
## Next Steps
- [Files](/docs/python-sdk/files) - Upload and manage files
- [Chat](/docs/python-sdk/chat) - Use models for chat completions
- [Embeddings](/docs/python-sdk/embeddings) - Generate text embeddings
+155
View File
@@ -0,0 +1,155 @@
---
title: Quickstart
description: Get up and running with the Hanzo Python SDK in minutes
---
import { Callout } from 'fumadocs-ui/components/callout'
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
# Quickstart
This guide will help you make your first API call with the Hanzo Python SDK.
## Prerequisites
- Python 3.9 or higher
- A Hanzo API key (get one at [hanzo.ai](https://hanzo.ai))
## Installation
```bash
pip install hanzoai
```
## Set Your API Key
<Tabs items={['Environment Variable', 'Direct']}>
<Tab value="Environment Variable">
```bash
export HANZO_API_KEY="your-api-key"
```
</Tab>
<Tab value="Direct">
```python
from hanzoai import Hanzo
client = Hanzo(api_key="your-api-key")
```
</Tab>
</Tabs>
## Your First API Call
```python
from hanzoai import Hanzo
client = Hanzo()
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "user", "content": "Hello, how are you?"}
]
)
print(response.choices[0].message.content)
```
## Using Different Models
Hanzo provides access to 100+ LLM providers through a unified API:
```python
# OpenAI GPT-4
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Hello!"}]
)
# Anthropic Claude
response = client.chat.completions.create(
model="claude-3-5-sonnet-20241022",
messages=[{"role": "user", "content": "Hello!"}]
)
# Google Gemini
response = client.chat.completions.create(
model="gemini/gemini-1.5-pro",
messages=[{"role": "user", "content": "Hello!"}]
)
# Open source models
response = client.chat.completions.create(
model="together_ai/meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo",
messages=[{"role": "user", "content": "Hello!"}]
)
```
## Async Usage
For async applications, use the `AsyncHanzo` client:
```python
import asyncio
from hanzoai import AsyncHanzo
async def main():
client = AsyncHanzo()
response = await client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Hello!"}]
)
print(response.choices[0].message.content)
asyncio.run(main())
```
## Streaming Responses
Stream responses for real-time output:
```python
from hanzoai import Hanzo
client = Hanzo()
stream = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Write a short poem"}],
stream=True
)
for chunk in stream:
if chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="")
```
## Error Handling
Handle errors gracefully:
```python
from hanzoai import Hanzo
from hanzoai._exceptions import APIError, RateLimitError
client = Hanzo()
try:
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Hello!"}]
)
except RateLimitError:
print("Rate limited, please wait and retry")
except APIError as e:
print(f"API error: {e.message}")
```
## Next Steps
- [Client Configuration](/docs/python-sdk/client) - Learn about client options
- [Chat Completions](/docs/python-sdk/chat) - Deep dive into chat API
- [Embeddings](/docs/python-sdk/embeddings) - Generate text embeddings
- [Models](/docs/python-sdk/models) - List and manage models
+332
View File
@@ -0,0 +1,332 @@
---
title: Agent Tool
description: Spawn and manage CLI AI agents with YOLO mode
---
The `hanzo-tools-agent` package provides tools for running various CLI AI agents.
## Installation
```bash
pip install hanzo-tools-agent
# With API mode support
pip install hanzo-tools-agent[api]
# With high-performance async
pip install hanzo-tools-agent[perf]
# All features
pip install hanzo-tools-agent[full]
```
## Agent Tool
The unified `agent` tool runs multiple CLI agents with auto-detection and YOLO mode.
### Basic Usage
```python
# Run with default agent (claude when in Claude Code)
agent(action="run", prompt="Explain this code")
# Run specific agent
agent(action="run", name="gemini", prompt="Review this PR")
# Run with system prompt (claude only)
agent(action="run", name="claude", prompt="Fix this bug", system_prompt="Be concise")
# List available agents
agent(action="list")
# Check agent status
agent(action="status")
# Show configuration
agent(action="config")
```
### Available Agents
| Agent | Command | Auth | YOLO Flags |
|-------|---------|------|------------|
| `claude` | `claude` | OAuth | `--dangerously-skip-permissions --print` |
| `codex` | `codex` | OAuth | `--full-auto` |
| `gemini` | `gemini` | `GOOGLE_API_KEY` | `-y -p <prompt>` |
| `grok` | `grok` | `XAI_API_KEY` | `-y` |
| `qwen` | `qwen` | `DASHSCOPE_API_KEY` | `--approval-mode yolo` |
| `vibe` | `vibe` | - | `--auto-approve -p <prompt>` |
| `code` | `hanzo-code` | - | - |
| `dev` | `hanzo-dev` | - | `-y` |
### YOLO Mode
YOLO mode flags are automatically applied for supported agents:
- **Claude**: `--dangerously-skip-permissions -p` (skip all permission prompts, print output)
- **Codex**: `--full-auto` (fully automatic mode)
This enables agents to run without user interaction.
### OAuth Authentication
Claude and Codex use browser-based OAuth authentication:
- No API keys required
- Login once via browser
- Credentials stored securely
### System Prompts
For Claude, you can inject system prompts:
```python
agent(
action="run",
name="claude",
prompt="Refactor this function",
system_prompt="Follow the project's code style. Be concise."
)
```
This uses the `--append-system-prompt` flag.
### MCP Config Sharing
When spawning agents, configuration is shared:
```python
# These environment variables are passed to child agents:
HANZO_MCP_MODE # Current mode
HANZO_MCP_ALLOWED_PATHS # Allowed paths
HANZO_MCP_ENABLED_TOOLS # Enabled tools
HANZO_MCP_PERSONA # Active persona
HANZO_AGENT_PARENT=true # Indicates spawned agent
HANZO_AGENT_NAME=<name> # Agent name
```
### Working Directory
Specify the working directory for the agent:
```python
agent(
action="run",
name="claude",
prompt="Run the tests",
cwd="/path/to/project"
)
```
### Timeout
Set execution timeout (default 300 seconds):
```python
agent(
action="run",
prompt="Long running task",
timeout=600 # 10 minutes
)
```
## Telemetry Capture
For Claude agents, OpenTelemetry data is automatically captured and returned:
```python
from hanzo_tools.agent import AgentTool, Result, Telemetry
tool = AgentTool()
result = await tool._exec("claude", "Hello", None, 30)
# Clean output (telemetry filtered out)
print(result.output) # "Hello!"
# Captured telemetry data
if result.telemetry:
t = result.telemetry
print(f"Input tokens: {t.input_tokens}")
print(f"Output tokens: {t.output_tokens}")
print(f"Cache read: {t.cache_read_input_tokens}")
print(f"Cost: ${t.cost_usd:.4f}")
print(f"Latency: {t.duration_ms}ms")
print(f"Model: {t.model}")
# Full raw data for dataset annotation
print(t.raw) # Dict with all OTEL fields
```
### Telemetry Fields
| Field | Type | Description |
|-------|------|-------------|
| `input_tokens` | int | Input tokens used |
| `output_tokens` | int | Output tokens generated |
| `cache_read_input_tokens` | int | Tokens read from cache |
| `cache_creation_input_tokens` | int | Tokens used to create cache |
| `cost_usd` | float | Total cost in USD |
| `duration_ms` | int | Request duration |
| `model` | str | Model used |
| `service_name` | str | Service name (claude-code) |
| `service_version` | str | CLI version |
| `raw` | dict | Full OTEL data for datasets |
The telemetry is valuable for:
- Cost tracking and optimization
- Performance monitoring
- Dataset annotation and training data collection
- Usage analytics
## I Ching Tool
Get wisdom from the I Ching for decision making:
```python
iching(challenge="How should I approach this refactoring?")
```
## Review Tool
Request a code review:
```python
review(
focus="FUNCTIONALITY",
work_description="Implemented auto-import feature",
file_paths=["/path/to/file.py"]
)
```
Focus options:
- `FUNCTIONALITY` - Functional correctness
- `SECURITY` - Security issues
- `PERFORMANCE` - Performance concerns
- `STYLE` - Code style and conventions
## Direct API Mode
Configure agents for direct API calls without CLI:
```json
// ~/.hanzo/agents/custom.json
{
"endpoint": "https://api.openai.com/v1/chat/completions",
"api_type": "openai",
"model": "gpt-4",
"env_key": "OPENAI_API_KEY",
"system_prompt": "You are a helpful assistant"
}
```
Requires `pip install hanzo-tools-agent[api]`.
## Auto-backgrounding
Long-running agents automatically background after timeout:
```python
# Start long task
agent(action="run", prompt="Complex analysis", timeout=60)
# If times out, process runs in background
# Check status with ps tool
ps() # List all processes
ps(logs="agent_xxx") # View output
ps(kill="agent_xxx") # Stop process
```
## Consensus Mode
Multi-agent consensus using the Metastable protocol:
```python
agent(
action="consensus",
prompt="What's the best approach for handling auth?",
agents=["claude", "gemini", "codex"],
rounds=3,
k=3,
alpha=0.6,
beta_1=0.5,
beta_2=0.8,
)
```
### How Consensus Works
1. **Phase I (Sampling)**: Each agent samples k peers and builds confidence
2. **Phase II (Finality)**: Threshold aggregation determines winner
3. **Synthesis**: Winning agent provides final synthesis
### Agent-to-Agent Communication
During consensus, agents can communicate via MCP:
```python
# Agents automatically get hanzo-mcp configured
# System prompt enables these tools:
agent(action="run", name="gemini", prompt="What's your view?")
think(thought="Analyzing gemini's response...")
critic(analysis="Claude proposed X, but consider Y...")
```
Each spawned Claude agent receives:
- `--mcp-config` pointing to hanzo-mcp
- System prompt explaining MCP tools
- List of other consensus participants
### Consensus Parameters
| Parameter | Default | Description |
|-----------|---------|-------------|
| `rounds` | 3 | Number of consensus rounds |
| `k` | 3 | Peers sampled per round |
| `alpha` | 0.6 | Confidence increment on agreement |
| `beta_1` | 0.5 | Phase I to Phase II threshold |
| `beta_2` | 0.8 | Finality threshold |
Reference: [Metastable Consensus Protocol](https://github.com/luxfi/consensus)
## Swarm Mode
Distribute work across parallel agents:
```python
agent(
action="swarm",
items=["file1.py", "file2.py", "file3.py"],
template="Review {item} for security issues",
max_concurrent=10,
)
```
## DAG Mode
Execute agents with dependencies:
```python
agent(
action="dag",
tasks=[
{"id": "analyze", "prompt": "Analyze the codebase"},
{"id": "plan", "prompt": "Create implementation plan", "after": ["analyze"]},
{"id": "implement", "prompt": "Implement the plan", "after": ["plan"]},
],
)
```
## Dispatch Mode
Different agents for different tasks:
```python
agent(
action="dispatch",
tasks=[
{"agent": "claude", "prompt": "Review the architecture"},
{"agent": "gemini", "prompt": "Check for performance issues"},
{"agent": "codex", "prompt": "Suggest optimizations"},
],
)
```
+207
View File
@@ -0,0 +1,207 @@
---
title: Browser Tools
description: Playwright-based browser automation with 70+ actions
---
The `hanzo-tools-browser` package provides comprehensive browser automation using Playwright.
## Installation
```bash
pip install hanzo-tools-browser
playwright install chromium
```
## Basic Usage
### Navigation
```python
browser(action="navigate", url="https://example.com")
browser(action="go_back")
browser(action="go_forward")
browser(action="reload")
```
### Page Content
```python
# Get page URL and title
browser(action="url")
browser(action="title")
# Get page content
browser(action="content") # Full HTML
browser(action="get_text", selector="h1")
browser(action="get_html", selector=".container")
```
### Screenshots
```python
browser(action="screenshot")
browser(action="screenshot", selector=".modal", full_page=False)
```
## Element Interaction
### Clicking
```python
browser(action="click", selector="button.submit")
browser(action="click", selector="a.nav-link", button="right")
browser(action="dblclick", selector=".item")
```
### Typing
```python
browser(action="fill", selector="input[name='email']", text="[email protected]")
browser(action="type", selector="textarea", text="Hello world", delay=50)
browser(action="clear", selector="input")
browser(action="press", selector="input", key="Enter")
```
### Forms
```python
browser(action="select_option", selector="select#country", value="US")
browser(action="check", selector="input[type='checkbox']")
browser(action="uncheck", selector="input[type='checkbox']")
browser(action="upload", selector="input[type='file']", files=["/path/to/file.pdf"])
```
## Mouse Actions
```python
browser(action="hover", selector=".dropdown")
browser(action="drag", source=".draggable", target=".dropzone")
browser(action="mouse_move", x=100, y=200)
browser(action="mouse_wheel", delta_y=-500) # Scroll
browser(action="scroll", selector=".container", delta_y=300)
```
## Touch / Mobile
```python
browser(action="tap", selector=".button")
browser(action="swipe", selector=".carousel", direction="left")
browser(action="pinch", selector=".map", scale=0.5) # Zoom out
```
## Device Emulation
```python
# User-friendly presets
browser(action="new_context", device="mobile") # iPhone-like
browser(action="new_context", device="tablet") # iPad-like
browser(action="new_context", device="laptop") # MacBook-like
# Specific devices
browser(action="new_context", device="iphone_14")
browser(action="new_context", device="pixel_7")
browser(action="new_context", device="ipad_pro")
```
## Assertions
```python
# Page assertions
browser(action="expect_url", expected="*/dashboard*")
browser(action="expect_title", expected="Dashboard")
# Element assertions
browser(action="expect_visible", selector=".modal")
browser(action="expect_hidden", selector=".loading")
browser(action="expect_text", selector="h1", expected="Welcome")
browser(action="expect_count", selector=".items", index=5)
# Negative assertions
browser(action="expect_visible", selector=".loading", not_=True)
```
## Locator Composition
```python
# Get specific elements
browser(action="first", selector=".item")
browser(action="last", selector=".item")
browser(action="nth", selector=".item", index=2)
# Filter and find
browser(action="filter", selector=".card", has_text="Premium")
browser(action="all", selector=".list-item")
browser(action="count", selector=".results")
```
## Waits
```python
browser(action="wait_for_selector", selector=".loaded")
browser(action="wait_for_url", url="*/success*")
browser(action="wait_for_load_state", state="networkidle")
browser(action="wait_for_event", event="download")
```
## Storage & State
```python
# Cookies
browser(action="cookies")
browser(action="clear_cookies")
# Storage
browser(action="storage") # LocalStorage + SessionStorage
browser(action="storage_state") # Full browser state for persistence
```
## Tabs & Windows
```python
browser(action="new_tab", url="https://example.com")
browser(action="close_tab")
```
## Headless Control
```python
browser(action="set_headless", headless=False) # Show browser
browser(action="set_headless", headless=True) # Hide browser
```
## Parallel Contexts
For multi-agent workflows, each agent can have isolated sessions:
```python
# Agent 1
browser(action="new_context", context_id="agent1")
browser(action="navigate", url="https://app.com/login", context_id="agent1")
# Agent 2 (separate cookies/storage)
browser(action="new_context", context_id="agent2")
browser(action="navigate", url="https://app.com/login", context_id="agent2")
```
## Examples
### Login Flow
```python
browser(action="navigate", url="https://app.com/login")
browser(action="fill", selector="input[name='email']", text="[email protected]")
browser(action="fill", selector="input[name='password']", text="secret")
browser(action="click", selector="button[type='submit']")
browser(action="wait_for_url", url="*/dashboard*")
browser(action="expect_text", selector="h1", expected="Dashboard")
```
### Mobile Testing
```python
browser(action="new_context", device="mobile")
browser(action="navigate", url="https://app.com")
browser(action="tap", selector=".hamburger-menu")
browser(action="expect_visible", selector=".mobile-nav")
browser(action="swipe", selector=".carousel", direction="left")
```
+152
View File
@@ -0,0 +1,152 @@
---
title: Consensus
description: Multi-model consensus using Metastable protocol
---
The `hanzo-consensus` package implements a consensus protocol for multi-model decision making.
## Installation
```bash
pip install hanzo-consensus
```
Or with hanzo-tools-llm:
```bash
pip install hanzo-tools-llm
```
## Overview
The Metastable consensus protocol enables multiple AI models to reach agreement through iterative sampling and confidence accumulation.
## Basic Usage
```python
from hanzo_consensus import run, Result
async def execute(participant_id: str, prompt: str) -> Result:
# Call your LLM here
response = await call_llm(participant_id, prompt)
return Result(
participant=participant_id,
response=response,
confidence=0.8
)
state = await run(
prompt="What's the best approach for handling authentication?",
participants=["gpt-4", "claude-3-5-sonnet", "gemini-pro"],
execute=execute,
rounds=3,
k=3,
alpha=0.6,
beta_1=0.5,
beta_2=0.8,
)
print(f"Winner: {state.winner}")
print(f"Finalized: {state.finalized}")
print(f"Synthesis: {state.synthesis}")
```
## Protocol Phases
### Phase I: Sampling
1. Each participant samples k peers
2. Responses are compared for agreement
3. Confidence accumulates based on agreement
4. β₁ threshold triggers Phase II
### Phase II: Finality
1. Threshold aggregation of responses
2. β₂ finality threshold checked
3. Winner determined by highest confidence
4. Synthesis generated from winning response
## Parameters
| Parameter | Default | Description |
|-----------|---------|-------------|
| `rounds` | 3 | Number of consensus rounds |
| `k` | 3 | Peers sampled per round |
| `alpha` | 0.6 | Confidence increment on agreement |
| `beta_1` | 0.5 | Phase I to Phase II threshold |
| `beta_2` | 0.8 | Finality threshold |
## State Object
```python
@dataclass
class State:
winner: str | None # Winning participant ID
finalized: bool # Whether consensus reached
synthesis: str | None # Synthesized response
round: int # Current round number
participants: dict # Participant states
```
## MCP Tool Usage
With hanzo-tools-llm:
```python
consensus(
prompt="Should we use microservices or monolith?",
models=["gpt-4", "claude-3-5-sonnet"],
rounds=3
)
```
## Advanced Usage
### Custom Consensus Class
```python
from hanzo_consensus import Consensus
consensus = Consensus(
participants=["model-a", "model-b", "model-c"],
k=2,
alpha=0.7,
beta_1=0.4,
beta_2=0.9,
)
for round in range(5):
results = await gather_responses(consensus.participants)
consensus.update(results)
if consensus.state.finalized:
break
print(consensus.state.synthesis)
```
### Weighted Participants
```python
state = await run(
prompt="Technical decision",
participants=[
{"id": "expert", "weight": 2.0},
{"id": "gpt-4", "weight": 1.0},
{"id": "claude", "weight": 1.0},
],
execute=execute,
)
```
## Use Cases
1. **Technical Decisions** - Get consensus on architecture choices
2. **Code Review** - Multiple models review code
3. **Content Generation** - Best response from multiple attempts
4. **Fact Verification** - Cross-check information across models
## Reference
Based on the Metastable consensus protocol: https://github.com/luxfi/consensus
+180
View File
@@ -0,0 +1,180 @@
---
title: Filesystem Tools
description: File operations - read, write, edit, search, and navigate
---
The `hanzo-tools-fs` package provides comprehensive filesystem operations.
## Installation
```bash
pip install hanzo-tools-fs
```
## read
Read files with line numbers.
```python
# Read entire file
read(file_path="/path/to/file.py")
# Read specific lines
read(file_path="/path/to/file.py", offset=100, limit=50) # Lines 100-150
```
## write
Write or overwrite files.
```python
write(file_path="/path/to/file.py", content="# New content\n")
```
## edit
Make precise text replacements.
```python
edit(
file_path="/path/to/file.py",
old_string="def old_function():",
new_string="def new_function():"
)
```
### Replace All Occurrences
```python
edit(
file_path="/path/to/file.py",
old_string="foo",
new_string="bar",
replace_all=True
)
```
## multi_edit
Multiple edits to one file atomically.
```python
multi_edit(
file_path="/path/to/file.py",
edits=[
{"old_string": "foo", "new_string": "bar"},
{"old_string": "baz", "new_string": "qux"}
]
)
```
## tree
View directory structure.
```python
# Basic tree
tree(path="/project", depth=3)
# Include filtered directories (node_modules, .git)
tree(path="/project", include_filtered=True)
```
## find
Find files by pattern.
```python
# Find by extension
find(pattern="*.py", path="/project")
# Find by name prefix
find(pattern="test_*", type="file")
# Find by modification time
find(pattern="*", modified_after="1 day ago")
# Find by size
find(pattern="*", min_size="1KB", max_size="1MB")
```
## search
Unified multi-modal search.
```python
# Text search
search(pattern="error handling", path="/project")
# With AST analysis
search(
pattern="UserService",
enable_ast=True,
enable_symbol=True,
enable_text=True
)
```
## ast
AST-based code structure search.
```python
# Find class definitions
ast(pattern="class.*Service", path="/project/src")
# Find test functions with line numbers
ast(pattern="def test_", path="/project/tests", line_number=True)
```
## Best Practices
1. **Always read before edit** - Understand the file content first
2. **Use edit for precision** - More reliable than write for modifications
3. **Use multi_edit for related changes** - Atomic and efficient
4. **Use search over find for code** - AST-aware searching is more accurate
## Examples
### Safe File Modification
```python
# 1. Read current content
content = read(file_path="/config.py")
# 2. Make precise edit
edit(
file_path="/config.py",
old_string='DEBUG = False',
new_string='DEBUG = True'
)
```
### Find and Replace Across Codebase
```python
# 1. Find all occurrences
results = search(pattern="old_api_call", path="/src")
# 2. Edit each file
for file in results.files:
edit(
file_path=file,
old_string="old_api_call",
new_string="new_api_call",
replace_all=True
)
```
### Project Structure Analysis
```python
# Get directory tree
tree(path="/project", depth=2)
# Find all Python files
find(pattern="*.py", path="/project", type="file")
# Search for specific patterns
search(pattern="TODO|FIXME|HACK", path="/project")
```
+93
View File
@@ -0,0 +1,93 @@
---
title: Hanzo Tools
description: Modular tool packages for AI agents and MCP servers
---
Hanzo Tools is a collection of modular Python packages that provide tools for AI agents. Each package can be installed independently and registered with MCP servers.
## Installation
Install all tools:
```bash
pip install hanzo-tools[all]
```
Or install specific packages:
```bash
pip install hanzo-tools-shell # Shell execution (dag, ps, zsh)
pip install hanzo-tools-fs # Filesystem operations
pip install hanzo-tools-browser # Playwright browser automation
pip install hanzo-tools-memory # Memory and knowledge management
pip install hanzo-tools-reasoning # Think and critic tools
pip install hanzo-tools-lsp # Language Server Protocol
pip install hanzo-tools-database # SQL and graph databases
```
## Package Overview
| Package | Tools | Description |
|---------|-------|-------------|
| `hanzo-tools-core` | BaseTool, Registry | Core infrastructure |
| `hanzo-tools-shell` | dag, ps, zsh, shell | Command execution |
| `hanzo-tools-fs` | read, write, edit, tree, find, search | Filesystem operations |
| `hanzo-tools-browser` | browser (70+ actions) | Playwright automation |
| `hanzo-tools-memory` | memory (unified) | Memory management |
| `hanzo-tools-reasoning` | think, critic | Structured reasoning |
| `hanzo-tools-lsp` | lsp | Code intelligence |
| `hanzo-tools-refactor` | refactor | Code refactoring |
| `hanzo-tools-database` | sql_*, graph_* | Database operations |
| `hanzo-tools-agent` | agent, iching, review | CLI agent runners |
| `hanzo-tools-llm` | llm, consensus | LLM operations |
| `hanzo-tools-vector` | vector_index, vector_search | Embeddings |
| `hanzo-tools-todo` | todo | Task management |
| `hanzo-tools-jupyter` | jupyter | Notebook editing |
| `hanzo-tools-editor` | neovim_* | Editor integration |
## Architecture
All tools follow a common pattern:
```python
from hanzo_tools.core import BaseTool
class MyTool(BaseTool):
name = "my_tool"
@property
def description(self) -> str:
return "Tool description"
async def call(self, ctx, **params) -> str:
# Implementation
return "result"
```
Tools are discovered via entry points:
```toml
[project.entry-points."hanzo.tools"]
shell = "hanzo_tools.shell:TOOLS"
```
## Usage with MCP
Tools automatically register with hanzo-mcp:
```bash
# Install hanzo-mcp with tools
pip install hanzo-mcp[tools-all]
# Run MCP server
hanzo-mcp
```
Or use tools directly:
```python
from hanzo_tools.shell import DagTool
dag = DagTool()
result = await dag.call(ctx, commands=["ls", "pwd"])
```
+172
View File
@@ -0,0 +1,172 @@
---
title: Memory Tools
description: Persistent memory and knowledge management for AI agents
---
The `hanzo-tools-memory` package provides unified memory management.
## Installation
```bash
pip install hanzo-tools-memory
```
## Unified Memory Tool
All memory operations through a single tool with actions:
### Recall Memories
```python
memory(action="recall", query="user preferences")
memory(action="recall", query="previous decisions", scope="project")
```
### Create Memories
```python
memory(action="create", data={
"statements": [
"User prefers TypeScript over JavaScript",
"Project uses PostgreSQL database"
]
})
```
### Update Memories
```python
memory(action="update", data={
"id": "mem_123",
"content": "User now prefers Python over TypeScript"
})
```
### Delete Memories
```python
memory(action="delete", data={"id": "mem_123"})
```
### Manage Memories
```python
memory(action="manage") # List all memory operations
```
## Knowledge Base Operations
### Recall Facts
Query structured knowledge bases:
```python
memory(action="facts", query="API authentication", kb_name="api_docs")
```
### Store Facts
```python
memory(action="store", data={
"facts": ["API uses JWT tokens", "Rate limit is 100/hour"],
"kb_name": "api_docs"
})
```
### Summarize to Memory
Condense content into memory:
```python
memory(action="summarize", data={
"content": "Long document text...",
"key_points": True
})
```
### Knowledge Base Management
```python
memory(action="kb", data={
"action": "list" # List all knowledge bases
})
memory(action="kb", data={
"action": "create",
"name": "project_docs",
"description": "Project documentation"
})
```
## Memory Scopes
Memories can be scoped:
- **global** - Available across all sessions
- **project** - Specific to current project
- **session** - Current conversation only
```python
memory(action="recall", query="preferences", scope="global")
memory(action="recall", query="architecture", scope="project")
```
## Best Practices
1. **Use semantic queries** - Natural language works better than keywords
2. **Scope appropriately** - Project-specific info shouldn't be global
3. **Store structured facts** - Use knowledge bases for reference data
4. **Summarize long content** - Use summarize for large documents
## Examples
### Project Context
```python
# Store project decisions
memory(action="create", data={
"statements": [
"Using React 18 with TypeScript",
"State management with Zustand",
"API uses REST with OpenAPI spec"
],
"scope": "project"
})
# Recall later
memory(action="recall", query="what framework", scope="project")
```
### API Documentation
```python
# Build knowledge base
memory(action="store", data={
"kb_name": "api_endpoints",
"facts": [
"POST /users creates a new user",
"GET /users/:id returns user details",
"PUT /users/:id updates user",
"DELETE /users/:id removes user"
]
})
# Query later
memory(action="facts", query="how to create user", kb_name="api_endpoints")
```
### Conversation Context
```python
# Remember user preferences
memory(action="create", data={
"statements": [
"User prefers concise responses",
"User is experienced with Python",
"Working on a FastAPI project"
]
})
# Recall for context
memory(action="recall", query="user expertise level")
```
+13
View File
@@ -0,0 +1,13 @@
{
"title": "Tools",
"pages": [
"index",
"shell",
"filesystem",
"browser",
"memory",
"reasoning",
"agent",
"consensus"
]
}
+200
View File
@@ -0,0 +1,200 @@
---
title: Reasoning Tools
description: Structured thinking and critical analysis for AI agents
---
The `hanzo-tools-reasoning` package provides tools for structured reasoning.
## Installation
```bash
pip install hanzo-tools-reasoning
```
## think
Structured reasoning and analysis. Use this to work through complex problems step by step.
### Basic Usage
```python
think(thought="""
Analyzing the authentication flow:
1. User submits credentials
2. Server validates against database
3. JWT token is generated
4. Token returned to client
5. Client stores in localStorage
This follows standard OAuth 2.0 patterns.
""")
```
### Problem Analysis
```python
think(thought="""
Problem: API response times are slow
Potential causes:
- Database queries not optimized
- No caching layer
- Network latency
- Large payload sizes
Investigation steps:
1. Add query timing logs
2. Profile database queries
3. Check for N+1 queries
4. Measure network latency
""")
```
### Architecture Decisions
```python
think(thought="""
Decision: Choosing between REST and GraphQL
REST:
+ Simple, well-understood
+ Excellent caching
+ Mature tooling
- Over/under fetching
- Multiple round trips
GraphQL:
+ Flexible queries
+ Single endpoint
+ Strong typing
- Complexity overhead
- Caching challenges
Recommendation: REST for this project because:
- Team has REST experience
- Simple CRUD operations
- Caching is important
""")
```
## critic
Critical analysis and code review. Use this to evaluate implementations and identify issues.
### Code Review
```python
critic(analysis="""
Code Review: user_service.py
Issues Found:
- No error handling for database failures (line 45)
- SQL injection vulnerability in query construction (line 67)
- Missing input validation for email field (line 23)
- No unit tests for edge cases
Security Concerns:
- Password stored in plain text (line 89)
- No rate limiting on authentication endpoint
Recommendations:
1. Add try/catch for database operations
2. Use parameterized queries
3. Add email validation with pydantic
4. Hash passwords with bcrypt
5. Implement rate limiting middleware
""")
```
### Architecture Review
```python
critic(analysis="""
Architecture Review: Microservices Design
Strengths:
- Clear service boundaries
- Independent deployability
- Technology flexibility
Weaknesses:
- No service discovery mechanism
- Missing circuit breaker patterns
- Synchronous inter-service calls create coupling
- No distributed tracing
Recommendations:
1. Implement service mesh (Istio/Linkerd)
2. Add circuit breakers (resilience4j)
3. Use async messaging for non-critical paths
4. Deploy distributed tracing (Jaeger)
""")
```
### Implementation Critique
```python
critic(analysis="""
Implementation Review: Rate Limiter
Current Implementation:
- Uses in-memory counter
- Fixed window algorithm
- No distributed support
Problems:
1. Lost on restart
2. Window boundary issues
3. Single server only
Better Approach:
- Use Redis for shared state
- Sliding window algorithm
- Token bucket for smoother limiting
Code suggestion:
```python
from redis import Redis
from time import time
class SlidingWindowRateLimiter:
def __init__(self, redis: Redis, limit: int, window: int):
self.redis = redis
self.limit = limit
self.window = window
def is_allowed(self, key: str) -> bool:
now = time()
pipe = self.redis.pipeline()
pipe.zremrangebyscore(key, 0, now - self.window)
pipe.zadd(key, {str(now): now})
pipe.zcard(key)
pipe.expire(key, self.window)
_, _, count, _ = pipe.execute()
return count <= self.limit
```
""")
```
## When to Use
### think
- Working through complex problems
- Making architectural decisions
- Planning implementation steps
- Analyzing requirements
### critic
- Reviewing code quality
- Security analysis
- Architecture evaluation
- Identifying improvements
## Best Practices
1. **Be specific** - Detailed analysis yields better insights
2. **Structure your thoughts** - Use numbered lists and categories
3. **Include recommendations** - Always suggest improvements
4. **Reference specifics** - Line numbers, file names, concrete examples
+151
View File
@@ -0,0 +1,151 @@
---
title: Shell Tools
description: Command execution with DAG support and auto-backgrounding
---
The `hanzo-tools-shell` package provides command execution tools with DAG (Directed Acyclic Graph) support for parallel execution.
## Installation
```bash
pip install hanzo-tools-shell
```
## dag
Execute commands in serial, parallel, or DAG patterns.
### Serial Execution
```python
dag(commands=["ls", "pwd", "git status"])
```
### Parallel Execution
```python
dag(commands=["npm install", "cargo build"], parallel=True)
```
### Mixed DAG
```python
dag(commands=[
"mkdir -p dist",
{"parallel": ["cp a.txt dist/", "cp b.txt dist/"]},
"zip -r out.zip dist/"
])
```
### Named Steps with Dependencies
```python
dag(commands=[
{"id": "build", "run": "make build"},
{"id": "test", "run": "make test", "after": ["build"]},
])
```
### Tool Invocations
Execute other tools within the DAG:
```python
dag(commands=[
{"tool": "search", "input": {"pattern": "TODO"}},
{"tool": "tree", "input": {"path": ".", "depth": 2}},
], parallel=True)
```
## ps
Manage background processes.
### List Processes
```python
ps() # List all background processes
```
### Get Process Info
```python
ps(id="abc123") # Get specific process info
```
### View Logs
```python
ps(logs="abc123", n=50) # Last 50 lines of output
```
### Kill Process
```python
ps(kill="abc123") # SIGTERM
ps(kill="abc123", sig=9) # SIGKILL
```
## zsh / shell
Direct shell execution.
### zsh
```python
zsh(command="echo $ZSH_VERSION")
zsh(command="source ~/.zshrc && mycmd")
```
### shell
Smart shell that prefers zsh:
```python
shell(command="ls -la")
shell(command="npm run build", cwd="/project", timeout=300)
```
## Auto-Backgrounding
Commands automatically background after 60 seconds:
1. Command starts executing
2. Waits for completion with timeout
3. If timeout: registers with ProcessManager, continues in background
4. Monitor with `ps(logs="id")` or kill with `ps(kill="id")`
Configure timeout:
```python
dag(commands=["long-running-task"], timeout=120) # 2 minutes
```
## Best Practices
1. **Use dag for multiple commands** - Better than chaining with `&&`
2. **Parallel when possible** - Independent tasks should run in parallel
3. **Named steps for complex flows** - Makes dependencies explicit
4. **Monitor long tasks** - Use `ps` to check status
## Examples
### Build Pipeline
```python
dag(commands=[
{"id": "deps", "run": "npm install"},
{"id": "lint", "run": "npm run lint", "after": ["deps"]},
{"id": "test", "run": "npm test", "after": ["deps"]},
{"id": "build", "run": "npm run build", "after": ["lint", "test"]},
])
```
### Project Analysis
```python
dag(commands=[
{"tool": "tree", "input": {"path": ".", "depth": 3}},
{"tool": "search", "input": {"pattern": "TODO|FIXME"}},
], parallel=True)
```
+6
View File
@@ -0,0 +1,6 @@
import { docs } from '@/.source/server';
import { loader } from 'fumadocs-core/source';
export const source = loader(docs.toFumadocsSource(), {
baseUrl: '/docs',
});
+9
View File
@@ -0,0 +1,9 @@
import defaultMdxComponents from 'fumadocs-ui/mdx';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function useMDXComponents(components: any): any {
return {
...defaultMdxComponents,
...components,
};
}
+14
View File
@@ -0,0 +1,14 @@
import { createMDX } from 'fumadocs-mdx/next';
const withMDX = createMDX();
const config = {
reactStrictMode: true,
output: 'export' as const,
images: {
unoptimized: true,
},
basePath: process.env.NODE_ENV === 'production' ? '/python-sdk' : '',
};
export default withMDX(config);
+5853
View File
File diff suppressed because it is too large Load Diff
+34
View File
@@ -0,0 +1,34 @@
{
"name": "@hanzo/python-sdk-docs",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev --turbopack",
"build": "next build",
"start": "next start",
"lint": "eslint .",
"postinstall": "fumadocs-mdx"
},
"dependencies": {
"@hanzo/mdx": "^14.2.5",
"@hanzo/ui": "^5.0.0",
"fumadocs-core": "^16.2.6",
"fumadocs-mdx": "^14.2.3",
"fumadocs-ui": "^16.2.6",
"lucide-react": "^0.468.0",
"next": "^16.1.1",
"next-themes": "^0.4.6",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"shiki": "^3.0.0"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.1.0",
"@types/node": "^22.0.0",
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"postcss": "^8.5.0",
"tailwindcss": "^4.1.0",
"typescript": "^5.7.0"
}
}
+5
View File
@@ -0,0 +1,5 @@
module.exports = {
plugins: {
'@tailwindcss/postcss': {},
},
};
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 366 B

+10
View File
@@ -0,0 +1,10 @@
<svg viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
<rect width="64" height="64" rx="8" fill="#000000"/>
<g transform="translate(8, 8) scale(0.716)">
<path d="M22.21 67V44.6369H0V67H22.21Z" fill="#ffffff"/>
<path d="M66.7038 22.3184H22.2534L0.0878906 44.6367H44.4634L66.7038 22.3184Z" fill="#ffffff"/>
<path d="M22.21 0H0V22.3184H22.21V0Z" fill="#ffffff"/>
<path d="M66.7198 0H44.5098V22.3184H66.7198V0Z" fill="#ffffff"/>
<path d="M66.7198 67V44.6369H44.5098V67H66.7198Z" fill="#ffffff"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 535 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

+9
View File
@@ -0,0 +1,9 @@
<svg viewBox="0 0 67 67" xmlns="http://www.w3.org/2000/svg">
<path d="M22.21 67V44.6369H0V67H22.21Z" fill="#ffffff"/>
<path d="M0 44.6369L22.21 46.8285V44.6369H0Z" fill="#DDDDDD"/>
<path d="M66.7038 22.3184H22.2534L0.0878906 44.6367H44.4634L66.7038 22.3184Z" fill="#ffffff"/>
<path d="M22.21 0H0V22.3184H22.21V0Z" fill="#ffffff"/>
<path d="M66.7198 0H44.5098V22.3184H66.7198V0Z" fill="#ffffff"/>
<path d="M66.6753 22.3185L44.5098 20.0822V22.3185H66.6753Z" fill="#DDDDDD"/>
<path d="M66.7198 67V44.6369H44.5098V67H66.7198Z" fill="#ffffff"/>
</svg>

After

Width:  |  Height:  |  Size: 576 B

+9
View File
@@ -0,0 +1,9 @@
<svg viewBox="0 0 67 67" xmlns="http://www.w3.org/2000/svg">
<path d="M22.21 67V44.6369H0V67H22.21Z" fill="#ffffff"/>
<path d="M0 44.6369L22.21 46.8285V44.6369H0Z" fill="#DDDDDD"/>
<path d="M66.7038 22.3184H22.2534L0.0878906 44.6367H44.4634L66.7038 22.3184Z" fill="#ffffff"/>
<path d="M22.21 0H0V22.3184H22.21V0Z" fill="#ffffff"/>
<path d="M66.7198 0H44.5098V22.3184H66.7198V0Z" fill="#ffffff"/>
<path d="M66.6753 22.3185L44.5098 20.0822V22.3185H66.6753Z" fill="#DDDDDD"/>
<path d="M66.7198 67V44.6369H44.5098V67H66.7198Z" fill="#ffffff"/>
</svg>

After

Width:  |  Height:  |  Size: 576 B

+18
View File
@@ -0,0 +1,18 @@
import { defineDocs, defineConfig } from 'fumadocs-mdx/config';
export const docs = defineDocs({
docs: {
async: true,
},
});
export default defineConfig({
mdxOptions: {
rehypeCodeOptions: {
themes: {
light: 'github-light',
dark: 'github-dark',
},
},
},
});
+41
View File
@@ -0,0 +1,41 @@
{
"compilerOptions": {
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react-jsx",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": [
"./*"
]
},
"target": "ES2017"
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
".next/dev/types/**/*.ts"
],
"exclude": [
"node_modules"
]
}