<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: rebornace</title>
    <description>The latest articles on DEV Community by rebornace (@rebornace).</description>
    <link>https://dev.to/rebornace</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4127161%2F90390741-aa85-460d-a5fd-8e6e7f58bcb0.jpg</url>
      <title>DEV Community: rebornace</title>
      <link>https://dev.to/rebornace</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rebornace"/>
    <language>en</language>
    <item>
      <title>Building a Lightweight AI Agent in Go: Baize's Architecture and Trade-offs</title>
      <dc:creator>rebornace</dc:creator>
      <pubDate>Thu, 17 Sep 2026 02:06:02 +0000</pubDate>
      <link>https://dev.to/rebornace/building-a-lightweight-ai-agent-in-go-baizes-architecture-and-trade-offs-4c0k</link>
      <guid>https://dev.to/rebornace/building-a-lightweight-ai-agent-in-go-baizes-architecture-and-trade-offs-4c0k</guid>
      <description>

&lt;h2&gt;
  
  
  Preface
&lt;/h2&gt;

&lt;p&gt;Baize is a "sidecar" AI assistant runtime: a single process that sits beside services you already run, turns API documentation into tools the assistant can call, and pauses important writes until a person approves them. Stop it and it leaves almost nothing behind.&lt;/p&gt;

&lt;p&gt;One sentence positioning: &lt;strong&gt;it's a runtime, not a framework.&lt;/strong&gt; That positioning drives every architecture decision below.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Why Go
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Single binary, zero dependencies
&lt;/h3&gt;

&lt;p&gt;The core requirement of sidecar deployment is "one process, copy it over, run it." A Go binary needs no interpreter, no dependency installation, no virtualenv on the target machine. For an assistant meant to live in enterprise environments, that's the lowest-cost delivery form.&lt;/p&gt;

&lt;h3&gt;
  
  
  Goroutine concurrency
&lt;/h3&gt;

&lt;p&gt;One assistant process serves multiple entry points at once: the web console, signed alert/ticket ingress, and IM channels. Goroutines make "one process handling many sessions concurrently" straightforward — and parallel tool calls later become almost free.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cross-compilation
&lt;/h3&gt;

&lt;p&gt;Enterprise environments run everything: Windows, Linux, macOS, ARM. A single &lt;code&gt;GOOS=linux GOARCH=arm64 go build&lt;/code&gt; produces a binary for the target platform, with no toolchain setup on the destination machine.&lt;/p&gt;

&lt;h3&gt;
  
  
  Static typing and tool contracts
&lt;/h3&gt;

&lt;p&gt;Tool input schemas come from OpenAPI documents; mapping them onto Go's strong types catches many errors at compile time. For a long-running daemon, that saves a lot of operational pain.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Architecture Overview
&lt;/h2&gt;

&lt;p&gt;A clean three-layer structure: &lt;strong&gt;core loop → tool router → executors&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Users / Channels ──► Agent Core Loop (think → pick tool → run → report)
                           │
                           ▼
                     Tool Router (Registry)
                           │
          ┌────────────────┼────────────────┐
    OpenAPI connector    HTTP plugin      MCP connector
          └────────────────┼────────────────┘
                           ▼
              Invoker closures (registered in Registry)
                           │
                     [ HITL approval gate ]
                           │
             ┌─────────────┴─────────────┐
       Direct execution          HTTP callback executor
     (plugin / proxy)            (callback to your side)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Core loop&lt;/strong&gt; (&lt;code&gt;internal/run&lt;/code&gt;): LLM thinks → picks a tool → executes → reports. The event stream (&lt;code&gt;llm.thinking&lt;/code&gt;, &lt;code&gt;llm.tool_call&lt;/code&gt;, &lt;code&gt;tool.result&lt;/code&gt;) is persisted, so the console can follow along in real time.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tool router&lt;/strong&gt; (&lt;code&gt;internal/tool&lt;/code&gt;): a mutex-protected map that registers not functions but "tool contracts + invoker closures".&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Executors&lt;/strong&gt; (&lt;code&gt;internal/connector&lt;/code&gt;): tools enter through three sources — OpenAPI docs, HTTP plugins, MCP tool servers — plus a "callback execution" mode that hands execution back to your side.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  3. Key Design Decisions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why HTTP callback instead of a plugin protocol
&lt;/h3&gt;

&lt;p&gt;This is Baize's most important trade-off.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The problem with plugin protocols&lt;/strong&gt;: in-process plugins (Go plugins, shared libraries, language-binding SDKs) require the plugin to compile with the host process — language, version, and ABI must all align. Most enterprise systems aren't written in Go: legacy systems, Java/.NET/Python services can't load an in-process plugin at all. And even when they could, upgrading a plugin means restarting the process — "sidecar in, clean out" is gone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What Baize does instead&lt;/strong&gt;: it doesn't execute the tool itself; it POSTs the invocation to your own endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tool"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"create_ticket"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"arguments"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"run_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"run_xxx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"agent_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"agent_xxx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"idempotency_key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"uuid-xxx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"callback_urls"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"event"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://your-service/baize-events"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your side executes it and returns the result. Benefits: language-agnostic, process-isolated, auditable; &lt;code&gt;idempotency_key&lt;/code&gt; makes retries safe (no duplicate execution); &lt;code&gt;callback_urls&lt;/code&gt; lets your side keep driving follow-up actions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The cost&lt;/strong&gt;: one extra network round-trip, the callback endpoint must be reachable, and to prevent forged callbacks you need signed requests (Baize uses callback signing with a TTL against replay).&lt;/p&gt;

&lt;h3&gt;
  
  
  Dynamic tool registration and discovery
&lt;/h3&gt;

&lt;p&gt;The registry (&lt;code&gt;tool.Registry&lt;/code&gt;) is the core data structure: a &lt;code&gt;sync.RWMutex&lt;/code&gt; guarding a map, with runtime register/unregister and per-connector bulk unregister — adding a tool or disabling a connector never requires a restart.&lt;/p&gt;

&lt;p&gt;Three tool sources share one registration path:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;OpenAPI docs&lt;/strong&gt;: import Swagger/OpenAPI/Postman-style docs, each operation becomes a tool;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;HTTP plugins&lt;/strong&gt;: a small companion service declares "what tools exist and how to invoke them";&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;MCP tool servers&lt;/strong&gt;: connect to external tool ecosystems as an MCP client.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Security policy is baked into each entry at registration time: &lt;code&gt;require_approval&lt;/code&gt; (needs a human), &lt;code&gt;require_login&lt;/code&gt; (needs a session), &lt;code&gt;security_schemes&lt;/code&gt; (which auth scheme to use). &lt;strong&gt;Security policy is decided at registration, not asked at execution time&lt;/strong&gt; — this is the precondition for letting the assistant actually act.&lt;/p&gt;

&lt;p&gt;Discovery is trivial: &lt;code&gt;Registry.List()&lt;/code&gt; / &lt;code&gt;Registry.Specs()&lt;/code&gt; feed the model's tool list, visible live in the console.&lt;/p&gt;

&lt;h3&gt;
  
  
  Graceful degradation on failure
&lt;/h3&gt;

&lt;p&gt;Failures are the norm in AI agents, so degradation design matters more than the happy path:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Timeout guardrail&lt;/strong&gt;: every tool invocation is bound to &lt;code&gt;context.WithTimeout&lt;/code&gt; (default 60s, configurable);&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Failure is content&lt;/strong&gt;: &lt;code&gt;Invoker&lt;/code&gt; returns &lt;code&gt;(content, isError, err)&lt;/code&gt; — &lt;code&gt;err&lt;/code&gt; is an infrastructure failure (timeout, network), &lt;code&gt;isError&lt;/code&gt; is a business-side failure. Both flow back to the model as structured content, so the model can retry, switch tools, or explain to the user — instead of crashing the whole session;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Approval rejection is not a crash&lt;/strong&gt;: when a human rejects a write, the run settles into an explicit "rejected" terminal state with a trail, no panic;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Everything is observable&lt;/strong&gt;: the &lt;code&gt;llm.tool_call → tool.result&lt;/code&gt; event stream is persisted, so any problem can be traced step by step;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Context compaction&lt;/strong&gt;: long sessions get rolling summaries so quality doesn't degrade as the thread grows.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  4. Go vs Python / Node.js for Agent Scenarios
&lt;/h2&gt;

&lt;p&gt;Let's be honest first: &lt;strong&gt;Python is the best choice in the AI/Agent ecosystem&lt;/strong&gt;. LangChain, LlamaIndex and most reference implementations live there. If your goal is fast experimentation and deep reuse of the LLM ecosystem, Python has no rival.&lt;/p&gt;

&lt;p&gt;Baize chose Go because its positioning is different:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Go&lt;/th&gt;
&lt;th&gt;Python&lt;/th&gt;
&lt;th&gt;Node.js&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Deployment&lt;/td&gt;
&lt;td&gt;Single binary, zero deps&lt;/td&gt;
&lt;td&gt;Interpreter + deps/venv&lt;/td&gt;
&lt;td&gt;Node runtime + node_modules&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Resource footprint&lt;/td&gt;
&lt;td&gt;Low; one resident process is cheap&lt;/td&gt;
&lt;td&gt;Higher; resident processes need care&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Concurrency&lt;/td&gt;
&lt;td&gt;Native goroutines&lt;/td&gt;
&lt;td&gt;GIL-limited; multi-process/async&lt;/td&gt;
&lt;td&gt;Event loop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Type safety&lt;/td&gt;
&lt;td&gt;Static, compile-time checks&lt;/td&gt;
&lt;td&gt;Dynamic, found at runtime&lt;/td&gt;
&lt;td&gt;Dynamic / TypeScript&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLM ecosystem&lt;/td&gt;
&lt;td&gt;Newer, catching up fast&lt;/td&gt;
&lt;td&gt;Richest&lt;/td&gt;
&lt;td&gt;Rich&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cross-platform&lt;/td&gt;
&lt;td&gt;Cross-compile to all platforms&lt;/td&gt;
&lt;td&gt;Needs interpreter on target&lt;/td&gt;
&lt;td&gt;Needs Node on target&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The conclusion isn't "Go is better than Python" — it's &lt;strong&gt;positioning decides the language&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Goal: framework / fast experimentation → Python;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Goal: sidecar, resident, one-command deployment to enterprise environments, running on modest hardware for a long time → Go's advantages in deployment and resource usage are hard to replace.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  5. Core Code Snippets (Go)
&lt;/h2&gt;

&lt;p&gt;All snippets are from the project source, lightly trimmed. Each comes with one line on what problem it solves.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. A tool = contract + invoker closure
&lt;/h3&gt;

&lt;p&gt;Modeling a "tool" as "a contract the model sees (Spec) + an invoker closure injected by the connector" fully decouples routing from execution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Invoker&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;isError&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Meta&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Spec&lt;/span&gt;            &lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ToolSpec&lt;/span&gt;
    &lt;span class="n"&gt;ConnectorID&lt;/span&gt;     &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;Method&lt;/span&gt;          &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;Path&lt;/span&gt;            &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;RequireLogin&lt;/span&gt;    &lt;span class="kt"&gt;bool&lt;/span&gt;
    &lt;span class="n"&gt;SecuritySchemes&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Dynamic registration with policy baked in
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;require_approval&lt;/code&gt; / &lt;code&gt;require_login&lt;/code&gt; are written into the entry at registration; the tool list is "hot" — adding/removing connectors never requires a restart:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Registry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;RegisterMeta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;meta&lt;/span&gt; &lt;span class="n"&gt;Meta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;inv&lt;/span&gt; &lt;span class="n"&gt;Invoker&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;requireApproval&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Spec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;            &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Spec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;invoker&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;         &lt;span class="n"&gt;inv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;requireApproval&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;requireApproval&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;requireLogin&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RequireLogin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;connectorID&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConnectorID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;          &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;            &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. HTTP callback executor
&lt;/h3&gt;

&lt;p&gt;Hands execution back to your side; the idempotency key makes network retries safe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s"&gt;"tool"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;            &lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"arguments"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;       &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"run_id"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;          &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RunID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"agent_id"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;        &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AgentID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"idempotency_key"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IdempotencyKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TrimSpace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CallbackEventURL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"callback_urls"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"event"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CallbackEventURL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;rawPayload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Marshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewRequestWithContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MethodPost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rawPayload&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Writes automatically enter the approval gate
&lt;/h3&gt;

&lt;p&gt;Non-GET/HEAD/OPTIONS operations are automatically flagged "needs approval" at registration, and only run after a human clicks approve/reject in the console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;needApproval&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RequireApproval&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;requireApprovalMutating&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;isMutatingMethod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Method&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Source&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ToolSourceSpec&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;needApproval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Timeouts and failure degradation
&lt;/h3&gt;

&lt;p&gt;A timeout guardrail plus "failure is content" semantics keeps one bad tool call from blowing up the whole session:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;toolCtx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;toolCancel&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;toolTimeout&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;toolCancel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;isError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;invErr&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tools&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;toolCtx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ToolName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Arguments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;invErr&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Infrastructure failure (timeout/network): persist and close the round&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;finalizeFailedRun&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;runID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;invErr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c"&gt;// When isError is true, the failure flows back to the model as content;&lt;/span&gt;
&lt;span class="c"&gt;// the model decides whether to retry or explain.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;Baize is still early. The trade-offs above are far from "optimal" — especially the approval UX, channel adapters, and executor extensibility. If you have real-world scenarios, I'd love to hear them.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Repo: &lt;a href="https://github.com/rebornace/baize" rel="noopener noreferrer"&gt;https://github.com/rebornace/baize&lt;/a&gt; (MIT)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Issues: open a discussion with your scenario — I'll follow up.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




</description>
      <category>ai</category>
      <category>agents</category>
      <category>go</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Add AI to Your Legacy System in 5 Minutes: A Baize Hands-On Tutorial (Spring Boot Example)</title>
      <dc:creator>rebornace</dc:creator>
      <pubDate>Wed, 16 Sep 2026 04:34:42 +0000</pubDate>
      <link>https://dev.to/rebornace/add-ai-to-your-legacy-system-in-5-minutes-a-baize-hands-on-tutorial-spring-boot-example-4i0p</link>
      <guid>https://dev.to/rebornace/add-ai-to-your-legacy-system-in-5-minutes-a-baize-hands-on-tutorial-spring-boot-example-4i0p</guid>
      <description>&lt;p&gt;Got a backend system that's been running for years — piles of APIs, complex business logic, and the thought of adding AI feels like a massive undertaking? This tutorial shows you how to hook up an AI assistant to your existing system in 5 minutes using &lt;strong&gt;Baize&lt;/strong&gt; — no refactoring, no core code changes, at most one new endpoint.&lt;/p&gt;

&lt;p&gt;Baize is an open-source AI Agent runtime written in Go. Its core ideas: &lt;strong&gt;sidecar deployment, APIs-as-tools, and human-in-the-loop approval for critical operations&lt;/strong&gt;. It connects to business systems over HTTP, so it doesn't matter what your backend is — Spring Boot, Django, Express, Gin, ThinkPHP — if it has APIs, it can connect. The AI understands user intent, calls the right tools automatically, and pauses for human confirmation on sensitive operations.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Language-agnostic, but we'll use Spring Boot as the main example.&lt;/strong&gt; The principles are fully universal — for other frameworks, just swap the Controller code in Step 3 for your language of choice. The Baize config file stays exactly the same.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;To follow along, you'll need two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A backend service&lt;/strong&gt; (we'll use a Spring Boot ticket system as an example, but feel free to use your own project)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Baize binary&lt;/strong&gt; (a single executable, zero dependencies)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can clone the repo which includes a demo ticket system and Baize configs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/rebornace/baize.git
&lt;span class="nb"&gt;cd &lt;/span&gt;baize
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;The Spring Boot code examples in this tutorial use Java 17 + Spring Boot 3.x. Readers using other languages or frameworks can focus on the configuration and protocol sections, and map the code examples to their own stack.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Step 1: Download Baize and Start It with One Command
&lt;/h2&gt;

&lt;p&gt;Baize is a single static binary written in Go — no runtime to install.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 1: Download the Binary (Recommended)
&lt;/h3&gt;

&lt;p&gt;Grab the latest release for your platform from &lt;a href="https://github.com/rebornace/baize/releases" rel="noopener noreferrer"&gt;GitHub Releases&lt;/a&gt;, unzip it, and you're ready to go.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 2: Build from Source
&lt;/h3&gt;

&lt;p&gt;If you have Go 1.25+ locally, you can build it yourself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/rebornace/baize.git
&lt;span class="nb"&gt;cd &lt;/span&gt;baize
go build &lt;span class="nt"&gt;-o&lt;/span&gt; baize ./cmd/baize
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Start in Demo Mode
&lt;/h3&gt;

&lt;p&gt;Baize comes with a &lt;strong&gt;demo mode&lt;/strong&gt; that uses a Mock LLM (no API key needed) and a built-in demo ticket system — perfect for getting the full flow working first.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Windows&lt;/span&gt;
.&lt;span class="se"&gt;\b&lt;/span&gt;aize demo

&lt;span class="c"&gt;# macOS / Linux&lt;/span&gt;
./baize demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you see output like this, it's running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;baize demo listening on :8080
mock ticket server listening on :18080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open your browser and go to &lt;strong&gt;&lt;a href="http://localhost:8080/ui" rel="noopener noreferrer"&gt;http://localhost:8080/ui&lt;/a&gt;&lt;/strong&gt; — you'll see Baize's web dashboard.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For production, use &lt;code&gt;baize start&lt;/code&gt; with a config file and a real LLM API key. We'll cover that later.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  No Config File? No Problem — Configure via the Web UI
&lt;/h3&gt;

&lt;p&gt;Baize supports &lt;strong&gt;zero-config startup&lt;/strong&gt; — just run &lt;code&gt;baize start&lt;/code&gt; without any config file, then open the Web UI and set everything up interactively: models, connectors, tools, approval rules — all of it. Great if you prefer a graphical interface over editing YAML by hand.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Windows PowerShell&lt;/span&gt;
&lt;span class="nv"&gt;$env&lt;/span&gt;:BAIZE_API_KEY &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"your-api-key"&lt;/span&gt;
.&lt;span class="se"&gt;\b&lt;/span&gt;aize start

&lt;span class="c"&gt;# macOS / Linux&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;BAIZE_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-api-key"&lt;/span&gt;
./baize start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once it's running, go to &lt;a href="http://localhost:8080/ui" rel="noopener noreferrer"&gt;http://localhost:8080/ui&lt;/a&gt; → &lt;strong&gt;Settings&lt;/strong&gt; in the sidebar. You can configure your LLM provider, add connectors, and manage tools directly from the page. Changes take effect immediately — no restart needed.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This tutorial uses YAML config files for clarity in the step-by-step flow. Both approaches produce the same result — pick whichever you prefer.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Step 2: Declare Tools in a Config File
&lt;/h2&gt;

&lt;p&gt;The core of how Baize connects to business systems is the &lt;strong&gt;Connector&lt;/strong&gt;. The most common approach is the &lt;strong&gt;OpenAPI Connector&lt;/strong&gt; — if you have an OpenAPI/Swagger spec, Baize automatically turns every endpoint into a tool the AI can use.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.1 Prepare the OpenAPI Spec
&lt;/h3&gt;

&lt;p&gt;Let's say your Spring Boot ticket system has these endpoints:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;operationId&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;/tickets&lt;/td&gt;
&lt;td&gt;List all tickets&lt;/td&gt;
&lt;td&gt;&lt;code&gt;list_tickets&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;/tickets&lt;/td&gt;
&lt;td&gt;Create a ticket&lt;/td&gt;
&lt;td&gt;&lt;code&gt;create_ticket&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;/tickets/{id}&lt;/td&gt;
&lt;td&gt;Get ticket details&lt;/td&gt;
&lt;td&gt;&lt;code&gt;get_ticket&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PATCH&lt;/td&gt;
&lt;td&gt;/tickets/{id}&lt;/td&gt;
&lt;td&gt;Update ticket status&lt;/td&gt;
&lt;td&gt;&lt;code&gt;update_ticket_status&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Here's the corresponding OpenAPI spec (&lt;code&gt;ticket-api.yaml&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;openapi&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;3.0.3&lt;/span&gt;
&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Ticket System API&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1.0.0&lt;/span&gt;
&lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;/tickets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;operationId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;list_tickets&lt;/span&gt;
      &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;List all tickets&lt;/span&gt;
      &lt;span class="na"&gt;responses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;200"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;List of tickets&lt;/span&gt;
    &lt;span class="na"&gt;post&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;operationId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;create_ticket&lt;/span&gt;
      &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Create a ticket&lt;/span&gt;
      &lt;span class="na"&gt;requestBody&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;application/json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;object&lt;/span&gt;
              &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
              &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;string&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;Ticket title&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
                &lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;string&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;Priority level&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;responses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;201"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Created successfully&lt;/span&gt;
  &lt;span class="s"&gt;/tickets/{id}&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;operationId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;get_ticket&lt;/span&gt;
      &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Get ticket details&lt;/span&gt;
      &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;id&lt;/span&gt;
          &lt;span class="na"&gt;in&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;path&lt;/span&gt;
          &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
          &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;string&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;responses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;200"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Ticket details&lt;/span&gt;
    &lt;span class="na"&gt;patch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;operationId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;update_ticket_status&lt;/span&gt;
      &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Update ticket status&lt;/span&gt;
      &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;id&lt;/span&gt;
          &lt;span class="na"&gt;in&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;path&lt;/span&gt;
          &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
          &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;string&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;requestBody&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;application/json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;object&lt;/span&gt;
              &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
              &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;string&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;New status&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;responses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;200"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Updated successfully&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;If your Spring Boot project already has springdoc-openapi or Swagger integrated, you can export the JSON directly from &lt;code&gt;/v3/api-docs&lt;/code&gt; — Baize understands both YAML and JSON.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  2.2 Write the Baize Config File
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;baize-config.yaml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;listen&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;:8080"&lt;/span&gt;
&lt;span class="na"&gt;store&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sqlite&lt;/span&gt;
  &lt;span class="na"&gt;sqlite_path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./data/baize.db&lt;/span&gt;
&lt;span class="na"&gt;ui&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="na"&gt;llm&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;openai_compatible&lt;/span&gt;
  &lt;span class="na"&gt;base_url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://api.deepseek.com&lt;/span&gt;
  &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deepseek-chat&lt;/span&gt;
  &lt;span class="na"&gt;api_key_env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;BAIZE_API_KEY&lt;/span&gt;

&lt;span class="na"&gt;agent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ticket-agent&lt;/span&gt;
  &lt;span class="na"&gt;system&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;You&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;are&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ticket&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;assistant.&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;You&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;can&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;only&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;access&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ticket&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;system&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;through&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;tools."&lt;/span&gt;

&lt;span class="na"&gt;connector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ticket-api&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;openapi&lt;/span&gt;
  &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./ticket-api.yaml&lt;/span&gt;
  &lt;span class="na"&gt;base_url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://localhost:8081&lt;/span&gt;   &lt;span class="c1"&gt;# Your Spring Boot service URL&lt;/span&gt;
  &lt;span class="na"&gt;require_approval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;create_ticket&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;update_ticket_status&lt;/span&gt;

&lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;max_steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;16&lt;/span&gt;
  &lt;span class="na"&gt;tool_timeout_sec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;60&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key configuration points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;connector.type: openapi&lt;/code&gt;&lt;/strong&gt; — Auto-discovers tools from the OpenAPI spec&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;connector.spec&lt;/code&gt;&lt;/strong&gt; — Path to your OpenAPI spec file&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;connector.base_url&lt;/code&gt;&lt;/strong&gt; — Your backend service URL; the AI calls this address when using tools&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;require_approval&lt;/code&gt;&lt;/strong&gt; — Tools listed here require human confirmation before execution (recommended for all write operations)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2.3 Start with the Config File
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Set your LLM API Key (DeepSeek example)&lt;/span&gt;
&lt;span class="c"&gt;# Windows PowerShell&lt;/span&gt;
&lt;span class="nv"&gt;$env&lt;/span&gt;:BAIZE_API_KEY &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"your-api-key"&lt;/span&gt;

&lt;span class="c"&gt;# macOS / Linux&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;BAIZE_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-api-key"&lt;/span&gt;

&lt;span class="c"&gt;# Start&lt;/span&gt;
./baize start &lt;span class="nt"&gt;-c&lt;/span&gt; baize-config.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;a href="http://localhost:8080/ui" rel="noopener noreferrer"&gt;http://localhost:8080/ui&lt;/a&gt; → click "Tools" in the sidebar, and you'll see all 4 ticket endpoints listed as AI-usable tools.&lt;/p&gt;

&lt;p&gt;At this step, &lt;strong&gt;you haven't changed a single line of your business code&lt;/strong&gt; — Baize is just another caller hitting your APIs, no different from a frontend app.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Add a Callback Endpoint for Full Control (Spring Boot Example)
&lt;/h2&gt;

&lt;p&gt;The OpenAPI approach above works great when "the APIs already exist and the AI can call them directly." But sometimes you need more control:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You want to add custom logic before/after AI calls (audit logging, rate limiting, parameter validation)&lt;/li&gt;
&lt;li&gt;The tool logic is complex and not suitable for exposing as a direct API&lt;/li&gt;
&lt;li&gt;You don't want the AI touching internal services directly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where &lt;strong&gt;Execution Callback&lt;/strong&gt; mode comes in: instead of calling your business APIs directly, Baize sends a message saying "which tool to call and with what arguments" to an endpoint you specify. You decide how to execute it. The protocol is dead simple — any language that can serve HTTP can implement it.&lt;/p&gt;

&lt;p&gt;Below is a Spring Boot example; the same pattern applies to Django, Express, Gin, and any other framework.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.1 Add an &lt;code&gt;/execute&lt;/code&gt; Endpoint (Spring Boot Version)
&lt;/h3&gt;

&lt;p&gt;Add one Controller with one method to your Spring Boot project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;com.example.ticket.controller&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.example.ticket.service.TicketService&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.web.bind.annotation.*&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.Map&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@RestController&lt;/span&gt;
&lt;span class="nd"&gt;@RequestMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/baize"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BaizeCallbackController&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;TicketService&lt;/span&gt; &lt;span class="n"&gt;ticketService&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;BaizeCallbackController&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TicketService&lt;/span&gt; &lt;span class="n"&gt;ticketService&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ticketService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ticketService&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@PostMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/execute"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="nd"&gt;@RequestHeader&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"X-Baize-Protocol"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;protocol&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="nd"&gt;@RequestBody&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="c1"&gt;// Protocol check&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(!&lt;/span&gt;&lt;span class="s"&gt;"v0"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;equals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;protocol&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;"content"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"error"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"unsupported protocol"&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
                &lt;span class="s"&gt;"is_error"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
            &lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;

        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"tool"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nd"&gt;@SuppressWarnings&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"unchecked"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;)&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"arguments"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Dispatch to your business logic based on tool name&lt;/span&gt;
        &lt;span class="nc"&gt;Object&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"list_tickets"&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ticketService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;listTickets&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"create_ticket"&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ticketService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createTicket&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"title"&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
                &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"priority"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"get_ticket"&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ticketService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getTicket&lt;/span&gt;&lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"update_ticket_status"&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ticketService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;updateStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
                &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"status"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="o"&gt;};&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;"content"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"error"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"unknown tool: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
                &lt;span class="s"&gt;"is_error"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
            &lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"content"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"is_error"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
        &lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it — one Controller, one method, one switch statement for dispatch. You're in full control of the execution logic. Add logging, add authorization checks, add caching — whatever you need.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.2 Update Baize Config to Point to the Callback URL
&lt;/h3&gt;

&lt;p&gt;Change the connector in &lt;code&gt;baize-config.yaml&lt;/code&gt; to callback mode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;connector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ticket-api&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;openapi&lt;/span&gt;
  &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./ticket-api.yaml&lt;/span&gt;
  &lt;span class="na"&gt;base_url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;                           &lt;span class="c1"&gt;# Can be empty in callback mode&lt;/span&gt;
  &lt;span class="na"&gt;execution_callback_url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://localhost:8081/baize/execute&lt;/span&gt;
  &lt;span class="na"&gt;require_approval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;create_ticket&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;update_ticket_status&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key addition is &lt;strong&gt;&lt;code&gt;execution_callback_url&lt;/code&gt;&lt;/strong&gt; — with this field set, Baize won't call &lt;code&gt;base_url&lt;/code&gt; directly. Instead, it sends tool invocation requests to this callback address.&lt;/p&gt;

&lt;p&gt;You still need the OpenAPI spec file because Baize uses it to discover what tools exist and what parameters they take — that's &lt;strong&gt;tool discovery&lt;/strong&gt;. The actual &lt;strong&gt;tool execution&lt;/strong&gt; is handled by your Spring Boot service.&lt;/p&gt;

&lt;p&gt;After restarting Baize, the user experience looks the same, but the execution path is completely different — every tool call now goes through your &lt;code&gt;/baize/execute&lt;/code&gt; endpoint.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Send a Message and Watch the AI Call Tools
&lt;/h2&gt;

&lt;p&gt;Once Baize is running, you can talk to the AI in two ways: via the Web UI or the HTTP API.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 1: Web UI (Recommended for Getting Started)
&lt;/h3&gt;

&lt;p&gt;Open &lt;strong&gt;&lt;a href="http://localhost:8080/ui" rel="noopener noreferrer"&gt;http://localhost:8080/ui&lt;/a&gt;&lt;/strong&gt; and type in the chat box:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Show me all the tickets
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll see the AI's reasoning process step by step, including the full &lt;code&gt;list_tickets&lt;/code&gt; tool call — both the request parameters and the returned result.&lt;/p&gt;

&lt;p&gt;Try this next:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Create a ticket with title "Login page loads slowly" and high priority
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because &lt;code&gt;create_ticket&lt;/code&gt; is in the &lt;code&gt;require_approval&lt;/code&gt; list, the AI will pause and wait for your confirmation. Click &lt;strong&gt;Approve&lt;/strong&gt; on the approval card that pops up, and only then will it actually execute the create operation. This is &lt;strong&gt;HITL (Human-in-the-Loop)&lt;/strong&gt; — sensitive operations are never executed blindly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 2: HTTP API (Great for Integration)
&lt;/h3&gt;

&lt;p&gt;Baize exposes a full REST control plane that you can call from scripts or other systems.&lt;/p&gt;

&lt;p&gt;Start a new conversation run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8080/v0/runs &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "agent_id": "ticket-agent",
    "messages": [
      {
        "role": "user",
        "content": "Show me all the tickets"
      }
    ]
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response includes a &lt;code&gt;run_id&lt;/code&gt;. Use it to check status and stream events:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Get run result&lt;/span&gt;
curl http://localhost:8080/v0/runs/&lt;span class="o"&gt;{&lt;/span&gt;run_id&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Stream events (SSE)&lt;/span&gt;
curl http://localhost:8080/v0/runs/&lt;span class="o"&gt;{&lt;/span&gt;run_id&lt;span class="o"&gt;}&lt;/span&gt;/stream
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Going Further
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Adding More Tools
&lt;/h3&gt;

&lt;p&gt;Tools come from the OpenAPI spec. Adding a tool = adding an endpoint to the spec, then restarting Baize (or hot-loading through the settings page).&lt;/p&gt;

&lt;p&gt;For example, if you want to add a "close ticket" tool, add this path to &lt;code&gt;ticket-api.yaml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="s"&gt;/tickets/{id}/close&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;post&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;operationId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;close_ticket&lt;/span&gt;
      &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Close a ticket&lt;/span&gt;
      &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;id&lt;/span&gt;
          &lt;span class="na"&gt;in&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;path&lt;/span&gt;
          &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
          &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;string&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;responses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;200"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Closed successfully&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add &lt;code&gt;close_ticket&lt;/code&gt; to &lt;code&gt;require_approval&lt;/code&gt;, and you're done.&lt;/p&gt;

&lt;h3&gt;
  
  
  Timeouts and Retries
&lt;/h3&gt;

&lt;p&gt;Baize has a default timeout for each tool call, which you can adjust in the config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;max_steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;16&lt;/span&gt;
  &lt;span class="na"&gt;tool_timeout_sec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;120&lt;/span&gt;    &lt;span class="c1"&gt;# Timeout per tool call, default is 60 seconds&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your business APIs are occasionally flaky, you can handle retries inside your Spring Boot callback endpoint — Spring Retry or Resilience4j both work great. Baize just "sends the request and waits for the result" — retry strategy is up to you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Handling Login / Auth
&lt;/h3&gt;

&lt;p&gt;If your system requires login before calling APIs, Baize has you covered. Configure &lt;code&gt;auth.capture&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;connector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ticket-api&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;openapi&lt;/span&gt;
  &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./ticket-api.yaml&lt;/span&gt;
  &lt;span class="na"&gt;execution_callback_url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://localhost:8081/baize/execute&lt;/span&gt;
  &lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;passthrough&lt;/span&gt;
    &lt;span class="na"&gt;capture&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;tool_name_glob&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*login*"&lt;/span&gt;
      &lt;span class="na"&gt;token_json_paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;accessToken"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data.accessToken"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;label_json_paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;header_template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;{{token}}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the AI detects that login is required, it calls the login tool first, automatically extracts the token from the response, and attaches it to subsequent calls. Login info is stored in the session identity and persists across conversations.&lt;/p&gt;




&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Q: Does Baize have to run in Kubernetes?
&lt;/h3&gt;

&lt;p&gt;No. Baize is a single binary — it runs on bare metal, VMs, Docker, and Kubernetes alike. Put it on the same internal network as your legacy system, or even deploy it on the public network and call back over VPN (security will be weaker, of course).&lt;/p&gt;

&lt;h3&gt;
  
  
  Q: What if my OpenAPI spec is outdated or doesn't cover all endpoints?
&lt;/h3&gt;

&lt;p&gt;Two options:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use &lt;strong&gt;Execution Callback&lt;/strong&gt; mode — the OpenAPI spec just declares "what tools exist," and you write the actual execution logic yourself&lt;/li&gt;
&lt;li&gt;Use the &lt;strong&gt;HTTP Plugin v0&lt;/strong&gt; protocol — write a small standalone service to add capabilities your original system doesn't have&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Q: Could the AI call APIs randomly and mess up my data?
&lt;/h3&gt;

&lt;p&gt;No. Two layers of protection:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Human approval&lt;/strong&gt; — tools in the &lt;code&gt;require_approval&lt;/code&gt; list pause before execution and wait for someone to click approve in the dashboard&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read-only first&lt;/strong&gt; — it's good practice to start with read-only endpoints open and put all write operations behind approval, then gradually open things up as you gain confidence&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Q: Which LLMs are supported?
&lt;/h3&gt;

&lt;p&gt;Any model compatible with the OpenAI API format works — including OpenAI, DeepSeek, Anthropic (via compatible gateways), local Ollama, and more. Just change &lt;code&gt;llm.base_url&lt;/code&gt; and &lt;code&gt;llm.model&lt;/code&gt; in the config.&lt;/p&gt;

&lt;h3&gt;
  
  
  Q: Can it connect to messaging platforms like Slack or Discord?
&lt;/h3&gt;

&lt;p&gt;Yes. Baize's channels are designed as an extensible capability. You can use the Webhook channel to adapt any platform. One assistant, one set of tools, one approval flow — reachable from multiple channels.&lt;/p&gt;

&lt;h3&gt;
  
  
  Q: What's the difference between demo mode and production mode?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Demo mode&lt;/strong&gt; (&lt;code&gt;baize demo&lt;/code&gt;): Uses Mock LLM, no API key needed, includes a built-in demo ticket system — great for trying it out quickly&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Production mode&lt;/strong&gt; (&lt;code&gt;baize start&lt;/code&gt;): Connects to a real LLM, works with real business systems — full feature set&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Let's recap the whole flow — really just four steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Download Baize&lt;/strong&gt; → single binary, zero dependencies&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write an OpenAPI spec + configure the connector&lt;/strong&gt; → APIs become tools automatically&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;(Optional) Add an &lt;code&gt;/execute&lt;/code&gt; endpoint&lt;/strong&gt; → full control over execution logic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chat&lt;/strong&gt; → the AI calls tools automatically, with human approval for sensitive operations&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Baize's design philosophy is "sidecar deployment, clean removal" — it doesn't intrude on your business system, it just adds an intelligent caller. Turn it on when you want to try it, turn it off when you don't. Your legacy system stays untouched.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Open Source Info&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Baize&lt;/strong&gt; · MIT License · Go 1.25 · Zero C dependencies&lt;/p&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/rebornace/baize" rel="noopener noreferrer"&gt;https://github.com/rebornace/baize&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Core features: ReAct Agent · OpenAPI Connector · HTTP Plugin v0 · Execution Callback · MCP Bridge/Export · HITL (Human-in-the-Loop) · Built-in Web UI&lt;/p&gt;

&lt;p&gt;Supported LLMs: OpenAI / DeepSeek / any OpenAI-compatible API&lt;/p&gt;

&lt;p&gt;Stars, issues, and PRs are all welcome.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>go</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
