A multi-provider AI API client for Go — carrying the Zheng He fleet onward.
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Simon Harms 486af7f1ec
feat: native decision model support via DecisionProvider
Add a third provider interface, DecisionProvider, for System One
decision models (Jev, GPT-6 Luna Decisions) alongside Provider and
AgentProvider.

- DecisionRequest builder with portable predicate/choice/score
  questions, shared state, structured state, image evidence, and
  extra provider fields; Build() validates names, duplicates, and
  arity per question type
- SystemOneProvider: NewTypeSafeProvider plus NewSystemOneProvider
  for any System One-compatible host (OpenRouter, Telnyx,
  DigitalOcean, Databricks, Opper, self-hosted Kev/Laya); normalizes
  noul answers to predicate answers
- OpenAIProvider.Decide: POST /v1/decisions with array-form
  questions, per-value/level probabilities, legend reconstruction,
  and inline base64 image input; rejects structured state with a
  clear error
- refusal answers normalize across both wire formats; Bool() reads
  false for refusals
- extract flattenExtra from ChatRequest so chat and decision
  requests share the extra-fields merge
- document the interface, constructors, model IDs, and provider
  differences in README
2026-10-18 22:05:47 -04:00
agent.go Sync provider defaults with current upstream APIs (Oct 2026) 2026-10-18 21:30:50 -04:00
agent_test.go feat: add Hermes Agent provider 2026-06-06 18:45:00 +00:00
anthropic.go Sync provider defaults with current upstream APIs (Oct 2026) 2026-10-18 21:30:50 -04:00
client.go Sync provider defaults with current upstream APIs (Oct 2026) 2026-10-18 21:30:50 -04:00
cloudflare.go Initial yongle Go AI client 2026-06-06 17:13:55 +00:00
decision.go feat: native decision model support via DecisionProvider 2026-10-18 22:05:47 -04:00
decision_test.go feat: native decision model support via DecisionProvider 2026-10-18 22:05:47 -04:00
doc.go Initial yongle Go AI client 2026-06-06 17:13:55 +00:00
gemini.go Sync provider defaults with current upstream APIs (Oct 2026) 2026-10-18 21:30:50 -04:00
go.mod Initial yongle Go AI client 2026-06-06 17:13:55 +00:00
helpers.go Initial yongle Go AI client 2026-06-06 17:13:55 +00:00
LICENSE Initial yongle Go AI client 2026-06-06 17:13:55 +00:00
message.go Initial yongle Go AI client 2026-06-06 17:13:55 +00:00
native_adapters_test.go Update provider defaults to current upstream APIs. 2026-08-02 06:45:28 -07:00
nous_test.go Initial yongle Go AI client 2026-06-06 17:13:55 +00:00
openai.go Sync provider defaults with current upstream APIs (Oct 2026) 2026-10-18 21:30:50 -04:00
openai_compat.go Sync provider defaults with current upstream APIs (Oct 2026) 2026-10-18 21:30:50 -04:00
README.md feat: native decision model support via DecisionProvider 2026-10-18 22:05:47 -04:00
request.go feat: native decision model support via DecisionProvider 2026-10-18 22:05:47 -04:00
response.go Update provider defaults to current upstream APIs. 2026-08-02 06:45:28 -07:00
sse.go Initial yongle Go AI client 2026-06-06 17:13:55 +00:00
tools.go Initial yongle Go AI client 2026-06-06 17:13:55 +00:00
yongle_test.go Sync provider defaults with current upstream APIs (Oct 2026) 2026-10-18 21:30:50 -04:00

yongle · 永乐

yongle is a multi-provider AI API client for Go, modeled after Simon's Rust crate baochuan.

The name keeps the Zheng He theme: Zheng He's treasure voyages sailed under the Yongle Emperor. If ZhengHe brought AI access to Java and baochuan became the treasure fleet for Rust, yongle is the imperial commission that sends the fleet onward into Go.

Features

  • Unified Provider interface for chat, streaming, model listing, and TTS.
  • Separate AgentProvider interface for stateful agent runtimes.
  • Separate DecisionProvider interface for decision models (TypeSafe Jev / System One, OpenAI Decisions API).
  • Direct HTTP implementation using Go's standard net/http stack.
  • OpenAI-compatible provider core for Nous Portal, OpenAI, DeepSeek, xAI/Grok, Mistral, OpenRouter, Moonshot/Kimi, Perplexity, Qwen compatible mode, GitHub Copilot, LM Studio, Ollama, and llama.cpp.
  • Native adapters for Anthropic Claude, Google Gemini, and Cloudflare Workers AI.
  • Decision models natively: TypeSafe Jev through the System One API, and OpenAI's /v1/decisions endpoint.
  • Hermes Agent /v1/responses support for non-streaming and streaming agent runs.
  • Native builders for chat, text-to-speech, and agent requests.
  • Server-Sent Events streaming parser exposed through Go iterator syntax.
  • Extensible: implement Provider, AgentProvider, or wrap OpenAICompatibleProvider for more backends.

Install

go get github.com/thesimonharms/yongle

For local development in this directory:

go test ./...

Quickstart

package main

import (
    "context"
    "fmt"
    "log"
    "os"

    "github.com/thesimonharms/yongle"
)

func main() {
    provider := yongle.NewDeepSeekProvider(os.Getenv("DEEPSEEK_API_KEY"))

    req, err := yongle.NewChatRequest("deepseek-flash").
        User("Tell me about the treasure ships of Zheng He.").
        MaxTokens(512).
        Build()
    if err != nil {
        log.Fatal(err)
    }

    resp, err := provider.Chat(context.Background(), req)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println(resp.Content())
}

Streaming

stream, err := provider.StreamChat(ctx, req)
if err != nil {
    log.Fatal(err)
}

for chunk, err := range stream {
    if err != nil {
        log.Fatal(err)
    }
    fmt.Print(chunk.DeltaContent())
}

Provider constructors

Constructor Default base URL / notes
NewOpenAIProvider https://api.openai.com/v1 (also implements TTS)
NewDeepSeekProvider https://api.deepseek.com
NewXAIProvider https://api.x.ai/v1
NewMistralProvider https://api.mistral.ai/v1
NewOpenRouterProvider https://openrouter.ai/api/v1 — set HTTP-Referer / X-OpenRouter-Title (or X-Title) via WithHeader for app attribution
NewMoonshotProvider https://api.moonshot.ai/v1 (global Kimi platform)
NewMoonshotCNProvider https://api.moonshot.cn/v1 (China platform)
NewPerplexityProvider https://api.perplexity.ai/v1 — Agent API; Sonar Chat Completions ended 2026-09-27, so use /router/v1 via WithBaseURL for chat-completions orchestration
NewNousProvider https://inference-api.nousresearch.com/v1
NewQwenProvider https://dashscope-intl.aliyuncs.com/compatible-mode/v1 — see NewQwenProviderWithHost for the workspace-scoped hosts Alibaba now issues
NewQwenCNProvider https://dashscope.aliyuncs.com/compatible-mode/v1
NewQwenProviderWithHost https://<WORKSPACE_ID>.<region>.maas.aliyuncs.com/compatible-mode/v1 (regions: cn-beijing, ap-southeast-1, cn-hongkong, us-east-1, ap-northeast-1)
NewCopilotProvider https://api.githubcopilot.com — sends Copilot-Integration-Id: copilot-developer-cli by default
NewLMStudioProvider http://localhost:1234/v1
NewOllamaProvider http://localhost:11434/v1
NewLlamaCPPProvider caller-supplied base URL
NewAnthropicProvider https://api.anthropic.com/v1 (x-api-key + anthropic-version: 2023-06-01)
NewGeminiProvider https://generativelanguage.googleapis.com/v1beta (x-goog-api-key)
NewCloudflareProvider https://api.cloudflare.com/client/v4
NewOpenAICompatibleProvider custom name + base URL
NewTypeSafeProvider https://api.typesafe.ai — Jev decision models via System One (DecisionProvider)
NewSystemOneProvider any System One-compatible host (OpenRouter, Telnyx, Databricks, self-hosted Kev/Laya)

Useful options:

yongle.WithBaseURL("http://localhost:8080/v1")
yongle.WithHTTPClient(customClient)
yongle.WithHeader("HTTP-Referer", "https://example.com")
yongle.WithHeader("X-OpenRouter-Title", "My App")

For OpenAI reasoning / o-series models, prefer MaxCompletionTokens over MaxTokens:

req, err := yongle.NewChatRequest("o4-mini").
    User("Explain Zheng He's voyages briefly.").
    MaxCompletionTokens(512).
    Build()

Hermes Agent runs

Hermes Agent is exposed as a separate AgentProvider because it targets the higher-level OpenAI Responses-style /v1/responses endpoint rather than plain chat completions:

agent := yongle.NewHermesAgentProvider(os.Getenv("API_SERVER_KEY"))

run, err := yongle.NewAgentRunRequest("hermes-agent", "What files are in this project?").
    Instructions("You are a helpful coding assistant.").
    Store(true).
    Build()
if err != nil { log.Fatal(err) }

resp, err := agent.Run(ctx, run)
if err != nil { log.Fatal(err) }
fmt.Println(resp.OutputText())

Streaming agent lifecycle events also use Go iterator syntax:

stream, err := agent.StreamRun(ctx, run)
if err != nil { log.Fatal(err) }

for event, err := range stream {
    if err != nil { log.Fatal(err) }
    fmt.Print(event.OutputTextDelta())
}

Decision models

Decision models ("System One" models) answer one bounded question instead of generating text. An answer is a probability, a choice from a fixed set, or a score. Use the third interface, DecisionProvider, next to Provider and AgentProvider:

var dp yongle.DecisionProvider = yongle.NewTypeSafeProvider(os.Getenv("TYPESAFE_API_KEY"))

req, err := yongle.NewDecisionRequest("jev-latest", "Help! My payouts have been failing for 3 days.").
    Predicate("is_urgent", "Does this need urgent help?").
    Choice("team", "Which team should handle this?",
        yongle.ChoiceOption("billing", "Payments, invoicing, refunds"),
        yongle.ChoiceOption("technical", "Bugs, outages, integrations"),
        yongle.ChoiceOption("sales", "Pricing, upgrades, new accounts")).
    Score("urgency", "Rate operational urgency.", "low", "normal", "high", "critical").
    Build()
if err != nil { log.Fatal(err) }

resp, err := dp.Decide(ctx, req)
if err != nil { log.Fatal(err) }

if resp.Bool("is_urgent") { /* page on-call */ }
fmt.Println(resp.Choice("team"), resp.Score("urgency"))

Three question types work on every provider:

  • Predicate: the probability that a condition holds. resp.Bool(name) applies a 0.5 threshold, and IsRefusal() reports a refused question.
  • Choice: one value from a fixed set, with per-value probabilities. Read it with resp.Choice(name).
  • Score: a probability-weighted average over ordered levels, so it can fall between levels. Read it with resp.Score(name).

NewTypeSafeProvider targets the hosted Jev models at TypeSafe. NewSystemOneProvider targets any host that serves the same System One wire format. Known hosts: OpenRouter (https://openrouter.ai/api/v1), Telnyx (https://api.telnyx.com/v2/ai/typesafe), DigitalOcean Inference, Databricks AI Gateway, Opper, and self-hosted Jev-compatible servers such as Kev or Laya. Switching hosts changes only the constructor. The request shape is identical.

The OpenAI Decisions API (POST /v1/decisions, currently gpt-6-luna) runs on OpenAIProvider, so chat and decisions share one client. It also accepts inline base64 image data URLs via .Image(...):

provider := yongle.NewOpenAIProvider(os.Getenv("OPENAI_API_KEY"))

req, _ := yongle.NewDecisionRequest("gpt-6-luna", "Inspect the product in this photo.").
    Image("data:image/png;base64,...").
    Predicate("visible_damage", "Does the product have visible damage? Ignore shadows.").
    Build()

resp, err := provider.Decide(ctx, req)

Differences to know: OpenAI requires unique question names, and Build enforces this. The API rejects structured state objects, so StateObject is System One only. OpenAI charges nothing for output tokens.

TTS

OpenAI-compatible chat providers return ErrUnsupported for TTS by default. OpenAIProvider implements /audio/speech:

tts, err := yongle.NewTTSRequest("tts-1", "Hello from yongle.").
    Voice("nova").
    Format("mp3").
    Build()
if err != nil { log.Fatal(err) }

audio, err := yongle.NewOpenAIProvider(os.Getenv("OPENAI_API_KEY")).TTS(ctx, tts)
if err != nil { log.Fatal(err) }
_ = os.WriteFile("hello.mp3", audio, 0644)

TTS models shut down Jan 6, 2027. tts-1, tts-1-hd, and gpt-4o-mini-tts are all deprecated; OpenAI's recommended replacement is gpt-realtime-2.1-mini, served by the Realtime API rather than /audio/speech. The endpoint still works until then, but new code should plan the migration.

Current model generation

Model IDs churn monthly and are not hardcoded here — pass the model name you want. As of October 2026, the current flagships are:

  • OpenAI: gpt-6-astra (flagship), gpt-6.1-sol, gpt-6-luna. OpenAI now leads with the Responses API; /v1/chat/completions still works and is not deprecated.
  • Anthropic: claude-opus-5-5 (recommended default), claude-fable-5-1 (demanding reasoning), claude-sonnet-5-5, claude-haiku-5-5.
  • Gemini: gemini-4-argon (flagship), gemini-3.8-flash.
  • xAI: grok-4.7. DeepSeek: deepseek-flash, deepseek-v4-pro (deepseek-chat/deepseek-reasoner retired 2026-07-24).
  • Mistral: mistral-large-4, mistral-large-2512 (IDs are now date-suffixed).
  • Moonshot/Kimi: kimi-k3. Qwen: qwen3.8-max, qwen3.7-plus.
  • Decision models: jev-latest / jev-1.13.0 (TypeSafe), gpt-6-luna (OpenAI, /v1/decisions only), microsoft-decision-1 (OpenRouter / Microsoft Foundry), pplx-decider-v1.1-27b (Perplexity, open weights).

Reasoning effort

Most providers now accept a reasoning-effort control, but they disagree on the vocabulary, so ReasoningEffort takes a free-form string:

req, _ := yongle.NewChatRequest("grok-4.7").
    User("Plan the treasure fleet route.").
    ReasoningEffort("high").
    Build()
  • OpenAI / xAI / Mistral / Anthropic: none, minimal, low, medium, high, xhigh (OpenAI and Anthropic also accept max)
  • DeepSeek / Kimi K3: low, high, max

Anthropic receives this as output_config.effort; OpenAI-compatible providers receive reasoning_effort.

Provider-specific fields

Some providers expose non-standard body fields. Use WithExtra to add them at the top level:

req, _ := yongle.NewChatRequest("deepseek-flash").
    User("Explain the voyage.").
    Thinking(true).                                  // adds {"thinking":{"type":"enabled"}}
    WithExtra("provider", map[string]any{"order": []string{"deepseek"}}). // OpenRouter routing
    Build()

Status

This Go port covers baochuan's core ergonomics with current upstream defaults: OpenAI-compatible providers (including Nous Portal), regional Moonshot/Qwen endpoints plus workspace-scoped DashScope hosts, Copilot integration headers, native Anthropic/Gemini/Cloudflare adapters, OpenAI max_completion_tokens, reasoning-effort support, provider-specific extra body fields, Hermes AgentProvider support, and native decision-model support through the DecisionProvider interface.

Two upstream shifts are worth knowing about when extending this library:

  • Gemini: the Interactions API (POST /v1beta/interactions) has been GA since June 2026 and is where new models and features launch; generateContent — which this adapter uses — is now legacy but remains fully supported.
  • OpenAI: the Responses API (POST /v1/responses) is the recommended interface and carries capabilities (conversation state, background execution, context compaction, mid-turn steering) that have no Chat Completions equivalent. AgentProvider targets it.

Additional provider-specific features can be layered on the same Provider and AgentProvider interfaces without changing callers.

License

MIT