1
1
# Copyright (c) Microsoft. All rights reserved.
2
2
3
-
3
+ import json
4
4
import os
5
+ import platform
6
+ from functools import reduce
5
7
6
8
import httpx
7
9
from aiohttp import ClientSession
8
- from azure_key_vault_settings import AzureKeyVaultSettings
9
10
11
+ from samples .concepts .plugins .azure_key_vault_settings import AzureKeyVaultSettings
10
12
from semantic_kernel import Kernel
13
+ from semantic_kernel .connectors .ai .function_call_behavior import FunctionCallBehavior
14
+ from semantic_kernel .connectors .ai .open_ai import OpenAIChatCompletion , OpenAIChatPromptExecutionSettings
11
15
from semantic_kernel .connectors .openai_plugin import OpenAIAuthenticationType , OpenAIFunctionExecutionParameters
12
- from semantic_kernel .functions import KernelPlugin
13
- from semantic_kernel .functions .kernel_arguments import KernelArguments
16
+ from semantic_kernel .contents import ChatHistory
17
+ from semantic_kernel .contents .chat_message_content import ChatMessageContent
18
+ from semantic_kernel .contents .function_call_content import FunctionCallContent
19
+ from semantic_kernel .contents .streaming_chat_message_content import StreamingChatMessageContent
20
+ from semantic_kernel .functions import KernelArguments , KernelFunction , KernelPlugin
14
21
22
+ # region Helper functions
15
23
16
- async def add_secret_to_key_vault (kernel : Kernel , plugin : KernelPlugin ):
17
- """Adds a secret to the Azure Key Vault."""
18
- arguments = KernelArguments ()
19
- arguments ["secret_name" ] = "Foo" # nosec
20
- arguments ["api_version" ] = "7.0"
21
- arguments ["value" ] = "Bar"
22
- arguments ["enabled" ] = True
23
- result = await kernel .invoke (
24
- function = plugin ["SetSecret" ],
25
- arguments = arguments ,
26
- )
27
24
28
- print (f"Secret added to Key Vault: { result } " )
25
+ def get_file_url (relative_path ):
26
+ absolute_path = os .path .abspath (relative_path )
27
+ if platform .system () == "Windows" :
28
+ return f"file:///{ absolute_path .replace ('\\ ' , '/' )} "
29
+ return f"file://{ absolute_path } "
29
30
30
31
31
- async def get_secret_from_key_vault (kernel : Kernel , plugin : KernelPlugin ):
32
- """Gets a secret from the Azure Key Vault."""
33
- arguments = KernelArguments ()
34
- arguments ["secret_name" ] = "Foo" # nosec
35
- arguments ["api_version" ] = "7.0"
36
- result = await kernel .invoke (
37
- function = plugin ["GetSecret" ],
38
- arguments = arguments ,
32
+ def load_and_update_openai_spec ():
33
+ # Construct the path to the OpenAI spec file
34
+ openai_spec_file = os .path .join (
35
+ os .path .dirname (os .path .dirname (os .path .realpath (__file__ ))),
36
+ "resources" ,
37
+ "open_ai_plugins" ,
38
+ "akv-openai.json"
39
39
)
40
40
41
- print (f"Secret retrieved from Key Vault: { result } " )
41
+ # Read the OpenAI spec file
42
+ with open (openai_spec_file ) as file :
43
+ openai_spec = json .load (file )
44
+
45
+ # Adjust the OpenAI spec file to use the correct file URL based on platform
46
+ openapi_yaml_path = os .path .join (
47
+ os .path .dirname (os .path .dirname (os .path .realpath (__file__ ))),
48
+ "resources" ,
49
+ "open_ai_plugins" ,
50
+ "akv-openapi.yaml"
51
+ )
52
+ openai_spec ["api" ]["url" ] = get_file_url (openapi_yaml_path )
53
+
54
+ return json .dumps (openai_spec , indent = 4 )
55
+
56
+
57
+ def print_tool_calls (message : ChatMessageContent ) -> None :
58
+ # A helper method to pretty print the tool calls from the message.
59
+ # This is only triggered if auto invoke tool calls is disabled.
60
+ items = message .items
61
+ formatted_tool_calls = []
62
+ for i , item in enumerate (items , start = 1 ):
63
+ if isinstance (item , FunctionCallContent ):
64
+ tool_call_id = item .id
65
+ function_name = item .name
66
+ function_arguments = item .arguments
67
+ formatted_str = (
68
+ f"tool_call { i } id: { tool_call_id } \n "
69
+ f"tool_call { i } function name: { function_name } \n "
70
+ f"tool_call { i } arguments: { function_arguments } "
71
+ )
72
+ formatted_tool_calls .append (formatted_str )
73
+ print ("Tool calls:\n " + "\n \n " .join (formatted_tool_calls ))
74
+
75
+ # endregion
76
+
77
+ # region Sample Authentication Provider
42
78
43
79
44
80
class OpenAIAuthenticationProvider :
@@ -101,28 +137,99 @@ async def authenticate_request(
101
137
auth_header = f"{ scheme } { credential } "
102
138
return {"Authorization" : auth_header }
103
139
140
+ # endregion
141
+
142
+ # region AKV Plugin Functions
143
+
144
+
145
+ async def add_secret_to_key_vault (kernel : Kernel , plugin : KernelPlugin ):
146
+ """Adds a secret to the Azure Key Vault."""
147
+ arguments = KernelArguments ()
148
+ arguments ["secret_name" ] = "Foo" # nosec
149
+ arguments ["api_version" ] = "7.0"
150
+ arguments ["value" ] = "Bar"
151
+ arguments ["enabled" ] = True
152
+ result = await kernel .invoke (
153
+ function = plugin ["SetSecret" ],
154
+ arguments = arguments ,
155
+ )
156
+
157
+ print (f"Secret added to Key Vault: { result } " )
158
+
159
+
160
+ async def get_secret_from_key_vault (kernel : Kernel , plugin : KernelPlugin ):
161
+ """Gets a secret from the Azure Key Vault."""
162
+ arguments = KernelArguments ()
163
+ arguments ["secret_name" ] = "Foo" # nosec
164
+ arguments ["api_version" ] = "7.0"
165
+ result = await kernel .invoke (
166
+ function = plugin ["GetSecret" ],
167
+ arguments = arguments ,
168
+ )
169
+
170
+ print (f"Secret retrieved from Key Vault: { result } " )
171
+
172
+ # endregion
104
173
105
- async def main ():
106
- # This example demonstrates how to connect an Azure Key Vault plugin to the Semantic Kernel.
107
- # To use this example, there are a few requirements:
108
- # 1. Register a client application with the Microsoft identity platform.
109
- # https://learn.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app
110
- #
111
- # 2. Create an Azure Key Vault
112
- # https://learn.microsoft.com/en-us/azure/key-vault/general/quick-create-portal
113
- # Please make sure to configure the AKV with a Vault Policy, instead of the default RBAC policy
114
- # This is because you will need to assign the Key Vault access policy to the client application you
115
- # registered in step 1. You should give the client application the "Get," "List," and "Set"
116
- # permissions for secrets.
117
- #
118
- # 3. Set your Key Vault endpoint, client ID, and client secret as user secrets using in your .env file:
119
- # AZURE_KEY_VAULT_ENDPOINT = ""
120
- # AZURE_KEY_VAULT_CLIENT_ID = ""
121
- # AZURE_KEY_VAULT_CLIENT_SECRET = ""
122
- #
123
- # 4. Replace your tenant ID with the "TENANT_ID" placeholder in
124
- # python/samples/kernel-syntax-examples/resources/akv-openai.json
125
174
175
+ kernel = Kernel ()
176
+
177
+ kernel .add_service (OpenAIChatCompletion (service_id = "chat" ))
178
+
179
+ chat_function = kernel .add_function (
180
+ prompt = "{{$chat_history}}{{$user_input}}" ,
181
+ plugin_name = "ChatBot" ,
182
+ function_name = "Chat" ,
183
+ )
184
+
185
+ execution_settings = OpenAIChatPromptExecutionSettings (
186
+ service_id = "chat" ,
187
+ max_tokens = 2000 ,
188
+ temperature = 0.7 ,
189
+ top_p = 0.8 ,
190
+ function_call_behavior = FunctionCallBehavior .EnableFunctions (
191
+ auto_invoke = True , filters = {"included_plugins" : ["AzureKeyVaultPlugin" ]}
192
+ ),
193
+ )
194
+
195
+ history = ChatHistory ()
196
+ history .add_system_message ("Use Api-version 7.0, if needed." )
197
+
198
+ arguments = KernelArguments (settings = execution_settings )
199
+
200
+
201
+ async def handle_streaming (
202
+ kernel : Kernel ,
203
+ chat_function : "KernelFunction" ,
204
+ arguments : KernelArguments ,
205
+ ) -> None :
206
+ """Handle streaming chat messages."""
207
+ response = kernel .invoke_stream (
208
+ chat_function ,
209
+ return_function_results = False ,
210
+ arguments = arguments ,
211
+ )
212
+
213
+ print ("Security Agent:> " , end = "" )
214
+ streamed_chunks : list [StreamingChatMessageContent ] = []
215
+ async for message in response :
216
+ if not execution_settings .function_call_behavior .auto_invoke_kernel_functions and isinstance (
217
+ message [0 ], StreamingChatMessageContent
218
+ ):
219
+ streamed_chunks .append (message [0 ])
220
+ else :
221
+ print (str (message [0 ]), end = "" )
222
+
223
+ if streamed_chunks :
224
+ streaming_chat_message = reduce (lambda first , second : first + second , streamed_chunks )
225
+ print ("Auto tool calls is disabled, printing returned tool calls..." )
226
+ print_tool_calls (streaming_chat_message )
227
+
228
+ print ("\n " )
229
+
230
+
231
+ async def main () -> None :
232
+ """Main function to run the chat bot."""
126
233
azure_keyvault_settings = AzureKeyVaultSettings .create ()
127
234
client_id = azure_keyvault_settings .client_id
128
235
client_secret = azure_keyvault_settings .client_secret .get_secret_value ()
@@ -138,17 +245,11 @@ async def main():
138
245
}
139
246
)
140
247
141
- kernel = Kernel ()
142
-
143
- openai_spec_file = os .path .join (
144
- os .path .dirname (os .path .dirname (os .path .realpath (__file__ ))), "resources" , "open_ai_plugins" , "akv-openai.json"
145
- )
146
- with open (openai_spec_file ) as file :
147
- openai_spec = file .read ()
248
+ openai_spec = load_and_update_openai_spec ()
148
249
149
250
http_client = httpx .AsyncClient ()
150
251
151
- plugin = await kernel .add_plugin_from_openai (
252
+ await kernel .add_plugin_from_openai (
152
253
plugin_name = "AzureKeyVaultPlugin" ,
153
254
plugin_str = openai_spec ,
154
255
execution_parameters = OpenAIFunctionExecutionParameters (
@@ -159,8 +260,36 @@ async def main():
159
260
),
160
261
)
161
262
162
- await add_secret_to_key_vault (kernel , plugin )
163
- await get_secret_from_key_vault (kernel , plugin )
263
+ chatting = True
264
+ print (
265
+ "Welcome to the chat bot!\
266
+ \n Type 'exit' to exit.\
267
+ \n Try chatting about Azure Key Vault!"
268
+ )
269
+ while chatting :
270
+ chatting = await chat ()
271
+
272
+
273
+ async def chat () -> bool :
274
+ """Chat with the bot."""
275
+ try :
276
+ user_input = input ("User:> " )
277
+ except KeyboardInterrupt :
278
+ print ("\n \n Exiting chat..." )
279
+ return False
280
+ except EOFError :
281
+ print ("\n \n Exiting chat..." )
282
+ return False
283
+
284
+ if user_input == "exit" :
285
+ print ("\n \n Exiting chat..." )
286
+ return False
287
+ arguments ["user_input" ] = user_input
288
+ arguments ["chat_history" ] = history
289
+
290
+ await handle_streaming (kernel , chat_function , arguments = arguments )
291
+
292
+ return True
164
293
165
294
166
295
if __name__ == "__main__" :
0 commit comments