nanobot/docs/python-sdk.md
chengyongru 4a58b83acc
docs: make onboarding friendlier for beginners (#4177)
* docs: make onboarding friendlier for beginners

* docs: build clearer documentation paths

Maintainer edit: turn the onboarding follow-up into a layered docs structure for first-time setup, provider selection, troubleshooting, CLI reference, and source-level architecture. This keeps quick start focused while giving advanced users precise reference paths.

* docs: render architecture flow with mermaid

Maintainer edit: replace the ASCII architecture sketch with a GitHub-rendered Mermaid flowchart so the core runtime path is easier to scan in the PR and README docs.

* docs: recommend model presets for model config

Maintainer edit: make named modelPresets the primary model configuration path and expand fallback preset examples so string fallbacks are clearly preset names, not raw model IDs.

* docs: document api base urls and langfuse setup

Maintainer edit: explain when users need apiBase/base URL in quick start and provider docs, and add Langfuse tracing setup with troubleshooting links.

* docs: use python module pip consistently

Maintainer edit: keep install commands tied to the active Python interpreter by using python -m pip in the Azure optional dependency notes too.

* docs: add non-technical getting started path

Maintainer edit: add a wizard-first guide for users without terminal or JSON background, including a text TUI menu example and links from the main docs entrypoints.

* docs: avoid hard-wrapped prose in user docs

Maintainer edit: unwrap ordinary prose across user-facing documentation while preserving markdown structure, code blocks, tables, lists, and prompt/template files.

* docs: keep desktop list continuations nested

Maintainer edit: preserve list nesting after unwrapping prose in the desktop WebUI sync guide.

* docs: add one-command installer

Maintainer edit: add auditable macOS/Linux and Windows install scripts that install nanobot-ai and start the onboarding wizard, then document the commands in the main onboarding entrypoints.

* docs: add installer dry run mode

Maintainer edit: add --dry-run to the one-command installer scripts so users can preview Python detection, install source, pip command, and wizard behavior without changing their environment.

* docs: clean installer error output

Maintainer edit: make PowerShell installer failures print a concise Error: message instead of Write-Error call-site details.

* docs: add provider setup cookbook

Maintainer edit: add pasteable provider recipes for common hosted, local, fallback, runtime switching, and Langfuse setups, then link the cookbook from onboarding and troubleshooting entrypoints.

* docs: address review feedback

* docs: clarify reader paths

* docs: explain terminal basics for beginners

* docs: clarify wizard navigation

* docs: avoid duplicate onboarding steps

* docs: add setup status check

* docs: explain status output

* docs: remove provider recommendation wording

* docs: explain status diagnostics

* docs: reduce hard-wrapped guidance

* docs: migrate config examples to presets

* docs: clarify python command fallbacks

* docs: improve installer failure recovery

* docs: expand install troubleshooting

* docs: cover installer download failures

* docs: put stable install paths first

* docs: add bundled webui quick path

* docs: clarify provider-neutral setup

* docs: clarify gateway setup for chat surfaces

* docs: improve docs navigation paths

* docs: add configuration quick jump

* docs: clarify provider secret variables

* chore: request PR review acknowledgement

Empty commit: please read the PR review comments and reply on the PR to confirm that you have received them.

This commit intentionally changes no files; it exists only to notify the remote Codex run so it can end its active goal.

* docs: add README start here guide

* docs: avoid provider recommendation wording

* docs: guide next steps after first reply

* docs: explain merging JSON snippets

* docs: add CLI command chooser

* docs: add configuration task map

* docs: add deployment readiness guide

* docs: simplify WebUI entry paths

* docs: add provider recipe chooser

* docs: fix provider factual references

Update OpenRouter and LongCat model examples, align Bedrock guidance, and make fallback snippets schema-valid.

Also correct group policy wording and image-generation provider lists to match the current code.

* fix: keep PowerShell installer from closing caller shell

* docs: mention self-guided configuration
2026-06-10 00:36:22 +08:00

6.4 KiB

Python SDK

Use nanobot as a library — no CLI, no gateway, just Python.

Before debugging SDK code, prove the same config works from the CLI:

nanobot agent -m "Hello!"

Nanobot.from_config() reuses your normal ~/.nanobot/config.json, so provider, model, tools, and workspace behavior match the CLI unless you override them.

Quick Start

import asyncio

from nanobot import Nanobot


async def main() -> None:
    async with Nanobot.from_config() as bot:
        result = await bot.run("What time is it in Tokyo?")
    print(result.content)


asyncio.run(main())

Use async with when possible so MCP connections and background cleanup work are closed before the event loop exits. If you manage the instance manually, call await bot.aclose() in a finally block.

Common Patterns

Use a specific config or workspace

from nanobot import Nanobot

bot = Nanobot.from_config(
    config_path="~/.nanobot/config.json",
    workspace="/my/project",
)

Isolate conversations with session_key

Different session keys keep independent conversation history:

await bot.run("hi", session_key="user-alice")
await bot.run("hi", session_key="task-42")

Attach hooks for observability

Hooks let you inspect tool calls, streaming, and iteration state without modifying nanobot internals:

from nanobot.agent import AgentHook, AgentHookContext


class AuditHook(AgentHook):
    async def before_execute_tools(self, context: AgentHookContext) -> None:
        for tc in context.tool_calls:
            print(f"[tool] {tc.name}")


result = await bot.run("Review this change", hooks=[AuditHook()])

API Reference

Nanobot.from_config(config_path=None, *, workspace=None)

Create a Nanobot instance from a config file.

Param Type Default Description
config_path str | Path | None None Path to config.json. Defaults to ~/.nanobot/config.json.
workspace str | Path | None None Override the workspace directory from config.

Raises FileNotFoundError if an explicit config path does not exist.

await bot.run(message, *, session_key="sdk:default", hooks=None)

Run the agent once and return a RunResult.

Param Type Default Description
message str (required) The user message to process.
session_key str "sdk:default" Session identifier for conversation isolation. Different keys get independent history.
hooks list[AgentHook] | None None Lifecycle hooks for this run only.

await bot.aclose()

Release resources held by the SDK instance, including MCP connections. The async context manager calls this automatically:

async with Nanobot.from_config() as bot:
    result = await bot.run("Summarize this repo")

RunResult

Field Type Description
content str The agent's final text response.
tools_used list[str] Reserved for richer SDK introspection; may be empty in current versions.
messages list[dict] Reserved for richer SDK introspection; may be empty in current versions.

Hooks

Hooks let you observe or customize the agent loop. Subclass AgentHook and override the methods you need.

Hook lifecycle

Method When
wants_streaming() Return True if you want token-by-token on_stream() callbacks
before_iteration(context) Before each LLM call
on_stream(context, delta) On each streamed token when streaming is enabled
on_stream_end(context, *, resuming) When streaming finishes
before_execute_tools(context) Before tool execution
after_iteration(context) After each iteration
finalize_content(context, content) Transform final output text

Useful fields on AgentHookContext include:

  • iteration
  • messages
  • response
  • usage
  • tool_calls
  • tool_results
  • tool_events
  • final_content
  • stop_reason
  • error

Example: audit tool calls

from nanobot.agent import AgentHook, AgentHookContext


class AuditHook(AgentHook):
    def __init__(self) -> None:
        super().__init__()
        self.calls: list[str] = []

    async def before_execute_tools(self, context: AgentHookContext) -> None:
        for tc in context.tool_calls:
            self.calls.append(tc.name)
            print(f"[audit] {tc.name}({tc.arguments})")
hook = AuditHook()
result = await bot.run("List files in /tmp", hooks=[hook])
print(result.content)
print(f"Tools observed: {hook.calls}")

Example: receive streaming tokens

from nanobot.agent import AgentHook, AgentHookContext


class StreamingHook(AgentHook):
    def wants_streaming(self) -> bool:
        return True

    async def on_stream(self, context: AgentHookContext, delta: str) -> None:
        print(delta, end="", flush=True)

    async def on_stream_end(self, context: AgentHookContext, *, resuming: bool) -> None:
        print()

Compose multiple hooks

Pass multiple hooks when you want to combine behaviors:

result = await bot.run("hi", hooks=[AuditHook(), MetricsHook()])

Async hook methods are fan-out with error isolation. finalize_content is a pipeline: each hook receives the previous hook's output.

Example: post-process final content

from nanobot.agent import AgentHook


class Censor(AgentHook):
    def finalize_content(self, context, content):
        return content.replace("secret", "***") if content else content

Full Example

import asyncio
import time

from nanobot import Nanobot
from nanobot.agent import AgentHook, AgentHookContext


class TimingHook(AgentHook):
    def __init__(self) -> None:
        super().__init__()
        self._started_at = 0.0

    async def before_iteration(self, context: AgentHookContext) -> None:
        self._started_at = time.perf_counter()

    async def after_iteration(self, context: AgentHookContext) -> None:
        elapsed_ms = (time.perf_counter() - self._started_at) * 1000
        print(f"[timing] iteration {context.iteration} took {elapsed_ms:.1f}ms")


async def main() -> None:
    bot = Nanobot.from_config(workspace="/my/project")
    result = await bot.run(
        "Explain the main function",
        session_key="sdk:demo",
        hooks=[TimingHook()],
    )
    print(result.content)


asyncio.run(main())