如果你的后端是 Go,这篇教程给你一个能直接嵌进现有服务的最小实现:用官方 Go Agent SDK 起三个接口——/get_config、/startAgent、/stopAgent。
浏览器端直接复用 《Next.js 搭建浏览器端语音智能体》那份客户端,不用另写。App Certificate、DeepSeek 和 MiniMax Key 全程留在 Go 服务端。
跑通之后,中文开场白、多轮对话、双方实时字幕、正常停止,一样不少。
一. 架构与准备工作
浏览器 RTC/RTM ──→ Go Gin API
│ agentkit/cn
▼
声网对话式 AI 云端
凤鸣 ASR → DeepSeek → MiniMax TTS
需要 Go 1.23、App ID、主要证书、DeepSeek 和 MiniMax Key。依赖固定这两个版本:
github.com/AgoraIO/agora-agents-go/v2 v2.3.0
github.com/gin-gonic/gin v1.11.0
二. 环境变量
AGORA_APP_ID=REPLACE_WITH_SHENGWANG_APP_ID
AGORA_APP_CERTIFICATE=REPLACE_WITH_PRIMARY_CERTIFICATE
DEEPSEEK_API_KEY=REPLACE_WITH_DEEPSEEK_KEY
MINIMAX_API_KEY=REPLACE_WITH_MINIMAX_KEY
WEB_ORIGINS=http://localhost:3000,http://127.0.0.1:3000
PORT=8000
客户端只能拿到 App ID、频道、UID 和短期 Token,其余三项凭证一律不下发。
三. 初始化客户端
Go SDK 和其他语言这里不太一样。入口不是在全局客户端上手写 URL,而是直接用 agentkit/cn 包,它会自动选好区域和接口路径。
package main
import (
"os"
agentkit "github.com/AgoraIO/agora-agents-go/v2/agentkit/cn"
)
func newChinaClient() *agentkit.AgoraClient {
return agentkit.NewAgoraClient(agentkit.ClientOptions{
AppID: os.Getenv("AGORA_APP_ID"),
AppCertificate: os.Getenv("AGORA_APP_CERTIFICATE"),
})
}
四. 配置凤鸣、DeepSeek 和 MiniMax
package main
import (
"os"
agora "github.com/AgoraIO/agora-agents-go/v2"
agentkit "github.com/AgoraIO/agora-agents-go/v2/agentkit/cn"
vendors "github.com/AgoraIO/agora-agents-go/v2/agentkit/cn/vendors"
)
func intPtr(value int) *int { return &value }
func floatPtr(value float64) *float64 { return &value }
func boolPtr(value bool) *bool { return &value }
func buildAgent(client *agentkit.AgoraClient) *agentkit.Agent {
language := agora.AsrLanguage("zh-CN")
dataChannel := agentkit.ParametersDataChannel("rtm")
llm := vendors.NewDeepSeek(vendors.DeepSeekOptions{
APIKey: os.Getenv("DEEPSEEK_API_KEY"),
BaseURL: "https://api.deepseek.com/chat/completions",
Model: "deepseek-v4-flash",
SystemMessages: []map[string]interface{}{{
"role": "system",
"content": "你是中文语音助手,回答自然、简短。",
}},
GreetingMessage: "你好,我是你的声网 Go 语音助手。",
FailureMessage: "抱歉,服务暂时不可用,请稍后再试。",
MaxHistory: intPtr(20),
MaxTokens: intPtr(512),
Temperature: floatPtr(0.5),
Params: map[string]interface{}{
"thinking": map[string]interface{}{"type": "disabled"},
},
})
tts := vendors.NewMiniMaxTTS(vendors.MiniMaxTTSOptions{
Key: os.Getenv("MINIMAX_API_KEY"),
Model: "speech-01-turbo",
VoiceSetting: &vendors.MiniMaxVoiceSetting{
VoiceID: "female-shaonv",
},
AudioSetting: &vendors.MiniMaxAudioSetting{
SampleRate: 16000,
},
LanguageBoost: "Chinese",
})
return agentkit.NewAgent(
client,
agentkit.WithTurnDetectionConfig(
&agentkit.TurnDetectionConfig{Language: &language},
),
agentkit.WithAdvancedFeatures(&agentkit.AdvancedFeatures{
EnableRtm: boolPtr(true),
}),
agentkit.WithParameters(&agentkit.SessionParams{
DataChannel: &dataChannel,
EnableMetrics: boolPtr(true),
EnableErrorMessage: boolPtr(true),
}),
agentkit.WithAudioScenario(
agentkit.ParametersAudioScenario("chorus"),
),
).WithStt(
vendors.NewFengmingSTT(),
).WithLlm(llm).WithTts(tts)
}
包名仍然是 AgoraIO,别机械地替换成别的。区域是由 agentkit/cn 决定的,不是由包名决定的。
五. 创建和停止会话
package main
import (
"context"
"strconv"
"time"
agentkit "github.com/AgoraIO/agora-agents-go/v2/agentkit/cn"
)
func start(
agent *agentkit.Agent,
channel string,
agentUID int,
userUID int,
) (string, *agentkit.AgentSession, error) {
expiresIn, err := agentkit.ExpiresInHours(1)
if err != nil {
return "", nil, err
}
idleTimeout := 120
enableStringUID := false
session := agent.CreateSession(agentkit.CreateSessionOptions{
Channel: channel,
AgentUID: strconv.Itoa(agentUID),
RemoteUIDs: []string{strconv.Itoa(userUID)},
IdleTimeout: &idleTimeout,
EnableStringUID: &enableStringUID,
ExpiresIn: expiresIn,
})
ctx, cancel := context.WithTimeout(
context.Background(), 30*time.Second,
)
defer cancel()
agentID, err := session.Start(ctx)
return agentID, session, err
}
生产代码要按 agentID 保存 AgentSession,停止时优先调 session.Stop(ctx);服务重启后 session 映射就没了,这时用 AgoraClient.StopAgent(ctx, agentID) 兜底。
六. Gin 三段式 API
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
type startRequest struct {
ChannelName string `json:"channelName" binding:"required"`
RTCUID int `json:"rtcUid" binding:"required,gt=0"`
UserUID int `json:"userUid" binding:"required,gt=0"`
}
func addRoutes(router *gin.Engine, service *agentService) {
router.GET("/get_config", getConfigHandler(service))
router.POST("/startAgent", func(c *gin.Context) {
var body startRequest
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"detail": "请求参数无效",
})
return
}
agentID, err := service.start(
body.ChannelName, body.RTCUID, body.UserUID,
)
if err != nil {
c.JSON(http.StatusBadGateway, gin.H{
"detail": "启动智能体失败",
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"data": gin.H{"agent_id": agentID},
})
})
}
完整可编译的 agent.go、main.go、测试和依赖文件都在 code/golang 目录里。
七. 运行与验证
cd code/golang
cp .env.example .env.local
go mod download
go test ./...
go run .
先试一下配置接口:
curl http://127.0.0.1:8000/get_config
预期返回 App ID、短期 Token、用户 UID、智能体 UID 和频道名。注意检查返回里没有 App Certificate 和模型 Key。这一眼值得多看两秒,密钥泄漏最常见的方式就是顺手多返回了一个字段。
然后让客户端加入这个频道,再调用:
curl -X POST http://127.0.0.1:8000/startAgent \
-H 'Content-Type: application/json' \
-d '{"channelName":"REPLACE_WITH_CHANNEL","rtcUid":1001,"userUid":2001}'
验收标准和 Python 快速开始一样:中文开场白、至少两轮语音对话、双方字幕、指标事件、正常停止、RTC/RTM 资源释放干净。
八. 故障排查
- 请求发到了错误的接口地址:检查 import 路径里确实包含
/agentkit/cn,别和其他客户端混用。 - MiniMax 构造器 panic:Key、Model、
VoiceSetting.VoiceID和音频设置四项都得传,少一个就会 panic。 - 回复里出现思考内容:
Params里要传thinking: {type: disabled}。 - Token 生成失败:检查 App ID、主要证书、频道名和 UID。再提醒一次,主要证书不能放前端。
九. 下一步
后端起来了,配一个前端就能开口说话——直接复用现成的客户端。
《Next.js 搭建浏览器端语音智能体》