Skip to content

Commit 32871c3

Browse files
committed
feat: add NanoGPT as AI provider
1 parent a07e078 commit 32871c3

6 files changed

Lines changed: 114 additions & 4 deletions

File tree

cmd/testai/main-testai.go

Lines changed: 63 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,56 @@ 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_APIKEY")
263+
if apiKey == "" {
264+
fmt.Println("Error: NANOGPT_APIKEY 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+
ThinkingLevel: uctypes.ThinkingLevelMedium,
275+
}
276+
277+
chatID := uuid.New().String()
278+
279+
aiMessage := &uctypes.AIMessage{
280+
MessageId: uuid.New().String(),
281+
Parts: []uctypes.AIMessagePart{
282+
{
283+
Type: uctypes.AIMessagePartTypeText,
284+
Text: message,
285+
},
286+
},
287+
}
288+
289+
fmt.Printf("Testing NanoGPT with WaveAIPostMessageWrap, model: %s\n", model)
290+
fmt.Printf("Message: %s\n", message)
291+
fmt.Printf("Chat ID: %s\n", chatID)
292+
fmt.Println("---")
293+
294+
testWriter := &TestResponseWriter{}
295+
sseHandler := sse.MakeSSEHandlerCh(testWriter, ctx)
296+
defer sseHandler.Close()
297+
298+
chatOpts := uctypes.WaveChatOpts{
299+
ChatId: chatID,
300+
ClientId: uuid.New().String(),
301+
Config: *opts,
302+
Tools: tools,
303+
SystemPrompt: []string{"You are a helpful assistant. Be concise and clear in your responses."},
304+
}
305+
err := aiusechat.WaveAIPostMessageWrap(ctx, sseHandler, aiMessage, chatOpts)
306+
if err != nil {
307+
fmt.Printf("NanoGPT streaming error: %v\n", err)
308+
}
309+
}
310+
260311
func testAnthropic(ctx context.Context, model, message string, tools []uctypes.ToolDefinition) {
261312
apiKey := os.Getenv("ANTHROPIC_APIKEY")
262313
if apiKey == "" {
@@ -381,7 +432,7 @@ func testT4(ctx context.Context) {
381432
}
382433

383434
func printUsage() {
384-
fmt.Println("Usage: go run main-testai.go [--anthropic|--openaicomp|--openrouter|--gemini] [--tools] [--model <model>] [message]")
435+
fmt.Println("Usage: go run main-testai.go [--anthropic|--openaicomp|--openrouter|--nanogpt|--gemini] [--tools] [--model <model>] [message]")
385436
fmt.Println("Examples:")
386437
fmt.Println(" go run main-testai.go 'What is 2+2?'")
387438
fmt.Println(" go run main-testai.go --model o4-mini 'What is 2+2?'")
@@ -390,6 +441,8 @@ func printUsage() {
390441
fmt.Println(" go run main-testai.go --openaicomp --model gpt-4o 'What is 2+2?'")
391442
fmt.Println(" go run main-testai.go --openrouter 'What is 2+2?'")
392443
fmt.Println(" go run main-testai.go --openrouter --model anthropic/claude-3.5-sonnet 'What is 2+2?'")
444+
fmt.Println(" go run main-testai.go --nanogpt 'What is 2+2?'")
445+
fmt.Println(" go run main-testai.go --nanogpt --model gpt-4o 'What is 2+2?'")
393446
fmt.Println(" go run main-testai.go --gemini 'What is 2+2?'")
394447
fmt.Println(" go run main-testai.go --gemini --model gemini-1.5-pro 'What is 2+2?'")
395448
fmt.Println(" go run main-testai.go --tools 'Help me configure GitHub Actions monitoring'")
@@ -399,24 +452,27 @@ func printUsage() {
399452
fmt.Printf(" Anthropic: %s\n", DefaultAnthropicModel)
400453
fmt.Printf(" OpenAI Completions: gpt-4o\n")
401454
fmt.Printf(" OpenRouter: %s\n", DefaultOpenRouterModel)
455+
fmt.Printf(" NanoGPT: %s\n", DefaultNanoGPTModel)
402456
fmt.Printf(" Google Gemini: %s\n", DefaultGeminiModel)
403457
fmt.Println("")
404458
fmt.Println("Environment variables:")
405459
fmt.Println(" OPENAI_APIKEY (for OpenAI models)")
406460
fmt.Println(" ANTHROPIC_APIKEY (for Anthropic models)")
407461
fmt.Println(" OPENROUTER_APIKEY (for OpenRouter models)")
462+
fmt.Println(" NANOGPT_APIKEY (for NanoGPT models)")
408463
fmt.Println(" GOOGLE_APIKEY (for Google Gemini models)")
409464
}
410465

411466
func main() {
412-
var anthropic, openaicomp, openrouter, gemini, tools, help, t1, t2, t3, t4 bool
467+
var anthropic, openaicomp, openrouter, nanogpt, gemini, tools, help, t1, t2, t3, t4 bool
413468
var model string
414469
flag.BoolVar(&anthropic, "anthropic", false, "Use Anthropic API instead of OpenAI")
415470
flag.BoolVar(&openaicomp, "openaicomp", false, "Use OpenAI Completions API")
416471
flag.BoolVar(&openrouter, "openrouter", false, "Use OpenRouter API")
472+
flag.BoolVar(&nanogpt, "nanogpt", false, "Use NanoGPT API")
417473
flag.BoolVar(&gemini, "gemini", false, "Use Google Gemini API")
418474
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))
475+
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))
420476
flag.BoolVar(&help, "help", false, "Show usage information")
421477
flag.BoolVar(&t1, "t1", false, fmt.Sprintf("Run preset T1 test (%s with 'what is 2+2')", DefaultAnthropicModel))
422478
flag.BoolVar(&t2, "t2", false, fmt.Sprintf("Run preset T2 test (%s with 'what is 2+2')", DefaultOpenAIModel))
@@ -457,6 +513,8 @@ func main() {
457513
model = "gpt-4o"
458514
} else if openrouter {
459515
model = DefaultOpenRouterModel
516+
} else if nanogpt {
517+
model = DefaultNanoGPTModel
460518
} else if gemini {
461519
model = DefaultGeminiModel
462520
} else {
@@ -481,6 +539,8 @@ func main() {
481539
testOpenAIComp(ctx, model, message, toolDefs)
482540
} else if openrouter {
483541
testOpenRouter(ctx, model, message, toolDefs)
542+
} else if nanogpt {
543+
testNanoGPT(ctx, model, message, toolDefs)
484544
} else if gemini {
485545
testGemini(ctx, model, message, toolDefs)
486546
} 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)]
@@ -230,6 +231,40 @@ For OpenRouter, you must manually specify `ai:capabilities` based on your model'
230231
```
231232
:::
232233

234+
### NanoGPT
235+
236+
[NanoGPT](https://nano-gpt.com) provides access to multiple AI models at competitive prices. Using the `nanogpt` provider simplifies configuration:
237+
238+
```json
239+
{
240+
"nanogpt-glm47": {
241+
"display:name": "NanoGPT - GLM 4.7",
242+
"ai:provider": "nanogpt",
243+
"ai:model": "zai-org/glm-4.7"
244+
}
245+
}
246+
```
247+
248+
The provider automatically sets:
249+
- `ai:endpoint` to `https://nano-gpt.com/api/v1/chat/completions`
250+
- `ai:apitype` to `openai-chat`
251+
- `ai:apitokensecretname` to `NANOGPT_KEY` (store your NanoGPT API key with this name)
252+
253+
:::note
254+
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:
255+
```json
256+
{
257+
"nanogpt-glm47": {
258+
"display:name": "NanoGPT - GLM 4.7",
259+
"ai:provider": "nanogpt",
260+
"ai:model": "zai-org/glm-4.7",
261+
"ai:capabilities": ["tools"]
262+
}
263+
}
264+
```
265+
For vision-capable models like `openai/gpt-5`, add `"images"` to capabilities.
266+
:::
267+
233268
### Google AI (Gemini)
234269

235270
[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
@@ -269,7 +269,7 @@ type AIModeConfigType struct {
269269
DisplayOrder float64 `json:"display:order,omitempty"`
270270
DisplayIcon string `json:"display:icon,omitempty"`
271271
DisplayDescription string `json:"display:description,omitempty"`
272-
Provider string `json:"ai:provider,omitempty" jsonschema:"enum=wave,enum=google,enum=openrouter,enum=openai,enum=azure,enum=azure-legacy,enum=custom"`
272+
Provider string `json:"ai:provider,omitempty" jsonschema:"enum=wave,enum=google,enum=openrouter,enum=nanogpt,enum=openai,enum=azure,enum=azure-legacy,enum=custom"`
273273
APIType string `json:"ai:apitype,omitempty" jsonschema:"enum=google-gemini,enum=openai-responses,enum=openai-chat"`
274274
Model string `json:"ai:model,omitempty"`
275275
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)