Integrations
Go
Use DEVUP AI from Go (Golang) with the official OpenAI Go SDK, openai-go, or with go-openai. Point the client at https://api.devupai.com/v1, and your code gets chat, streaming, tool calling, structured output and embeddings on DEVUP AI models.
Requirements
- Go 1.25 or newer for openai-go v3. go-openai works with Go 1.18 or newer.
- a DEVUP AI API key from https://devupai.com/dashboard/api-keys.
Install the OpenAI Go SDK
go mod init myapp
go get github.com/openai/openai-go/v3Set your key in the terminal that runs your program:
PowerShell
$env:DEVUP_API_KEY = "YOUR_DEVUP_API_KEY"Bash
export DEVUP_API_KEY="YOUR_DEVUP_API_KEY"Quick start
Save this as main.go and run go run . in the same folder:
package main
import (
"context"
"fmt"
"log"
"os"
"time"
"github.com/openai/openai-go/v3"
"github.com/openai/openai-go/v3/option"
)
func main() {
client := openai.NewClient(
option.WithBaseURL("https://api.devupai.com/v1"),
option.WithAPIKey(os.Getenv("DEVUP_API_KEY")),
)
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
completion, err := client.Chat.Completions.New(ctx, openai.ChatCompletionNewParams{
Model: "deepseek-ai/DeepSeek-V4-Pro",
Messages: []openai.ChatCompletionMessageParamUnion{
openai.UserMessage("Reply with exactly: Salam DEVUP"),
},
})
if err != nil {
log.Fatal(err)
}
if len(completion.Choices) == 0 {
log.Fatal("response contained no choices")
}
fmt.Println(completion.Choices[0].Message.Content)
}It prints Salam DEVUP.
Configure with environment variables
openai.NewClient() reads OPENAI_BASE_URL and OPENAI_API_KEY when you pass no options. Point them at DEVUP AI:
PowerShell
$env:OPENAI_BASE_URL = "https://api.devupai.com/v1"
$env:OPENAI_API_KEY = "YOUR_DEVUP_API_KEY"Bash
export OPENAI_BASE_URL="https://api.devupai.com/v1"
export OPENAI_API_KEY="YOUR_DEVUP_API_KEY"package main
import (
"context"
"fmt"
"log"
"time"
"github.com/openai/openai-go/v3"
)
func main() {
// NewClient reads OPENAI_BASE_URL and OPENAI_API_KEY from the environment.
client := openai.NewClient()
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
completion, err := client.Chat.Completions.New(ctx, openai.ChatCompletionNewParams{
Model: "deepseek-ai/DeepSeek-V4-Pro",
Messages: []openai.ChatCompletionMessageParamUnion{
openai.UserMessage("Reply with exactly: Salam DEVUP"),
},
})
if err != nil {
log.Fatal(err)
}
if len(completion.Choices) == 0 {
log.Fatal("response contained no choices")
}
fmt.Println(completion.Choices[0].Message.Content)
}Create a reusable client
Wrap the client and a chat call in functions you can reuse. The functions on this page share one package; goimports or your editor adds the standard-library imports each one uses.
// newClient points the official OpenAI Go SDK at DEVUP AI.
// The key comes from the environment and is never written into source code.
func newClient() openai.Client {
return openai.NewClient(
option.WithBaseURL("https://api.devupai.com/v1"),
option.WithAPIKey(os.Getenv("DEVUP_API_KEY")),
)
}
// chat sends one message and returns the model's reply.
func chat(ctx context.Context, client openai.Client) (string, error) {
completion, err := client.Chat.Completions.New(ctx, openai.ChatCompletionNewParams{
Model: "deepseek-ai/DeepSeek-V4-Pro",
Messages: []openai.ChatCompletionMessageParamUnion{
openai.UserMessage("Reply with exactly: Salam DEVUP"),
},
})
if err != nil {
return "", err
}
if len(completion.Choices) == 0 {
return "", errors.New("response contained no choices")
}
return completion.Choices[0].Message.Content, nil
}Stream responses
// streamChat prints tokens as they arrive and returns the assembled completion.
// With IncludeUsage set, token usage arrives once, in the final chunk.
func streamChat(ctx context.Context, client openai.Client) (openai.ChatCompletion, error) {
stream := client.Chat.Completions.NewStreaming(ctx, openai.ChatCompletionNewParams{
Model: "deepseek-ai/DeepSeek-V4-Pro",
Messages: []openai.ChatCompletionMessageParamUnion{
openai.UserMessage("Count from 1 to 5, separated by spaces."),
},
StreamOptions: openai.ChatCompletionStreamOptionsParam{
IncludeUsage: openai.Bool(true),
},
})
defer func() { _ = stream.Close() }()
acc := openai.ChatCompletionAccumulator{}
for stream.Next() {
chunk := stream.Current()
if !acc.AddChunk(chunk) {
return openai.ChatCompletion{}, errors.New("stream chunk could not be accumulated")
}
if len(chunk.Choices) > 0 {
fmt.Print(chunk.Choices[0].Delta.Content)
}
}
if err := stream.Err(); err != nil {
return openai.ChatCompletion{}, err
}
fmt.Printf("\ntokens: %d in, %d out\n", acc.Usage.PromptTokens, acc.Usage.CompletionTokens)
return acc.ChatCompletion, nil
}With IncludeUsage set, token usage arrives once, in the final chunk, and ChatCompletionAccumulator returns the totals.
Tool calling
The model asks for get_order_status, your code runs it and sends the result back, and the model answers with the tracking number from your system.
// getOrderStatus stands in for a lookup in your own system.
func getOrderStatus(orderID string) string {
out, _ := json.Marshal(map[string]string{
"order_id": orderID,
"status": "shipped",
"tracking_number": "DZ-4471",
})
return string(out)
}
// toolRoundTrip lets the model call get_order_status, runs it locally,
// sends the result back, and returns the model's final answer.
func toolRoundTrip(ctx context.Context, client openai.Client) (string, error) {
params := openai.ChatCompletionNewParams{
Model: "deepseek-ai/DeepSeek-V4-Pro",
Messages: []openai.ChatCompletionMessageParamUnion{
openai.UserMessage("Where is my order 1042? Include its tracking number."),
},
Tools: []openai.ChatCompletionToolUnionParam{
openai.ChatCompletionFunctionTool(openai.FunctionDefinitionParam{
Name: "get_order_status",
Description: openai.String("Look up the shipping status and tracking number of an order"),
Parameters: openai.FunctionParameters{
"type": "object",
"properties": map[string]any{
"order_id": map[string]string{"type": "string"},
},
"required": []string{"order_id"},
},
}),
},
}
first, err := client.Chat.Completions.New(ctx, params)
if err != nil {
return "", err
}
if len(first.Choices) == 0 {
return "", errors.New("response contained no choices")
}
msg := first.Choices[0].Message
if len(msg.ToolCalls) == 0 {
return "", errors.New("the model answered without calling the tool")
}
// Every tool call must get a tool message back, including unknown ones.
params.Messages = append(params.Messages, msg.ToParam())
for _, call := range msg.ToolCalls {
result := `{"error":"unknown tool"}`
if call.Function.Name == "get_order_status" {
var args struct {
OrderID string `json:"order_id"`
}
if err := json.Unmarshal([]byte(call.Function.Arguments), &args); err != nil {
return "", fmt.Errorf("decode tool arguments: %w", err)
}
fmt.Printf("get_order_status(%q)\n", args.OrderID)
result = getOrderStatus(args.OrderID)
}
params.Messages = append(params.Messages, openai.ToolMessage(result, call.ID))
}
final, err := client.Chat.Completions.New(ctx, params)
if err != nil {
return "", err
}
if len(final.Choices) == 0 {
return "", errors.New("response contained no choices")
}
return final.Choices[0].Message.Content, nil
}Answer every tool call with a tool message, as the loop does, even for a tool name you do not recognise.
Structured output
Ask for JSON that matches a strict schema, then decode it into a Go struct.
// Capital is the shape the model must return.
type Capital struct {
City string `json:"city"`
Country string `json:"country"`
}
// structuredOutput asks for JSON that matches a strict schema and decodes it.
func structuredOutput(ctx context.Context, client openai.Client) (Capital, error) {
schema := map[string]any{
"type": "object",
"properties": map[string]any{
"city": map[string]string{"type": "string"},
"country": map[string]string{"type": "string"},
},
"required": []string{"city", "country"},
"additionalProperties": false,
}
completion, err := client.Chat.Completions.New(ctx, openai.ChatCompletionNewParams{
Model: "deepseek-ai/DeepSeek-V4-Pro",
Messages: []openai.ChatCompletionMessageParamUnion{
openai.UserMessage("What is the capital of Algeria?"),
},
ResponseFormat: openai.ChatCompletionNewParamsResponseFormatUnion{
OfJSONSchema: &openai.ResponseFormatJSONSchemaParam{
JSONSchema: openai.ResponseFormatJSONSchemaJSONSchemaParam{
Name: "capital",
Schema: schema,
Strict: openai.Bool(true),
},
},
},
})
if err != nil {
return Capital{}, err
}
if len(completion.Choices) == 0 {
return Capital{}, errors.New("response contained no choices")
}
var out Capital
if err := json.Unmarshal([]byte(completion.Choices[0].Message.Content), &out); err != nil {
return Capital{}, fmt.Errorf("decode structured output: %w", err)
}
return out, nil
}It returns Algiers, Algeria.
Embeddings
BAAI/bge-m3 returns one 1024-dimensional vector per input.
// embed returns one vector per input, in input order.
func embed(ctx context.Context, client openai.Client, texts []string) ([][]float64, error) {
res, err := client.Embeddings.New(ctx, openai.EmbeddingNewParams{
Model: "BAAI/bge-m3",
Input: openai.EmbeddingNewParamsInputUnion{OfArrayOfStrings: texts},
})
if err != nil {
return nil, err
}
vectors := make([][]float64, len(texts))
for _, d := range res.Data {
if d.Index < 0 || int(d.Index) >= len(vectors) {
return nil, fmt.Errorf("embedding index %d out of range", d.Index)
}
vectors[d.Index] = d.Embedding
}
return vectors, nil
}Handle errors
API errors come back as *openai.Error with the HTTP status. By default openai-go retries connection errors and 408, 409, 429 and 5xx responses twice, honouring Retry-After; option.WithMaxRetries changes that.
// explainError turns an SDK error into a message you can act on.
// By default the SDK retries connection errors and 408, 409, 429 and 5xx
// responses twice, honouring Retry-After; option.WithMaxRetries changes that.
func explainError(err error) string {
var apiErr *openai.Error
if errors.As(err, &apiErr) {
switch apiErr.StatusCode {
case 401:
return "invalid or revoked API key (401)"
case 429:
return "rate limit reached, retry later (429)"
default:
return fmt.Sprintf("API error %d: %s", apiErr.StatusCode, apiErr.Message)
}
}
if errors.Is(err, context.DeadlineExceeded) {
return "request timed out"
}
return err.Error()
}Use go-openai
Already on github.com/sashabaranov/go-openai? Set its BaseURL to DEVUP AI.
go get github.com/sashabaranov/go-openaiImport it as goopenai "github.com/sashabaranov/go-openai" so it does not clash with openai-go.
// newGoOpenAIClient points github.com/sashabaranov/go-openai at DEVUP AI.
func newGoOpenAIClient() *goopenai.Client {
cfg := goopenai.DefaultConfig(os.Getenv("DEVUP_API_KEY"))
cfg.BaseURL = "https://api.devupai.com/v1"
return goopenai.NewClientWithConfig(cfg)
}
func chatGoOpenAI(ctx context.Context, client *goopenai.Client) (string, error) {
resp, err := client.CreateChatCompletion(ctx, goopenai.ChatCompletionRequest{
Model: "deepseek-ai/DeepSeek-V4-Pro",
Messages: []goopenai.ChatCompletionMessage{
{Role: goopenai.ChatMessageRoleUser, Content: "Reply with exactly: Salam DEVUP"},
},
})
if err != nil {
return "", err
}
if len(resp.Choices) == 0 {
return "", errors.New("response contained no choices")
}
return resp.Choices[0].Message.Content, nil
}Streaming with usage:
// streamGoOpenAI prints tokens as they arrive and returns the full text and usage.
func streamGoOpenAI(ctx context.Context, client *goopenai.Client) (string, *goopenai.Usage, error) {
stream, err := client.CreateChatCompletionStream(ctx, goopenai.ChatCompletionRequest{
Model: "deepseek-ai/DeepSeek-V4-Pro",
Messages: []goopenai.ChatCompletionMessage{
{Role: goopenai.ChatMessageRoleUser, Content: "Count from 1 to 5, separated by spaces."},
},
StreamOptions: &goopenai.StreamOptions{IncludeUsage: true},
})
if err != nil {
return "", nil, err
}
defer func() { _ = stream.Close() }()
var text strings.Builder
var usage *goopenai.Usage
for {
resp, err := stream.Recv()
if errors.Is(err, io.EOF) {
break
}
if err != nil {
return "", nil, err
}
if len(resp.Choices) > 0 {
fmt.Print(resp.Choices[0].Delta.Content)
text.WriteString(resp.Choices[0].Delta.Content)
}
if resp.Usage != nil {
usage = resp.Usage
}
}
fmt.Println()
return text.String(), usage, nil
}Choosing models
Use exact catalog IDs as the Model value: a chat model such as deepseek-ai/DeepSeek-V4-Pro, and BAAI/bge-m3 for embeddings. Tool calling needs a model with tool calling. See the Models catalog, Embeddings guide, and Tool Calling guide.
Troubleshooting
- 'go' is not recognized after installing Go:Open a new terminal so it picks up the updated PATH, then run go version.
- go reports that openai-go needs a newer Go:openai-go v3 needs Go 1.25 or newer. Update Go, then run go get again.
- The request fails with 401:The key is missing, mistyped or revoked. Check that DEVUP_API_KEY is set in the terminal that runs your program, or create a new key in the dashboard.
FAQ
Does the OpenAI Go SDK work with DEVUP AI?
Yes. Pass option.WithBaseURL("https://api.devupai.com/v1") and your DEVUP AI key to openai.NewClient, then use any DEVUP AI catalog ID as the model.
Which Go version do I need?
Go 1.25 or newer for openai-go v3. go-openai works with Go 1.18 or newer.
How is usage from Go billed on DEVUP AI?
Every chat and embedding request is a regular DEVUP AI API call, metered in Algerian Dinar on your account.
Does streaming report token usage?
Yes. Set IncludeUsage in StreamOptions. Usage arrives once, in the final chunk, and ChatCompletionAccumulator returns the totals.
Can I use go-openai instead of the official SDK?
Yes. Create the config with goopenai.DefaultConfig, set BaseURL to https://api.devupai.com/v1, and build the client with NewClientWithConfig.
Can I create embeddings from Go?
Yes. Call client.Embeddings.New with the model BAAI/bge-m3. Each input returns a 1024-dimensional vector.
Related integrations
- Node.js SDK: The official DEVUP AI SDK for Node.js and TypeScript.
- Python: DEVUP AI from Python and its frameworks.
- PHP: DEVUP AI from PHP with openai-php/client.
- OpenAI SDK: OpenAI SDKs with the DEVUP AI base URL.