From 78fcd1de50662d7ea468a40a0aa3af44d60df750 Mon Sep 17 00:00:00 2001 From: Rob Chambers Date: Thu, 14 Dec 2023 10:19:51 -0800 Subject: [PATCH] =?UTF-8?q?update=20base=20python=20openai-chat=20ai=20dev?= =?UTF-8?q?=20new=20template,=20to=20serve=20as=20base=20=E2=80=A6=20(#136?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * update base python openai-chat ai dev new template, to serve as base for next step, streaming; add streaming template; add requirements.txt to both * update api version for python templates * update api version more correctly * ok... this time, got it right * porting reflection code exercise, in ideas/porter * fixed ctrl-c bugs --- ideas/porter/1-instructions.md | 49 ++++++++++ ideas/porter/2-instructions-show-usage.md | 82 +++++++++++++++++ ideas/porter/3-instructions-try-it-fix-it.md | 89 +++++++++++++++++++ ideas/porter/calculator_helper_functions.py | 30 +++++++ ideas/porter/custom_funcs.py | 5 ++ ideas/porter/date_time_helpers.py | 4 + ideas/porter/example_usage.py | 23 +++++ ideas/porter/file_helper_functions.py | 44 +++++++++ .../helper_function_description_attribute.py | 4 + ideas/porter/helper_function_factory.py | 40 +++++++++ .../helper_function_factory_extensions.py | 17 ++++ ideas/porter/helper_function_factory_usage.py | 30 +++++++ ...unction_parameter_description_attribute.py | 4 + src/ai/.x/templates/openai-chat-py/_.json | 2 +- ...lo_world.py => openai_chat_completions.py} | 18 ++-- .../templates/openai-chat-py/requirements.txt | 1 + .../templates/openai-chat-streaming-py/_.json | 9 ++ .../openai_chat_completions_streaming.py | 56 ++++++++++++ .../openai-chat-streaming-py/requirements.txt | 1 + src/ai/commands/chat_command.cs | 6 +- src/ai/commands/complete_command.cs | 2 +- src/common/Program.cs | 8 +- src/common/details/console/ConsoleHelpers.cs | 30 +++++++ src/common/details/console/gui/Screen.cs | 25 ++++-- .../console/gui_helpers/ask_prompt_helper.cs | 2 +- .../RunPythonHelperFunction.cs | 32 +++++++ src/spx/commands/synthesize_command.cs | 2 +- 27 files changed, 593 insertions(+), 22 deletions(-) create mode 100644 ideas/porter/1-instructions.md create mode 100644 ideas/porter/2-instructions-show-usage.md create mode 100644 ideas/porter/3-instructions-try-it-fix-it.md create mode 100644 ideas/porter/calculator_helper_functions.py create mode 100644 ideas/porter/custom_funcs.py create mode 100644 ideas/porter/date_time_helpers.py create mode 100644 ideas/porter/example_usage.py create mode 100644 ideas/porter/file_helper_functions.py create mode 100644 ideas/porter/helper_function_description_attribute.py create mode 100644 ideas/porter/helper_function_factory.py create mode 100644 ideas/porter/helper_function_factory_extensions.py create mode 100644 ideas/porter/helper_function_factory_usage.py create mode 100644 ideas/porter/helper_function_parameter_description_attribute.py rename src/ai/.x/templates/openai-chat-py/{openai_chat_completions_hello_world.py => openai_chat_completions.py} (84%) create mode 100644 src/ai/.x/templates/openai-chat-py/requirements.txt create mode 100644 src/ai/.x/templates/openai-chat-streaming-py/_.json create mode 100644 src/ai/.x/templates/openai-chat-streaming-py/openai_chat_completions_streaming.py create mode 100644 src/ai/.x/templates/openai-chat-streaming-py/requirements.txt create mode 100644 src/extensions/helper_functions_extension/RunPythonHelperFunction.cs diff --git a/ideas/porter/1-instructions.md b/ideas/porter/1-instructions.md new file mode 100644 index 00000000..bcfa020c --- /dev/null +++ b/ideas/porter/1-instructions.md @@ -0,0 +1,49 @@ +You are an AI assistant that ports code from C# to Python. + +## On your profile and general capabilities: +- Your logic and reasoning should be rigorous and intelligent. +- You **must always** select one or more API Names to call to satisfy requests. +- You prefer action to words; just do the task, don't tell me about it. + +## Rules for writing new code +- You **must always** write complete source files, not snippets, no placeholders, and no TODO comments. +- You **must always** write code that compiles and runs without errors, with no additional work. +- You **must always** write code that is well-formatted and easy to read. +- You **must always** write code that is well-documented and easy to understand. +- You **must always** use descriptive names for classes, methods, and variables, specific to the task. +- You **must always** carefully escape source code before calling APIs provided to write files to disk, including double quoted strings that look like: `$"..."`; those must be turned into `$\"...\"`. + +## Rules for writing files or creating directories +- You **must always** write new classes into new files on disk using APIs provided. +- You **must always** use filenames that match the class name. +- You **must never** create new directories. + +## Scenario description + +I have a C# project in this directory (*.cs, and *.csproj) that I want to make an exact copy of in Python (*.py, requirements.txt). + +Your job is to help me, by doing the following: +1. You **must** read all the C# files in the current directory +2. You **must** create a corresponding python module for each corresponding C# file +3. Each module you create **must** have the same functionality as the C# file it was created from +4. Each module you create **must** be named similarly, but using Python naming conventions +5. Each module you create **must** have the same class names and method names, but using Python naming conventions +6. Each function you create **must** have the same functionality as the C# function it was created from + +## Do it now +Task you must perform: +1. Please begin, by reading each C# source file. +2. Please create a corresponding python module for each C# source file. +3. Some of the modules have already been ported; you only hve to do these + * HelperFunctionDescriptionAttribute.cs + * HelperFunctionFactory.cs + * HelperFunctionFactoryExtensions.cs + * HelperFunctionParameterDescriptionAttribute.cs + +## Bonus +If you do it perfectly, I'll give you a $100 tip. + +## Time to begin +Don't show me the code, just create the files. Do it now. + + diff --git a/ideas/porter/2-instructions-show-usage.md b/ideas/porter/2-instructions-show-usage.md new file mode 100644 index 00000000..b89a6690 --- /dev/null +++ b/ideas/porter/2-instructions-show-usage.md @@ -0,0 +1,82 @@ +You are an AI assistant that ports code from C# to Python. + +## On your profile and general capabilities: +- Your logic and reasoning should be rigorous and intelligent. +- You **must always** select one or more API Names to call to satisfy requests. +- You prefer action to words; just do the task, don't tell me about it. + +## Rules for writing new code +- You **must always** write complete source files, not snippets, no placeholders, and no TODO comments. +- You **must always** write code that compiles and runs without errors, with no additional work. +- You **must always** write code that is well-formatted and easy to read. +- You **must always** write code that is well-documented and easy to understand. +- You **must always** use descriptive names for classes, methods, and variables, specific to the task. +- You **must always** carefully escape source code before calling APIs provided to write files to disk, including double quoted strings that look like: `$"..."`; those must be turned into `$\"...\"`. + +## Rules for writing files or creating directories +- You **must always** write new classes into new files on disk using APIs provided. +- You **must always** use filenames that match the class name. +- You **must never** create new directories. + +## Scenario description + +Previously, I asked you to do this: + +``` +I have a C# project in this directory (*.cs, and *.csproj) that I want to make an exact copy of in Python (*.py, requirements.txt). + +Your job is to help me, by doing the following: +1. You **must** read all the C# files in the current directory +2. You **must** create a corresponding python module for each corresponding C# file +3. Each module you create **must** have the same functionality as the C# file it was created from +4. Each module you create **must** be named similarly, but using Python naming conventions +5. Each module you create **must** have the same class names and method names, but using Python naming conventions +6. Each function you create **must** have the same functionality as the C# function it was created from +``` + +Which, you did very nicely!! + +--- + +Now, I want you to show me how to use the python version you created (that is in helper_function_factory.py). + +Here's C# that demonstrates how to create a HelperFunctionFactory in C# (which is in HelperFunctionFactory.cs): + +``` + private HelperFunctionFactory CreateFunctionFactoryForCustomFunctions(string customFunctions) + { + var factory = new HelperFunctionFactory(); + + var patterns = customFunctions.Split(new char[] { ';', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); + foreach (var pattern in patterns) + { + var files = FileHelpers.FindFiles(pattern, _values); + if (files.Count() == 0) + { + files = FileHelpers.FindFilesInOsPath(pattern); + } + + foreach (var file in files) + { + if (Program.Debug) Console.WriteLine($"Trying to load custom functions from: {file}"); + var assembly = TryCatchHelpers.TryCatchNoThrow(() => Assembly.LoadFrom(file), null, out var ex); + if (assembly != null) factory.AddFunctions(assembly); + } + } + + return factory; + } +``` + +Show me how to use the python version of it (that is in helper_function_factory.py), in a very similar way. + +## Do it now +Task you must perform: +1. Please begin, by reading each C# source file. +2. Next, read the Python source file. +3. Think about how you'd use it in the same way as requestd. +4. Write the code to do it. +5. Do it now! + +## Bonus +If you do it perfectly, I'll give you a $100 tip. diff --git a/ideas/porter/3-instructions-try-it-fix-it.md b/ideas/porter/3-instructions-try-it-fix-it.md new file mode 100644 index 00000000..a4d817b2 --- /dev/null +++ b/ideas/porter/3-instructions-try-it-fix-it.md @@ -0,0 +1,89 @@ +You are an AI assistant that ports code from C# to Python, and helps fix such code. + +## On your profile and general capabilities: +- Your logic and reasoning should be rigorous and intelligent. +- You **must always** select one or more API Names to call to satisfy requests. +- You prefer action to words; just do the task, don't tell me about it. + +## Rules for writing new code +- You **must always** write complete source files, not snippets, no placeholders, and no TODO comments. +- You **must always** write code that compiles and runs without errors, with no additional work. +- You **must always** write code that is well-formatted and easy to read. +- You **must always** write code that is well-documented and easy to understand. +- You **must always** use descriptive names for classes, methods, and variables, specific to the task. +- You **must always** carefully escape source code before calling APIs provided to write files to disk, including double quoted strings that look like: `$"..."`; those must be turned into `$\"...\"`. + +## Rules for writing files or creating directories +- You **must always** write new classes into new files on disk using APIs provided. +- You **must always** use filenames that match the class name. +- You **must never** create new directories. + +## Scenario description + +Previously, I asked you to do this: + +""" +I have a C# project in this directory (*.cs, and *.csproj) that I want to make an exact copy of in Python (*.py, requirements.txt). + +Your job is to help me, by doing the following: +1. You **must** read all the C# files in the current directory +2. You **must** create a corresponding python module for each corresponding C# file +3. Each module you create **must** have the same functionality as the C# file it was created from +4. Each module you create **must** be named similarly, but using Python naming conventions +5. Each module you create **must** have the same class names and method names, but using Python naming conventions +6. Each function you create **must** have the same functionality as the C# function it was created from +""" + +and: + +""" +Now, I want you to show me how to use the python version you created (that is in helper_function_factory.py). + +Here's C# that demonstrates how to create a HelperFunctionFactory in C# (which is in HelperFunctionFactory.cs): + +``` + private HelperFunctionFactory CreateFunctionFactoryForCustomFunctions(string customFunctions) + { + var factory = new HelperFunctionFactory(); + + var patterns = customFunctions.Split(new char[] { ';', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); + foreach (var pattern in patterns) + { + var files = FileHelpers.FindFiles(pattern, _values); + if (files.Count() == 0) + { + files = FileHelpers.FindFilesInOsPath(pattern); + } + + foreach (var file in files) + { + if (Program.Debug) Console.WriteLine($"Trying to load custom functions from: {file}"); + var assembly = TryCatchHelpers.TryCatchNoThrow(() => Assembly.LoadFrom(file), null, out var ex); + if (assembly != null) factory.AddFunctions(assembly); + } + } + + return factory; + } +``` + +Show me how to use the python version of it (that is in helper_function_factory.py), in a very similar way. +""" + +Which, you did very nicely!! Now, I want you to try and run it, and fix errors that you find. + +You created a file, called `example_usage.py`, which can run, like this: + +``` +python example_usage.py +``` + +## Do it now +Task you must perform: +1. Please begin, by trying to run it. You can use the provided helper functions to do so. +2. When you find an error from the output, please read the appropriate files to find the error. +3. When you find the error, please fix it, and try again. +4. Do it now! + +## Bonus +If you do it perfectly, I'll give you a $100 tip. diff --git a/ideas/porter/calculator_helper_functions.py b/ideas/porter/calculator_helper_functions.py new file mode 100644 index 00000000..05d4768e --- /dev/null +++ b/ideas/porter/calculator_helper_functions.py @@ -0,0 +1,30 @@ +def add_floats(a, b): + return a + b + +def subtract_floats(a, b): + return a - b + +def multiply_floats(a, b): + return a * b + +def divide_floats(a, b): + return a / b + +def add_integers(a, b): + return a + b + +def subtract_integers(a, b): + return a - b + +def multiply_integers(a, b): + return a * b + +def divide_integers(a, b): + return a / b + +def average(numbers): + return sum(numbers) / len(numbers) if len(numbers) > 0 else 0 + +def standard_deviation(numbers): + average = sum(numbers) / len(numbers) if len(numbers) > 0 else 0 + return (sum((x - average) ** 2 for x in numbers) / len(numbers)) ** 0.5 if len(numbers) > 0 else 0 \ No newline at end of file diff --git a/ideas/porter/custom_funcs.py b/ideas/porter/custom_funcs.py new file mode 100644 index 00000000..e1f6e738 --- /dev/null +++ b/ideas/porter/custom_funcs.py @@ -0,0 +1,5 @@ +def greet(name: str) -> str: + return f'Hello, {name}!' + +def farewell(name: str) -> str: + return f'Goodbye, {name}!' diff --git a/ideas/porter/date_time_helpers.py b/ideas/porter/date_time_helpers.py new file mode 100644 index 00000000..f7823d51 --- /dev/null +++ b/ideas/porter/date_time_helpers.py @@ -0,0 +1,4 @@ +from datetime import datetime + +def get_current_date_time(): + return datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ') \ No newline at end of file diff --git a/ideas/porter/example_usage.py b/ideas/porter/example_usage.py new file mode 100644 index 00000000..36cf8976 --- /dev/null +++ b/ideas/porter/example_usage.py @@ -0,0 +1,23 @@ +import asyncio +from helper_function_factory_usage import create_function_factory_for_custom_functions + +# Example of custom functions +def greet(name: str) -> str: + return f'Hello, {name}!' + +def farewell(name: str) -> str: + return f'Goodbye, {name}!' + +# Save custom functions to a python file +with open('custom_funcs.py', 'w') as f: + f.write('def greet(name: str) -> str:\n return f\'Hello, {name}!\'\n\ndef farewell(name: str) -> str:\n return f\'Goodbye, {name}!\'\n') + +# Use the factory to load the custom functions +factory = create_function_factory_for_custom_functions('custom_funcs.py') + +# Use the loaded functions +async def main(): + print(await factory.call_function('greet', '{"name": "John"}')) + print(await factory.call_function('farewell', '{"name": "John"}')) + +asyncio.run(main()) \ No newline at end of file diff --git a/ideas/porter/file_helper_functions.py b/ideas/porter/file_helper_functions.py new file mode 100644 index 00000000..0d72bb43 --- /dev/null +++ b/ideas/porter/file_helper_functions.py @@ -0,0 +1,44 @@ +import os +import glob + + +def file_exists(file_name): + return os.path.exists(file_name) + +def read_text_from_file(file_name): + if file_exists(file_name): + with open(file_name, 'r') as file: + return file.read() + return '' + +def create_file_and_save_text(file_name, text): + with open(file_name, 'w') as file: + file.write(text) + return True + +def append_text_to_file(file_name, text): + with open(file_name, 'a') as file: + file.write(text) + return True + +def directory_create(directory_name): + os.makedirs(directory_name, exist_ok=True) + return True + +def find_all_files(): + return glob.glob('**', recursive=True) + +def find_files_matching_pattern(pattern): + return glob.glob(pattern, recursive=True) + +def find_text_in_all_files(text): + return find_text_in_files_matching_pattern(text, '**') + +def find_text_in_files_matching_pattern(text, pattern): + files = find_files_matching_pattern(pattern) + result = [] + for file in files: + with open(file, 'r') as f: + if text in f.read(): + result.append(file) + return result \ No newline at end of file diff --git a/ideas/porter/helper_function_description_attribute.py b/ideas/porter/helper_function_description_attribute.py new file mode 100644 index 00000000..3fd07613 --- /dev/null +++ b/ideas/porter/helper_function_description_attribute.py @@ -0,0 +1,4 @@ +class HelperFunctionDescriptionAttribute: + + def __init__(self, description=None): + self.description = description \ No newline at end of file diff --git a/ideas/porter/helper_function_factory.py b/ideas/porter/helper_function_factory.py new file mode 100644 index 00000000..53d06025 --- /dev/null +++ b/ideas/porter/helper_function_factory.py @@ -0,0 +1,40 @@ +import asyncio +import inspect +import json +from typing import Any, Callable, Dict, List, Optional, Tuple, Type, Union + + +class HelperFunctionFactory: + + def __init__(self): + self._functions: Dict[str, Callable[..., Any]] = {} + + async def call_function(self, function_name: str, arguments_as_json: str) -> Any: + function = self._functions.get(function_name) + if function is None: + raise KeyError(f'Function not found: {function_name}') + + arguments = json.loads(arguments_as_json) + if asyncio.iscoroutinefunction(function): + return await function(**arguments) + else: + return function(**arguments) + + def add_function(self, function_name: str, function: Callable[..., Any]) -> None: + self._functions[function_name] = function + + def remove_function(self, function_name: str) -> None: + self._functions.pop(function_name, None) + + def list_functions(self) -> List[str]: + return list(self._functions.keys()) + + def get_function_spec(self, function_name: str) -> Optional[Tuple[str, str]]: + function = self._functions.get(function_name) + if function is None: + return None + + signature = inspect.signature(function) + parameters = str(signature.parameters) + return_type = str(signature.return_annotation) + return parameters, return_type \ No newline at end of file diff --git a/ideas/porter/helper_function_factory_extensions.py b/ideas/porter/helper_function_factory_extensions.py new file mode 100644 index 00000000..6e983c91 --- /dev/null +++ b/ideas/porter/helper_function_factory_extensions.py @@ -0,0 +1,17 @@ +from typing import Any, Dict + +import helper_function_factory + + +def add_functions(options: Dict[str, Any], function_factory: helper_function_factory.HelperFunctionFactory) -> Dict[str, Any]: + for function_name in function_factory.list_functions(): + parameters, return_type = function_factory.get_function_spec(function_name) + options['functions'][function_name] = { + 'parameters': parameters, + 'return_type': return_type, + } + return options + + +def try_call_function(options: Dict[str, Any], function_name: str, arguments_as_json: str) -> Any: + return options['functions'][function_name](arguments_as_json) \ No newline at end of file diff --git a/ideas/porter/helper_function_factory_usage.py b/ideas/porter/helper_function_factory_usage.py new file mode 100644 index 00000000..3fb59536 --- /dev/null +++ b/ideas/porter/helper_function_factory_usage.py @@ -0,0 +1,30 @@ +import os +import glob +import importlib.util +import inspect + +from helper_function_factory import HelperFunctionFactory + +def create_function_factory_for_custom_functions(custom_functions: str) -> HelperFunctionFactory: + factory = HelperFunctionFactory() + + patterns = custom_functions.replace('\r', ';').replace('\n', ';').split(';') + for pattern in patterns: + files = glob.glob(pattern) + if not files: + files = glob.glob(os.path.join(os.environ.get('PATH', ''), pattern)) + + for file in files: + print(f"Trying to load custom functions from: {file}") + spec = importlib.util.spec_from_file_location("custom_funcs", file) + custom_funcs = importlib.util.module_from_spec(spec) + spec.loader.exec_module(custom_funcs) + + for func_name, func in inspect.getmembers(custom_funcs, inspect.isfunction): + factory.add_function(func_name, func) + + return factory + + +# Usage: +# factory = create_function_factory_for_custom_functions(custom_functions) \ No newline at end of file diff --git a/ideas/porter/helper_function_parameter_description_attribute.py b/ideas/porter/helper_function_parameter_description_attribute.py new file mode 100644 index 00000000..59cb1c51 --- /dev/null +++ b/ideas/porter/helper_function_parameter_description_attribute.py @@ -0,0 +1,4 @@ +class HelperFunctionParameterDescriptionAttribute: + + def __init__(self, description=None): + self.description = description \ No newline at end of file diff --git a/src/ai/.x/templates/openai-chat-py/_.json b/src/ai/.x/templates/openai-chat-py/_.json index d079788a..42d9549b 100644 --- a/src/ai/.x/templates/openai-chat-py/_.json +++ b/src/ai/.x/templates/openai-chat-py/_.json @@ -3,7 +3,7 @@ "_Language": "Python", "OPENAI_ENDPOINT": "", "OPENAI_API_KEY": "", - "OPENAI_API_VERSION": "2023-09-01-preview", + "OPENAI_API_VERSION": "2023-12-01-preview", "AZURE_OPENAI_CHAT_DEPLOYMENT": "", "AZURE_OPENAI_SYSTEM_PROMPT": "You are a helpful AI assistant." } \ No newline at end of file diff --git a/src/ai/.x/templates/openai-chat-py/openai_chat_completions_hello_world.py b/src/ai/.x/templates/openai-chat-py/openai_chat_completions.py similarity index 84% rename from src/ai/.x/templates/openai-chat-py/openai_chat_completions_hello_world.py rename to src/ai/.x/templates/openai-chat-py/openai_chat_completions.py index 96ec4349..810615ee 100644 --- a/src/ai/.x/templates/openai-chat-py/openai_chat_completions_hello_world.py +++ b/src/ai/.x/templates/openai-chat-py/openai_chat_completions.py @@ -11,7 +11,7 @@ openai.api_type = "azure" openai.api_base = os.getenv("OPENAI_ENDPOINT") or "<#= OPENAI_ENDPOINT #>" openai.api_key = os.getenv("OPENAI_API_KEY") or "<#= OPENAI_API_KEY #>" -openai.api_version = os.getenv("OPENAI_API_VERSION") or "<#= 2023-05-15 #>" +openai.api_version = os.getenv("OPENAI_API_VERSION") or "<#= OPENAI_API_VERSION #>" deploymentName = os.getenv("AZURE_OPENAI_CHAT_DEPLOYMENT") or "<#= AZURE_OPENAI_CHAT_DEPLOYMENT #>" systemPrompt = os.getenv("AZURE_OPENAI_SYSTEM_PROMPT") or "<#= AZURE_OPENAI_SYSTEM_PROMPT #>" @@ -20,12 +20,9 @@ {"role": "system", "content": systemPrompt}, ] -while True: - userPrompt = input("User: ") - if userPrompt == "" or userPrompt == "exit": - break - +def getChatCompletions() -> str: messages.append({"role": "user", "content": userPrompt}) + response = openai.ChatCompletion.create( engine=deploymentName, messages=messages, @@ -34,5 +31,12 @@ response_content = response["choices"][0]["message"]["content"] messages.append({"role": "assistant", "content": response_content}) - print(f"\nAssistant: {response_content}\n") + return response_content + +while True: + userPrompt = input("User: ") + if userPrompt == "" or userPrompt == "exit": + break + response_content = getChatCompletions() + print(f"\nAssistant: {response_content}\n") \ No newline at end of file diff --git a/src/ai/.x/templates/openai-chat-py/requirements.txt b/src/ai/.x/templates/openai-chat-py/requirements.txt new file mode 100644 index 00000000..d008bb14 --- /dev/null +++ b/src/ai/.x/templates/openai-chat-py/requirements.txt @@ -0,0 +1 @@ +openai==0.28.1 diff --git a/src/ai/.x/templates/openai-chat-streaming-py/_.json b/src/ai/.x/templates/openai-chat-streaming-py/_.json new file mode 100644 index 00000000..fb3a3ab9 --- /dev/null +++ b/src/ai/.x/templates/openai-chat-streaming-py/_.json @@ -0,0 +1,9 @@ +{ + "_Name": "OpenAI Chat Completions (Streaming) in Python", + "_Language": "Python", + "OPENAI_ENDPOINT": "", + "OPENAI_API_KEY": "", + "OPENAI_API_VERSION": "2023-12-01-preview", + "AZURE_OPENAI_CHAT_DEPLOYMENT": "", + "AZURE_OPENAI_SYSTEM_PROMPT": "You are a helpful AI assistant." +} \ No newline at end of file diff --git a/src/ai/.x/templates/openai-chat-streaming-py/openai_chat_completions_streaming.py b/src/ai/.x/templates/openai-chat-streaming-py/openai_chat_completions_streaming.py new file mode 100644 index 00000000..43b23c16 --- /dev/null +++ b/src/ai/.x/templates/openai-chat-streaming-py/openai_chat_completions_streaming.py @@ -0,0 +1,56 @@ +<#@ template hostspecific="true" #> +<#@ output extension=".py" encoding="utf-8" #> +<#@ parameter type="System.String" name="OPENAI_ENDPOINT" #> +<#@ parameter type="System.String" name="OPENAI_API_KEY" #> +<#@ parameter type="System.String" name="OPENAI_API_VERSION" #> +<#@ parameter type="System.String" name="AZURE_OPENAI_CHAT_DEPLOYMENT" #> +<#@ parameter type="System.String" name="AZURE_OPENAI_SYSTEM_PROMPT" #> +import os +import openai + +openai.api_type = "azure" +openai.api_base = os.getenv("OPENAI_ENDPOINT") or "<#= OPENAI_ENDPOINT #>" +openai.api_key = os.getenv("OPENAI_API_KEY") or "<#= OPENAI_API_KEY #>" +openai.api_version = os.getenv("OPENAI_API_VERSION") or "<#= OPENAI_API_VERSION #>" + +deploymentName = os.getenv("AZURE_OPENAI_CHAT_DEPLOYMENT") or "<#= AZURE_OPENAI_CHAT_DEPLOYMENT #>" +systemPrompt = os.getenv("AZURE_OPENAI_SYSTEM_PROMPT") or "<#= AZURE_OPENAI_SYSTEM_PROMPT #>" + +messages=[ + {"role": "system", "content": systemPrompt}, +] + +def getChatStreamingCompletions() -> str: + messages.append({"role": "user", "content": userPrompt}) + + response_content = "" + response = openai.ChatCompletion.create( + engine=deploymentName, + messages=messages, + stream=True) + + for update in response: + + choices = update["choices"] if "choices" in update else [] + choice0 = choices[0] if len(choices) > 0 else {} + delta = choice0["delta"] if "delta" in choice0 else {} + + content = delta["content"] if "content" in delta else "" + response_content += content + print(content, end="") + + finish_reason = choice0["finish_reason"] if "finish_reason" in choice0 else "" + if finish_reason == "length": + content += f"{content}\nERROR: Exceeded max token length!" + + messages.append({"role": "assistant", "content": response_content}) + return response_content + +while True: + userPrompt = input("User: ") + if userPrompt == "" or userPrompt == "exit": + break + + print("\nAssistant: ", end="") + response_content = getChatStreamingCompletions() + print("\n") diff --git a/src/ai/.x/templates/openai-chat-streaming-py/requirements.txt b/src/ai/.x/templates/openai-chat-streaming-py/requirements.txt new file mode 100644 index 00000000..d008bb14 --- /dev/null +++ b/src/ai/.x/templates/openai-chat-streaming-py/requirements.txt @@ -0,0 +1 @@ +openai==0.28.1 diff --git a/src/ai/commands/chat_command.cs b/src/ai/commands/chat_command.cs index 8a49c9d4..b4ccebf4 100644 --- a/src/ai/commands/chat_command.cs +++ b/src/ai/commands/chat_command.cs @@ -303,7 +303,7 @@ private async Task ChatInteractively() { DisplayUserChatPromptLabel(); - var text = ReadLineOrSimulateInput(ref userPrompt); + var text = ReadLineOrSimulateInput(ref userPrompt, "exit"); if (text.ToLower() == "") { text = PickInteractiveContextMenu(speechInput); @@ -455,11 +455,11 @@ private async Task> GetNormalChatTextHandler() return handler; } - private static string ReadLineOrSimulateInput(ref string inputToSimulate) + private static string ReadLineOrSimulateInput(ref string inputToSimulate, string defaultOnEndOfRedirectedInput = null) { var simulate = !string.IsNullOrEmpty(inputToSimulate); - var input = simulate ? inputToSimulate : Console.ReadLine(); + var input = simulate ? inputToSimulate : ConsoleHelpers.ReadLineOrDefault("", defaultOnEndOfRedirectedInput); inputToSimulate = null; if (simulate) Console.WriteLine(input); diff --git a/src/ai/commands/complete_command.cs b/src/ai/commands/complete_command.cs index a9e038ed..a8020294 100644 --- a/src/ai/commands/complete_command.cs +++ b/src/ai/commands/complete_command.cs @@ -64,7 +64,7 @@ private void CompleteInteractively(bool repeatedly = false) while (true) { Console.Write("[complete] >>> "); - var text = Console.ReadLine(); + var text = ConsoleHelpers.ReadLineOrDefault("", "exit"); if (text.ToLower() == "") break; if (text.ToLower() == "stop") break; diff --git a/src/common/Program.cs b/src/common/Program.cs index 8137c44e..ba129695 100644 --- a/src/common/Program.cs +++ b/src/common/Program.cs @@ -23,13 +23,15 @@ public static int Main(IProgramData data, string[] mainArgs) { _data = data; + var screen = ConsoleGui.Screen.Current; Console.OutputEncoding = Encoding.UTF8; Console.CancelKeyPress += (s, e) => { - ConsoleGui.Screen.Current.SetCursorVisible(true); - ConsoleGui.Screen.Current.ResetColors(); + e.Cancel = true; + screen.SetCursorVisible(true); + screen.ResetColors(); Console.WriteLine(" received... terminating ... "); - Process.GetCurrentProcess().Kill(); + Environment.Exit(1); }; ICommandValues values = new CommandValues(); diff --git a/src/common/details/console/ConsoleHelpers.cs b/src/common/details/console/ConsoleHelpers.cs index 5efe9e64..68527ec8 100644 --- a/src/common/details/console/ConsoleHelpers.cs +++ b/src/common/details/console/ConsoleHelpers.cs @@ -8,11 +8,41 @@ using System.ComponentModel; using System.Linq; using System.Text; +using Azure.AI.Details.Common.CLI.ConsoleGui; namespace Azure.AI.Details.Common.CLI { public static class ConsoleHelpers { + public static string ReadLineOrDefault(string defaultOnEmpty = "", string defaultOnEndOfRedirectedInput = null) + { + if (Console.IsInputRedirected) + { + var line = Console.ReadLine(); + line ??= defaultOnEndOfRedirectedInput; + if (line != null) Console.WriteLine(line); + return line; + } + + Screen.Current.MakeSpaceAtCursor(0, 1); + + var saved = new Cursor(); + saved.Save(); + + while (true) + { + var line = Console.ReadLine(); + if (line == null) // Ctrl+C + { + saved.Restore(); + Screen.Current.ClearLineRight(); + continue; + } + + return string.IsNullOrEmpty(line) ? defaultOnEmpty : line; + } + } + public static string ReadAllStandardInputText() { if (stdinText == null) diff --git a/src/common/details/console/gui/Screen.cs b/src/common/details/console/gui/Screen.cs index 4f33c35c..f1ceac09 100644 --- a/src/common/details/console/gui/Screen.cs +++ b/src/common/details/console/gui/Screen.cs @@ -28,15 +28,21 @@ public Screen() public void SetCursorVisible(bool visible) { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - Console.CursorVisible = visible; - } + if (Console.IsInputRedirected) return; + if (Console.IsOutputRedirected) return; + if (Console.IsErrorRedirected) return; + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return; + + Console.CursorVisible = visible; } public static bool GetCursorVisible() { - return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && Console.CursorVisible; + if (Console.IsInputRedirected) return false; + if (Console.IsOutputRedirected) return false; + if (Console.IsErrorRedirected) return false; + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return false; + return Console.CursorVisible; } public Colors ColorsStart @@ -191,6 +197,15 @@ private void UpdateBiggestYSoFar() #region write char/text + public void ClearLineRight() + { + var x = Console.CursorLeft; + var y = Console.CursorTop; + var width = GetWindowWidth() - x - 1; + WriteChar(x, y, ' ', width); + Console.CursorLeft = x; + } + public void WriteChar(char ch, int count = 1) { if (count == 1) diff --git a/src/common/details/console/gui_helpers/ask_prompt_helper.cs b/src/common/details/console/gui_helpers/ask_prompt_helper.cs index ab395d51..7ad51f97 100644 --- a/src/common/details/console/gui_helpers/ask_prompt_helper.cs +++ b/src/common/details/console/gui_helpers/ask_prompt_helper.cs @@ -28,7 +28,7 @@ public static string AskPrompt(string prompt, string value = null, bool useEditB return value; } - return Console.ReadLine(); + return ConsoleHelpers.ReadLineOrDefault(); } } } diff --git a/src/extensions/helper_functions_extension/RunPythonHelperFunction.cs b/src/extensions/helper_functions_extension/RunPythonHelperFunction.cs new file mode 100644 index 00000000..e41b6791 --- /dev/null +++ b/src/extensions/helper_functions_extension/RunPythonHelperFunction.cs @@ -0,0 +1,32 @@ +// using System.Diagnostics; + +// namespace Azure.AI.Details.Common.CLI.Extensions.HelperFunctions +// { +// public static class RunPythonHelperFunctions +// { +// [HelperFunctionDescription("Runs python code by executing 'python ...', returning the combined stdout and stderr as a string")] +// public static string RunPython(string fileName, string arguments) +// { +// var processStartInfo = new ProcessStartInfo +// { +// FileName = "python", +// Arguments = $"{fileName} {arguments}", +// RedirectStandardOutput = true, +// RedirectStandardError = true, +// UseShellExecute = false, +// CreateNoWindow = true, +// }; + +// var process = new Process +// { +// StartInfo = processStartInfo, +// }; + +// process.Start(); +// string output = process.StandardOutput.ReadToEnd() + process.StandardError.ReadToEnd(); +// process.WaitForExit(); + +// return output; +// } +// } +// } \ No newline at end of file diff --git a/src/spx/commands/synthesize_command.cs b/src/spx/commands/synthesize_command.cs index cc9c4bef..75fe1db2 100644 --- a/src/spx/commands/synthesize_command.cs +++ b/src/spx/commands/synthesize_command.cs @@ -114,7 +114,7 @@ private void SynthesizeInteractive(bool repeatedly = false) while (true) { Console.Write("Enter text: "); - var text = Console.ReadLine(); + var text = ConsoleHelpers.ReadLineOrDefault("", "exit"); if (text.ToLower() == "") break; if (text.ToLower() == "stop") break;