Refactor model capabilities to use OpenAI struct

- Updated the model capabilities throughout the agent to utilize the new gouOpenAI.Capabilities struct instead of the previous ModelCapabilities.
- Adjusted related methods and types to ensure compatibility with the new capabilities structure, enhancing clarity and maintainability.
- Improved context handling and message processing by directly integrating OpenAI capabilities, streamlining the overall architecture.
This commit is contained in:
Max 2025-12-02 11:33:09 +08:00
parent 7e8b2d8d9f
commit 3dd63e5530
21 changed files with 244 additions and 363 deletions

View file

@ -6,6 +6,7 @@ import (
jsoniter "github.com/json-iterator/go" jsoniter "github.com/json-iterator/go"
"github.com/yaoapp/gou/connector" "github.com/yaoapp/gou/connector"
"github.com/yaoapp/gou/connector/openai"
"github.com/yaoapp/kun/log" "github.com/yaoapp/kun/log"
"github.com/yaoapp/yao/agent/assistant/handlers" "github.com/yaoapp/yao/agent/assistant/handlers"
"github.com/yaoapp/yao/agent/context" "github.com/yaoapp/yao/agent/context"
@ -303,7 +304,7 @@ func (ast *Assistant) Stream(ctx *context.Context, inputMessages []context.Messa
// GetConnector get the connector object, capabilities, and error with priority: createResponse > ctx > ast // GetConnector get the connector object, capabilities, and error with priority: createResponse > ctx > ast
// Note: createResponse.Connector is already applied to ctx.Connector by applyContextAdjustments in create.go // Note: createResponse.Connector is already applied to ctx.Connector by applyContextAdjustments in create.go
// Returns: (connector, capabilities, error) // Returns: (connector, capabilities, error)
func (ast *Assistant) GetConnector(ctx *context.Context) (connector.Connector, *context.ModelCapabilities, error) { func (ast *Assistant) GetConnector(ctx *context.Context) (connector.Connector, *openai.Capabilities, error) {
// Determine connector ID with priority // Determine connector ID with priority
connectorID := ast.Connector connectorID := ast.Connector
if ctx.Connector != "" { if ctx.Connector != "" {
@ -328,62 +329,27 @@ func (ast *Assistant) GetConnector(ctx *context.Context) (connector.Connector, *
} }
// getConnectorCapabilities get the capabilities of a connector from settings // getConnectorCapabilities get the capabilities of a connector from settings
func (ast *Assistant) getConnectorCapabilities(connectorID string) *context.ModelCapabilities { func (ast *Assistant) getConnectorCapabilities(connectorID string) *openai.Capabilities {
// Initialize with default capabilities (all disabled)
falseVal := false
capabilities := &context.ModelCapabilities{
Vision: falseVal,
ToolCalls: &falseVal,
Audio: &falseVal,
Reasoning: &falseVal,
Streaming: &falseVal,
}
// Get model capabilities from global configuration // Get model capabilities from global configuration
modelCaps, exists := modelCapabilities[connectorID] modelCaps, exists := modelCapabilities[connectorID]
if !exists { if !exists {
// Return default capabilities if model not found in configuration // Return default capabilities if model not found in configuration
return capabilities falseVal := false
return &openai.Capabilities{
Vision: falseVal,
ToolCalls: false,
Audio: false,
Reasoning: false,
Streaming: false,
JSON: false,
Multimodal: false,
TemperatureAdjustable: true, // Default to true for non-reasoning models
}
} }
// Update capabilities based on model configuration // Return capabilities directly
// Vision can be bool or string (VisionFormat) // Note: TemperatureAdjustable is automatically set in connector.Setting() based on Reasoning flag
if modelCaps.Vision != nil { return &modelCaps
capabilities.Vision = modelCaps.Vision
}
// Handle both Tools (deprecated) and ToolCalls
if modelCaps.ToolCalls || modelCaps.Tools {
v := true
capabilities.ToolCalls = &v
}
if modelCaps.Audio {
v := true
capabilities.Audio = &v
}
if modelCaps.Reasoning {
v := true
capabilities.Reasoning = &v
}
if modelCaps.Streaming {
v := true
capabilities.Streaming = &v
}
if modelCaps.JSON {
v := true
capabilities.JSON = &v
}
if modelCaps.Multimodal {
v := true
capabilities.Multimodal = &v
}
return capabilities
} }
// Info get the assistant information // Info get the assistant information

View file

@ -11,6 +11,7 @@ import (
jsoniter "github.com/json-iterator/go" jsoniter "github.com/json-iterator/go"
"github.com/spf13/cast" "github.com/spf13/cast"
"github.com/yaoapp/gou/application" "github.com/yaoapp/gou/application"
gouOpenAI "github.com/yaoapp/gou/connector/openai"
"github.com/yaoapp/gou/fs" "github.com/yaoapp/gou/fs"
v8 "github.com/yaoapp/gou/runtime/v8" v8 "github.com/yaoapp/gou/runtime/v8"
"github.com/yaoapp/yao/agent/assistant/hook" "github.com/yaoapp/yao/agent/assistant/hook"
@ -26,7 +27,7 @@ import (
var loaded = NewCache(200) // 200 is the default capacity var loaded = NewCache(200) // 200 is the default capacity
var storage store.Store = nil var storage store.Store = nil
var search interface{} = nil var search interface{} = nil
var modelCapabilities map[string]ModelCapabilities = map[string]ModelCapabilities{} var modelCapabilities map[string]gouOpenAI.Capabilities = map[string]gouOpenAI.Capabilities{}
var defaultConnector string = "" // default connector var defaultConnector string = "" // default connector
var globalUses *context.Uses = nil // global uses configuration from agent.yml var globalUses *context.Uses = nil // global uses configuration from agent.yml
@ -131,7 +132,7 @@ func SetStorage(s store.Store) {
} }
// SetModelCapabilities set the model capabilities configuration // SetModelCapabilities set the model capabilities configuration
func SetModelCapabilities(capabilities map[string]ModelCapabilities) { func SetModelCapabilities(capabilities map[string]gouOpenAI.Capabilities) {
modelCapabilities = capabilities modelCapabilities = capabilities
} }

View file

@ -3,6 +3,7 @@ package assistant
import ( import (
"fmt" "fmt"
"github.com/yaoapp/gou/connector/openai"
"github.com/yaoapp/kun/log" "github.com/yaoapp/kun/log"
"github.com/yaoapp/yao/agent/context" "github.com/yaoapp/yao/agent/context"
"github.com/yaoapp/yao/agent/i18n" "github.com/yaoapp/yao/agent/i18n"
@ -48,7 +49,7 @@ func (ast *Assistant) traceCreateHook(agentNode types.Node, createResponse *cont
} }
// traceConnectorCapabilities logs the connector capabilities to the agent trace node // traceConnectorCapabilities logs the connector capabilities to the agent trace node
func (ast *Assistant) traceConnectorCapabilities(agentNode types.Node, capabilities *context.ModelCapabilities) { func (ast *Assistant) traceConnectorCapabilities(agentNode types.Node, capabilities *openai.Capabilities) {
if agentNode == nil { if agentNode == nil {
return return
} }

View file

@ -40,19 +40,6 @@ type Assistant struct {
// toolCalls bool // Whether this assistant supports tool_calls // toolCalls bool // Whether this assistant supports tool_calls
} }
// ModelCapabilities defines the capabilities of a language model
// This configuration is loaded from agent/models.yml
type ModelCapabilities struct {
Vision interface{} `json:"vision,omitempty" yaml:"vision,omitempty"` // Supports vision/image input: bool or VisionFormat string ("openai", "claude"/"base64", "default")
Tools bool `json:"tools,omitempty" yaml:"tools,omitempty"` // Supports tool/function calling (deprecated, use ToolCalls)
ToolCalls bool `json:"tool_calls,omitempty" yaml:"tool_calls,omitempty"` // Supports tool/function calling
Audio bool `json:"audio,omitempty" yaml:"audio,omitempty"` // Supports audio input/output
Reasoning bool `json:"reasoning,omitempty" yaml:"reasoning,omitempty"` // Supports reasoning/thinking mode (o1, DeepSeek R1)
Streaming bool `json:"streaming,omitempty" yaml:"streaming,omitempty"` // Supports streaming responses
JSON bool `json:"json,omitempty" yaml:"json,omitempty"` // Supports JSON mode
Multimodal bool `json:"multimodal,omitempty" yaml:"multimodal,omitempty"` // Supports multimodal input
}
// VisionCapableModels list of LLM models that support vision capabilities // VisionCapableModels list of LLM models that support vision capabilities
var VisionCapableModels = map[string]bool{ var VisionCapableModels = map[string]bool{
// OpenAI Models // OpenAI Models

View file

@ -3,6 +3,7 @@ package context
import ( import (
"time" "time"
"github.com/yaoapp/gou/connector/openai"
"github.com/yaoapp/yao/agent/output" "github.com/yaoapp/yao/agent/output"
"github.com/yaoapp/yao/agent/output/message" "github.com/yaoapp/yao/agent/output/message"
) )
@ -290,18 +291,10 @@ func (ctx *Context) getOutput() (*output.Output, error) {
Accept: string(ctx.Accept), Accept: string(ctx.Accept),
} }
// Convert ModelCapabilities to message.ModelCapabilities // Set ModelCapabilities (now using openai.Capabilities directly)
if ctx.Capabilities != nil { if ctx.Capabilities != nil {
options.Capabilities = &message.ModelCapabilities{ caps := openai.Capabilities(*ctx.Capabilities)
Vision: ctx.Capabilities.Vision, options.Capabilities = &caps
ToolCalls: ctx.Capabilities.ToolCalls,
Audio: ctx.Capabilities.Audio,
Reasoning: ctx.Capabilities.Reasoning,
Streaming: ctx.Capabilities.Streaming,
JSON: ctx.Capabilities.JSON,
Multimodal: ctx.Capabilities.Multimodal,
TemperatureAdjustable: ctx.Capabilities.TemperatureAdjustable,
}
} }
var err error var err error

View file

@ -5,6 +5,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/yaoapp/gou/connector/openai"
"github.com/yaoapp/gou/plan" "github.com/yaoapp/gou/plan"
"github.com/yaoapp/gou/store" "github.com/yaoapp/gou/store"
"github.com/yaoapp/yao/agent/output" "github.com/yaoapp/yao/agent/output"
@ -238,7 +239,7 @@ type Context struct {
Skip *Skip `json:"skip,omitempty"` // Skip configuration (history, trace, etc.), nil means don't skip anything Skip *Skip `json:"skip,omitempty"` // Skip configuration (history, trace, etc.), nil means don't skip anything
// Model capabilities (set by assistant, used by output adapters) // Model capabilities (set by assistant, used by output adapters)
Capabilities *ModelCapabilities `json:"-"` // Model capabilities for the current connector Capabilities *openai.Capabilities `json:"-"` // Model capabilities for the current connector
// Interrupt control (all interrupt-related logic is encapsulated in InterruptController) // Interrupt control (all interrupt-related logic is encapsulated in InterruptController)
Interrupt *InterruptController `json:"-"` // Interrupt controller for handling user interrupts during streaming Interrupt *InterruptController `json:"-"` // Interrupt controller for handling user interrupts during streaming

View file

@ -1,6 +1,9 @@
package context package context
import "github.com/yaoapp/yao/agent/output/message" import (
"github.com/yaoapp/gou/connector/openai"
"github.com/yaoapp/yao/agent/output/message"
)
// Uses represents the wrapper configurations for assistant // Uses represents the wrapper configurations for assistant
// Used to specify which assistant or MCP server to use for vision, audio, search, and fetch operations // Used to specify which assistant or MCP server to use for vision, audio, search, and fetch operations
@ -28,17 +31,13 @@ const (
VisionFormatDefault VisionFormat = "default" VisionFormatDefault VisionFormat = "default"
) )
// ModelCapabilities defines the capabilities of a language model
// Used by LLM to select appropriate provider and validate requests
type ModelCapabilities message.ModelCapabilities
// GetVisionSupport returns whether vision is supported and the format // GetVisionSupport returns whether vision is supported and the format
func (m *ModelCapabilities) GetVisionSupport() (bool, VisionFormat) { func GetVisionSupport(cap *openai.Capabilities) (bool, VisionFormat) {
if m == nil || m.Vision == nil { if cap == nil || cap.Vision == nil {
return false, VisionFormatNone return false, VisionFormatNone
} }
switch v := m.Vision.(type) { switch v := cap.Vision.(type) {
case bool: case bool:
// Legacy bool format // Legacy bool format
return v, VisionFormatDefault return v, VisionFormatDefault
@ -65,7 +64,7 @@ func (m *ModelCapabilities) GetVisionSupport() (bool, VisionFormat) {
type CompletionOptions struct { type CompletionOptions struct {
// Model capabilities (used by LLM to select appropriate provider) // Model capabilities (used by LLM to select appropriate provider)
// nil means capabilities are not specified/checked // nil means capabilities are not specified/checked
Capabilities *ModelCapabilities `json:"capabilities,omitempty"` Capabilities *openai.Capabilities `json:"capabilities,omitempty"`
// User-specified tools for vision, audio, search, and fetch processing // User-specified tools for vision, audio, search, and fetch processing
Uses *Uses `json:"uses,omitempty"` Uses *Uses `json:"uses,omitempty"`

View file

@ -1,6 +1,7 @@
package adapters package adapters
import ( import (
"github.com/yaoapp/gou/connector/openai"
"github.com/yaoapp/yao/agent/context" "github.com/yaoapp/yao/agent/context"
) )
@ -28,7 +29,7 @@ type ReasoningAdapter struct {
// NewReasoningAdapter creates a new reasoning adapter // NewReasoningAdapter creates a new reasoning adapter
// If cap.TemperatureAdjustable is provided, it overrides the default behavior // If cap.TemperatureAdjustable is provided, it overrides the default behavior
func NewReasoningAdapter(format ReasoningFormat, cap *context.ModelCapabilities) *ReasoningAdapter { func NewReasoningAdapter(format ReasoningFormat, cap *openai.Capabilities) *ReasoningAdapter {
supportsEffort := false supportsEffort := false
supportsTemperature := true supportsTemperature := true
@ -49,8 +50,8 @@ func NewReasoningAdapter(format ReasoningFormat, cap *context.ModelCapabilities)
} }
// Override with explicit capability if provided // Override with explicit capability if provided
if cap != nil && cap.TemperatureAdjustable != nil { if cap != nil {
supportsTemperature = *cap.TemperatureAdjustable supportsTemperature = cap.TemperatureAdjustable
} }
return &ReasoningAdapter{ return &ReasoningAdapter{

View file

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"github.com/yaoapp/gou/connector" "github.com/yaoapp/gou/connector"
"github.com/yaoapp/gou/connector/openai"
"github.com/yaoapp/yao/agent/context" "github.com/yaoapp/yao/agent/context"
) )
@ -11,11 +12,11 @@ import (
// Provides common functionality for all LLM providers // Provides common functionality for all LLM providers
type Provider struct { type Provider struct {
Connector connector.Connector Connector connector.Connector
Capabilities *context.ModelCapabilities Capabilities *openai.Capabilities
} }
// NewProvider create a new base provider // NewProvider create a new base provider
func NewProvider(conn connector.Connector, capabilities *context.ModelCapabilities) *Provider { func NewProvider(conn connector.Connector, capabilities *openai.Capabilities) *Provider {
return &Provider{ return &Provider{
Connector: conn, Connector: conn,
Capabilities: capabilities, Capabilities: capabilities,
@ -74,33 +75,33 @@ func (p *Provider) SupportsVision() bool {
if p.Capabilities == nil { if p.Capabilities == nil {
return false return false
} }
supported, _ := p.Capabilities.GetVisionSupport() supported, _ := context.GetVisionSupport(p.Capabilities)
return supported return supported
} }
// SupportsAudio check if this provider supports audio // SupportsAudio check if this provider supports audio
func (p *Provider) SupportsAudio() bool { func (p *Provider) SupportsAudio() bool {
return p.Capabilities != nil && p.Capabilities.Audio != nil && *p.Capabilities.Audio return p.Capabilities != nil && p.Capabilities.Audio
} }
// SupportsTools check if this provider supports tool calls // SupportsTools check if this provider supports tool calls
func (p *Provider) SupportsTools() bool { func (p *Provider) SupportsTools() bool {
return p.Capabilities != nil && p.Capabilities.ToolCalls != nil && *p.Capabilities.ToolCalls return p.Capabilities != nil && p.Capabilities.ToolCalls
} }
// SupportsStreaming check if this provider supports streaming // SupportsStreaming check if this provider supports streaming
func (p *Provider) SupportsStreaming() bool { func (p *Provider) SupportsStreaming() bool {
return p.Capabilities != nil && p.Capabilities.Streaming != nil && *p.Capabilities.Streaming return p.Capabilities != nil && p.Capabilities.Streaming
} }
// SupportsJSON check if this provider supports JSON mode // SupportsJSON check if this provider supports JSON mode
func (p *Provider) SupportsJSON() bool { func (p *Provider) SupportsJSON() bool {
return p.Capabilities != nil && p.Capabilities.JSON != nil && *p.Capabilities.JSON return p.Capabilities != nil && p.Capabilities.JSON
} }
// SupportsReasoning check if this provider supports reasoning mode // SupportsReasoning check if this provider supports reasoning mode
func (p *Provider) SupportsReasoning() bool { func (p *Provider) SupportsReasoning() bool {
return p.Capabilities != nil && p.Capabilities.Reasoning != nil && *p.Capabilities.Reasoning return p.Capabilities != nil && p.Capabilities.Reasoning
} }
// GetConnectorSetting gets a setting value from the connector // GetConnectorSetting gets a setting value from the connector

View file

@ -5,6 +5,7 @@ import (
"testing" "testing"
"github.com/yaoapp/gou/connector" "github.com/yaoapp/gou/connector"
"github.com/yaoapp/gou/connector/openai"
"github.com/yaoapp/gou/plan" "github.com/yaoapp/gou/plan"
"github.com/yaoapp/yao/agent/context" "github.com/yaoapp/yao/agent/context"
"github.com/yaoapp/yao/agent/llm" "github.com/yaoapp/yao/agent/llm"
@ -60,15 +61,13 @@ func TestClaudeSonnet4StreamBasic(t *testing.T) {
t.Fatalf("Failed to select connector: %v", err) t.Fatalf("Failed to select connector: %v", err)
} }
trueVal := true
falseVal := false
options := &context.CompletionOptions{ options := &context.CompletionOptions{
Capabilities: &context.ModelCapabilities{ Capabilities: &openai.Capabilities{
Streaming: &trueVal, Streaming: true,
Reasoning: &falseVal, // Claude Sonnet 4 (non-thinking) doesn't expose reasoning Reasoning: false, // Claude Sonnet 4 (non-thinking) doesn't expose reasoning
ToolCalls: &trueVal, ToolCalls: true,
Vision: &trueVal, Vision: "claude", // Claude requires base64 format
Multimodal: &trueVal, Multimodal: true,
}, },
} }
@ -140,15 +139,13 @@ func TestClaudeSonnet4PostBasic(t *testing.T) {
t.Fatalf("Failed to select connector: %v", err) t.Fatalf("Failed to select connector: %v", err)
} }
trueVal := true
falseVal := false
options := &context.CompletionOptions{ options := &context.CompletionOptions{
Capabilities: &context.ModelCapabilities{ Capabilities: &openai.Capabilities{
Streaming: &falseVal, Streaming: false,
Reasoning: &falseVal, Reasoning: false,
ToolCalls: &trueVal, ToolCalls: true,
Vision: &trueVal, Vision: "claude", // Claude requires base64 format
Multimodal: &trueVal, Multimodal: true,
}, },
} }
@ -206,15 +203,13 @@ func TestClaudeSonnet4WithToolCalls(t *testing.T) {
t.Fatalf("Failed to select connector: %v", err) t.Fatalf("Failed to select connector: %v", err)
} }
trueVal := true
falseVal := false
options := &context.CompletionOptions{ options := &context.CompletionOptions{
Capabilities: &context.ModelCapabilities{ Capabilities: &openai.Capabilities{
Streaming: &falseVal, Streaming: false,
Reasoning: &falseVal, Reasoning: false,
ToolCalls: &trueVal, ToolCalls: true,
Vision: &trueVal, Vision: "claude", // Claude requires base64 format
Multimodal: &trueVal, Multimodal: true,
}, },
} }
@ -244,8 +239,8 @@ func TestClaudeSonnet4WithToolCalls(t *testing.T) {
options.Tools = []map[string]interface{}{simpleTool} options.Tools = []map[string]interface{}{simpleTool}
options.ToolChoice = "auto" options.ToolChoice = "auto"
// Set lower max_tokens for faster response // Set enough tokens for tool call response
maxTokens := 50 maxTokens := 150
options.MaxTokens = &maxTokens options.MaxTokens = &maxTokens
llmInstance, err := llm.New(conn, options) llmInstance, err := llm.New(conn, options)
@ -256,7 +251,7 @@ func TestClaudeSonnet4WithToolCalls(t *testing.T) {
messages := []context.Message{ messages := []context.Message{
{ {
Role: context.RoleUser, Role: context.RoleUser,
Content: "Call get_info with query='A' and count=1", Content: "Please use the get_info function to retrieve information. Pass 'A' as the query parameter and 1 as the count parameter.",
}, },
} }
@ -299,15 +294,13 @@ func TestClaudeSonnet4Vision(t *testing.T) {
t.Fatalf("Failed to select connector: %v", err) t.Fatalf("Failed to select connector: %v", err)
} }
trueVal := true
falseVal := false
options := &context.CompletionOptions{ options := &context.CompletionOptions{
Capabilities: &context.ModelCapabilities{ Capabilities: &openai.Capabilities{
Streaming: &falseVal, Streaming: false,
Reasoning: &falseVal, Reasoning: false,
ToolCalls: &trueVal, ToolCalls: true,
Vision: &trueVal, Vision: "claude", // Claude requires base64 format
Multimodal: &trueVal, Multimodal: true,
}, },
} }
@ -376,15 +369,13 @@ func TestClaudeSonnet4ThinkingStream(t *testing.T) {
t.Fatalf("Failed to select connector: %v", err) t.Fatalf("Failed to select connector: %v", err)
} }
trueVal := true
falseVal := false
options := &context.CompletionOptions{ options := &context.CompletionOptions{
Capabilities: &context.ModelCapabilities{ Capabilities: &openai.Capabilities{
Streaming: &trueVal, Streaming: true,
Reasoning: &trueVal, // Claude Thinking mode exposes reasoning Reasoning: true, // Claude Thinking mode exposes reasoning
ToolCalls: &falseVal, ToolCalls: false,
Vision: &trueVal, Vision: "claude", // Claude requires base64 format
Multimodal: &trueVal, Multimodal: true,
}, },
} }
@ -456,15 +447,13 @@ func TestClaudeSonnet4ThinkingPost(t *testing.T) {
t.Fatalf("Failed to select connector: %v", err) t.Fatalf("Failed to select connector: %v", err)
} }
trueVal := true
falseVal := false
options := &context.CompletionOptions{ options := &context.CompletionOptions{
Capabilities: &context.ModelCapabilities{ Capabilities: &openai.Capabilities{
Streaming: &falseVal, Streaming: false,
Reasoning: &trueVal, Reasoning: true,
ToolCalls: &falseVal, ToolCalls: false,
Vision: &trueVal, Vision: "claude", // Claude requires base64 format
Multimodal: &trueVal, Multimodal: true,
}, },
} }
@ -550,14 +539,12 @@ func TestClaudeTemperatureHandling(t *testing.T) {
t.Fatalf("Failed to select connector: %v", err) t.Fatalf("Failed to select connector: %v", err)
} }
trueVal := true
falseVal := false
options := &context.CompletionOptions{ options := &context.CompletionOptions{
Capabilities: &context.ModelCapabilities{ Capabilities: &openai.Capabilities{
Streaming: &falseVal, Streaming: false,
Reasoning: &tt.reasoning, Reasoning: tt.reasoning,
ToolCalls: &trueVal, ToolCalls: true,
Vision: &trueVal, Vision: true,
}, },
} }

View file

@ -7,6 +7,7 @@ import (
jsoniter "github.com/json-iterator/go" jsoniter "github.com/json-iterator/go"
"github.com/yaoapp/gou/connector" "github.com/yaoapp/gou/connector"
"github.com/yaoapp/gou/connector/openai"
"github.com/yaoapp/gou/plan" "github.com/yaoapp/gou/plan"
"github.com/yaoapp/yao/agent/context" "github.com/yaoapp/yao/agent/context"
"github.com/yaoapp/yao/agent/llm" "github.com/yaoapp/yao/agent/llm"
@ -28,16 +29,14 @@ func TestDeepSeekR1StreamBasic(t *testing.T) {
} }
// Create LLM instance with capabilities // Create LLM instance with capabilities
trueVal := true
falseVal := false
options := &context.CompletionOptions{ options := &context.CompletionOptions{
Capabilities: &context.ModelCapabilities{ Capabilities: &openai.Capabilities{
Streaming: &trueVal, Streaming: true,
Reasoning: &trueVal, // DeepSeek R1 supports reasoning Reasoning: true, // DeepSeek R1 supports reasoning
ToolCalls: &falseVal, // R1 doesn't support native tool calls ToolCalls: false, // R1 doesn't support native tool calls
Vision: &falseVal, Vision: false,
Audio: &falseVal, Audio: false,
Multimodal: &falseVal, Multimodal: false,
}, },
} }
@ -207,15 +206,13 @@ func TestDeepSeekR1PostBasic(t *testing.T) {
} }
// Create LLM instance // Create LLM instance
trueVal := true
falseVal := false
options := &context.CompletionOptions{ options := &context.CompletionOptions{
Capabilities: &context.ModelCapabilities{ Capabilities: &openai.Capabilities{
Reasoning: &trueVal, Reasoning: true,
ToolCalls: &falseVal, ToolCalls: false,
Vision: &falseVal, Vision: false,
Audio: &falseVal, Audio: false,
Multimodal: &falseVal, Multimodal: false,
}, },
} }
@ -295,16 +292,14 @@ func TestDeepSeekR1LogicPuzzle(t *testing.T) {
t.Fatalf("Failed to select connector: %v", err) t.Fatalf("Failed to select connector: %v", err)
} }
trueVal := true
falseVal := false
options := &context.CompletionOptions{ options := &context.CompletionOptions{
Capabilities: &context.ModelCapabilities{ Capabilities: &openai.Capabilities{
Streaming: &trueVal, Streaming: true,
Reasoning: &trueVal, Reasoning: true,
ToolCalls: &falseVal, ToolCalls: false,
Vision: &falseVal, Vision: false,
Audio: &falseVal, Audio: false,
Multimodal: &falseVal, Multimodal: false,
}, },
} }

View file

@ -5,6 +5,7 @@ import (
"testing" "testing"
"github.com/yaoapp/gou/connector" "github.com/yaoapp/gou/connector"
"github.com/yaoapp/gou/connector/openai"
"github.com/yaoapp/gou/plan" "github.com/yaoapp/gou/plan"
"github.com/yaoapp/yao/agent/context" "github.com/yaoapp/yao/agent/context"
"github.com/yaoapp/yao/agent/llm" "github.com/yaoapp/yao/agent/llm"
@ -24,16 +25,14 @@ func TestDeepSeekV3StreamBasic(t *testing.T) {
t.Fatalf("Failed to select connector: %v", err) t.Fatalf("Failed to select connector: %v", err)
} }
trueVal := true
falseVal := false
options := &context.CompletionOptions{ options := &context.CompletionOptions{
Capabilities: &context.ModelCapabilities{ Capabilities: &openai.Capabilities{
Streaming: &trueVal, Streaming: true,
Reasoning: &falseVal, // V3 doesn't support reasoning Reasoning: false, // V3 doesn't support reasoning
ToolCalls: &trueVal, // V3 supports tool calls ToolCalls: true, // V3 supports tool calls
Vision: &falseVal, Vision: false,
Audio: &falseVal, Audio: false,
Multimodal: &falseVal, Multimodal: false,
}, },
} }
@ -135,15 +134,13 @@ func TestDeepSeekV3PostBasic(t *testing.T) {
t.Fatalf("Failed to select connector: %v", err) t.Fatalf("Failed to select connector: %v", err)
} }
trueVal := true
falseVal := false
options := &context.CompletionOptions{ options := &context.CompletionOptions{
Capabilities: &context.ModelCapabilities{ Capabilities: &openai.Capabilities{
Reasoning: &falseVal, Reasoning: false,
ToolCalls: &trueVal, ToolCalls: true,
Vision: &falseVal, Vision: false,
Audio: &falseVal, Audio: false,
Multimodal: &falseVal, Multimodal: false,
}, },
} }
@ -226,12 +223,10 @@ func TestDeepSeekV3WithToolCalls(t *testing.T) {
t.Fatalf("Failed to select connector: %v", err) t.Fatalf("Failed to select connector: %v", err)
} }
trueVal := true
falseVal := false
options := &context.CompletionOptions{ options := &context.CompletionOptions{
Capabilities: &context.ModelCapabilities{ Capabilities: &openai.Capabilities{
Reasoning: &falseVal, Reasoning: false,
ToolCalls: &trueVal, ToolCalls: true,
}, },
} }
@ -318,13 +313,11 @@ func TestDeepSeekV3NoReasoningEffort(t *testing.T) {
t.Fatalf("Failed to select connector: %v", err) t.Fatalf("Failed to select connector: %v", err)
} }
trueVal := true
falseVal := false
effort := "high" effort := "high"
options := &context.CompletionOptions{ options := &context.CompletionOptions{
Capabilities: &context.ModelCapabilities{ Capabilities: &openai.Capabilities{
Reasoning: &falseVal, // V3 doesn't support reasoning Reasoning: false, // V3 doesn't support reasoning
ToolCalls: &trueVal, ToolCalls: true,
}, },
ReasoningEffort: &effort, // Should be ignored by adapter ReasoningEffort: &effort, // Should be ignored by adapter
} }

View file

@ -5,6 +5,7 @@ import (
"testing" "testing"
"github.com/yaoapp/gou/connector" "github.com/yaoapp/gou/connector"
"github.com/yaoapp/gou/connector/openai"
"github.com/yaoapp/gou/plan" "github.com/yaoapp/gou/plan"
"github.com/yaoapp/yao/agent/context" "github.com/yaoapp/yao/agent/context"
"github.com/yaoapp/yao/agent/llm" "github.com/yaoapp/yao/agent/llm"
@ -24,14 +25,13 @@ func TestGPT5StreamBasic(t *testing.T) {
t.Fatalf("Failed to select connector: %v", err) t.Fatalf("Failed to select connector: %v", err)
} }
trueVal := true
options := &context.CompletionOptions{ options := &context.CompletionOptions{
Capabilities: &context.ModelCapabilities{ Capabilities: &openai.Capabilities{
Streaming: &trueVal, Streaming: true,
Reasoning: &trueVal, // GPT-5 supports reasoning Reasoning: true, // GPT-5 supports reasoning
ToolCalls: &trueVal, ToolCalls: true,
Vision: &trueVal, Vision: true,
Multimodal: &trueVal, Multimodal: true,
}, },
} }
@ -105,11 +105,10 @@ func TestGPT5ReasoningEffort(t *testing.T) {
for _, effort := range effortLevels { for _, effort := range effortLevels {
t.Run("effort_"+effort, func(t *testing.T) { t.Run("effort_"+effort, func(t *testing.T) {
trueVal := true
options := &context.CompletionOptions{ options := &context.CompletionOptions{
Capabilities: &context.ModelCapabilities{ Capabilities: &openai.Capabilities{
Reasoning: &trueVal, Reasoning: true,
ToolCalls: &trueVal, ToolCalls: true,
}, },
ReasoningEffort: &effort, ReasoningEffort: &effort,
} }
@ -172,11 +171,10 @@ func TestGPT5PostWithToolCalls(t *testing.T) {
t.Fatalf("Failed to select connector: %v", err) t.Fatalf("Failed to select connector: %v", err)
} }
trueVal := true
options := &context.CompletionOptions{ options := &context.CompletionOptions{
Capabilities: &context.ModelCapabilities{ Capabilities: &openai.Capabilities{
Reasoning: &trueVal, Reasoning: true,
ToolCalls: &trueVal, ToolCalls: true,
}, },
} }
@ -259,12 +257,11 @@ func TestGPT5Vision(t *testing.T) {
t.Fatalf("Failed to select connector: %v", err) t.Fatalf("Failed to select connector: %v", err)
} }
trueVal := true
options := &context.CompletionOptions{ options := &context.CompletionOptions{
Capabilities: &context.ModelCapabilities{ Capabilities: &openai.Capabilities{
Reasoning: &trueVal, Reasoning: true,
Vision: &trueVal, Vision: true,
Multimodal: &trueVal, Multimodal: true,
}, },
} }
@ -331,13 +328,11 @@ func TestGPT5ReasoningEffortWithGPT4o(t *testing.T) {
t.Fatalf("Failed to select connector: %v", err) t.Fatalf("Failed to select connector: %v", err)
} }
trueVal := true
falseVal := false
effort := "high" effort := "high"
options := &context.CompletionOptions{ options := &context.CompletionOptions{
Capabilities: &context.ModelCapabilities{ Capabilities: &openai.Capabilities{
Reasoning: &falseVal, // GPT-4o doesn't support reasoning Reasoning: false, // GPT-4o doesn't support reasoning
ToolCalls: &trueVal, ToolCalls: true,
}, },
ReasoningEffort: &effort, // Should be ignored by adapter ReasoningEffort: &effort, // Should be ignored by adapter
} }

View file

@ -8,6 +8,7 @@ import (
jsoniter "github.com/json-iterator/go" jsoniter "github.com/json-iterator/go"
"github.com/yaoapp/gou/connector" "github.com/yaoapp/gou/connector"
gouOpenAI "github.com/yaoapp/gou/connector/openai"
"github.com/yaoapp/gou/http" "github.com/yaoapp/gou/http"
"github.com/yaoapp/kun/log" "github.com/yaoapp/kun/log"
"github.com/yaoapp/yao/agent/context" "github.com/yaoapp/yao/agent/context"
@ -142,7 +143,7 @@ func buildAPIURL(host, endpoint string) string {
} }
// New create a new OpenAI provider with capability adapters // New create a new OpenAI provider with capability adapters
func New(conn connector.Connector, capabilities *context.ModelCapabilities) *Provider { func New(conn connector.Connector, capabilities *gouOpenAI.Capabilities) *Provider {
return &Provider{ return &Provider{
Provider: base.NewProvider(conn, capabilities), Provider: base.NewProvider(conn, capabilities),
adapters: buildAdapters(capabilities), adapters: buildAdapters(capabilities),
@ -150,7 +151,7 @@ func New(conn connector.Connector, capabilities *context.ModelCapabilities) *Pro
} }
// buildAdapters builds capability adapters based on model capabilities // buildAdapters builds capability adapters based on model capabilities
func buildAdapters(cap *context.ModelCapabilities) []adapters.CapabilityAdapter { func buildAdapters(cap *gouOpenAI.Capabilities) []adapters.CapabilityAdapter {
if cap == nil { if cap == nil {
return []adapters.CapabilityAdapter{} return []adapters.CapabilityAdapter{}
} }
@ -158,12 +159,10 @@ func buildAdapters(cap *context.ModelCapabilities) []adapters.CapabilityAdapter
result := make([]adapters.CapabilityAdapter, 0) result := make([]adapters.CapabilityAdapter, 0)
// Tool call adapter // Tool call adapter
if cap.ToolCalls != nil { result = append(result, adapters.NewToolCallAdapter(cap.ToolCalls))
result = append(result, adapters.NewToolCallAdapter(*cap.ToolCalls))
}
// Vision adapter // Vision adapter
visionSupport, visionFormat := cap.GetVisionSupport() visionSupport, visionFormat := context.GetVisionSupport(cap)
if visionSupport { if visionSupport {
result = append(result, adapters.NewVisionAdapter(true, visionFormat)) result = append(result, adapters.NewVisionAdapter(true, visionFormat))
} else if cap.Vision != nil { } else if cap.Vision != nil {
@ -172,31 +171,27 @@ func buildAdapters(cap *context.ModelCapabilities) []adapters.CapabilityAdapter
} }
// Audio adapter // Audio adapter
if cap.Audio != nil { result = append(result, adapters.NewAudioAdapter(cap.Audio))
result = append(result, adapters.NewAudioAdapter(*cap.Audio))
}
// Reasoning adapter (always add to handle reasoning_effort and temperature parameters) // Reasoning adapter (always add to handle reasoning_effort and temperature parameters)
// Even if the model doesn't support reasoning, we need the adapter to strip reasoning_effort // Even if the model doesn't support reasoning, we need the adapter to strip reasoning_effort
if cap.Reasoning != nil { if cap.Reasoning {
if *cap.Reasoning { // Detect reasoning format based on capabilities
// Detect reasoning format based on capabilities format := detectReasoningFormat(cap)
format := detectReasoningFormat(cap) result = append(result, adapters.NewReasoningAdapter(format, cap))
result = append(result, adapters.NewReasoningAdapter(format, cap)) } else {
} else { // Model doesn't support reasoning, use None format to strip reasoning parameters
// Model doesn't support reasoning, use None format to strip reasoning parameters result = append(result, adapters.NewReasoningAdapter(adapters.ReasoningFormatNone, cap))
result = append(result, adapters.NewReasoningAdapter(adapters.ReasoningFormatNone, cap))
}
} }
return result return result
} }
// detectReasoningFormat detects the reasoning format based on capabilities // detectReasoningFormat detects the reasoning format based on capabilities
func detectReasoningFormat(cap *context.ModelCapabilities) adapters.ReasoningFormat { func detectReasoningFormat(cap *gouOpenAI.Capabilities) adapters.ReasoningFormat {
// TODO: Implement better detection logic // TODO: Implement better detection logic
// For now, default to OpenAI o1 format if reasoning is supported // For now, default to OpenAI o1 format if reasoning is supported
if cap.Reasoning != nil && *cap.Reasoning { if cap.Reasoning {
return adapters.ReasoningFormatOpenAI return adapters.ReasoningFormatOpenAI
} }
return adapters.ReasoningFormatNone return adapters.ReasoningFormatNone

View file

@ -8,6 +8,7 @@ import (
"time" "time"
"github.com/yaoapp/gou/connector" "github.com/yaoapp/gou/connector"
"github.com/yaoapp/gou/connector/openai"
"github.com/yaoapp/gou/plan" "github.com/yaoapp/gou/plan"
"github.com/yaoapp/yao/agent/context" "github.com/yaoapp/yao/agent/context"
"github.com/yaoapp/yao/agent/llm" "github.com/yaoapp/yao/agent/llm"
@ -29,11 +30,10 @@ func TestOpenAIStreamBasic(t *testing.T) {
} }
// Create LLM instance with capabilities // Create LLM instance with capabilities
trueVal := true
options := &context.CompletionOptions{ options := &context.CompletionOptions{
Capabilities: &context.ModelCapabilities{ Capabilities: &openai.Capabilities{
Streaming: &trueVal, Streaming: true,
ToolCalls: &trueVal, ToolCalls: true,
}, },
} }
@ -117,10 +117,9 @@ func TestOpenAIPostBasic(t *testing.T) {
} }
// Create LLM instance // Create LLM instance
trueVal := true
options := &context.CompletionOptions{ options := &context.CompletionOptions{
Capabilities: &context.ModelCapabilities{ Capabilities: &openai.Capabilities{
ToolCalls: &trueVal, ToolCalls: true,
}, },
} }
@ -192,11 +191,10 @@ func TestOpenAIStreamWithToolCalls(t *testing.T) {
} }
// Create LLM instance with tool call capabilities // Create LLM instance with tool call capabilities
trueVal := true
options := &context.CompletionOptions{ options := &context.CompletionOptions{
Capabilities: &context.ModelCapabilities{ Capabilities: &openai.Capabilities{
Streaming: &trueVal, Streaming: true,
ToolCalls: &trueVal, ToolCalls: true,
}, },
} }
@ -307,10 +305,9 @@ func TestOpenAIPostWithToolCalls(t *testing.T) {
} }
// Create LLM instance with tool call capabilities // Create LLM instance with tool call capabilities
trueVal := true
options := &context.CompletionOptions{ options := &context.CompletionOptions{
Capabilities: &context.ModelCapabilities{ Capabilities: &openai.Capabilities{
ToolCalls: &trueVal, ToolCalls: true,
}, },
} }
@ -424,11 +421,10 @@ func TestOpenAIStreamWithInvalidToolCall(t *testing.T) {
} }
// Create LLM instance // Create LLM instance
trueVal := true
options := &context.CompletionOptions{ options := &context.CompletionOptions{
Capabilities: &context.ModelCapabilities{ Capabilities: &openai.Capabilities{
Streaming: &trueVal, Streaming: true,
ToolCalls: &trueVal, ToolCalls: true,
}, },
} }
@ -523,11 +519,10 @@ func TestOpenAIStreamRetry(t *testing.T) {
} }
// Create LLM instance // Create LLM instance
trueVal := true
options := &context.CompletionOptions{ options := &context.CompletionOptions{
Capabilities: &context.ModelCapabilities{ Capabilities: &openai.Capabilities{
Streaming: &trueVal, Streaming: true,
ToolCalls: &trueVal, // Need this to select OpenAI provider ToolCalls: true, // Need this to select OpenAI provider
}, },
} }
@ -582,11 +577,10 @@ func TestOpenAIStreamChunkTypes(t *testing.T) {
t.Fatalf("Failed to select connector: %v", err) t.Fatalf("Failed to select connector: %v", err)
} }
trueVal := true
options := &context.CompletionOptions{ options := &context.CompletionOptions{
Capabilities: &context.ModelCapabilities{ Capabilities: &openai.Capabilities{
Streaming: &trueVal, Streaming: true,
ToolCalls: &trueVal, ToolCalls: true,
}, },
} }
@ -649,11 +643,10 @@ func TestOpenAIStreamErrorCallback(t *testing.T) {
t.Fatalf("Failed to create test connector: %v", err) t.Fatalf("Failed to create test connector: %v", err)
} }
trueVal := true
options := &context.CompletionOptions{ options := &context.CompletionOptions{
Capabilities: &context.ModelCapabilities{ Capabilities: &openai.Capabilities{
Streaming: &trueVal, Streaming: true,
ToolCalls: &trueVal, ToolCalls: true,
}, },
} }
@ -711,11 +704,10 @@ func TestOpenAIToolCallValidationRetry(t *testing.T) {
t.Fatalf("Failed to select connector: %v", err) t.Fatalf("Failed to select connector: %v", err)
} }
trueVal := true
options := &context.CompletionOptions{ options := &context.CompletionOptions{
Capabilities: &context.ModelCapabilities{ Capabilities: &openai.Capabilities{
Streaming: &trueVal, Streaming: true,
ToolCalls: &trueVal, ToolCalls: true,
}, },
Tools: []map[string]interface{}{ Tools: []map[string]interface{}{
{ {
@ -815,11 +807,10 @@ func TestOpenAIJSONMode(t *testing.T) {
t.Fatalf("Failed to select connector: %v", err) t.Fatalf("Failed to select connector: %v", err)
} }
trueVal := true
options := &context.CompletionOptions{ options := &context.CompletionOptions{
Capabilities: &context.ModelCapabilities{ Capabilities: &openai.Capabilities{
Streaming: &trueVal, Streaming: true,
ToolCalls: &trueVal, ToolCalls: true,
}, },
ResponseFormat: &context.ResponseFormat{ ResponseFormat: &context.ResponseFormat{
Type: context.ResponseFormatJSON, Type: context.ResponseFormatJSON,
@ -902,10 +893,9 @@ func TestOpenAIJSONModePost(t *testing.T) {
t.Fatalf("Failed to select connector: %v", err) t.Fatalf("Failed to select connector: %v", err)
} }
trueVal := true
options := &context.CompletionOptions{ options := &context.CompletionOptions{
Capabilities: &context.ModelCapabilities{ Capabilities: &openai.Capabilities{
ToolCalls: &trueVal, ToolCalls: true,
}, },
ResponseFormat: &context.ResponseFormat{ ResponseFormat: &context.ResponseFormat{
Type: context.ResponseFormatJSON, Type: context.ResponseFormatJSON,
@ -973,8 +963,6 @@ func TestOpenAIJSONSchema(t *testing.T) {
t.Fatalf("Failed to select connector: %v", err) t.Fatalf("Failed to select connector: %v", err)
} }
trueVal := true
// Define a strict JSON schema // Define a strict JSON schema
// Note: For OpenAI strict mode, 'required' must include ALL properties // Note: For OpenAI strict mode, 'required' must include ALL properties
schema := map[string]interface{}{ schema := map[string]interface{}{
@ -1009,9 +997,9 @@ func TestOpenAIJSONSchema(t *testing.T) {
} }
options := &context.CompletionOptions{ options := &context.CompletionOptions{
Capabilities: &context.ModelCapabilities{ Capabilities: &openai.Capabilities{
Streaming: &trueVal, Streaming: true,
ToolCalls: &trueVal, ToolCalls: true,
}, },
ResponseFormat: &context.ResponseFormat{ ResponseFormat: &context.ResponseFormat{
Type: context.ResponseFormatJSONSchema, Type: context.ResponseFormatJSONSchema,
@ -1019,7 +1007,7 @@ func TestOpenAIJSONSchema(t *testing.T) {
Name: "user_info", Name: "user_info",
Description: "User information schema", Description: "User information schema",
Schema: schema, Schema: schema,
Strict: &trueVal, Strict: func() *bool { v := true; return &v }(),
}, },
}, },
} }
@ -1121,8 +1109,6 @@ func TestOpenAIJSONSchemaPost(t *testing.T) {
t.Fatalf("Failed to select connector: %v", err) t.Fatalf("Failed to select connector: %v", err)
} }
trueVal := true
// Simple schema for testing // Simple schema for testing
// Note: For OpenAI strict mode, 'required' must include ALL properties // Note: For OpenAI strict mode, 'required' must include ALL properties
schema := map[string]interface{}{ schema := map[string]interface{}{
@ -1144,8 +1130,8 @@ func TestOpenAIJSONSchemaPost(t *testing.T) {
} }
options := &context.CompletionOptions{ options := &context.CompletionOptions{
Capabilities: &context.ModelCapabilities{ Capabilities: &openai.Capabilities{
ToolCalls: &trueVal, ToolCalls: true,
}, },
ResponseFormat: &context.ResponseFormat{ ResponseFormat: &context.ResponseFormat{
Type: context.ResponseFormatJSONSchema, Type: context.ResponseFormatJSONSchema,
@ -1153,7 +1139,7 @@ func TestOpenAIJSONSchemaPost(t *testing.T) {
Name: "api_response", Name: "api_response",
Description: "API response format", Description: "API response format",
Schema: schema, Schema: schema,
Strict: &trueVal, Strict: func() *bool { v := true; return &v }(),
}, },
}, },
} }
@ -1263,11 +1249,10 @@ func TestOpenAIStreamLifecycleEvents(t *testing.T) {
t.Fatalf("Failed to select connector: %v", err) t.Fatalf("Failed to select connector: %v", err)
} }
trueVal := true
options := &context.CompletionOptions{ options := &context.CompletionOptions{
Capabilities: &context.ModelCapabilities{ Capabilities: &openai.Capabilities{
Streaming: &trueVal, Streaming: true,
ToolCalls: &trueVal, ToolCalls: true,
}, },
} }
@ -1369,11 +1354,10 @@ func TestOpenAIStreamContextCancellation(t *testing.T) {
t.Fatalf("Failed to select connector: %v", err) t.Fatalf("Failed to select connector: %v", err)
} }
trueVal := true
options := &context.CompletionOptions{ options := &context.CompletionOptions{
Capabilities: &context.ModelCapabilities{ Capabilities: &openai.Capabilities{
Streaming: &trueVal, Streaming: true,
ToolCalls: &trueVal, ToolCalls: true,
}, },
} }
@ -1443,13 +1427,12 @@ func TestOpenAIStreamWithTemperature(t *testing.T) {
t.Fatalf("Failed to select connector: %v", err) t.Fatalf("Failed to select connector: %v", err)
} }
trueVal := true
temperature := 0.7 // Moderate temperature temperature := 0.7 // Moderate temperature
options := &context.CompletionOptions{ options := &context.CompletionOptions{
Capabilities: &context.ModelCapabilities{ Capabilities: &openai.Capabilities{
Streaming: &trueVal, Streaming: true,
ToolCalls: &trueVal, // Need this to select OpenAI provider ToolCalls: true, // Need this to select OpenAI provider
}, },
Temperature: &temperature, Temperature: &temperature,
} }

View file

@ -5,6 +5,7 @@ import (
"testing" "testing"
"github.com/yaoapp/gou/connector" "github.com/yaoapp/gou/connector"
"github.com/yaoapp/gou/connector/openai"
"github.com/yaoapp/gou/plan" "github.com/yaoapp/gou/plan"
"github.com/yaoapp/yao/agent/context" "github.com/yaoapp/yao/agent/context"
"github.com/yaoapp/yao/agent/llm" "github.com/yaoapp/yao/agent/llm"
@ -23,11 +24,10 @@ func TestTemperatureGPT5AutoReset(t *testing.T) {
t.Fatalf("Failed to select connector: %v", err) t.Fatalf("Failed to select connector: %v", err)
} }
trueVal := true
invalidTemp := 0.7 // GPT-5 doesn't support this invalidTemp := 0.7 // GPT-5 doesn't support this
options := &context.CompletionOptions{ options := &context.CompletionOptions{
Capabilities: &context.ModelCapabilities{ Capabilities: &openai.Capabilities{
Reasoning: &trueVal, Reasoning: true,
}, },
Temperature: &invalidTemp, // Should be reset to 1.0 Temperature: &invalidTemp, // Should be reset to 1.0
} }
@ -73,11 +73,10 @@ func TestTemperatureDeepSeekR1AutoReset(t *testing.T) {
t.Fatalf("Failed to select connector: %v", err) t.Fatalf("Failed to select connector: %v", err)
} }
trueVal := true
invalidTemp := 0.5 // DeepSeek R1 doesn't support this invalidTemp := 0.5 // DeepSeek R1 doesn't support this
options := &context.CompletionOptions{ options := &context.CompletionOptions{
Capabilities: &context.ModelCapabilities{ Capabilities: &openai.Capabilities{
Reasoning: &trueVal, Reasoning: true,
}, },
Temperature: &invalidTemp, // Should be reset to 1.0 Temperature: &invalidTemp, // Should be reset to 1.0
} }
@ -126,13 +125,11 @@ func TestTemperatureGPT4oPreserved(t *testing.T) {
t.Fatalf("Failed to select connector: %v", err) t.Fatalf("Failed to select connector: %v", err)
} }
trueVal := true
falseVal := false
customTemp := 0.3 // GPT-4o should preserve this customTemp := 0.3 // GPT-4o should preserve this
options := &context.CompletionOptions{ options := &context.CompletionOptions{
Capabilities: &context.ModelCapabilities{ Capabilities: &openai.Capabilities{
Reasoning: &falseVal, // Not a reasoning model Reasoning: false, // Not a reasoning model
ToolCalls: &trueVal, ToolCalls: true,
}, },
Temperature: &customTemp, // Should be preserved Temperature: &customTemp, // Should be preserved
} }
@ -178,13 +175,11 @@ func TestTemperatureDeepSeekV3Preserved(t *testing.T) {
t.Fatalf("Failed to select connector: %v", err) t.Fatalf("Failed to select connector: %v", err)
} }
trueVal := true
falseVal := false
customTemp := 0.8 // DeepSeek V3 should preserve this customTemp := 0.8 // DeepSeek V3 should preserve this
options := &context.CompletionOptions{ options := &context.CompletionOptions{
Capabilities: &context.ModelCapabilities{ Capabilities: &openai.Capabilities{
Reasoning: &falseVal, // Not a reasoning model Reasoning: false, // Not a reasoning model
ToolCalls: &trueVal, ToolCalls: true,
}, },
Temperature: &customTemp, // Should be preserved Temperature: &customTemp, // Should be preserved
} }
@ -230,11 +225,10 @@ func TestTemperatureGPT5Default(t *testing.T) {
t.Fatalf("Failed to select connector: %v", err) t.Fatalf("Failed to select connector: %v", err)
} }
trueVal := true
defaultTemp := 1.0 // GPT-5's valid temperature defaultTemp := 1.0 // GPT-5's valid temperature
options := &context.CompletionOptions{ options := &context.CompletionOptions{
Capabilities: &context.ModelCapabilities{ Capabilities: &openai.Capabilities{
Reasoning: &trueVal, Reasoning: true,
}, },
Temperature: &defaultTemp, // Should work fine Temperature: &defaultTemp, // Should work fine
} }
@ -293,16 +287,14 @@ func TestTemperatureNoTemperatureProvided(t *testing.T) {
t.Fatalf("Failed to select connector: %v", err) t.Fatalf("Failed to select connector: %v", err)
} }
trueVal := true
falseVal := false
options := &context.CompletionOptions{ options := &context.CompletionOptions{
Capabilities: &context.ModelCapabilities{ Capabilities: &openai.Capabilities{
Reasoning: &falseVal, Reasoning: false,
ToolCalls: &trueVal, ToolCalls: true,
}, },
} }
if tc.reasoning { if tc.reasoning {
options.Capabilities.Reasoning = &trueVal options.Capabilities.Reasoning = true
} }
// Temperature not set - should use API default // Temperature not set - should use API default

View file

@ -6,6 +6,7 @@ import (
"github.com/yaoapp/gou/application" "github.com/yaoapp/gou/application"
"github.com/yaoapp/gou/connector" "github.com/yaoapp/gou/connector"
gouOpenAI "github.com/yaoapp/gou/connector/openai"
"github.com/yaoapp/yao/agent/assistant" "github.com/yaoapp/yao/agent/assistant"
"github.com/yaoapp/yao/agent/context" "github.com/yaoapp/yao/agent/context"
"github.com/yaoapp/yao/agent/i18n" "github.com/yaoapp/yao/agent/i18n"
@ -116,7 +117,7 @@ func initModelCapabilities() error {
return err return err
} }
var models map[string]assistant.ModelCapabilities = map[string]assistant.ModelCapabilities{} var models map[string]gouOpenAI.Capabilities = map[string]gouOpenAI.Capabilities{}
err = application.Parse("models.yml", bytes, &models) err = application.Parse("models.yml", bytes, &models)
if err != nil { if err != nil {
return err return err

View file

@ -35,7 +35,7 @@ type AdapterConfig struct {
Locale string Locale string
} }
// ModelCapabilities is a simplified version of context.ModelCapabilities // ModelCapabilities is a simplified version of openai.Capabilities
// We use a local type to avoid circular dependencies // We use a local type to avoid circular dependencies
type ModelCapabilities struct { type ModelCapabilities struct {
Reasoning *bool // Supports reasoning/thinking mode (o1, DeepSeek R1) Reasoning *bool // Supports reasoning/thinking mode (o1, DeepSeek R1)

View file

@ -22,9 +22,10 @@ type Writer struct {
func NewWriter(options message.Options) (*Writer, error) { func NewWriter(options message.Options) (*Writer, error) {
// Get model capabilities from context (set by assistant) // Get model capabilities from context (set by assistant)
var capabilities *ModelCapabilities var capabilities *ModelCapabilities
if options.Capabilities != nil && options.Capabilities.Reasoning != nil { if options.Capabilities != nil && options.Capabilities.Reasoning {
v := true
capabilities = &ModelCapabilities{ capabilities = &ModelCapabilities{
Reasoning: options.Capabilities.Reasoning, Reasoning: &v,
} }
} }

View file

@ -3,6 +3,7 @@ package message
import ( import (
"net/http" "net/http"
"github.com/yaoapp/gou/connector/openai"
traceTypes "github.com/yaoapp/yao/trace/types" traceTypes "github.com/yaoapp/yao/trace/types"
) )
@ -12,23 +13,10 @@ type Options struct {
Accept string Accept string
Writer http.ResponseWriter Writer http.ResponseWriter
Trace traceTypes.Manager Trace traceTypes.Manager
Capabilities *ModelCapabilities Capabilities *openai.Capabilities
Locale string Locale string
} }
// ModelCapabilities defines the capabilities of a language model
// Used by LLM to select appropriate provider and validate requests
type ModelCapabilities struct {
Vision interface{} `json:"vision,omitempty"` // Supports vision/image input: bool or VisionFormat string ("openai", "claude"/"base64", "default")
ToolCalls *bool `json:"tool_calls,omitempty"` // Supports tool/function calling
Audio *bool `json:"audio,omitempty"` // Supports audio input/output
Reasoning *bool `json:"reasoning,omitempty"` // Supports reasoning/thinking mode (o1, DeepSeek R1)
Streaming *bool `json:"streaming,omitempty"` // Supports streaming responses
JSON *bool `json:"json,omitempty"` // Supports JSON mode
Multimodal *bool `json:"multimodal,omitempty"` // Supports multimodal input (text + images + audio)
TemperatureAdjustable *bool `json:"temperature_adjustable,omitempty"` // Supports temperature adjustment (reasoning models typically don't)
}
// Message represents a universal message structure (DSL) // Message represents a universal message structure (DSL)
// All messages are expressed through Type + Props, without predefining specific types // All messages are expressed through Type + Props, without predefining specific types
type Message struct { type Message struct {

View file

@ -1,6 +1,7 @@
package types package types
import ( import (
"github.com/yaoapp/gou/connector/openai"
"github.com/yaoapp/yao/agent/assistant" "github.com/yaoapp/yao/agent/assistant"
store "github.com/yaoapp/yao/agent/store/types" store "github.com/yaoapp/yao/agent/store/types"
) )
@ -16,7 +17,7 @@ type DSL struct {
// Global External Settings - model capabilities, tools, etc. // Global External Settings - model capabilities, tools, etc.
// =============================== // ===============================
Models map[string]assistant.ModelCapabilities `json:"models,omitempty" yaml:"models,omitempty"` // The model capabilities configuration Models map[string]openai.Capabilities `json:"models,omitempty" yaml:"models,omitempty"` // The model capabilities configuration
// Internal // Internal
// =============================== // ===============================