HTTP API
完整服务
示例将 Handler 挂载到应用自己的 HTTP 服务,用 Bearer token 保护全部路由,并禁用技能管理接口。聊天请求设有大小限制,线程保存为 JSONL 文件,服务仅监听本机回环地址。
export OPENAI_API_KEY='your-provider-key'
export AGENT_MODEL='your-model'
export CHAT_API_TOKEN='your-local-access-token'
go run ./example/http-serverSESSION_DIR 默认为 ./sessions。使用其他兼容模型服务时,可以设置 OPENAI_BASE_URL。实际部署应从安全的配置来源读取 token,避免将它留在 Shell 历史或源码中。
package main
import (
"context"
"crypto/subtle"
"errors"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"strings"
"syscall"
"time"
"github.com/covoyage/covonaut/agentcore"
"github.com/covoyage/covonaut/provider/chatcompat"
"github.com/covoyage/covonaut/server"
"github.com/covoyage/covonaut/session"
)
func main() {
if err := run(); err != nil {
log.Fatal(err)
}
}
func run() error {
apiKey, model, token := os.Getenv("OPENAI_API_KEY"), os.Getenv("AGENT_MODEL"), os.Getenv("CHAT_API_TOKEN")
if apiKey == "" || model == "" || token == "" {
return errors.New("set OPENAI_API_KEY, AGENT_MODEL, and CHAT_API_TOKEN")
}
directory := os.Getenv("SESSION_DIR")
if directory == "" {
directory = "./sessions"
}
sessions, err := session.NewFileStore(directory)
if err != nil {
return err
}
cfg := agentcore.NewConfig(
agentcore.WithModel(model),
agentcore.WithProvider(chatcompat.New(chatcompat.Config{
APIKey: apiKey, BaseURL: os.Getenv("OPENAI_BASE_URL"),
})),
agentcore.WithStore(session.NewAgentStore(sessions, ".")),
agentcore.WithStreaming(true),
agentcore.WithMaxTurns(10),
)
cfg.DisableSkillRegistryAPI = true
cfg.DisableSkillReloadAPI = true
application := server.New(cfg)
defer application.Close()
application.SetMaxRequestBodyBytes(1 << 20)
handler := application.Handler()
protected := http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
provided := strings.TrimPrefix(request.Header.Get("Authorization"), "Bearer ")
if !strings.HasPrefix(request.Header.Get("Authorization"), "Bearer ") ||
subtle.ConstantTimeCompare([]byte(provided), []byte(token)) != 1 {
http.Error(writer, "unauthorized", http.StatusUnauthorized)
return
}
handler.ServeHTTP(writer, request)
})
httpServer := &http.Server{
Addr: "127.0.0.1:8080", Handler: protected,
ReadHeaderTimeout: 5 * time.Second,
ReadTimeout: 30 * time.Second,
IdleTimeout: 2 * time.Minute,
}
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
finished := make(chan error, 1)
go func() { finished <- httpServer.ListenAndServe() }()
fmt.Println("Chat API: http://127.0.0.1:8080")
select {
case err := <-finished:
if errors.Is(err, http.ErrServerClosed) {
return nil
}
return err
case <-ctx.Done():
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
return httpServer.Shutdown(shutdownCtx)
}
}聊天请求
curl http://127.0.0.1:8080/api/chat \
-H "Authorization: Bearer $CHAT_API_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"message":"Hello","stream":false}'未提供线程 ID 时,会话存储会创建新线程。后续请求携带返回的 thread_id,即可继续对话。将 stream 设为 true 可以接收 SSE,客户端需要解析这些事件;本地测试时可用 curl -N。
每次请求可以设置 model、response_format、thinking 和 skills。请求配置优先于线程配置,线程配置优先于服务默认值。省略字段不一定会清除已经保存的值。
线程、分支与状态
会话指南列出了线程端点。创建分支时使用 entry_id,不要把数组下标当作条目 ID。读取、修改或清除线程配置及思考设置时,应遵循对应端点的约定。
/api/states 用于访问状态,与线程接口分开。/agui/{path} 接受 AG-UI 的请求格式,不使用上面的 ChatRequest。
安全与生命周期
框架不提供完整的身份认证服务。示例使用的共享 token 只适合本地单用户环境。生产环境应将线程与用户绑定,逐线程检查权限,并限制用户可选择的模型及调用配额。
CORS 不能代替身份认证。应明确配置允许的来源,不要通过代理公开所有状态。SetMaxRequestBodyBytes 不一定作用于单独挂载的 AG-UI Handler,应用需要为全部路由设置请求大小和速率限制。
挂载 Handler 时,由应用负责关闭流程:先等待正在处理的请求结束,再释放资源。代理的缓冲和超时设置需要适合 SSE。模型和工具也应设置各自的执行超时,HTTP 读取超时不能限制模型推理时间。