Skip to content

Go SDK

官方 Go SDK,封装 AI 应用开发平台的 OpenAPI(/v1/openapi/*)。

安装

SDK 源码托管在内网私有 GitLab(gitlab.i.huaxisy.com),需要登录认证才能拉取。

1. 配置私有仓库认证

bash
# 设置 GOPRIVATE,跳过公共代理校验
go env -w GOPRIVATE=gitlab.i.huaxisy.com

# 配置 Git 认证(推荐 SSH 方式)
git config --global url."ssh://gitlab.i.huaxisy.com/".insteadOf "https://gitlab.i.huaxisy.com/"

TIP

SSH 方式需要确保本机已配置 GitLab SSH Key。如使用 HTTPS 方式,可通过 ~/.netrc 或 Git 凭证管理器提供用户名/密码。

2. 安装 SDK

go.mod 中添加依赖并指定 replace:

go
require hxsyai.com/ai-adp-sdk/go v0.2.0
replace hxsyai.com/ai-adp-sdk/go v0.2.0 => gitlab.i.huaxisy.com/dail-tech/aiadp/sdk/sdk-go.git v0.2.0

然后执行:

bash
go mod tidy

go get 无法直接拉取私有仓库,必须通过 go.mod 中的 require + replace 指定。

要求 Go 1.24+。

快速开始

go
import (
    aiadp "hxsyai.com/ai-adp-sdk/go"
    "hxsyai.com/ai-adp-sdk/go/chat"
)

client, err := aiadp.NewClient(
    "sk-xxx",
    "https://runtime-gateway.invalid",
)
if err != nil {
    panic(err)
}

chatCli := chat.New(client)
res, err := chatCli.SendMessagesBlocking(ctx, &chat.SendMessagesRequest{
    AppID:  "<your-app-id>",
    UserID: "user-1",
    Query:  "你好",
})
fmt.Println(res.Output.Content)

底层 aiadp.Client 仅负责通信。每个业务子包都用 New(client) 构造自己的客户端:

用途
apps应用列表 / 详情 / 运行参数
chatAgent 对话(send-messages、resume、stop、会话 / 消息历史)
filesAgent 工作空间文件操作
knowledge知识库列表 / 详情 / 检索
workflow工作流执行(blocking / streaming)
thirdparty三方服务列表 / 工具列表 / 调用
voice语音合成 / 识别 / 音色列表(含 WebSocket 流式)
eventSSE 事件强类型(chat / workflow)
sse底层 SSE 行解析器
compatible模型发现、能力判断与 Rerank 平台扩展
compatible/openai构造已接入平台的 OpenAI 官方客户端
compatible/anthropic构造已接入平台的 Anthropic 官方客户端

NewClient 的第二个参数必须是网关根地址 https://runtime-gateway.invalid,不要追加 /v1/openapi/v1/compatible-mode。SDK 会在内部生成业务 OpenAPI 与模型兼容接口地址,并在两类调用间共享 API Key、HTTP Client、超时、重试、日志和 User-Agent 配置。

配置

go
client, _ := aiadp.NewClient(apiKey, baseURL,
    aiadp.WithTimeout(30*time.Second),
    aiadp.WithStreamConnectTimeout(10*time.Second),
    aiadp.WithRetry(3, time.Second),
    aiadp.WithLogger(aiadp.NewSlogLogger(slog.Default())),
    aiadp.WithUserAgent("my-service/1.0"),
    aiadp.WithHTTPClient(myCustomClient),
)

平台客户端还提供以下只读配置,供 compatible、WebSocket 或自定义子客户端复用:

go
client.BaseURL()             // 网关根地址
client.OpenAPIBaseURL()      // 业务 OpenAPI 地址
client.CompatibleBaseURL()   // 模型兼容接口地址
client.APIKey()
client.HTTPClient()
client.Timeout()
client.StreamConnectTimeout()
client.MaxRetries()
client.UserAgent()
client.Logger()

应用 apps

go
func (cli *Client) List(ctx, *ListRequest) (*ListResult, error)
func (cli *Client) Detail(ctx, *DetailRequest) (*DetailResult, error)
func (cli *Client) Parameters(ctx, *ParametersRequest) (*ParametersResult, error)
go
cli := apps.New(client)

list, err := cli.List(ctx, &apps.ListRequest{Page: 1, PageSize: 20})
for _, a := range list.Record {
    fmt.Printf("%s %s (type=%d)\n", a.ID, a.Name, a.AppType)
}

// 应用详情:含完整 app_schema(模型、提示词、工具、知识库、记忆、交互配置等)
detail, err := cli.Detail(ctx, &apps.DetailRequest{AppID: appID})
fmt.Printf("model=%s prologue=%s\n",
    detail.AppSchema.ModelInfo.ModelID,
    detail.AppSchema.InteractionConfig.ChatPrologue.Prologue)

// 运行参数:扁平化的交互配置(开场白、建议问题、语音 TTS/ASR、运行约束)
params, err := cli.Parameters(ctx, &apps.ParametersRequest{AppID: appID})
fmt.Printf("tts=%s asr=%s max_steps=%d\n",
    params.Voice.TTS.Code, params.Voice.ASR.Provider, params.RuntimeSetting.MaxSteps)

DetailResult.AppSchema 是应用的完整配置快照,ParametersResult 是面向运行时的精简交互配置;两者都支持 Version 指定历史版本。

对话 chat

go
type Client struct{ /* ... */ }
func New(c Doer) *Client

func (cli *Client) SendMessagesBlocking(ctx, *SendMessagesRequest) (*SendMessagesResult, error)
func (cli *Client) SendMessagesStream(ctx, *SendMessagesRequest) (*event.ChatStream, error)
func (cli *Client) ResumeBlocking(ctx, *ResumeRequest) (*SendMessagesResult, error)
func (cli *Client) ResumeStream(ctx, *ResumeRequest) (*event.ChatStream, error)
func (cli *Client) ReconnectStream(ctx, *ReconnectRequest) (*event.ChatStream, error)
func (cli *Client) Stop(ctx, taskID string) error
func (cli *Client) Conversations(ctx, *ConversationsRequest) (*ConversationsResult, error)
func (cli *Client) ChatHistory(ctx, *HistoryRequest) ([]HistoryItem, error)
func (cli *Client) SuggestQuestions(ctx, *SuggestQuestionsRequest) (*SuggestQuestionsResult, error)

请求与响应字段:

go
type SendMessagesRequest struct {
    AppID          string         `json:"app_id"`
    UserID         string         `json:"user_id"`
    Username       string         `json:"username,omitempty"`
    ConversationID string         `json:"conversation_id,omitempty"`
    Query          string         `json:"query"`
    Files          []FileItem     `json:"files,omitempty"`
    Input          map[string]any `json:"input,omitempty"`
    WithTTS        bool           `json:"with_tts,omitempty"`
    TTSFormat      string         `json:"tts_format,omitempty"`
    SelectModelID  string         `json:"select_model_id,omitempty"`
}

type SendMessagesResult struct {
    ConversationID string         `json:"conversation_id"`
    MessageID      string         `json:"message_id"`
    TaskID         string         `json:"task_id"`
    Status         string         `json:"status"`
    Duration       int64          `json:"duration"`
    Output         BlockingOutput `json:"output"` // Content / FinishReason / Tokens
}

ChatFlow 应用还会在同一条对话流中返回以下强类型事件:

事件名Go 类型主要数据
workflow.started*event.WorkflowStartedWorkflowRunID
workflow.finished*event.WorkflowFinished状态、输出、错误、耗时、Token 与步骤数
node.started*event.NodeStarted节点执行 ID、节点类型、标题与输入
node.finished*event.NodeFinished状态、输出、耗时与输入/输出 Token
node.error*event.NodeError节点错误、耗时与输入/输出 Token

这些类型由 ChatStream.NextNextWithID 自动分发;不需要自行解析 SSE event 字段。

response_mode 由 SDK 自动注入,请勿自行设置。

流式响应

go
stream, err := chatCli.SendMessagesStream(ctx, req)
if err != nil {
    log.Fatal(err)
}
defer stream.Close()

for {
    ev, err := stream.Next(ctx)
    if errors.Is(err, io.EOF) {
        break
    }
    if err != nil {
        log.Fatal(err)
    }

    switch e := ev.(type) {
    case *event.Start:
    case *event.Chunk:
        fmt.Print(e.Data.Content)
    case *event.Interrupt:
        // HITL,见下方 Resume
    case *event.Completed:
    }
}

HITL 中断与恢复

对话流出现 *event.Interrupt 时,会带回 CheckpointIDInterrupts[].InterruptID。按 interrupt ID 给出应答后调用 ResumeStream

go
resume, err := chatCli.ResumeStream(ctx, &chat.ResumeRequest{
    AppID:          appID,
    UserID:         userID,
    ConversationID: conversationID,
    CheckpointID:   checkpointID,
    Responses: map[string]event.ResumeResponse{
        interruptID: event.ChoiceResponse{Choice: "staging"},
    },
})

event.ResumeResponse 的实现类型对应不同交互:ChoiceResponseConfirmResponseTextResponse 等。

断流重连

需要断点续传时,使用 NextWithID 保存服务端返回的 SSE ID,再通过 ReconnectStream 继续读取:

go
_, lastEventID, err := stream.NextWithID(ctx)
if err != nil {
    log.Fatal(err)
}

reconnected, err := chatCli.ReconnectStream(ctx, &chat.ReconnectRequest{
    AppID:       appID,
    UserID:      userID,
    TaskID:      taskID,
    LastEventID: lastEventID,
})
if err != nil {
    log.Fatal(err)
}
defer reconnected.Close()

同一个 ChatStream 只选择 NextNextWithIDRawNext 中的一种读取方式,不要混用。

停止生成

*event.Start 拿到 TaskID 后随时可停:

go
if err := chatCli.Stop(ctx, taskID); err != nil {
    log.Fatal(err)
}

会话与消息历史

go
// 会话列表(按应用,可选按用户过滤、分页)
conv, err := chatCli.Conversations(ctx, &chat.ConversationsRequest{
    AppID: appID, UserID: "demo-user", Page: 1, PageSize: 20,
})
for _, c := range conv.Record {
    fmt.Printf("%s %s\n", c.ID, c.Name)
}

// 某会话的消息历史
items, err := chatCli.ChatHistory(ctx, &chat.HistoryRequest{
    AppID: appID, ConversationID: conversationID, Page: 1, PageSize: 20,
})
for _, m := range items {
    fmt.Printf("Q=%s A=%s\n", m.Query, m.Content)
}

HistoryItemQuery / Content / Thinking / ToolCalls / Items(子 Agent 与工具调用明细)等完整执行轨迹。

建议问题

应用启用 UserSuggestion 后,可以根据某条回复生成后续建议问题:

go
result, err := chatCli.SuggestQuestions(ctx, &chat.SuggestQuestionsRequest{
    AppID:     appID,
    MessageID: messageID,
})
if err != nil {
    log.Fatal(err)
}
for _, question := range result.Questions {
    fmt.Println(question)
}

文件 files

go
func (cli *Client) Upload(ctx, *UploadRequest) (*FileObject, error)
func (cli *Client) ListDirectory(ctx, *ListRequest) (*ListResult, error)
func (cli *Client) ListTree(ctx, *TreeRequest) (*TreeResult, error)
func (cli *Client) Edit(ctx, *EditRequest) error
func (cli *Client) Copy(ctx, *CopyRequest) (*FileObject, error)
func (cli *Client) Move(ctx, *MoveRequest) (*FileObject, error)
func (cli *Client) Delete(ctx, *DeleteRequest) error
func (cli *Client) Mkdir(ctx, *MkdirRequest) (*FileObject, error)
func (cli *Client) Search(ctx, *SearchRequest) (*SearchResult, error)
func (cli *Client) Query(ctx, *QueryRequest) (*QueryResult, error)
func (cli *Client) Download(ctx, userID, filePath string) (*DownloadResult, error)
func (cli *Client) DownloadDir(ctx, userID, dirPath string) (*DownloadResult, error)
func (cli *Client) InitMultipartUpload(ctx, *InitMultipartUploadRequest) (*InitMultipartUploadResult, error)
func (cli *Client) UploadPart(ctx, *UploadPartRequest) (*MultipartPart, error)
func (cli *Client) CompleteMultipartUpload(ctx, *CompleteMultipartUploadRequest) (*FileObject, error)
func (cli *Client) MultipartUploadStatus(ctx, *MultipartUploadStatusRequest) (*MultipartUploadStatusResult, error)
func (cli *Client) AbortMultipartUpload(ctx, *AbortMultipartUploadRequest) error

所有文件接口都需要 UserID 定位 Agent 工作空间。UploadFile 字段是任意 io.Reader

go
cli := files.New(client)

obj, err := cli.Upload(ctx, &files.UploadRequest{
    UserID:   "demo-user",
    Path:     "/data/hello.txt",
    Filename: "hello.txt",
    File:     strings.NewReader("hello world"),
})
fmt.Printf("uploaded: path=%s url=%s\n", obj.Path, obj.URL)

list, err := cli.ListDirectory(ctx, &files.ListRequest{UserID: "demo-user", Path: "/data"})
for _, f := range list.Files {
    fmt.Printf("  - %s (size=%d)\n", f.Path, f.Size)
}

Move 对应网关的 rename 接口,同目录即重命名、跨目录即移动。ListTree 递归列出文件树,MaxDepth 为 0 表示不限深度。Query 可按路径批量查询文件并返回完整访问 URL;大文件上传使用 init → part → complete 流程,并可查询状态或中止任务。

知识库 knowledge

go
func (cli *Client) List(ctx, *ListRequest) (*ListResult, error)
func (cli *Client) Detail(ctx, knowledgeID string) (*Detail, error)
func (cli *Client) Retrieve(ctx, knowledgeID string, *RetrieveRequest) (*RetrieveResult, error)
go
cli := knowledge.New(client)

list, err := cli.List(ctx, &knowledge.ListRequest{Page: 1, PageSize: 10})
first := list.Items[0]

detail, err := cli.Detail(ctx, first.ID)
fmt.Printf("retriever_type=%d top_k=%d\n",
    detail.RetrieverConfig.RetrieverType, detail.RetrieverConfig.TopK)

res, err := cli.Retrieve(ctx, first.ID, &knowledge.RetrieveRequest{
    Query: "高血压患者能不能吃布洛芬?",
})
for _, r := range res.Records {
    fmt.Printf("- score=%.3f content=%s\n", r.Score, r.Content)
}

可选地用 RetrievalModel 覆盖单次检索的 TopK / ScoreThreshold 等参数。

注意类型差异:详情响应里 ScoreThresholdEnabledint(0/1),而检索请求体里的同名字段是 bool

工作流 workflow

go
func (cli *Client) InvokeBlocking(ctx, *InvokeRequest) (*InvokeResult, error)
func (cli *Client) InvokeStream(ctx, *InvokeRequest) (*event.WorkflowStream, error)
go
cli := workflow.New(client)

res, err := cli.InvokeBlocking(ctx, &workflow.InvokeRequest{
    WorkflowID: workflowID,
    Input:      map[string]any{"input": "华西"},
})
fmt.Printf("final_output=%v token=%d\n", res.FinalOutput, res.Token)

stream, err := cli.InvokeStream(ctx, &workflow.InvokeRequest{
    WorkflowID: workflowID,
    Input:      map[string]any{"input": "华西"},
})
defer stream.Close()
for {
    ev, err := stream.Next(ctx)
    if errors.Is(err, io.EOF) {
        break
    }
    switch e := ev.(type) {
    case *event.WorkflowMessage:
        fmt.Print(e.Content)
    case *event.WorkflowDone:
        fmt.Printf("\n[done] status=%s\n", e.Status)
    case *event.WorkflowError:
        fmt.Printf("\n[error] %s\n", e.ErrorMessage)
    }
}

三方服务 thirdparty

go
func (cli *Client) ListServices(ctx) ([]Service, error)
func (cli *Client) ListTools(ctx, serviceSlug string) (*ToolsResult, error)
func (cli *Client) Invoke(ctx, serviceSlug, toolSlug string, *InvokeRequest) (*InvokeResult, error)
go
cli := thirdparty.New(client)

services, err := cli.ListServices(ctx)
for _, s := range services {
    fmt.Printf("- %s (configured=%v)\n", s.Name, s.IsConfigured)
}

tools, err := cli.ListTools(ctx, services[0].Name)
fmt.Printf("service=%s tools=%d\n", tools.Service.Name, len(tools.Tools))

res, err := cli.Invoke(ctx, services[0].Name, tools.Tools[0].Name,
    &thirdparty.InvokeRequest{Inputs: map[string]any{}})
fmt.Printf("output=%s\n", string(res.Output)) // Output 是 json.RawMessage

不同工具返回结构不同,InvokeResult.Outputjson.RawMessage,按需自行反序列化。

语音 voice

go
// 非流式
func (cli *Client) Synthesize(ctx, *SynthesizeRequest) (*TTSResult, error)
func (cli *Client) Recognize(ctx, *RecognizeRequest) (*STTResult, error)
func (cli *Client) ListVoices(ctx, provider string) ([]VoiceOption, error)

// WebSocket 流式
func (cli *Client) TTSStream(ctx, *SynthesizeRequest) (*TTSStream, error)
func (cli *Client) STTStream(ctx, *RecognizeRequest) (*STTStream, error)

音频在请求/响应中以 base64 传输(AudioData 字段)。语音合成:

go
cli := voice.New(client)

voices, err := cli.ListVoices(ctx, "aliyun")
for _, v := range voices {
    fmt.Printf("- %s (%s) %s/%s\n", v.Name, v.Value, v.Language, v.Scene)
}

res, err := cli.Synthesize(ctx, &voice.SynthesizeRequest{
    Provider:  "aliyun",
    Text:      "你好,欢迎使用语音合成。",
    VoiceCode: voices[0].Value,
    Format:    "mp3",
})
audio, _ := base64.StdEncoding.DecodeString(res.AudioData)
fmt.Printf("format=%s duration=%dms bytes=%d\n", res.Format, res.DurationMs, len(audio))

语音识别(非流式):

go
res, err := cli.Recognize(ctx, &voice.RecognizeRequest{
    Provider:   "aliyun",
    AudioData:  base64.StdEncoding.EncodeToString(data),
    Format:     "pcm",
    SampleRate: 16000,
})
fmt.Printf("text=%q confidence=%.2f\n", res.Text, res.Confidence)

WebSocket 流式

  • TTSTTSStream.Recv() 循环读取音频分片,io.EOF 结束。
  • STTSTTStream.SendAudio(data) 边推送音频边由 Recv() 接收 *STTStreamResult(含 IsPartial / IsFinal),推送完调 CloseSend()

两个流都实现 Close(),记得 defer 释放连接。STT 流式时 RecognizeRequest 的非音频字段作为首帧配置,不要带 AudioData

模型服务 compatible

Go SDK 可以从同一个平台客户端构造 OpenAI、Anthropic 官方客户端,以及模型发现和 Rerank 扩展客户端:

go
import (
    aiadp "hxsyai.com/ai-adp-sdk/go"
    "hxsyai.com/ai-adp-sdk/go/compatible"
    compatibleanthropic "hxsyai.com/ai-adp-sdk/go/compatible/anthropic"
    compatibleopenai "hxsyai.com/ai-adp-sdk/go/compatible/openai"
)

platform, err := aiadp.NewClient(apiKey, "https://runtime-gateway.invalid")
if err != nil {
    log.Fatal(err)
}

openaiClient, err := compatibleopenai.New(platform)
if err != nil {
    log.Fatal(err)
}
anthropicClient, err := compatibleanthropic.New(platform)
if err != nil {
    log.Fatal(err)
}
modelClient, err := compatible.New(platform)
if err != nil {
    log.Fatal(err)
}
客户端用途返回类型与错误
compatible/openaiChat Completions、Responses、Embedding、语音等 OpenAI 兼容能力OpenAI 官方 Go SDK 类型与官方错误
compatible/anthropicMessages、流式 Messages、Token 计数Anthropic 官方 Go SDK 类型与官方错误
compatible模型列表、模型详情、能力判断、RerankAI ADP 扩展类型;服务错误为 *compatible.Error

三个客户端自动复用平台客户端的 API Key、HTTP Client、超时、重试、日志和 User-Agent。标准协议的调用方式与官方 SDK 一致,平台地址无需再次配置。

模型发现与能力判断

go
models, err := modelClient.ListModels(ctx)
if err != nil {
    log.Fatal(err)
}

for _, model := range models.Data {
    if model.HasCapability("chat_completions") {
        fmt.Println(model.ID)
    }
}

model, err := modelClient.GetModel(ctx, modelID)
if err != nil {
    log.Fatal(err)
}

模型 ID 可以包含提供商前缀,例如 provider/model-name;直接传入模型列表返回的 Model.ID,不要自行拼接。

Rerank

go
topN := 2
result, err := modelClient.Rerank(ctx, &compatible.RerankRequest{
    Model: modelID,
    Query: "什么是向量检索?",
    Documents: []compatible.RerankDocument{
        compatible.StringDocument("向量检索通过相似度召回内容。"),
        compatible.ObjectDocument("doc-2", "关系型数据库使用表组织数据。"),
    },
    TopN: &topN,
})
if err != nil {
    log.Fatal(err)
}

RerankDocument 同时支持字符串和带 ID 的对象;应使用 StringDocumentObjectDocument 构造,空文档会返回 compatible.ErrInvalidRerankDocument。完整协议能力参见模型服务说明

错误处理

go
if errors.Is(err, aiadp.ErrAPIKeyExpired) {
    // 重新生成 API Key
}

var apiErr *aiadp.APIError
if errors.As(err, &apiErr) {
    log.Printf("status=%d code=%d message=%s trace=%s",
        apiErr.HTTPStatus, apiErr.Code, apiErr.Message, apiErr.TraceID)
}

模型发现与 Rerank 的协议错误可读取结构化字段:

go
var compatibleErr *compatible.Error
if errors.As(err, &compatibleErr) {
    log.Printf("status=%d type=%s code=%s request_id=%s",
        compatibleErr.HTTPStatus,
        compatibleErr.Type,
        compatibleErr.Code,
        compatibleErr.RequestID,
    )
}

OpenAI 与 Anthropic 标准客户端保留各自官方 SDK 的错误类型,不会转换为 aiadp.APIErrorcompatible.Error

错误码与文案以网关/服务实际返回为准,详见 错误处理

示例

完整可运行示例见 SDK 仓库的 examples/,每个目录是一个独立 main

示例功能
apps-list / apps-detail / apps-parameters应用列表 / 详情 / 运行参数
chat-blocking / chat-streaming对话两种模式
chat-resumeHITL 中断 + resume(choice)
chat-stoptask-id 停止
chat-history会话列表 + 消息历史
files-upload / files-download / files-tree普通与分片上传 / 文件与目录下载 / 文件树
knowledge-retrievelist / detail / retrieve
workflow-invokeblocking + streaming
thirdparty-invokeservices / tools / invoke
voice-tts / voice-stt语音合成 / 识别(含流式)
compatible-openaiOpenAI Responses 流式输出思考与回答
compatible-anthropicAnthropic Messages 流式输出与 Token 计数
compatible-rerank模型能力判断与 Rerank

运行前设置环境变量:

bash
export AIADP_API_KEY=sk-xxx
export AIADP_BASE_URL=https://runtime-gateway.invalid
export AIADP_MODEL_ID=your-model-id
go run ./examples/chat-streaming

只有兼容模型示例需要 AIADP_MODEL_ID;其他示例按各自 README 设置应用、用户、知识库或工作流等业务参数。

AI 应用开发平台 - 面向医疗场景的 AI 应用创新引擎