Skip to content

Commit ae18cb4

Browse files
[azopenai] Adding in support for using Azure Chat Extensions with external data sources (Azure#21426)
Adding in support for chat extensions which allow you (in Azure OpenAI) to use your own data from a Cognitive Search index. Also, updated the example files to use the `context.TODO()` consistently, and to use our new "comment + panic" in testable examples. Fixes Azure#21373
1 parent f325f4b commit ae18cb4

23 files changed

+1055
-104
lines changed

sdk/ai/azopenai/CHANGELOG.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
# Release History
22

3-
## 0.1.2 (Unreleased)
3+
## 0.2.0 (2023-08-28)
44

55
### Features Added
66

7+
- ChatCompletions supports Azure OpenAI's newest feature to use Azure OpenAI with your own data. See `example_client_getchatcompletions_extensions_test.go`
8+
for a working example. (PR#21426)
9+
710
### Breaking Changes
811

912
- ChatCompletionsOptions, CompletionsOptions, EmbeddingsOptions `DeploymentID` field renamed to `Deployment`.
@@ -13,9 +16,7 @@
1316

1417
- EventReader, used by GetChatCompletionsStream and GetCompletionsStream for streaming results, would not return an
1518
error if the underlying Body reader was closed or EOF'd before the actual DONE: token arrived. This could result in an
16-
infinite loop for callers. (PR#)
17-
18-
### Other Changes
19+
infinite loop for callers. (PR#21323)
1920

2021
## 0.1.1 (2023-07-26)
2122

sdk/ai/azopenai/assets.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
"AssetsRepo": "Azure/azure-sdk-assets",
33
"AssetsRepoPrefixPath": "go",
44
"TagPrefix": "go/ai/azopenai",
5-
"Tag": "go/ai/azopenai_2bf13bba09"
5+
"Tag": "go/ai/azopenai_7be6ae3c15"
66
}

sdk/ai/azopenai/autorest.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,15 @@ directive:
100100
- from: openapi-document
101101
where: $.components.schemas["ChatChoice"].properties.finish_reason
102102
transform: $["$ref"] = "#/components/schemas/CompletionsFinishReason"; delete $.oneOf;
103+
- from: openapi-document
104+
where: $.components.schemas["AzureChatExtensionConfiguration"].properties.type
105+
transform: $["$ref"] = "#/components/schemas/AzureChatExtensionType"; delete $.allOf;
106+
- from: openapi-document
107+
where: $.components.schemas["AzureChatExtensionConfiguration"].properties.type
108+
transform: $["$ref"] = "#/components/schemas/AzureChatExtensionType"; delete $.allOf;
109+
- from: openapi-document
110+
where: $.components.schemas["AzureCognitiveSearchChatExtensionConfiguration"].properties.queryType
111+
transform: $["$ref"] = "#/components/schemas/AzureCognitiveSearchQueryType"; delete $.allOf;
103112
# Fix "AutoGenerated" models
104113
- from: openapi-document
105114
where: $.components.schemas["ChatCompletions"].properties.usage
@@ -294,4 +303,45 @@ directive:
294303
return $
295304
.replace(/populate\(objectMap, "model", (c|e).Model\)/g, 'populate(objectMap, "model", &$1.Deployment)')
296305
.replace(/err = unpopulate\(val, "Model", &(c|e).Model\)/g, 'err = unpopulate(val, "Model", &$1.Deployment)');
306+
307+
# Make the Azure extensions internal - we expose these through the GetChatCompletions*() functions
308+
# and just treat which endpoint we use as an implementation detail.
309+
- from: client.go
310+
where: $
311+
transform: |
312+
return $
313+
.replace(/GetChatCompletionsWithAzureExtensions([ (])/g, "getChatCompletionsWithAzureExtensions$1")
314+
.replace(/GetChatCompletions([ (])/g, "getChatCompletions$1");
315+
316+
# move the Azure extensions options into place
317+
- from: models.go
318+
where: $
319+
transform: return $.replace(/(\/\/ The configuration entries for Azure OpenAI.+?)DataSources \[\]AzureChatExtensionConfiguration/s, "$1AzureExtensionsOptions *AzureChatExtensionOptions");
320+
- from: models_serde.go
321+
where: $
322+
transform: |
323+
return $
324+
.replace(/populate\(objectMap, "dataSources", c.DataSources\)/, 'if c.AzureExtensionsOptions != nil { populate(objectMap, "dataSources", c.AzureExtensionsOptions.Extensions) }')
325+
// technically not used, but let's be completionists...
326+
.replace(/err = unpopulate\(val, "DataSources", &c.DataSources\)/, 'c.AzureExtensionsOptions = &AzureChatExtensionOptions{}; err = unpopulate(val, "DataSources", &c.AzureExtensionsOptions.Extensions)')
327+
328+
# try to fix some of the generated types.
329+
330+
# swap the `Parameters` and `Type` fields (Type really drives what's in Parameters)
331+
- from: models.go
332+
where: $
333+
transform: |
334+
let typeRE = /(\/\/ REQUIRED; The label for the type of an Azure chat extension.*?Type \*AzureChatExtensionType)/s;
335+
let paramsRE = /(\/\/ REQUIRED; The configuration payload used for the Azure chat extension.*?Parameters any)/s;
336+
337+
return $
338+
.replace(paramsRE, "")
339+
.replace(typeRE, $.match(typeRE)[1] + "\n\n" + $.match(paramsRE)[1]);
340+
341+
- from: constants.go
342+
where: $
343+
transform: |
344+
return $.replace(
345+
/(AzureChatExtensionTypeAzureCognitiveSearch AzureChatExtensionType)/,
346+
"// AzureChatExtensionTypeAzureCognitiveSearch enables the use of an Azure Cognitive Search index with chat completions.\n// [AzureChatExtensionConfiguration.Parameter] should be of type [AzureCognitiveSearchChatExtensionConfiguration].\n$1");
297347
```

sdk/ai/azopenai/ci.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,9 @@ stages:
5555
OPENAI_EMBEDDINGS_MODEL: $(OPENAI-EMBEDDINGS-MODEL)
5656
OPENAI_CHAT_COMPLETIONS_MODEL: $(OPENAI-CHAT-COMPLETIONS-MODEL)
5757
OPENAI_COMPLETIONS_MODEL: $(OPENAI-COMPLETIONS-MODEL)
58+
59+
# used for BYOD scenarios with ChatCompletions
60+
COGNITIVE_SEARCH_API_ENDPOINT: $(COGNITIVE-SEARCH-API-ENDPOINT)
61+
COGNITIVE_SEARCH_API_INDEX: $(COGNITIVE-SEARCH-API-INDEX)
62+
COGNITIVE_SEARCH_API_KEY: $(COGNITIVE-SEARCH-API-KEY)
63+

sdk/ai/azopenai/client.go

Lines changed: 66 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
//go:build go1.18
2+
// +build go1.18
3+
4+
// Copyright (c) Microsoft Corporation. All rights reserved.
5+
// Licensed under the MIT License. See License.txt in the project root for license information.
6+
7+
package azopenai_test
8+
9+
import (
10+
"context"
11+
"errors"
12+
"io"
13+
"testing"
14+
15+
"github.com/Azure/azure-sdk-for-go/sdk/ai/azopenai"
16+
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
17+
"github.com/stretchr/testify/require"
18+
)
19+
20+
func TestChatCompletions_extensions_bringYourOwnData(t *testing.T) {
21+
client := newAzureOpenAIClientForTest(t, azureOpenAI)
22+
23+
resp, err := client.GetChatCompletions(context.Background(), azopenai.ChatCompletionsOptions{
24+
Messages: []azopenai.ChatMessage{
25+
{Content: to.Ptr("What does PR complete mean?"), Role: to.Ptr(azopenai.ChatRoleUser)},
26+
},
27+
MaxTokens: to.Ptr[int32](512),
28+
AzureExtensionsOptions: &azopenai.AzureChatExtensionOptions{
29+
Extensions: []azopenai.AzureChatExtensionConfiguration{
30+
{
31+
Type: to.Ptr(azopenai.AzureChatExtensionTypeAzureCognitiveSearch),
32+
Parameters: azureOpenAI.Cognitive,
33+
},
34+
},
35+
},
36+
Deployment: "gpt-4",
37+
}, nil)
38+
require.NoError(t, err)
39+
40+
// when you BYOD you get some extra content showing you metadata/info from the external
41+
// data source.
42+
msgContext := resp.Choices[0].Message.Context
43+
require.NotEmpty(t, msgContext.Messages[0].Content)
44+
require.Equal(t, azopenai.ChatRoleTool, *msgContext.Messages[0].Role)
45+
46+
require.NotEmpty(t, *resp.Choices[0].Message.Content)
47+
require.Equal(t, azopenai.CompletionsFinishReasonStop, *resp.Choices[0].FinishReason)
48+
}
49+
50+
func TestChatExtensionsStreaming_extensions_bringYourOwnData(t *testing.T) {
51+
client := newAzureOpenAIClientForTest(t, azureOpenAI)
52+
53+
streamResp, err := client.GetChatCompletionsStream(context.Background(), azopenai.ChatCompletionsOptions{
54+
Messages: []azopenai.ChatMessage{
55+
{Content: to.Ptr("What does PR complete mean?"), Role: to.Ptr(azopenai.ChatRoleUser)},
56+
},
57+
MaxTokens: to.Ptr[int32](512),
58+
AzureExtensionsOptions: &azopenai.AzureChatExtensionOptions{
59+
Extensions: []azopenai.AzureChatExtensionConfiguration{
60+
{
61+
Type: to.Ptr(azopenai.AzureChatExtensionTypeAzureCognitiveSearch),
62+
Parameters: azureOpenAI.Cognitive,
63+
},
64+
},
65+
},
66+
Deployment: "gpt-4",
67+
}, nil)
68+
69+
require.NoError(t, err)
70+
defer streamResp.ChatCompletionsStream.Close()
71+
72+
text := ""
73+
74+
first := false
75+
76+
for {
77+
event, err := streamResp.ChatCompletionsStream.Read()
78+
79+
if errors.Is(err, io.EOF) {
80+
break
81+
}
82+
83+
require.NoError(t, err)
84+
85+
if first {
86+
// when you BYOD you get some extra content showing you metadata/info from the external
87+
// data source.
88+
first = false
89+
msgContext := event.Choices[0].Message.Context
90+
require.NotEmpty(t, msgContext.Messages[0].Content)
91+
require.Equal(t, azopenai.ChatRoleTool, *msgContext.Messages[0].Role)
92+
}
93+
94+
for _, choice := range event.Choices {
95+
if choice.Delta != nil && choice.Delta.Content != nil {
96+
text += *choice.Delta.Content
97+
}
98+
}
99+
}
100+
101+
require.NotEmpty(t, text)
102+
}

0 commit comments

Comments
 (0)