A production-grade, type-safe Python Agent framework
Project description
Nonoka
A production-grade, type-safe Python agent framework with deterministic orchestration, conversational execution, and first-class MCP integration.
Features
- Type-safe core — Pydantic-validated schemas throughout; agents, tools, and plans are all strongly typed
- Deterministic orchestration —
Plan+Step+ref()for explicit control flow, not just prompt-and-pray - Conversational execution —
ReActAgent,ReflectiveAgent, andPlanExecutorparadigms out of the box - First-class tools —
@tooldecorator with automatic Pydantic schema generation - Prompt engineering —
@promptdecorator andPromptTemplatefor composable, type-safe prompt construction - MCP ready — built-in MCP (Model Context Protocol) support via
mcp - Resilient execution — structured error taxonomy (
TransientError,LogicError,SafetyError, etc.) with configurableRetryPolicy - Observable hooks —
Hookssystem for tracing, logging, and custom middleware - Multi-backend LLM — powered by
litellm, supporting OpenAI, Anthropic, DeepSeek, and 100+ providers
Installation
pip install nonoka
Or with uv:
uv add nonoka
Quick Start
import asyncio
import nonoka
@nonoka.tool
async def get_weather(city: str) -> str:
"""Get the weather for a city."""
return f"Sunny in {city}!"
# Sync functions are also supported
@nonoka.tool
def get_time() -> str:
"""Get the current time."""
return "It's noon."
async def main():
agent = nonoka.Agent(
model="gpt-4o",
tools=[get_weather, get_time],
)
runner = nonoka.Runner() # execution coordinator
result = await runner.run_react(agent, "What's the weather in Tokyo?", deps=None)
print(result.data) # result.data (not result.output)
asyncio.run(main())
Key concept:
Agentis a pure configuration object. Execution is handled byRunner, which owns the LLM provider, checkpoint store, and memory backend.
Plans & Orchestration
Explicit multi-step workflows with type-safe references, executed deterministically via Runner.run_plan:
from nonoka import PlanBuilder, ref, Runner
plan = (
PlanBuilder(objective="Research workflow")
.step("research", search_tool, query="Latest AI breakthroughs")
.step("summarize", summarize_tool, content=ref("research"))
.build()
)
runner = Runner()
result = await runner.run_plan(agent, plan=plan, deps=None)
print(result.data)
Prompt Templates
Composable, type-safe prompts:
from nonoka import prompt, PromptTemplate
@prompt
def translate(text: str, target: str = "Chinese") -> str:
"""Translate the following text to {target}:
{text}
"""
# Or programmatically with Jinja2 syntax
tpl = PromptTemplate("Summarize this in {{style}}:\n{{content}}")
output = tpl.render(style="bullet points", content=long_text)
ReAct Agent
from nonoka import Agent, tool, Runner
@tool
async def search(query: str) -> dict:
...
@tool
async def calculator(expr: str) -> float:
...
agent = Agent(model="gpt-4o", tools=[search, calculator])
runner = Runner()
result = await runner.run_react(agent, "What is 42 * the current temperature in Paris?", deps=None)
print(result.data)
Tool Responses
Tools can return plain values or a ToolResponse to communicate pagination and metadata to the agent loop:
from nonoka import ToolResponse, tool
@tool
async def search_web(ctx, query: str, cursor: str | None = None) -> ToolResponse:
results, next_cursor = await _do_search(query, cursor)
return ToolResponse(
data={"results": results, "query": query},
has_more=next_cursor is not None,
next_cursor=next_cursor,
suggested_next_step="Summarise the findings and stop searching."
if len(results) >= 5 else "Refine query and search again.",
)
Gateway (IM Platform Integration)
Gateway standardizes requests from QQ, Telegram, Discord, etc. and routes them to Agents, then pushes Agent outputs back to the original platforms.
from nonoka.ext.gateway.core import Gateway
from nonoka.ext.gateway.limiter import TokenBucketLimiter
runner = Runner()
gateway = Gateway(runner, limiter=TokenBucketLimiter(default_rate=1, default_burst=3))
gateway.register_adapter(TelegramAdapter(token="..."))
gateway.set_default_agent(agent)
await gateway.start()
Configuration
Nonoka supports three ways to configure agents: declarative files (YAML/JSON/TOML), fluent builders, and direct code.
Declarative Config (YAML)
Write a nonoka.yaml and load it:
# nonoka.yaml
agents:
weather_assistant:
model: gpt-4o
system_prompt: "You are a weather assistant."
max_turns: 10
tools:
- import: my_tools.weather:get_weather
code_assistant:
model: deepseek-chat
system_prompt: "You are a coding assistant."
# Runner backend configuration (defaults are SQLite persistent)
# Use "memory" / "disabled" for testing
runner:
checkpoint: sqlite # or "memory", "disabled"
memory: sqlite # or "in_memory", "disabled"
defaults:
model: deepseek-chat
max_turns: 10
from nonoka import Config
config = Config.load("nonoka.yaml") # or Config.auto_find()
agent = config.agents["weather_assistant"].build()
runner = config.runner.build()
Single-agent shorthand (no agents: dict needed):
agent:
model: gpt-4o
system_prompt: "You are helpful."
agent = config.agent.build()
Environment Variables in Config
Use ${VAR} or ${VAR:-default} in YAML values:
agent:
model: ${NONOKA_MODEL:-gpt-4o}
system_prompt: ${NONOKA_PROMPT}
Fluent Builder API
from nonoka import AgentBuilder, tool
@tool
async def get_weather(city: str) -> str:
return f"Sunny in {city}!"
agent = (
AgentBuilder()
.model("gpt-4o")
.system_prompt("You are a weather assistant.")
.tool(get_weather)
.tool_by_import("my_tools.search:search_city")
.max_turns(20)
.retry(max_retries=5, backoff=1.5)
.metadata(category="weather")
.tag("production")
.build()
)
From Dict / YAML / JSON
from nonoka import Agent
# From dict
agent = Agent.from_dict({
"model": "gpt-4o",
"tools": ["my_tools:get_weather"],
})
# From file
agent = Agent.from_yaml("agent.yaml")
agent = Agent.from_json("agent.json")
Environment-driven Settings
Nonoka also integrates with pydantic-settings for framework-level config:
from nonoka.core.config import settings
print(settings.default_model) # from NONOKA_DEFAULT_MODEL env var
print(settings.openai_api_key) # from NONOKA_OPENAI_API_KEY env var
Requirements
- Python >= 3.10
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file nonoka-1.1.6.tar.gz.
File metadata
- Download URL: nonoka-1.1.6.tar.gz
- Upload date:
- Size: 107.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
501fb64a7ab46e8b32f7d3a64714fb937991438fb7abc2f8fc2a518fa875765f
|
|
| MD5 |
b41150547b73f4303c9a86deac6f8d02
|
|
| BLAKE2b-256 |
a0aff37714a00c2c596f24bf121b2c96a040515d41aee1c3c4ecf4719cbaff2c
|
File details
Details for the file nonoka-1.1.6-py3-none-any.whl.
File metadata
- Download URL: nonoka-1.1.6-py3-none-any.whl
- Upload date:
- Size: 84.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
089aecd990dc7bf4b8b5004645de9782f4b0855ebbabfd11f1d29914ea90c3ff
|
|
| MD5 |
aaf399b4b98d59219e5ca3420b2fc5ef
|
|
| BLAKE2b-256 |
c48ec4e481d341266094893ec40e88c26325ac5c7259648d8522078eb7dd47b1
|