From ebd79a4eea02c8f004e91d66fbe1baeb6d829e3e Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Tue, 25 Jun 2024 16:13:29 -0700 Subject: [PATCH 1/8] Add samples to use local models --- .../local_models/lm_studio_chat_completion.py | 83 ++++++++++++++++++ .../local_models/lm_studio_text_embedding.py | 62 +++++++++++++ .../local_models/ollama_chat_completion.py | 86 +++++++++++++++++++ 3 files changed, 231 insertions(+) create mode 100644 python/samples/concepts/local_models/lm_studio_chat_completion.py create mode 100644 python/samples/concepts/local_models/lm_studio_text_embedding.py create mode 100644 python/samples/concepts/local_models/ollama_chat_completion.py diff --git a/python/samples/concepts/local_models/lm_studio_chat_completion.py b/python/samples/concepts/local_models/lm_studio_chat_completion.py new file mode 100644 index 000000000000..d1c480720c89 --- /dev/null +++ b/python/samples/concepts/local_models/lm_studio_chat_completion.py @@ -0,0 +1,83 @@ +# Copyright (c) Microsoft. All rights reserved. + + +import asyncio + +from openai import AsyncOpenAI + +from semantic_kernel.connectors.ai.open_ai.services.open_ai_chat_completion import OpenAIChatCompletion +from semantic_kernel.contents.chat_history import ChatHistory +from semantic_kernel.functions.kernel_arguments import KernelArguments +from semantic_kernel.kernel import Kernel + +# This concept sample shows how to use the OpenAI connector to create a +# chat experience with a local model running in LM studio: https://lmstudio.ai/ +# Please follow the instructions here: https://lmstudio.ai/docs/local-server to set up LM studio. +# The default model used in this sample is phi3 due to its compact size. + +system_message = """ +You are a chat bot. Your name is Mosscap and +you have one goal: figure out what people need. +Your full name, should you need to know it, is +Splendid Speckled Mosscap. You communicate +effectively, but you tend to answer with long +flowery prose. +""" + +kernel = Kernel() + +service_id = "local-gpt" + +openAIClient: AsyncOpenAI = AsyncOpenAI( + api_key="fake-key", # This cannot be an empty string, use a fake key + base_url="http://localhost:1234/v1", +) +kernel.add_service(OpenAIChatCompletion(service_id=service_id, ai_model_id="phi3", async_client=openAIClient)) + +settings = kernel.get_prompt_execution_settings_from_service_id(service_id) +settings.max_tokens = 2000 +settings.temperature = 0.7 +settings.top_p = 0.8 + +chat_function = kernel.add_function( + plugin_name="ChatBot", + function_name="Chat", + prompt="{{$chat_history}}{{$user_input}}", + template_format="semantic-kernel", + prompt_execution_settings=settings, +) + +chat_history = ChatHistory(system_message=system_message) +chat_history.add_user_message("Hi there, who are you?") +chat_history.add_assistant_message("I am Mosscap, a chat bot. I'm trying to figure out what people need") + + +async def chat() -> bool: + try: + user_input = input("User:> ") + except KeyboardInterrupt: + print("\n\nExiting chat...") + return False + except EOFError: + print("\n\nExiting chat...") + return False + + if user_input == "exit": + print("\n\nExiting chat...") + return False + + answer = await kernel.invoke(chat_function, KernelArguments(user_input=user_input, chat_history=chat_history)) + chat_history.add_user_message(user_input) + chat_history.add_assistant_message(str(answer)) + print(f"Mosscap:> {answer}") + return True + + +async def main() -> None: + chatting = True + while chatting: + chatting = await chat() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/python/samples/concepts/local_models/lm_studio_text_embedding.py b/python/samples/concepts/local_models/lm_studio_text_embedding.py new file mode 100644 index 000000000000..807c0aff349c --- /dev/null +++ b/python/samples/concepts/local_models/lm_studio_text_embedding.py @@ -0,0 +1,62 @@ +# Copyright (c) Microsoft. All rights reserved. + +import asyncio + +from openai import AsyncOpenAI + +from semantic_kernel.connectors.ai.open_ai.services.open_ai_text_embedding import OpenAITextEmbedding +from semantic_kernel.core_plugins.text_memory_plugin import TextMemoryPlugin +from semantic_kernel.kernel import Kernel +from semantic_kernel.memory.semantic_text_memory import SemanticTextMemory +from semantic_kernel.memory.volatile_memory_store import VolatileMemoryStore + +# This concept sample shows how to use the OpenAI connector to add memory +# to applications with a local embedding model running in LM studio: https://lmstudio.ai/ +# Please follow the instructions here: https://lmstudio.ai/docs/local-server to set up LM studio. +# The default model used in this sample is from nomic.ai due to its compact size. + +kernel = Kernel() + +service_id = "local-gpt" + +openAIClient: AsyncOpenAI = AsyncOpenAI( + api_key="fake_key", # This cannot be an empty string, use a fake key + base_url="http://localhost:1234/v1", +) +kernel.add_service( + OpenAITextEmbedding( + service_id=service_id, ai_model_id="Nomic-embed-text-v1.5-Embedding-GGUF", async_client=openAIClient + ) +) + +memory = SemanticTextMemory(storage=VolatileMemoryStore(), embeddings_generator=kernel.get_service(service_id)) +kernel.add_plugin(TextMemoryPlugin(memory), "TextMemoryPlugin") + + +async def populate_memory(memory: SemanticTextMemory, collection_id="generic") -> None: + # Add some documents to the semantic memory + await memory.save_information(collection=collection_id, id="info1", text="Your budget for 2024 is $100,000") + await memory.save_information(collection=collection_id, id="info2", text="Your savings from 2023 are $50,000") + await memory.save_information(collection=collection_id, id="info3", text="Your investments are $80,000") + + +async def search_memory_examples(memory: SemanticTextMemory, collection_id="generic") -> None: + questions = [ + "What is my budget for 2024?", + "What are my savings from 2023?", + "What are my investments?", + ] + + for question in questions: + print(f"Question: {question}") + result = await memory.search(collection_id, question) + print(f"Answer: {result[0].text}\n") + + +async def main() -> None: + await populate_memory(memory) + await search_memory_examples(memory) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/python/samples/concepts/local_models/ollama_chat_completion.py b/python/samples/concepts/local_models/ollama_chat_completion.py new file mode 100644 index 000000000000..601d28c3f460 --- /dev/null +++ b/python/samples/concepts/local_models/ollama_chat_completion.py @@ -0,0 +1,86 @@ +# Copyright (c) Microsoft. All rights reserved. + + +import asyncio + +from openai import AsyncOpenAI + +from semantic_kernel.connectors.ai.open_ai.services.open_ai_chat_completion import OpenAIChatCompletion +from semantic_kernel.contents.chat_history import ChatHistory +from semantic_kernel.functions.kernel_arguments import KernelArguments +from semantic_kernel.kernel import Kernel + +# This concept sample shows how to use the OpenAI connector with +# a local model running in Ollama: https://github.com/ollama/ollama +# The default model used in this sample is phi3 due to its compact size. +# At the time of creating this sample, Ollama only provides experimental +# compatibility with the `chat/completions` endpoint: +# https://github.com/ollama/ollama/blob/main/docs/openai.md +# Please follow the instructions in the Ollama repository to set up Ollama. + +system_message = """ +You are a chat bot. Your name is Mosscap and +you have one goal: figure out what people need. +Your full name, should you need to know it, is +Splendid Speckled Mosscap. You communicate +effectively, but you tend to answer with long +flowery prose. +""" + +kernel = Kernel() + +service_id = "local-gpt" + +openAIClient: AsyncOpenAI = AsyncOpenAI( + api_key="fake-key", # This cannot be an empty string, use a fake key + base_url="http://localhost:11434/v1", +) +kernel.add_service(OpenAIChatCompletion(service_id=service_id, ai_model_id="phi3", async_client=openAIClient)) + +settings = kernel.get_prompt_execution_settings_from_service_id(service_id) +settings.max_tokens = 2000 +settings.temperature = 0.7 +settings.top_p = 0.8 + +chat_function = kernel.add_function( + plugin_name="ChatBot", + function_name="Chat", + prompt="{{$chat_history}}{{$user_input}}", + template_format="semantic-kernel", + prompt_execution_settings=settings, +) + +chat_history = ChatHistory(system_message=system_message) +chat_history.add_user_message("Hi there, who are you?") +chat_history.add_assistant_message("I am Mosscap, a chat bot. I'm trying to figure out what people need") + + +async def chat() -> bool: + try: + user_input = input("User:> ") + except KeyboardInterrupt: + print("\n\nExiting chat...") + return False + except EOFError: + print("\n\nExiting chat...") + return False + + if user_input == "exit": + print("\n\nExiting chat...") + return False + + answer = await kernel.invoke(chat_function, KernelArguments(user_input=user_input, chat_history=chat_history)) + chat_history.add_user_message(user_input) + chat_history.add_assistant_message(str(answer)) + print(f"Mosscap:> {answer}") + return True + + +async def main() -> None: + chatting = True + while chatting: + chatting = await chat() + + +if __name__ == "__main__": + asyncio.run(main()) From 13bfe5c0efaff415203884e465e1aac273a739e8 Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Wed, 26 Jun 2024 10:40:39 -0700 Subject: [PATCH 2/8] Setup Ollama for sample tests --- .github/workflows/python-samples-tests.yml | 7 +- python/tests/samples/test_concepts.py | 126 +++++++++------------ python/tests/samples/test_samples_utils.py | 12 +- 3 files changed, 66 insertions(+), 79 deletions(-) diff --git a/.github/workflows/python-samples-tests.yml b/.github/workflows/python-samples-tests.yml index ed442503c9f7..707bebe634b4 100644 --- a/.github/workflows/python-samples-tests.yml +++ b/.github/workflows/python-samples-tests.yml @@ -18,7 +18,7 @@ jobs: matrix: python-version: ["3.10", "3.11", "3.12"] os: [ubuntu-latest, windows-latest, macos-latest] - service: ['AzureOpenAI'] + service: ["AzureOpenAI"] steps: - uses: actions/checkout@v4 - name: Install poetry @@ -28,6 +28,10 @@ jobs: with: python-version: ${{ matrix.python-version }} cache: "poetry" + - name: Setup Ollama + run: | + docker run -d -v ollama:/root/.ollama -p 11434:11434 --name ollama ollama/ollama + docker exec -it ollama ollama pull phi3 - name: Run samples Tests id: run_tests shell: bash @@ -52,4 +56,3 @@ jobs: run: | cd python poetry run pytest ./tests/samples -v - diff --git a/python/tests/samples/test_concepts.py b/python/tests/samples/test_concepts.py index 3692f3761916..2a8439294b16 100644 --- a/python/tests/samples/test_concepts.py +++ b/python/tests/samples/test_concepts.py @@ -1,91 +1,67 @@ # Copyright (c) Microsoft. All rights reserved. +import copy + from pytest import mark, param -from samples.concepts.auto_function_calling.azure_python_code_interpreter_function_calling import ( - main as azure_python_code_interpreter_function_calling, -) -from samples.concepts.auto_function_calling.chat_gpt_api_function_calling import main as chat_gpt_api_function_calling -from samples.concepts.chat_completion.azure_chat_gpt_api import main as azure_chat_gpt_api -from samples.concepts.chat_completion.azure_chat_image_input import main as azure_chat_image_input from samples.concepts.chat_completion.chat_gpt_api import main as chat_gpt_api -from samples.concepts.chat_completion.chat_streaming import main as chat_streaming -from samples.concepts.chat_completion.openai_logit_bias import main as openai_logit_bias -from samples.concepts.filtering.auto_function_invoke_filters import main as auto_function_invoke_filters -from samples.concepts.filtering.function_invocation_filters import main as function_invocation_filters -from samples.concepts.filtering.function_invocation_filters_stream import main as function_invocation_filters_stream -from samples.concepts.filtering.prompt_filters import main as prompt_filters -from samples.concepts.functions.kernel_arguments import main as kernel_arguments -from samples.concepts.grounding.grounded import main as grounded -from samples.concepts.memory.azure_cognitive_search_memory import main as azure_cognitive_search_memory -from samples.concepts.memory.memory import main as memory -from samples.concepts.planners.azure_openai_function_calling_stepwise_planner import ( - main as azure_openai_function_calling_stepwise_planner, -) -from samples.concepts.planners.openai_function_calling_stepwise_planner import ( - main as openai_function_calling_stepwise_planner, -) -from samples.concepts.planners.sequential_planner import main as sequential_planner -from samples.concepts.plugins.openai_function_calling_with_custom_plugin import ( - main as openai_function_calling_with_custom_plugin, -) -from samples.concepts.plugins.openai_plugin_azure_key_vault import main as openai_plugin_azure_key_vault -from samples.concepts.plugins.openai_plugin_klarna import main as openai_plugin_klarna -from samples.concepts.plugins.plugins_from_dir import main as plugins_from_dir -from samples.concepts.prompt_templates.azure_chat_gpt_api_handlebars import main as azure_chat_gpt_api_handlebars -from samples.concepts.prompt_templates.azure_chat_gpt_api_jinja2 import main as azure_chat_gpt_api_jinja2 -from samples.concepts.prompt_templates.configuring_prompts import main as configuring_prompts -from samples.concepts.prompt_templates.load_yaml_prompt import main as load_yaml_prompt -from samples.concepts.prompt_templates.template_language import main as template_language -from samples.concepts.rag.rag_with_text_memory_plugin import main as rag_with_text_memory_plugin -from samples.concepts.search.bing_search_plugin import main as bing_search_plugin -from samples.concepts.service_selector.custom_service_selector import main as custom_service_selector +from samples.concepts.local_models.lm_studio_chat_completion import main as lm_studio_chat_completion +from samples.concepts.local_models.lm_studio_text_embedding import main as lm_studio_text_embedding +from samples.concepts.local_models.ollama_chat_completion import main as ollama_chat_completion from tests.samples.test_samples_utils import retry concepts = [ - param( - azure_python_code_interpreter_function_calling, - ["print('Hello, World!')", "exit"], - id="azure_python_code_interpreter_function_calling", - ), - param(chat_gpt_api_function_calling, ["What is 3+3?", "exit"], id="cht_gpt_api_function_calling"), - param(azure_chat_gpt_api, ["Why is the sky blue?", "exit"], id="azure_chat_gpt_api"), + # param( + # azure_python_code_interpreter_function_calling, + # ["print('Hello, World!')", "exit"], + # ), + # param(chat_gpt_api_function_calling, ["What is 3+3?", "exit"], id="cht_gpt_api_function_calling"), + # param(azure_chat_gpt_api, ["Why is the sky blue?", "exit"], id="azure_chat_gpt_api"), param(chat_gpt_api, ["What is life?", "exit"], id="chat_gpt_api"), - param(chat_streaming, ["Why is the sun hot?", "exit"], id="chat_streaming"), - param(openai_logit_bias, [], id="openai_logit_bias"), - param(auto_function_invoke_filters, ["What is 3+3?", "exit"], id="auo_function_invoke_filters"), - param(function_invocation_filters, ["What is 3+3?", "exit"], id="function_invocation_filters"), - param(function_invocation_filters_stream, ["What is 3+3?", "exit"], id="function_invocation_filters_stream"), - param(prompt_filters, ["What is the fastest animal?", "exit"], id="prompt_filters"), - param(kernel_arguments, [], id="kernel_arguments"), - param(grounded, [], id="grounded"), - param(azure_cognitive_search_memory, [], id="azure_cognitive_search_memory"), - param(memory, ["What are my investments?", "exit"], id="memory"), - param(azure_openai_function_calling_stepwise_planner, [], id="azure_openai_function_calling_stepwise_planner"), - param(openai_function_calling_stepwise_planner, [], id="openai_function_calling_stepwise_planner"), - param(sequential_planner, [], id="sequential_planner"), - param(openai_function_calling_with_custom_plugin, [], id="openai_function_calling_with_custom_plugin"), - param( - openai_plugin_azure_key_vault, - ["Create a secret with the name 'Foo' and value 'Bar'", "exit"], - id="openai_plugin_azure_key_vault", - ), - param(openai_plugin_klarna, [], id="openai_plugin_klarna"), - param(plugins_from_dir, [], id="plugins_from_dir"), - param(azure_chat_gpt_api_handlebars, ["What is 3+3?", "exit"], id="azure_chat_gpt_api_handlebars"), - param(azure_chat_gpt_api_jinja2, ["What is 3+3?", "exit"], id="azure_chat_gpt_api_jinja2"), - param(configuring_prompts, ["What is my name?", "exit"], id="configuring_prompts"), - param(load_yaml_prompt, [], id="load_yaml_prompt"), - param(template_language, [], id="template_language"), - param(rag_with_text_memory_plugin, [], id="rag_with_text_memory_plugin"), - param(bing_search_plugin, [], id="bing_search_plugin"), - param(azure_chat_image_input, [], id="azure_chat_image_input"), - param(custom_service_selector, [], id="custom_service_selector"), + # param(chat_streaming, ["Why is the sun hot?", "exit"], id="chat_streaming"), + # param(openai_logit_bias, [], id="openai_logit_bias"), + # param(auto_function_invoke_filters, ["What is 3+3?", "exit"], id="auo_function_invoke_filters"), + # param(function_invocation_filters, ["What is 3+3?", "exit"], id="function_invocation_filters"), + # param(function_invocation_filters_stream, ["What is 3+3?", "exit"], id="function_invocation_filters_stream"), + # param(prompt_filters, ["What is the fastest animal?", "exit"], id="prompt_filters"), + # param(kernel_arguments, [], id="kernel_arguments"), + # param(grounded, [], id="grounded"), + # param(azure_cognitive_search_memory, [], id="azure_cognitive_search_memory"), + # param(memory, ["What are my investments?", "exit"], id="memory"), + # param(azure_openai_function_calling_stepwise_planner, [], id="azure_openai_function_calling_stepwise_planner"), + # param(openai_function_calling_stepwise_planner, [], id="openai_function_calling_stepwise_planner"), + # param(sequential_planner, [], id="sequential_planner"), + # param(openai_function_calling_with_custom_plugin, [], id="openai_function_calling_with_custom_plugin"), + # param( + # openai_plugin_azure_key_vault, + # ["Create a secret with the name 'Foo' and value 'Bar'", "exit"], + # id="openai_plugin_azure_key_vault", + # ), + # param(openai_plugin_klarna, [], id="openai_plugin_klarna"), + # param(plugins_from_dir, [], id="plugins_from_dir"), + # param(azure_chat_gpt_api_handlebars, ["What is 3+3?", "exit"], id="azure_chat_gpt_api_handlebars"), + # param(azure_chat_gpt_api_jinja2, ["What is 3+3?", "exit"], id="azure_chat_gpt_api_jinja2"), + # param(configuring_prompts, ["What is my name?", "exit"], id="configuring_prompts"), + # param(load_yaml_prompt, [], id="load_yaml_prompt"), + # param(template_language, [], id="template_language"), + # param(rag_with_text_memory_plugin, [], id="rag_with_text_memory_plugin"), + # param(bing_search_plugin, [], id="bing_search_plugin"), + # param(azure_chat_image_input, [], id="azure_chat_image_input"), + # param(custom_service_selector, [], id="custom_service_selector"), + param(ollama_chat_completion, ["Why is the sky blue?", "exit"], id="ollama_chat_completion"), + param(lm_studio_chat_completion, ["Why is the sky blue?", "exit"], id="lm_studio_chat_completion"), + param(lm_studio_text_embedding, [], id="lm_studio_text_embedding"), ] @mark.asyncio @mark.parametrize("func, responses", concepts) async def test_concepts(func, responses, monkeypatch): + saved_responses = copy.deepcopy(responses) + + def reset(): + responses.clear() + responses.extend(saved_responses) + monkeypatch.setattr("builtins.input", lambda _: responses.pop(0)) - await retry(lambda: func()) + await retry(lambda: func(), reset=reset) diff --git a/python/tests/samples/test_samples_utils.py b/python/tests/samples/test_samples_utils.py index d04b39d3656b..de2b8257e7b7 100644 --- a/python/tests/samples/test_samples_utils.py +++ b/python/tests/samples/test_samples_utils.py @@ -7,11 +7,19 @@ logger = logging.getLogger() -async def retry(func, max_retries=3): - """Retry a function a number of times before raising an exception.""" +async def retry(func, reset=None, max_retries=3): + """Retry a function a number of times before raising an exception. + + args: + func: the async function to retry (required) + reset: a function to reset the state of any variables used in the function (optional) + max_retries: the number of times to retry the function before raising an exception (optional) + """ attempt = 0 while attempt < max_retries: try: + if reset: + reset() await func() break except Exception as e: From 918f4d88cf29cc6092afc63e185a500ecd1aece8 Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Wed, 26 Jun 2024 13:37:14 -0700 Subject: [PATCH 3/8] Mark skip --- python/tests/samples/test_concepts.py | 131 ++++++++++++++++++-------- 1 file changed, 92 insertions(+), 39 deletions(-) diff --git a/python/tests/samples/test_concepts.py b/python/tests/samples/test_concepts.py index 2a8439294b16..c7cca89f354e 100644 --- a/python/tests/samples/test_concepts.py +++ b/python/tests/samples/test_concepts.py @@ -2,55 +2,108 @@ import copy +import pytest from pytest import mark, param +from samples.concepts.auto_function_calling.azure_python_code_interpreter_function_calling import ( + main as azure_python_code_interpreter_function_calling, +) +from samples.concepts.auto_function_calling.chat_gpt_api_function_calling import main as chat_gpt_api_function_calling +from samples.concepts.chat_completion.azure_chat_gpt_api import main as azure_chat_gpt_api +from samples.concepts.chat_completion.azure_chat_image_input import main as azure_chat_image_input from samples.concepts.chat_completion.chat_gpt_api import main as chat_gpt_api +from samples.concepts.chat_completion.chat_streaming import main as chat_streaming +from samples.concepts.chat_completion.openai_logit_bias import main as openai_logit_bias +from samples.concepts.filtering.auto_function_invoke_filters import main as auto_function_invoke_filters +from samples.concepts.filtering.function_invocation_filters import main as function_invocation_filters +from samples.concepts.filtering.function_invocation_filters_stream import main as function_invocation_filters_stream +from samples.concepts.filtering.prompt_filters import main as prompt_filters +from samples.concepts.functions.kernel_arguments import main as kernel_arguments +from samples.concepts.grounding.grounded import main as grounded from samples.concepts.local_models.lm_studio_chat_completion import main as lm_studio_chat_completion from samples.concepts.local_models.lm_studio_text_embedding import main as lm_studio_text_embedding from samples.concepts.local_models.ollama_chat_completion import main as ollama_chat_completion +from samples.concepts.memory.azure_cognitive_search_memory import main as azure_cognitive_search_memory +from samples.concepts.memory.memory import main as memory +from samples.concepts.planners.azure_openai_function_calling_stepwise_planner import ( + main as azure_openai_function_calling_stepwise_planner, +) +from samples.concepts.planners.openai_function_calling_stepwise_planner import ( + main as openai_function_calling_stepwise_planner, +) +from samples.concepts.planners.sequential_planner import main as sequential_planner +from samples.concepts.plugins.openai_function_calling_with_custom_plugin import ( + main as openai_function_calling_with_custom_plugin, +) +from samples.concepts.plugins.openai_plugin_azure_key_vault import main as openai_plugin_azure_key_vault +from samples.concepts.plugins.openai_plugin_klarna import main as openai_plugin_klarna +from samples.concepts.plugins.plugins_from_dir import main as plugins_from_dir +from samples.concepts.prompt_templates.azure_chat_gpt_api_handlebars import main as azure_chat_gpt_api_handlebars +from samples.concepts.prompt_templates.azure_chat_gpt_api_jinja2 import main as azure_chat_gpt_api_jinja2 +from samples.concepts.prompt_templates.configuring_prompts import main as configuring_prompts +from samples.concepts.prompt_templates.load_yaml_prompt import main as load_yaml_prompt +from samples.concepts.prompt_templates.template_language import main as template_language +from samples.concepts.rag.rag_with_text_memory_plugin import main as rag_with_text_memory_plugin +from samples.concepts.search.bing_search_plugin import main as bing_search_plugin +from samples.concepts.service_selector.custom_service_selector import main as custom_service_selector from tests.samples.test_samples_utils import retry concepts = [ - # param( - # azure_python_code_interpreter_function_calling, - # ["print('Hello, World!')", "exit"], - # ), - # param(chat_gpt_api_function_calling, ["What is 3+3?", "exit"], id="cht_gpt_api_function_calling"), - # param(azure_chat_gpt_api, ["Why is the sky blue?", "exit"], id="azure_chat_gpt_api"), + param( + azure_python_code_interpreter_function_calling, + ["print('Hello, World!')", "exit"], + ), + param(chat_gpt_api_function_calling, ["What is 3+3?", "exit"], id="cht_gpt_api_function_calling"), + param(azure_chat_gpt_api, ["Why is the sky blue?", "exit"], id="azure_chat_gpt_api"), param(chat_gpt_api, ["What is life?", "exit"], id="chat_gpt_api"), - # param(chat_streaming, ["Why is the sun hot?", "exit"], id="chat_streaming"), - # param(openai_logit_bias, [], id="openai_logit_bias"), - # param(auto_function_invoke_filters, ["What is 3+3?", "exit"], id="auo_function_invoke_filters"), - # param(function_invocation_filters, ["What is 3+3?", "exit"], id="function_invocation_filters"), - # param(function_invocation_filters_stream, ["What is 3+3?", "exit"], id="function_invocation_filters_stream"), - # param(prompt_filters, ["What is the fastest animal?", "exit"], id="prompt_filters"), - # param(kernel_arguments, [], id="kernel_arguments"), - # param(grounded, [], id="grounded"), - # param(azure_cognitive_search_memory, [], id="azure_cognitive_search_memory"), - # param(memory, ["What are my investments?", "exit"], id="memory"), - # param(azure_openai_function_calling_stepwise_planner, [], id="azure_openai_function_calling_stepwise_planner"), - # param(openai_function_calling_stepwise_planner, [], id="openai_function_calling_stepwise_planner"), - # param(sequential_planner, [], id="sequential_planner"), - # param(openai_function_calling_with_custom_plugin, [], id="openai_function_calling_with_custom_plugin"), - # param( - # openai_plugin_azure_key_vault, - # ["Create a secret with the name 'Foo' and value 'Bar'", "exit"], - # id="openai_plugin_azure_key_vault", - # ), - # param(openai_plugin_klarna, [], id="openai_plugin_klarna"), - # param(plugins_from_dir, [], id="plugins_from_dir"), - # param(azure_chat_gpt_api_handlebars, ["What is 3+3?", "exit"], id="azure_chat_gpt_api_handlebars"), - # param(azure_chat_gpt_api_jinja2, ["What is 3+3?", "exit"], id="azure_chat_gpt_api_jinja2"), - # param(configuring_prompts, ["What is my name?", "exit"], id="configuring_prompts"), - # param(load_yaml_prompt, [], id="load_yaml_prompt"), - # param(template_language, [], id="template_language"), - # param(rag_with_text_memory_plugin, [], id="rag_with_text_memory_plugin"), - # param(bing_search_plugin, [], id="bing_search_plugin"), - # param(azure_chat_image_input, [], id="azure_chat_image_input"), - # param(custom_service_selector, [], id="custom_service_selector"), - param(ollama_chat_completion, ["Why is the sky blue?", "exit"], id="ollama_chat_completion"), - param(lm_studio_chat_completion, ["Why is the sky blue?", "exit"], id="lm_studio_chat_completion"), - param(lm_studio_text_embedding, [], id="lm_studio_text_embedding"), + param(chat_streaming, ["Why is the sun hot?", "exit"], id="chat_streaming"), + param(openai_logit_bias, [], id="openai_logit_bias"), + param(auto_function_invoke_filters, ["What is 3+3?", "exit"], id="auo_function_invoke_filters"), + param(function_invocation_filters, ["What is 3+3?", "exit"], id="function_invocation_filters"), + param(function_invocation_filters_stream, ["What is 3+3?", "exit"], id="function_invocation_filters_stream"), + param(prompt_filters, ["What is the fastest animal?", "exit"], id="prompt_filters"), + param(kernel_arguments, [], id="kernel_arguments"), + param(grounded, [], id="grounded"), + param(azure_cognitive_search_memory, [], id="azure_cognitive_search_memory"), + param(memory, ["What are my investments?", "exit"], id="memory"), + param(azure_openai_function_calling_stepwise_planner, [], id="azure_openai_function_calling_stepwise_planner"), + param(openai_function_calling_stepwise_planner, [], id="openai_function_calling_stepwise_planner"), + param(sequential_planner, [], id="sequential_planner"), + param(openai_function_calling_with_custom_plugin, [], id="openai_function_calling_with_custom_plugin"), + param( + openai_plugin_azure_key_vault, + ["Create a secret with the name 'Foo' and value 'Bar'", "exit"], + id="openai_plugin_azure_key_vault", + ), + param(openai_plugin_klarna, [], id="openai_plugin_klarna"), + param(plugins_from_dir, [], id="plugins_from_dir"), + param(azure_chat_gpt_api_handlebars, ["What is 3+3?", "exit"], id="azure_chat_gpt_api_handlebars"), + param(azure_chat_gpt_api_jinja2, ["What is 3+3?", "exit"], id="azure_chat_gpt_api_jinja2"), + param(configuring_prompts, ["What is my name?", "exit"], id="configuring_prompts"), + param(load_yaml_prompt, [], id="load_yaml_prompt"), + param(template_language, [], id="template_language"), + param(rag_with_text_memory_plugin, [], id="rag_with_text_memory_plugin"), + param(bing_search_plugin, [], id="bing_search_plugin"), + param(azure_chat_image_input, [], id="azure_chat_image_input"), + param(custom_service_selector, [], id="custom_service_selector"), + param( + ollama_chat_completion, + ["Why is the sky blue?", "exit"], + id="ollama_chat_completion", + marks=pytest.mark.skip(reason="Need to set up Ollama locally."), + ), + param( + lm_studio_chat_completion, + ["Why is the sky blue?", "exit"], + id="lm_studio_chat_completion", + marks=pytest.mark.skip(reason="Need to set up LM Studio locally."), + ), + param( + lm_studio_text_embedding, + [], + id="lm_studio_text_embedding", + marks=pytest.mark.skip(reason="Need to set up LM Studio locally."), + ), ] From d32628a1f3f06ef7ee2ac3979b93114098e9dd56 Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Wed, 26 Jun 2024 13:41:43 -0700 Subject: [PATCH 4/8] Misc --- .github/workflows/python-samples-tests.yml | 58 -------------------- python/tests/samples/test_concepts.py | 1 + python/tests/samples/test_learn_resources.py | 13 ++++- 3 files changed, 12 insertions(+), 60 deletions(-) delete mode 100644 .github/workflows/python-samples-tests.yml diff --git a/.github/workflows/python-samples-tests.yml b/.github/workflows/python-samples-tests.yml deleted file mode 100644 index 707bebe634b4..000000000000 --- a/.github/workflows/python-samples-tests.yml +++ /dev/null @@ -1,58 +0,0 @@ -# -# This workflow will run all python samples tests. -# - -name: Python Samples Tests - -on: - workflow_dispatch: - schedule: - - cron: "0 1 * * 0" # Run at 1AM UTC daily on Sunday - -jobs: - python-samples-tests: - runs-on: ${{ matrix.os }} - strategy: - max-parallel: 1 - fail-fast: true - matrix: - python-version: ["3.10", "3.11", "3.12"] - os: [ubuntu-latest, windows-latest, macos-latest] - service: ["AzureOpenAI"] - steps: - - uses: actions/checkout@v4 - - name: Install poetry - run: pipx install poetry - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - cache: "poetry" - - name: Setup Ollama - run: | - docker run -d -v ollama:/root/.ollama -p 11434:11434 --name ollama ollama/ollama - docker exec -it ollama ollama pull phi3 - - name: Run samples Tests - id: run_tests - shell: bash - env: # Set Azure credentials secret as an input - GLOBAL_LLM_SERVICE: ${{ matrix.service }} - AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME: ${{ vars.AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME }} - AZURE_OPENAI_CHAT_DEPLOYMENT_NAME: ${{ vars.AZURE_OPENAI_CHAT_DEPLOYMENT_NAME }} - AZURE_OPENAI_TEXT_DEPLOYMENT_NAME: ${{ vars.AZURE_OPENAI_TEXT_DEPLOYMENT_NAME }} - AZURE_OPENAI_API_VERSION: ${{ vars.AZURE_OPENAI_API_VERSION }} - AZURE_OPENAI_ENDPOINT: ${{ secrets.AZURE_OPENAI_ENDPOINT }} - AZURE_OPENAI_API_KEY: ${{ secrets.AZURE_OPENAI_API_KEY }} - BING_API_KEY: ${{ secrets.BING_API_KEY }} - OPENAI_CHAT_MODEL_ID: ${{ vars.OPENAI_CHAT_MODEL_ID }} - OPENAI_TEXT_MODEL_ID: ${{ vars.OPENAI_TEXT_MODEL_ID }} - OPENAI_EMBEDDING_MODEL_ID: ${{ vars.OPENAI_EMBEDDING_MODEL_ID }} - OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - PINECONE_API_KEY: ${{ secrets.PINECONE__APIKEY }} - POSTGRES_CONNECTION_STRING: ${{secrets.POSTGRES__CONNECTIONSTR}} - AZURE_AI_SEARCH_API_KEY: ${{secrets.AZURE_AI_SEARCH_API_KEY}} - AZURE_AI_SEARCH_ENDPOINT: ${{secrets.AZURE_AI_SEARCH_ENDPOINT}} - MONGODB_ATLAS_CONNECTION_STRING: ${{secrets.MONGODB_ATLAS_CONNECTION_STRING}} - run: | - cd python - poetry run pytest ./tests/samples -v diff --git a/python/tests/samples/test_concepts.py b/python/tests/samples/test_concepts.py index c7cca89f354e..e6b2e9935bbc 100644 --- a/python/tests/samples/test_concepts.py +++ b/python/tests/samples/test_concepts.py @@ -52,6 +52,7 @@ param( azure_python_code_interpreter_function_calling, ["print('Hello, World!')", "exit"], + id="azure_python_code_interpreter_function_calling", ), param(chat_gpt_api_function_calling, ["What is 3+3?", "exit"], id="cht_gpt_api_function_calling"), param(azure_chat_gpt_api, ["Why is the sky blue?", "exit"], id="azure_chat_gpt_api"), diff --git a/python/tests/samples/test_learn_resources.py b/python/tests/samples/test_learn_resources.py index 82a6e5d00175..305f853a6ee0 100644 --- a/python/tests/samples/test_learn_resources.py +++ b/python/tests/samples/test_learn_resources.py @@ -1,5 +1,7 @@ # Copyright (c) Microsoft. All rights reserved. +import copy + from pytest import mark from samples.learn_resources.ai_services import main as ai_services @@ -44,8 +46,15 @@ ], ) async def test_learn_resources(func, responses, monkeypatch): + saved_responses = copy.deepcopy(responses) + + def reset(): + responses.clear() + responses.extend(saved_responses) + monkeypatch.setattr("builtins.input", lambda _: responses.pop(0)) if func.__module__ == "samples.learn_resources.your_first_prompt": - await retry(lambda: func(delay=10)) + await retry(lambda: func(delay=10), reset=reset) return - await retry(lambda: func()) + + await retry(lambda: func(), reset=reset) From d403cc59f1c6027af7bb9218fab40cdcd5d5c259 Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Wed, 26 Jun 2024 13:45:43 -0700 Subject: [PATCH 5/8] Misc 2 --- .../samples/concepts/local_models/ollama_chat_completion.py | 1 + python/tests/samples/test_concepts.py | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/python/samples/concepts/local_models/ollama_chat_completion.py b/python/samples/concepts/local_models/ollama_chat_completion.py index 601d28c3f460..32413d91a530 100644 --- a/python/samples/concepts/local_models/ollama_chat_completion.py +++ b/python/samples/concepts/local_models/ollama_chat_completion.py @@ -12,6 +12,7 @@ # This concept sample shows how to use the OpenAI connector with # a local model running in Ollama: https://github.com/ollama/ollama +# A docker image is also available: https://hub.docker.com/r/ollama/ollama # The default model used in this sample is phi3 due to its compact size. # At the time of creating this sample, Ollama only provides experimental # compatibility with the `chat/completions` endpoint: diff --git a/python/tests/samples/test_concepts.py b/python/tests/samples/test_concepts.py index e6b2e9935bbc..ad0e218eea40 100644 --- a/python/tests/samples/test_concepts.py +++ b/python/tests/samples/test_concepts.py @@ -91,19 +91,19 @@ ollama_chat_completion, ["Why is the sky blue?", "exit"], id="ollama_chat_completion", - marks=pytest.mark.skip(reason="Need to set up Ollama locally."), + marks=pytest.mark.skip(reason="Need to set up Ollama locally. Check out the module for more details."), ), param( lm_studio_chat_completion, ["Why is the sky blue?", "exit"], id="lm_studio_chat_completion", - marks=pytest.mark.skip(reason="Need to set up LM Studio locally."), + marks=pytest.mark.skip(reason="Need to set up LM Studio locally. Check out the module for more details."), ), param( lm_studio_text_embedding, [], id="lm_studio_text_embedding", - marks=pytest.mark.skip(reason="Need to set up LM Studio locally."), + marks=pytest.mark.skip(reason="Need to set up LM Studio locally. Check out the module for more details."), ), ] From 85e51f79258781691b320db97868050b35539e1a Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Thu, 27 Jun 2024 10:20:13 -0700 Subject: [PATCH 6/8] Update readme --- python/samples/concepts/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/python/samples/concepts/README.md b/python/samples/concepts/README.md index 72028080bd2a..7832d6717a9b 100644 --- a/python/samples/concepts/README.md +++ b/python/samples/concepts/README.md @@ -9,6 +9,7 @@ This section contains code snippets that demonstrate the usage of Semantic Kerne | Filtering | Creating and using Filters | | Functions | Invoking [`Method`](https://github.com/microsoft/semantic-kernel/blob/main/python/semantic_kernel/functions/kernel_function_from_method.py) or [`Prompt`](https://github.com/microsoft/semantic-kernel/blob/main/python/semantic_kernel/functions/kernel_function_from_prompt.py) functions with [`Kernel`](https://github.com/microsoft/semantic-kernel/blob/main/python/semantic_kernel/kernel.py) | | Grounding | An example of how to perform LLM grounding | +| Local Models | Using the [`OpenAI connector`](https://github.com/microsoft/semantic-kernel/blob/main/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_chat_completion.py) to talk to models hosted locally in Ollama and LM Studio | | Logging | Showing how to set up logging | | Memory | Using [`Memory`](https://github.com/microsoft/semantic-kernel/tree/main/dotnet/src/SemanticKernel.Abstractions/Memory) AI concepts | | On Your Data | Examples of using AzureOpenAI [`On Your Data`](https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/use-your-data?tabs=mongo-db) | From b45f1a73c196e9790b94f35e4de9c07d6789dc43 Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Fri, 5 Jul 2024 10:17:28 -0700 Subject: [PATCH 7/8] Make api key optional --- .../ai/open_ai/services/open_ai_chat_completion.py | 9 +++++++-- .../connectors/ai/open_ai/settings/open_ai_settings.py | 6 ++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_chat_completion.py b/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_chat_completion.py index d808bdd5a8af..5d6b4425c065 100644 --- a/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_chat_completion.py +++ b/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_chat_completion.py @@ -57,8 +57,13 @@ def __init__( ) except ValidationError as ex: raise ServiceInitializationError("Failed to create OpenAI settings.", ex) from ex - if not openai_settings.chat_model_id: - raise ServiceInitializationError("The OpenAI chat model ID is required.") + + if not async_client: + if not openai_settings.api_key: + raise ServiceInitializationError("The OpenAI API key is required.") + if not openai_settings.chat_model_id: + raise ServiceInitializationError("The OpenAI chat model ID is required.") + super().__init__( ai_model_id=openai_settings.chat_model_id, api_key=openai_settings.api_key.get_secret_value() if openai_settings.api_key else None, diff --git a/python/semantic_kernel/connectors/ai/open_ai/settings/open_ai_settings.py b/python/semantic_kernel/connectors/ai/open_ai/settings/open_ai_settings.py index f005536343ed..f6266cab0f73 100644 --- a/python/semantic_kernel/connectors/ai/open_ai/settings/open_ai_settings.py +++ b/python/semantic_kernel/connectors/ai/open_ai/settings/open_ai_settings.py @@ -15,11 +15,9 @@ class OpenAISettings(KernelBaseSettings): encoding 'utf-8'. If the settings are not found in the .env file, the settings are ignored; however, validation will fail alerting that the settings are missing. - Required settings for prefix 'OPENAI_' are: + Optional settings for prefix 'OPENAI_' are: - api_key: SecretStr - OpenAI API key, see https://platform.openai.com/account/api-keys (Env var OPENAI_API_KEY) - - Optional settings for prefix 'OPENAI_' are: - org_id: str | None - This is usually optional unless your account belongs to multiple organizations. (Env var OPENAI_ORG_ID) - chat_model_id: str | None - The OpenAI chat model ID to use, for example, gpt-3.5-turbo or gpt-4. @@ -33,7 +31,7 @@ class OpenAISettings(KernelBaseSettings): env_prefix: ClassVar[str] = "OPENAI_" - api_key: SecretStr + api_key: SecretStr | None = None org_id: str | None = None chat_model_id: str | None = None text_model_id: str | None = None From 8f96c188f60f41fb66de1666ec1a982311d31e8a Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Fri, 5 Jul 2024 15:06:19 -0700 Subject: [PATCH 8/8] Fix integration test --- .../samples/concepts/plugins/openai_plugin_azure_key_vault.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/samples/concepts/plugins/openai_plugin_azure_key_vault.py b/python/samples/concepts/plugins/openai_plugin_azure_key_vault.py index e0d92e17e2e7..221fc44d2191 100644 --- a/python/samples/concepts/plugins/openai_plugin_azure_key_vault.py +++ b/python/samples/concepts/plugins/openai_plugin_azure_key_vault.py @@ -209,7 +209,7 @@ async def handle_streaming( print("Security Agent:> ", end="") streamed_chunks: list[StreamingChatMessageContent] = [] async for message in response: - if not execution_settings.function_call_behavior.auto_invoke_kernel_functions and isinstance( + if not execution_settings.function_choice_behavior.auto_invoke_kernel_functions and isinstance( message[0], StreamingChatMessageContent ): streamed_chunks.append(message[0])