Files
ai-agent-scaffold-go/internal/infrastructure/ai/eino_adapter.go

118 lines
2.8 KiB
Go
Raw Normal View History

2026-05-30 23:16:49 +08:00
package ai
import (
"context"
"fmt"
"strings"
"time"
"ai-agent-scaffold-go/internal/domain/agent/model"
"ai-agent-scaffold-go/internal/domain/agent/ports"
)
type EinoProvider struct {
requestTimeout time.Duration
}
type EinoAPIConfig struct {
BaseURL string
APIKey string
CompletionsPath string
EmbeddingsPath string
}
type EinoChatModel struct {
client *OpenAIClient
tools []ports.Tool
}
type EinoTool struct {
ToolName string
}
func NewEinoProvider() *EinoProvider {
return &EinoProvider{requestTimeout: 5 * time.Minute}
}
func (p *EinoProvider) WithRequestTimeout(timeout time.Duration) *EinoProvider {
if timeout > 0 {
p.requestTimeout = timeout
}
return p
}
func (p *EinoProvider) NewAPI(_ context.Context, config model.AiAPIConfig) (ports.ModelAPI, error) {
if strings.TrimSpace(config.BaseURL) == "" {
return nil, fmt.Errorf("base url is required")
}
if strings.TrimSpace(config.APIKey) == "" {
return nil, fmt.Errorf("api key is required")
}
return EinoAPIConfig{
BaseURL: config.BaseURL,
APIKey: config.APIKey,
CompletionsPath: config.CompletionsPath,
EmbeddingsPath: config.EmbeddingsPath,
}, nil
}
func (p *EinoProvider) NewChatModel(_ context.Context, api ports.ModelAPI, config model.ChatModelConfig, tools []ports.Tool) (ports.ChatModel, error) {
if api == nil {
return nil, fmt.Errorf("model api is required")
}
if strings.TrimSpace(config.Model) == "" {
return nil, fmt.Errorf("model is required")
}
apiCfg, ok := api.(EinoAPIConfig)
if !ok {
return nil, fmt.Errorf("unsupported model api type %T", api)
}
completionsURL := joinURL(apiCfg.BaseURL, apiCfg.CompletionsPath)
timeout := p.requestTimeout
if timeout <= 0 {
timeout = 5 * time.Minute
}
client := NewOpenAIClient(completionsURL, apiCfg.APIKey, config.Model, timeout)
return &EinoChatModel{client: client, tools: tools}, nil
}
func (m *EinoChatModel) Tools() []ports.Tool {
return m.tools
}
func (m *EinoChatModel) Generate(ctx context.Context, messages []ports.ChatMessage) (ports.ChatReply, error) {
return m.client.Generate(ctx, messages, m.toolDefs())
}
func (m *EinoChatModel) Stream(ctx context.Context, messages []ports.ChatMessage) (<-chan ports.ChatStreamEvent, <-chan error) {
return m.client.Stream(ctx, messages, m.toolDefs())
}
func (m *EinoChatModel) toolDefs() []OpenAIToolDef {
if len(m.tools) == 0 {
return nil
}
defs := make([]OpenAIToolDef, 0, len(m.tools))
for _, tool := range m.tools {
desc := ""
if d, ok := tool.(ports.ToolDescriptor); ok {
desc = d.Description()
}
defs = append(defs, OpenAIToolDef{Name: tool.Name(), Description: desc})
}
return defs
}
func (t EinoTool) Name() string {
return t.ToolName
}
func joinURL(base, path string) string {
base = strings.TrimRight(base, "/")
path = strings.TrimLeft(path, "/")
if path == "" {
return base
}
return base + "/" + path
}