Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ check-eof:

clean:
rm -f coverage.out integration_coverage.out
go clean -testcache
go clean -cache -testcache

install-hooks:
./scripts/install-hooks.sh
Expand Down
28 changes: 28 additions & 0 deletions QUICKSTART.md
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,34 @@ totalCost += result.Usage.Cost
totalTokens += result.Usage.TotalTokens
```

Supported models are validated via the built-in model catalog:

```go
fmt.Println(dsgo.IsValidModel("openai/gpt-4o-mini"))
for _, m := range dsgo.ListModelsByProvider("openai") {
pricing, ok := dsgo.DefaultCostCalculator.GetPricing(m.ID)
if !ok {
fmt.Println(m.ID)
continue
}

fmt.Printf("%s price=%+v ctx=%d out=%d tools=%v json=%v\n",
m.ID,
pricing,
m.Limits.ContextTokens,
m.Limits.OutputTokens,
m.Capabilities.ToolCall,
m.Capabilities.StructuredOutput,
)
}
```

If you register a custom provider via `dsgo.RegisterLM`, you must also register its supported models:

```go
_ = dsgo.RegisterModel(dsgo.Model{ID: "myprovider/my-model"})
```

### Caching

DSGo includes automatic LRU caching:
Expand Down
28 changes: 28 additions & 0 deletions dsgo.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ package dsgo

import (
"github.com/assagman/dsgo/internal/core"
"github.com/assagman/dsgo/internal/cost"
"github.com/assagman/dsgo/internal/env"
"github.com/assagman/dsgo/internal/logging"
"github.com/assagman/dsgo/internal/mcp"
"github.com/assagman/dsgo/internal/modelcatalog"
"github.com/assagman/dsgo/internal/module"
signature_typed "github.com/assagman/dsgo/internal/signature_typed"

Expand Down Expand Up @@ -69,6 +71,17 @@ type (
ToolParameter = core.ToolParameter
)

// Re-export model catalog and cost types
type (
Model = modelcatalog.Model
Pricing = modelcatalog.Pricing
Limits = modelcatalog.Limits
Capabilities = modelcatalog.Capabilities
Modalities = modelcatalog.Modalities
Metadata = modelcatalog.Metadata
CostCalculator = cost.Calculator
)

// Re-export module types
type (
Predict = module.Predict
Expand Down Expand Up @@ -163,6 +176,7 @@ var (
WithModel = core.WithModel
WithTimeout = core.WithTimeout
WithLM = core.WithLM
WithSkipModelValidation = core.WithSkipModelValidation
WithAPIKey = core.WithAPIKey
WithMaxRetries = core.WithMaxRetries
WithTracing = core.WithTracing
Expand Down Expand Up @@ -198,6 +212,20 @@ var (
StripMarkers = core.StripMarkers
)

// Re-export model catalog and cost functions
var (
RegisterModel = modelcatalog.RegisterModel
RegisterAlias = modelcatalog.RegisterAlias
ResolveModel = modelcatalog.Resolve
IsValidModel = modelcatalog.IsValid
IsValidCanonicalModel = modelcatalog.IsValidCanonical
ListModels = modelcatalog.ListModels
ListModelsByProvider = modelcatalog.ListModelsByProvider
GetModel = modelcatalog.GetModel
DefaultCostCalculator = cost.DefaultCalculator
CalculateCost = cost.Calculate
)

// Re-export module functions
var (
NewPredict = module.NewPredict
Expand Down
36 changes: 35 additions & 1 deletion dsgo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func TestPackageInit(t *testing.T) {
},
{
name: "OpenRouter provider registered",
model: "openrouter/anthropic/claude-3.5-sonnet",
model: "openrouter/anthropic/claude-3.7-sonnet",
shouldWork: true,
expectedError: "",
skipIfNoAPIKey: true,
Expand Down Expand Up @@ -110,6 +110,40 @@ func TestPackageInit(t *testing.T) {
}
}

// TestModelCatalog verifies model catalog and cost APIs
func TestModelCatalog(t *testing.T) {
if !IsValidModel("openai/gpt-4o") {
t.Fatal("expected openai/gpt-4o to be valid")
}
if !IsValidModel("gpt-4o") {
t.Fatal("expected alias gpt-4o to be valid")
}
if IsValidModel("openai/does-not-exist") {
t.Fatal("expected unknown model to be invalid")
}

models := ListModelsByProvider("openai")
if len(models) == 0 {
t.Fatal("expected some openai models")
}

found := false
for _, m := range models {
if m.ID == "openai/gpt-4o" {
found = true
break
}
}
if !found {
t.Fatal("expected openai/gpt-4o to be listed")
}

pricing, ok := DefaultCostCalculator.GetPricing("openai/gpt-4o")
if !ok || pricing.PromptPrice == 0 {
t.Fatal("expected pricing for openai/gpt-4o")
}
}

// TestReexportedTypes verifies that all re-exported types are accessible and properly typed
func TestReexportedTypes(t *testing.T) {
// This test ensures that the type aliases in dsgo.go compile and are accessible
Expand Down
2 changes: 1 addition & 1 deletion examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@ Runnable examples for DSGo.
| `yaml_program/` | Declarative pipeline builder | `cd examples/yaml_program && go run main.go` | YAML config, MCP |

Notes:
- Use provider-prefixed model IDs (e.g. `openai/gpt-4o-mini`, `openrouter/anthropic/claude-3-opus`).
- Use provider-prefixed model IDs (e.g. `openai/gpt-4o-mini`, `openrouter/google/gemini-2.5-flash-lite-preview-09-2025`).
- Most examples require `OPENAI_API_KEY` or `OPENROUTER_API_KEY`.
2 changes: 1 addition & 1 deletion examples/caching/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func getModelName() string {
if model := os.Getenv("EXAMPLES_DEFAULT_MODEL"); model != "" {
return model
}
return "openrouter/amazon/nova-2-lite-v1"
return "openrouter/google/gemini-2.5-flash-lite-preview-09-2025"
}

// SentimentInput defines input for sentiment classification
Expand Down
2 changes: 1 addition & 1 deletion examples/codebase_analysis/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func getModelName() string {
if model := os.Getenv("EXAMPLES_DEFAULT_MODEL"); model != "" {
return model
}
return "openrouter/amazon/nova-2-lite-v1" // default fallback
return "openrouter/google/gemini-2.5-flash-lite-preview-09-2025" // default fallback
}

// ============================================================================
Expand Down
8 changes: 8 additions & 0 deletions examples/filesystem_mcp/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,11 @@ go 1.25
replace github.com/assagman/dsgo => ../..

require github.com/assagman/dsgo v0.0.0-00010101000000-000000000000

require (
github.com/openai/openai-go/v3 v3.13.0 // indirect
github.com/tidwall/gjson v1.18.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/sjson v1.2.5 // indirect
)
12 changes: 12 additions & 0 deletions examples/filesystem_mcp/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
github.com/openai/openai-go/v3 v3.13.0 h1:arSFmVHcBHNVYG5iqspPJrLoin0Qqn2JcCLWWcTcM1Q=
github.com/openai/openai-go/v3 v3.13.0/go.mod h1:cdufnVK14cWcT9qA1rRtrXx4FTRsgbDPW7Ia7SS5cZo=
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
Binary file removed examples/modules/parallel/parallel
Binary file not shown.
8 changes: 8 additions & 0 deletions examples/package_analysis/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ require (
github.com/assagman/dsgo/examples/shared v0.0.0
)

require (
github.com/openai/openai-go/v3 v3.13.0 // indirect
github.com/tidwall/gjson v1.18.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/sjson v1.2.5 // indirect
)

replace github.com/assagman/dsgo => ../..

replace github.com/assagman/dsgo/examples/shared => ../shared
12 changes: 12 additions & 0 deletions examples/package_analysis/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
github.com/openai/openai-go/v3 v3.13.0 h1:arSFmVHcBHNVYG5iqspPJrLoin0Qqn2JcCLWWcTcM1Q=
github.com/openai/openai-go/v3 v3.13.0/go.mod h1:cdufnVK14cWcT9qA1rRtrXx4FTRsgbDPW7Ia7SS5cZo=
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
2 changes: 1 addition & 1 deletion examples/package_analysis/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const (
FieldNextSteps = "next_steps"

// Model
ModelName = "openrouter/amazon/nova-2-lite-v1"
ModelName = "openrouter/minimax/minimax-m2"
)

func main() {
Expand Down
4 changes: 1 addition & 3 deletions examples/react_experiment/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,12 @@ func main() {
ctx := context.Background()

models := []string{
"openrouter/amazon/nova-2-lite-v1",
"openrouter/google/gemini-2.5-flash-lite-preview-09-2025",
"openrouter/openai/gpt-4o-mini",
"openrouter/openai/gpt-5-mini-2025-08-07",
"openrouter/openai/gpt-5-nano-2025-08-07",
"openrouter/openai/gpt-4.1-2025-04-14",
"openrouter/google/gemini-2.5-flash",
"openrouter/google/gemini-2.5-flash-lite-preview-09-2025",
"openrouter/anthropic/claude-haiku-4.5",
"openrouter/x-ai/grok-code-fast-1",
"openrouter/deepseek/deepseek-v3.2",
"openrouter/qwen/qwen3-next-80b-a3b-instruct",
Expand Down
2 changes: 1 addition & 1 deletion examples/snowflake_trainer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export EXA_API_KEY="..."
export TAVILY_API_KEY="..."

# Optional Configuration
export TRAINER_MODEL="openrouter/anthropic/claude-3.5-sonnet"
export TRAINER_MODEL="openrouter/google/gemini-2.5-flash-lite-preview-09-2025"
export TRAINER_MAX_WORKERS="6"
export TRAINER_TOPIC="Snowflake Data Cloud"
```
Expand Down
8 changes: 8 additions & 0 deletions examples/snowflake_trainer/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,12 @@ go 1.25

require github.com/assagman/dsgo v0.0.0

require (
github.com/openai/openai-go/v3 v3.13.0 // indirect
github.com/tidwall/gjson v1.18.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/sjson v1.2.5 // indirect
)

replace github.com/assagman/dsgo => ../..
12 changes: 12 additions & 0 deletions examples/snowflake_trainer/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
github.com/openai/openai-go/v3 v3.13.0 h1:arSFmVHcBHNVYG5iqspPJrLoin0Qqn2JcCLWWcTcM1Q=
github.com/openai/openai-go/v3 v3.13.0/go.mod h1:cdufnVK14cWcT9qA1rRtrXx4FTRsgbDPW7Ia7SS5cZo=
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
2 changes: 1 addition & 1 deletion examples/snowflake_trainer/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import (
)

const (
DefaultModel = "openrouter/google/gemini-2.5-flash"
DefaultModel = "openrouter/minimax/minimax-m2"
DefaultTopic = "Snowflake Data Cloud Platform"
Timeout = 15 * time.Minute
)
Expand Down
6 changes: 3 additions & 3 deletions examples/yaml_program/pipeline_todo.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
# go run . pipeline_todo.yaml

name: pipeline_todo
description: Generates a repo-aware numbered TODO list from a task
description: Project TODO list generation via codebase analysis and web research

model:
name: openrouter/z-ai/glm-4.6
Expand All @@ -33,7 +33,7 @@ mcp:
filesystem:
type: filesystem
allowed_dirs:
- ~/source/me/dsgo/example-generator/
- .

signatures:
codebase_analysis:
Expand Down Expand Up @@ -139,4 +139,4 @@ pipeline:

inputs:
task: |
Review cost operations
Review project code quality
12 changes: 6 additions & 6 deletions integration/cache_behavior_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ func TestCacheIntegration_MockProvider_CacheHitAvoidsSecondRequest(t *testing.T)
t.Cleanup(globalConfigMu.Unlock)
t.Cleanup(dsgo.ResetConfig)

server, recorder := newValidatedMockServer(t, validateChatCompletionRequest("gpt-4"), func(w http.ResponseWriter, r *http.Request, _ []byte) {
server, recorder := newValidatedMockServer(t, validateChatCompletionRequest("gpt-4o"), func(w http.ResponseWriter, r *http.Request, _ []byte) {
w.WriteHeader(http.StatusOK)
_, _ = fmt.Fprint(w, `{
"id":"test",
Expand All @@ -112,7 +112,7 @@ func TestCacheIntegration_MockProvider_CacheHitAvoidsSecondRequest(t *testing.T)
// Wire mock provider to server.
t.Setenv("DSGO_MOCK_BASE_URL", server.URL)

lm, err := dsgo.NewLM(context.Background(), "mock/gpt-4")
lm, err := dsgo.NewLM(context.Background(), "mock/gpt-4o")
if err != nil {
t.Fatalf("failed to create mock LM: %v", err)
}
Expand Down Expand Up @@ -147,7 +147,7 @@ func TestCacheIntegration_MockProvider_CacheTTLExpires(t *testing.T) {
t.Cleanup(globalConfigMu.Unlock)
t.Cleanup(dsgo.ResetConfig)

server, recorder := newValidatedMockServer(t, validateChatCompletionRequest("gpt-4"), func(w http.ResponseWriter, r *http.Request, _ []byte) {
server, recorder := newValidatedMockServer(t, validateChatCompletionRequest("gpt-4o"), func(w http.ResponseWriter, r *http.Request, _ []byte) {
w.WriteHeader(http.StatusOK)
_, _ = fmt.Fprint(w, `{
"id":"test",
Expand All @@ -171,7 +171,7 @@ func TestCacheIntegration_MockProvider_CacheTTLExpires(t *testing.T) {
t.Fatal("expected DefaultCache to be configured")
}

lm, err := dsgo.NewLM(context.Background(), "mock/gpt-4")
lm, err := dsgo.NewLM(context.Background(), "mock/gpt-4o")
if err != nil {
t.Fatalf("failed to create mock LM: %v", err)
}
Expand Down Expand Up @@ -202,7 +202,7 @@ func TestCacheIntegration_MockProvider_CacheClearForcesRefetch(t *testing.T) {
t.Cleanup(globalConfigMu.Unlock)
t.Cleanup(dsgo.ResetConfig)

server, recorder := newValidatedMockServer(t, validateChatCompletionRequest("gpt-4"), func(w http.ResponseWriter, r *http.Request, _ []byte) {
server, recorder := newValidatedMockServer(t, validateChatCompletionRequest("gpt-4o"), func(w http.ResponseWriter, r *http.Request, _ []byte) {
w.WriteHeader(http.StatusOK)
_, _ = fmt.Fprint(w, `{
"id":"test",
Expand All @@ -221,7 +221,7 @@ func TestCacheIntegration_MockProvider_CacheClearForcesRefetch(t *testing.T) {
t.Fatal("expected DefaultCache to be configured")
}

lm, err := dsgo.NewLM(context.Background(), "mock/gpt-4")
lm, err := dsgo.NewLM(context.Background(), "mock/gpt-4o")
if err != nil {
t.Fatalf("failed to create mock LM: %v", err)
}
Expand Down
Loading