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
4 changes: 4 additions & 0 deletions go/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,14 @@ require (
)

require (
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/tidwall/gjson v1.14.4 // 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
golang.org/x/net v0.34.0 // indirect
golang.org/x/text v0.21.0 // indirect
)
26 changes: 24 additions & 2 deletions go/go.sum
Original file line number Diff line number Diff line change
@@ -1,20 +1,34 @@
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 h1:g0EZJwz7xkXQiZAI5xi9f3WWFYBlX1CPTrR+NDToRkQ=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0/go.mod h1:XCW7KnZet0Opnr7HccfUw1PLc4CjHqpcaxW8DHklNkQ=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH5sE0o6eCJuNDTmH09nDpbc=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/openai/openai-go/v3 v3.6.1 h1:f8J6jhT9wkYnNvHTKR7bxHXSZrSvvcfpHGkmBra04tI=
github.com/openai/openai-go/v3 v3.6.1/go.mod h1:UOpNxkqC9OdNXNUfpNByKOtB4jAL0EssQXq5p8gO0Xs=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
Expand All @@ -31,5 +45,13 @@ go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGX
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
142 changes: 123 additions & 19 deletions go/pkg/openai/openai.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/microsoft/agent-framework/go/pkg/agent"
"github.com/microsoft/agent-framework/go/pkg/agent/chat"
"github.com/openai/openai-go/v3"
"github.com/openai/openai-go/v3/azure"
"github.com/openai/openai-go/v3/option"
"github.com/openai/openai-go/v3/packages/param"
"github.com/openai/openai-go/v3/shared"
Expand Down Expand Up @@ -76,7 +77,12 @@ func (c *ChatClient) CompleteStream(ctx context.Context, options *chat.Options,
return func(yield func(*chat.ResponseUpdate, error) bool) {
defer stream.Close()
for stream.Next() {
choice := stream.Current().Choices[0]
current := stream.Current()
// Skip if no choices are available (common in streaming responses)
if len(current.Choices) == 0 {
continue
}
choice := current.Choices[0]
resp := &chat.ResponseUpdate{
Delta: chat.NewMessage(agent.Role(choice.Delta.Role), choice.Delta.Content),
FinishReason: agent.FinishReason(choice.FinishReason),
Expand Down Expand Up @@ -139,42 +145,140 @@ func (c *ChatClient) buildCompletionParams(options *chat.Options, messages ...*c
// AzureOpenAIChatClient is a ChatClient implementation for Azure OpenAI.
type AzureOpenAIChatClient struct {
*chat.BaseChatClient
apiKey string
endpoint string
deploymentName string
client *openai.Client
}

// AzureOpenAIChatClientConfig contains configuration for AzureOpenAIChatClient.
type AzureOpenAIChatClientConfig struct {
APIKey string
Endpoint string
DeploymentName string
Model string
APIKey string // Optional, if not set will use Azure authentication
Endpoint string // Azure OpenAI endpoint (e.g., https://your-resource.openai.azure.com/)
DeploymentName string // Deployment name for the model
APIVersion string // Optional, defaults to latest API version
}

// NewAzureOpenAIChatClient creates a new AzureOpenAIChatClient.
func NewAzureOpenAIChatClient(config AzureOpenAIChatClientConfig) (*AzureOpenAIChatClient, error) {
func NewAzureOpenAIChatClient(config AzureOpenAIChatClientConfig) *AzureOpenAIChatClient {
ops := make([]option.RequestOption, 0, 3)

// Set API version for Azure OpenAI
apiVersion := config.APIVersion
if apiVersion == "" {
// The latest API versions, including previews, can be found here:
// https://learn.microsoft.com/en-us/azure/ai-services/openai/reference#rest-api-versioning
apiVersion = "2025-01-01-preview" // Default to latest stable version
}

// Configure Azure OpenAI specific settings
ops = append(ops, azure.WithEndpoint(config.Endpoint, apiVersion))

// Configure API key if provided
if config.APIKey != "" {
ops = append(ops, azure.WithAPIKey(config.APIKey))
}

// Create Azure OpenAI client
client := openai.NewClient(ops...)
return &AzureOpenAIChatClient{
BaseChatClient: chat.NewBaseChatClient(config.Model),
apiKey: config.APIKey,
endpoint: config.Endpoint,
deploymentName: config.DeploymentName,
}, nil
BaseChatClient: chat.NewBaseChatClient(config.DeploymentName),
client: &client,
}
}

// NewAgent creates a new agent that uses this Azure OpenAI chat client.
func (c *AzureOpenAIChatClient) NewAgent(instructions string) agent.Agent[*chat.Message] {
return chat.New(chat.Config{
Name: "Azure OpenAI Chat Agent",
Instructions: instructions,
Client: c,
})
}

// Complete generates a single response for the given messages.
func (c *AzureOpenAIChatClient) Complete(ctx context.Context, options *chat.Options, messages ...*chat.Message) (*chat.Response, error) {
// TODO: Implement Azure OpenAI API call
resp, err := c.client.Chat.Completions.New(ctx, c.buildCompletionParams(options, messages...))
if err != nil {
return nil, err
}
choice := resp.Choices[0]
if len(choice.Message.ToolCalls) > 0 {
// Handle tool calls

}
return &chat.Response{
Message: chat.NewMessage("assistant", "Not implemented"),
FinishReason: "stop",
ModelID: c.ModelID,
Message: chat.NewMessage(agent.Role(choice.Message.Role), choice.Message.Content),
FinishReason: agent.FinishReason(choice.FinishReason),
ModelID: resp.Model,
}, nil
}

// CompleteStream generates a streaming response for the given messages.
func (c *AzureOpenAIChatClient) CompleteStream(ctx context.Context, options *chat.Options, messages ...*chat.Message) iter.Seq2[*chat.ResponseUpdate, error] {
stream := c.client.Chat.Completions.NewStreaming(ctx, c.buildCompletionParams(options, messages...))
return func(yield func(*chat.ResponseUpdate, error) bool) {
// TODO: Implement Azure OpenAI streaming API call
defer stream.Close()
for stream.Next() {
current := stream.Current()
// Skip if no choices are available (common in streaming responses)
if len(current.Choices) == 0 {
continue
}
choice := current.Choices[0]
resp := &chat.ResponseUpdate{
Delta: chat.NewMessage(agent.Role(choice.Delta.Role), choice.Delta.Content),
FinishReason: agent.FinishReason(choice.FinishReason),
}
if !yield(resp, nil) {
return
}
}
if stream.Err() != nil {
yield(nil, stream.Err())
}
}
}

// buildCompletionParams constructs the parameters for the Azure OpenAI chat completion API.
// This method is similar to the regular OpenAI client but uses the deployment name as the model.
func (c *AzureOpenAIChatClient) buildCompletionParams(options *chat.Options, messages ...*chat.Message) openai.ChatCompletionNewParams {
params := openai.ChatCompletionNewParams{
Model: c.ModelID, // For Azure OpenAI, this is the deployment name
N: openai.Int(1),
Messages: make([]openai.ChatCompletionMessageParamUnion, 0, len(messages)),
}
for _, msg := range messages {
// TODO: support roles, content types, and multiple messages
params.Messages = append(params.Messages, openai.ChatCompletionMessageParamUnion{
OfUser: &openai.ChatCompletionUserMessageParam{
Content: openai.ChatCompletionUserMessageParamContentUnion{
OfString: param.NewOpt(msg.Text()),
},
},
})
}
if options != nil {
if options.Temperature != nil {
params.Temperature = openai.Float(*options.Temperature)
}
if options.TopP != nil {
params.TopP = openai.Float(*options.TopP)
}
if options.MaxTokens != nil {
params.MaxTokens = openai.Int(int64(*options.MaxTokens))
}
for _, tool := range options.Tools {
params.Tools = append(params.Tools, openai.ChatCompletionToolUnionParam{
OfFunction: &openai.ChatCompletionFunctionToolParam{
Function: shared.FunctionDefinitionParam{
Name: tool.Name,
Description: param.NewOpt(tool.Description),
Parameters: tool.Schema,
},
},
})
}
params.ToolChoice = openai.ChatCompletionToolChoiceOptionUnionParam{
OfAuto: openai.String(string(options.ToolMode)),
}
}
return params
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package main

import (
"context"
"fmt"
"log"
"os"

"github.com/microsoft/agent-framework/go/pkg/agent"
"github.com/microsoft/agent-framework/go/pkg/agent/chat"
"github.com/microsoft/agent-framework/go/pkg/openai"
)

func main() {
// Azure OpenAI configuration
// You can also set these via environment variables:
// - AZURE_OPENAI_API_KEY
// - AZURE_OPENAI_ENDPOINT
// - AZURE_OPENAI_DEPLOYMENT_NAME
client := openai.NewAzureOpenAIChatClient(openai.AzureOpenAIChatClientConfig{
APIKey: os.Getenv("AZURE_OPENAI_API_KEY"), // or set directly
Endpoint: os.Getenv("AZURE_OPENAI_ENDPOINT"), // e.g., "https://your-resource.openai.azure.com/"
DeploymentName: os.Getenv("AZURE_OPENAI_DEPLOYMENT_NAME"), // e.g., "gpt-4o"
APIVersion: "2025-01-01-preview", // optional, uses default if not specified
})

ag := client.NewAgent("You are a helpful weather agent.")

nonStreamingExample(ag, "What's the weather like in Seattle?")
streamingExample(ag, "What's the weather like in Portland?")
}

func nonStreamingExample(ag agent.Agent[*chat.Message], query string) {
ctx := context.Background()
log.Printf("=== Non-streaming Response Example ===\n")
log.Printf("User: %s\n", query)
resp, err := ag.Run(ctx, nil, nil, chat.NewMessage(agent.RoleUser, query))
if err != nil {
log.Fatal(err)
}
log.Printf("Result: %s\n", resp.Message.Text())
}

func streamingExample(ag agent.Agent[*chat.Message], query string) {
ctx := context.Background()
log.Printf("=== Streaming Response Example ===\n")
log.Printf("User: %s\n", query)
stream := agent.RunStream(ctx, ag, nil, nil, chat.NewMessage(agent.RoleUser, query))
for update := range stream {
if update.Delta != nil {
fmt.Print(update.Delta.Text())
}
}
fmt.Print("\n")
}
12 changes: 11 additions & 1 deletion go/samples/getting_started/openai/openai_chat_client_basic.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,25 @@ import (
"context"
"fmt"
"log"
"os"

"github.com/microsoft/agent-framework/go/pkg/agent"
"github.com/microsoft/agent-framework/go/pkg/agent/chat"
"github.com/microsoft/agent-framework/go/pkg/openai"
)

func main() {
// OpenAI configuration
// Set your API key via environment variable: export OPENAI_API_KEY=your-key-here
// Or get one from: https://platform.openai.com/account/api-keys
apiKey := os.Getenv("OPENAI_API_KEY")
if apiKey == "" {
log.Fatal("OPENAI_API_KEY environment variable is required. Get your key from https://platform.openai.com/account/api-keys")
}

client := openai.NewChatClient(openai.ChatClientConfig{
Model: "gpt-5-nano",
Model: "gpt-5-nano",
APIKey: apiKey,
})
ag := client.NewAgent("You are a helpful weather agent.")

Expand Down