- Go 100%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
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 |
||
| agent.go | ||
| agent_test.go | ||
| anthropic.go | ||
| client.go | ||
| cloudflare.go | ||
| decision.go | ||
| decision_test.go | ||
| doc.go | ||
| gemini.go | ||
| go.mod | ||
| helpers.go | ||
| LICENSE | ||
| message.go | ||
| native_adapters_test.go | ||
| nous_test.go | ||
| openai.go | ||
| openai_compat.go | ||
| README.md | ||
| request.go | ||
| response.go | ||
| sse.go | ||
| tools.go | ||
| yongle_test.go | ||
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
Providerinterface for chat, streaming, model listing, and TTS. - Separate
AgentProviderinterface for stateful agent runtimes. - Separate
DecisionProviderinterface for decision models (TypeSafe Jev / System One, OpenAI Decisions API). - Direct HTTP implementation using Go's standard
net/httpstack. - 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/decisionsendpoint. - Hermes Agent
/v1/responsessupport 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 wrapOpenAICompatibleProviderfor 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, andIsRefusal()reports a refused question.Choice: one value from a fixed set, with per-value probabilities. Read it withresp.Choice(name).Score: a probability-weighted average over ordered levels, so it can fall between levels. Read it withresp.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, andgpt-4o-mini-ttsare all deprecated; OpenAI's recommended replacement isgpt-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/completionsstill 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-reasonerretired 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/decisionsonly),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 acceptmax) - 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.AgentProvidertargets it.
Additional provider-specific features can be layered on the same Provider and AgentProvider interfaces without changing callers.
License
MIT