Merge pull request #73 from dj-oyu/feat/per-provider-streaming

feat: per-provider SSE streaming via model_list stream field
This commit is contained in:
dj-oyu 2026-03-21 02:20:00 +09:00 committed by GitHub
commit fc6714b52b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 36 additions and 21 deletions

View file

@ -87,13 +87,7 @@ func CreateProviderFromConfig(cfg *config.ModelConfig) (LLMProvider, string, err
if apiBase == "" {
apiBase = getDefaultAPIBase(protocol)
}
return NewHTTPProviderWithMaxTokensFieldAndRequestTimeout(
cfg.APIKey,
apiBase,
cfg.Proxy,
cfg.MaxTokensField,
cfg.RequestTimeout,
), modelID, nil
return NewHTTPProviderFromConfig(cfg, apiBase), modelID, nil
case "azure", "azure-openai":
// Azure OpenAI uses deployment-based URLs, api-key header auth,
@ -126,13 +120,7 @@ func CreateProviderFromConfig(cfg *config.ModelConfig) (LLMProvider, string, err
if apiBase == "" {
apiBase = getDefaultAPIBase(protocol)
}
return NewHTTPProviderWithMaxTokensFieldAndRequestTimeout(
cfg.APIKey,
apiBase,
cfg.Proxy,
cfg.MaxTokensField,
cfg.RequestTimeout,
), modelID, nil
return NewHTTPProviderFromConfig(cfg, apiBase), modelID, nil
case "anthropic":
if cfg.AuthMethod == "oauth" || cfg.AuthMethod == "token" {
@ -151,13 +139,7 @@ func CreateProviderFromConfig(cfg *config.ModelConfig) (LLMProvider, string, err
if cfg.APIKey == "" {
return nil, "", fmt.Errorf("api_key is required for anthropic protocol (model: %s)", cfg.Model)
}
return NewHTTPProviderWithMaxTokensFieldAndRequestTimeout(
cfg.APIKey,
apiBase,
cfg.Proxy,
cfg.MaxTokensField,
cfg.RequestTimeout,
), modelID, nil
return NewHTTPProviderFromConfig(cfg, apiBase), modelID, nil
case "anthropic-messages":
// Anthropic Messages API with native format (HTTP-based, no SDK)

View file

@ -10,7 +10,9 @@ import (
"context"
"time"
"github.com/sipeed/picoclaw/pkg/config"
"github.com/sipeed/picoclaw/pkg/providers/openai_compat"
"github.com/sipeed/picoclaw/pkg/providers/protocoltypes"
)
type HTTPProvider struct {
@ -42,6 +44,21 @@ func NewHTTPProviderWithMaxTokensFieldAndRequestTimeout(
}
}
// NewHTTPProviderFromConfig creates an HTTPProvider from a ModelConfig,
// honoring all optional fields including stream.
func NewHTTPProviderFromConfig(cfg *config.ModelConfig, apiBase string) *HTTPProvider {
opts := []openai_compat.Option{
openai_compat.WithMaxTokensField(cfg.MaxTokensField),
openai_compat.WithRequestTimeout(time.Duration(cfg.RequestTimeout) * time.Second),
}
if cfg.Stream != nil && *cfg.Stream {
opts = append(opts, openai_compat.WithStream(true))
}
return &HTTPProvider{
delegate: openai_compat.NewProvider(cfg.APIKey, apiBase, cfg.Proxy, opts...),
}
}
func (p *HTTPProvider) Chat(
ctx context.Context,
messages []Message,
@ -52,6 +69,22 @@ func (p *HTTPProvider) Chat(
return p.delegate.Chat(ctx, messages, tools, model, options)
}
// CanStream returns true when SSE streaming is enabled.
func (p *HTTPProvider) CanStream() bool {
return p.delegate.CanStream()
}
// ChatStream opens an SSE connection and returns a channel of StreamEvent.
func (p *HTTPProvider) ChatStream(
ctx context.Context,
messages []Message,
tools []ToolDefinition,
model string,
options map[string]any,
) (<-chan protocoltypes.StreamEvent, error) {
return p.delegate.ChatStream(ctx, messages, tools, model, options)
}
func (p *HTTPProvider) GetDefaultModel() string {
return ""
}