Skip to content

Commit 69435ae

Browse files
authored
feat: add NanoGPT as AI provider (#2746)
adds https://nano-gpt.com as a provider
1 parent 1201273 commit 69435ae

6 files changed

Lines changed: 113 additions & 4 deletions

File tree

cmd/testai/main-testai.go

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ const (
2727
DefaultAnthropicModel = "claude-sonnet-4-5"
2828
DefaultOpenAIModel = "gpt-5.1"
2929
DefaultOpenRouterModel = "mistralai/mistral-small-3.2-24b-instruct"
30+
DefaultNanoGPTModel = "zai-org/glm-4.7"
3031
DefaultGeminiModel = "gemini-3-pro-preview"
3132
)
3233

@@ -257,6 +258,55 @@ func testOpenRouter(ctx context.Context, model, message string, tools []uctypes.
257258
}
258259
}
259260

261+
func testNanoGPT(ctx context.Context, model, message string, tools []uctypes.ToolDefinition) {
262+
apiKey := os.Getenv("NANOGPT_KEY")
263+
if apiKey == "" {
264+
fmt.Println("Error: NANOGPT_KEY environment variable not set")
265+
os.Exit(1)
266+
}
267+
268+
opts := &uctypes.AIOptsType{
269+
APIType: uctypes.APIType_OpenAIChat,
270+
APIToken: apiKey,
271+
Endpoint: "https://nano-gpt.com/api/v1/chat/completions",
272+
Model: model,
273+
MaxTokens: 4096,
274+
}
275+
276+
chatID := uuid.New().String()
277+
278+
aiMessage := &uctypes.AIMessage{
279+
MessageId: uuid.New().String(),
280+
Parts: []uctypes.AIMessagePart{
281+
{
282+
Type: uctypes.AIMessagePartTypeText,
283+
Text: message,
284+
},
285+
},
286+
}
287+
288+
fmt.Printf("Testing NanoGPT with WaveAIPostMessageWrap, model: %s\n", model)
289+
fmt.Printf("Message: %s\n", message)
290+
fmt.Printf("Chat ID: %s\n", chatID)
291+
fmt.Println("---")
292+
293+
testWriter := &TestResponseWriter{}
294+
sseHandler := sse.MakeSSEHandlerCh(testWriter, ctx)
295+
defer sseHandler.Close()
296+
297+
chatOpts := uctypes.WaveChatOpts{
298+
ChatId: chatID,
299+
ClientId: uuid.New().String(),
300+
Config: *opts,
301+
Tools: tools,
302+
SystemPrompt: []string{"You are a helpful assistant. Be concise and clear in your responses."},
303+
}
304+
err := aiusechat.WaveAIPostMessageWrap(ctx, sseHandler, aiMessage, chatOpts)
305+
if err != nil {
306+
fmt.Printf("NanoGPT streaming error: %v\n", err)
307+
}
308+
}
309+
260310
func testAnthropic(ctx context.Context, model, message string, tools []uctypes.ToolDefinition) {
261311
apiKey := os.Getenv("ANTHROPIC_APIKEY")
262312
if apiKey == "" {
@@ -381,7 +431,7 @@ func testT4(ctx context.Context) {
381431
}
382432

383433
func printUsage() {
384-
fmt.Println("Usage: go run main-testai.go [--anthropic|--openaicomp|--openrouter|--gemini] [--tools] [--model <model>] [message]")
434+
fmt.Println("Usage: go run main-testai.go [--anthropic|--openaicomp|--openrouter|--nanogpt|--gemini] [--tools] [--model <model>] [message]")
385435
fmt.Println("Examples:")
386436
fmt.Println(" go run main-testai.go 'What is 2+2?'")
387437
fmt.Println(" go run main-testai.go --model o4-mini 'What is 2+2?'")
@@ -390,6 +440,8 @@ func printUsage() {
390440
fmt.Println(" go run main-testai.go --openaicomp --model gpt-4o 'What is 2+2?'")
391441
fmt.Println(" go run main-testai.go --openrouter 'What is 2+2?'")
392442
fmt.Println(" go run main-testai.go --openrouter --model anthropic/claude-3.5-sonnet 'What is 2+2?'")
443+
fmt.Println(" go run main-testai.go --nanogpt 'What is 2+2?'")
444+
fmt.Println(" go run main-testai.go --nanogpt --model gpt-4o 'What is 2+2?'")
393445
fmt.Println(" go run main-testai.go --gemini 'What is 2+2?'")
394446
fmt.Println(" go run main-testai.go --gemini --model gemini-1.5-pro 'What is 2+2?'")
395447
fmt.Println(" go run main-testai.go --tools 'Help me configure GitHub Actions monitoring'")
@@ -399,24 +451,27 @@ func printUsage() {
399451
fmt.Printf(" Anthropic: %s\n", DefaultAnthropicModel)
400452
fmt.Printf(" OpenAI Completions: gpt-4o\n")
401453
fmt.Printf(" OpenRouter: %s\n", DefaultOpenRouterModel)
454+
fmt.Printf(" NanoGPT: %s\n", DefaultNanoGPTModel)
402455
fmt.Printf(" Google Gemini: %s\n", DefaultGeminiModel)
403456
fmt.Println("")
404457
fmt.Println("Environment variables:")
405458
fmt.Println(" OPENAI_APIKEY (for OpenAI models)")
406459
fmt.Println(" ANTHROPIC_APIKEY (for Anthropic models)")
407460
fmt.Println(" OPENROUTER_APIKEY (for OpenRouter models)")
461+
fmt.Println(" NANOGPT_KEY (for NanoGPT models)")
408462
fmt.Println(" GOOGLE_APIKEY (for Google Gemini models)")
409463
}
410464

411465
func main() {
412-
var anthropic, openaicomp, openrouter, gemini, tools, help, t1, t2, t3, t4 bool
466+
var anthropic, openaicomp, openrouter, nanogpt, gemini, tools, help, t1, t2, t3, t4 bool
413467
var model string
414468
flag.BoolVar(&anthropic, "anthropic", false, "Use Anthropic API instead of OpenAI")
415469
flag.BoolVar(&openaicomp, "openaicomp", false, "Use OpenAI Completions API")
416470
flag.BoolVar(&openrouter, "openrouter", false, "Use OpenRouter API")
471+
flag.BoolVar(&nanogpt, "nanogpt", false, "Use NanoGPT API")
417472
flag.BoolVar(&gemini, "gemini", false, "Use Google Gemini API")
418473
flag.BoolVar(&tools, "tools", false, "Enable GitHub Actions Monitor tools for testing")
419-
flag.StringVar(&model, "model", "", fmt.Sprintf("AI model to use (defaults: %s for OpenAI, %s for Anthropic, %s for OpenRouter, %s for Gemini)", DefaultOpenAIModel, DefaultAnthropicModel, DefaultOpenRouterModel, DefaultGeminiModel))
474+
flag.StringVar(&model, "model", "", fmt.Sprintf("AI model to use (defaults: %s for OpenAI, %s for Anthropic, %s for OpenRouter, %s for NanoGPT, %s for Gemini)", DefaultOpenAIModel, DefaultAnthropicModel, DefaultOpenRouterModel, DefaultNanoGPTModel, DefaultGeminiModel))
420475
flag.BoolVar(&help, "help", false, "Show usage information")
421476
flag.BoolVar(&t1, "t1", false, fmt.Sprintf("Run preset T1 test (%s with 'what is 2+2')", DefaultAnthropicModel))
422477
flag.BoolVar(&t2, "t2", false, fmt.Sprintf("Run preset T2 test (%s with 'what is 2+2')", DefaultOpenAIModel))
@@ -457,6 +512,8 @@ func main() {
457512
model = "gpt-4o"
458513
} else if openrouter {
459514
model = DefaultOpenRouterModel
515+
} else if nanogpt {
516+
model = DefaultNanoGPTModel
460517
} else if gemini {
461518
model = DefaultGeminiModel
462519
} else {
@@ -481,6 +538,8 @@ func main() {
481538
testOpenAIComp(ctx, model, message, toolDefs)
482539
} else if openrouter {
483540
testOpenRouter(ctx, model, message, toolDefs)
541+
} else if nanogpt {
542+
testNanoGPT(ctx, model, message, toolDefs)
484543
} else if gemini {
485544
testGemini(ctx, model, message, toolDefs)
486545
} else {

docs/docs/waveai-modes.mdx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ Wave AI now supports provider-based configuration which automatically applies se
3434

3535
- **`openai`** - OpenAI API (automatically configures endpoint and secret name) [[see example](#openai)]
3636
- **`openrouter`** - OpenRouter API (automatically configures endpoint and secret name) [[see example](#openrouter)]
37+
- **`nanogpt`** - NanoGPT API (automatically configures endpoint and secret name) [[see example](#nanogpt)]
3738
- **`google`** - Google AI (Gemini) [[see example](#google-ai-gemini)]
3839
- **`azure`** - Azure OpenAI Service (modern API) [[see example](#azure-openai-modern-api)]
3940
- **`azure-legacy`** - Azure OpenAI Service (legacy deployment API) [[see example](#azure-openai-legacy-deployment-api)]
@@ -260,6 +261,40 @@ For OpenRouter, you must manually specify `ai:capabilities` based on your model'
260261
```
261262
:::
262263

264+
### NanoGPT
265+
266+
[NanoGPT](https://nano-gpt.com) provides access to multiple AI models at competitive prices. Using the `nanogpt` provider simplifies configuration:
267+
268+
```json
269+
{
270+
"nanogpt-glm47": {
271+
"display:name": "NanoGPT - GLM 4.7",
272+
"ai:provider": "nanogpt",
273+
"ai:model": "zai-org/glm-4.7"
274+
}
275+
}
276+
```
277+
278+
The provider automatically sets:
279+
- `ai:endpoint` to `https://nano-gpt.com/api/v1/chat/completions`
280+
- `ai:apitype` to `openai-chat`
281+
- `ai:apitokensecretname` to `NANOGPT_KEY` (store your NanoGPT API key with this name)
282+
283+
:::note
284+
NanoGPT is a proxy service that provides access to multiple AI models. You must manually specify `ai:capabilities` based on the model's features. NanoGPT supports OpenAI-compatible tool calling for models that have that capability. Check the model's `capabilities.vision` field from the [NanoGPT models API](https://nano-gpt.com/api/v1/models?detailed=true) to determine image support. Example for a text-only model with tool support:
285+
```json
286+
{
287+
"nanogpt-glm47": {
288+
"display:name": "NanoGPT - GLM 4.7",
289+
"ai:provider": "nanogpt",
290+
"ai:model": "zai-org/glm-4.7",
291+
"ai:capabilities": ["tools"]
292+
}
293+
}
294+
```
295+
For vision-capable models like `openai/gpt-5`, add `"images"` to capabilities.
296+
:::
297+
263298
### Google AI (Gemini)
264299

265300
[Google AI](https://ai.google.dev) provides the Gemini family of models. Using the `google` provider simplifies configuration:

pkg/aiusechat/uctypes/uctypes.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ const (
2727
AIProvider_Wave = "wave"
2828
AIProvider_Google = "google"
2929
AIProvider_OpenRouter = "openrouter"
30+
AIProvider_NanoGPT = "nanogpt"
3031
AIProvider_OpenAI = "openai"
3132
AIProvider_Azure = "azure"
3233
AIProvider_AzureLegacy = "azure-legacy"

pkg/aiusechat/usechat-mode.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ const (
2121
OpenAIResponsesEndpoint = "https://api.openai.com/v1/responses"
2222
OpenAIChatEndpoint = "https://api.openai.com/v1/chat/completions"
2323
OpenRouterChatEndpoint = "https://openrouter.ai/api/v1/chat/completions"
24+
NanoGPTChatEndpoint = "https://nano-gpt.com/api/v1/chat/completions"
2425
AzureLegacyEndpointTemplate = "https://%s.openai.azure.com/openai/deployments/%s/chat/completions?api-version=%s"
2526
AzureResponsesEndpointTemplate = "https://%s.openai.azure.com/openai/v1/responses"
2627
AzureChatEndpointTemplate = "https://%s.openai.azure.com/openai/v1/chat/completions"
@@ -30,6 +31,7 @@ const (
3031

3132
OpenAIAPITokenSecretName = "OPENAI_KEY"
3233
OpenRouterAPITokenSecretName = "OPENROUTER_KEY"
34+
NanoGPTAPITokenSecretName = "NANOGPT_KEY"
3335
AzureOpenAIAPITokenSecretName = "AZURE_OPENAI_KEY"
3436
GoogleAIAPITokenSecretName = "GOOGLE_AI_KEY"
3537
)
@@ -99,6 +101,17 @@ func applyProviderDefaults(config *wconfig.AIModeConfigType) {
99101
config.APITokenSecretName = OpenRouterAPITokenSecretName
100102
}
101103
}
104+
if config.Provider == uctypes.AIProvider_NanoGPT {
105+
if config.APIType == "" {
106+
config.APIType = uctypes.APIType_OpenAIChat
107+
}
108+
if config.Endpoint == "" {
109+
config.Endpoint = NanoGPTChatEndpoint
110+
}
111+
if config.APITokenSecretName == "" {
112+
config.APITokenSecretName = NanoGPTAPITokenSecretName
113+
}
114+
}
102115
if config.Provider == uctypes.AIProvider_AzureLegacy {
103116
if config.AzureAPIVersion == "" {
104117
config.AzureAPIVersion = AzureLegacyDefaultAPIVersion

pkg/wconfig/settingsconfig.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ type AIModeConfigType struct {
274274
DisplayOrder float64 `json:"display:order,omitempty"`
275275
DisplayIcon string `json:"display:icon,omitempty"`
276276
DisplayDescription string `json:"display:description,omitempty"`
277-
Provider string `json:"ai:provider,omitempty" jsonschema:"enum=wave,enum=google,enum=openrouter,enum=openai,enum=azure,enum=azure-legacy,enum=custom"`
277+
Provider string `json:"ai:provider,omitempty" jsonschema:"enum=wave,enum=google,enum=openrouter,enum=nanogpt,enum=openai,enum=azure,enum=azure-legacy,enum=custom"`
278278
APIType string `json:"ai:apitype,omitempty" jsonschema:"enum=google-gemini,enum=openai-responses,enum=openai-chat"`
279279
Model string `json:"ai:model,omitempty"`
280280
ThinkingLevel string `json:"ai:thinkinglevel,omitempty" jsonschema:"enum=low,enum=medium,enum=high"`

schema/waveai.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"wave",
2222
"google",
2323
"openrouter",
24+
"nanogpt",
2425
"openai",
2526
"azure",
2627
"azure-legacy",

0 commit comments

Comments
 (0)