From e7af45985cfb58143dad5d6c20eb6f34b11209f7 Mon Sep 17 00:00:00 2001 From: Curtis Chong Date: Sun, 26 Nov 2023 17:42:10 -0500 Subject: [PATCH 1/6] update to v1 --- chatgpt/chatgpt.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/chatgpt/chatgpt.py b/chatgpt/chatgpt.py index 6377be4..10f4d20 100644 --- a/chatgpt/chatgpt.py +++ b/chatgpt/chatgpt.py @@ -8,7 +8,7 @@ import diskcache import openai -from openai.error import OpenAIError +from openai import OpenAIError, AsyncOpenAI logs_dir = os.path.join(os.getcwd(), '.chatgpt_history/logs') @@ -57,7 +57,7 @@ def complete(messages=None, model='gpt-4', temperature=0, use_cache=False, **kwa key = get_key(messages) if key in cache: return cache.get(key) - response = openai.ChatCompletion.create( + response = openai.chat.completions.create( messages=messages, model=model, temperature=temperature, @@ -72,7 +72,8 @@ async def acomplete(messages=None, model='gpt-4', temperature=0, use_cache=False key = get_key(messages) if key in cache: return cache.get(key) - response = await openai.ChatCompletion.acreate( + client = AsyncOpenAI() + response = await client.chat.completions.create( messages=messages, model=model, temperature=temperature, From bc8f35f0cef39fab127f05ded51ef7bdc1ee6398 Mon Sep 17 00:00:00 2001 From: Curtis Chong Date: Sun, 26 Nov 2023 18:55:34 -0500 Subject: [PATCH 2/6] wip porting over. I should probably just ignore logs and get it to work with the reasoner without it first --- chatgpt/chatgpt.py | 69 +++++++++++++++++++++++++++------------------- 1 file changed, 40 insertions(+), 29 deletions(-) diff --git a/chatgpt/chatgpt.py b/chatgpt/chatgpt.py index 10f4d20..305b4f3 100644 --- a/chatgpt/chatgpt.py +++ b/chatgpt/chatgpt.py @@ -8,7 +8,8 @@ import diskcache import openai -from openai import OpenAIError, AsyncOpenAI +from openai import OpenAIError, AsyncOpenAI, AsyncStream, Stream +from openai.types.chat import ChatCompletionMessage, ChatCompletion, ChatCompletion, ChatCompletionChunk, ChatCompletionMessageParam logs_dir = os.path.join(os.getcwd(), '.chatgpt_history/logs') @@ -52,12 +53,12 @@ def sync_wrapper(*args, **kwargs): @retry_on_exception() -def complete(messages=None, model='gpt-4', temperature=0, use_cache=False, **kwargs): +def complete(messages:list[ChatCompletionMessageParam]=None, model='gpt-4', temperature=0, use_cache=False, **kwargs): if use_cache: key = get_key(messages) if key in cache: return cache.get(key) - response = openai.chat.completions.create( + response: ChatCompletion | Stream[ChatCompletionChunk] = openai.chat.completions.create( messages=messages, model=model, temperature=temperature, @@ -67,26 +68,26 @@ def complete(messages=None, model='gpt-4', temperature=0, use_cache=False, **kwa @retry_on_exception() -async def acomplete(messages=None, model='gpt-4', temperature=0, use_cache=False, **kwargs): +async def acomplete(messages:list[ChatCompletionMessageParam]=None, model='gpt-4', temperature=0, use_cache=False, **kwargs): if use_cache: key = get_key(messages) if key in cache: return cache.get(key) client = AsyncOpenAI() - response = await client.chat.completions.create( + response: ChatCompletion | AsyncStream[ChatCompletionChunk] = await client.chat.completions.create( messages=messages, model=model, temperature=temperature, **kwargs ) - return parse_response(response, messages, **kwargs) + return await parse_response(response, messages, **kwargs) -def parse_response(response, messages, **kwargs): +async def parse_response(response: ChatCompletion | Stream[ChatCompletionChunk] | AsyncStream[ChatCompletionChunk], messages:list[ChatCompletionMessageParam], **kwargs): n = kwargs.get('n', 1) stream = kwargs.get('stream', False) if stream: - return parse_stream(response, messages, n=n) + return await parse_stream(response, messages, n=n) results = [] for choice in response.choices: @@ -109,9 +110,10 @@ def parse_response(response, messages, **kwargs): return output -def parse_stream(response, messages, n=1): +async def parse_stream(response: Stream[ChatCompletionChunk] | AsyncStream[ChatCompletionChunk], messages:list[ChatCompletionMessageParam], n=1): results = ['' for _ in range(n)] - for chunk in response: + chunk: ChatCompletionChunk + async for chunk in response: for choice in chunk.choices: if not choice.delta: continue @@ -126,33 +128,42 @@ def parse_stream(response, messages, n=1): yield (text, idx) for r in results: - log_completion(messages + [{'role': 'assistant', 'content': r}]) + # log_completion(messages + [{'role': 'assistant', 'content': r}]) + log_completion(messages, r) cache.set(get_key(messages), results) -def log_completion(messages): +def log_completion(messages:list[ChatCompletionMessageParam], completionMessage: ChatCompletionMessage = None): timestamp = datetime.now().strftime('%Y-%m-%d_%H-%M-%S-%f') save_path = os.path.join(logs_dir, timestamp + '.txt') os.makedirs(os.path.dirname(save_path), exist_ok=True) + # import pdb; pdb.set_trace() + + # first print the ChatCompletionMessageParam + # log = "" + # log += role.upper() + ' ' + '-'*100 + '\n\n' + + + # then print the result, which is the ChatCompletionMessage + if not isinstance(completionMessage, ChatCompletionMessage): + raise TypeError(f"Expected ChatCompletionMessage, got {type(completionMessage)}") + role = completionMessage.role + log += role.upper() + ' ' + '-'*100 + '\n\n' + if completionMessage.content: + log += '\nContent:\n' + completionMessage['content'] + if completionMessage.function_call: + log += '\nCalled function:\n' + completionMessage.function_call + args = completionMessage.function_call.arguments + log += f'{args}\n' + if completionMessage.tool_calls: + for tool in completionMessage.tool_calls: + log += f'\nCalled {tool.type}:\n' + if tool.type == 'function': + log += f'{tool.function.name}({tool.function.arguments}) id={tool.id}\n' + else: + raise NotImplementedError(f"Tool type {tool.type} not implemented in logger") - log = "" - for message in messages: - log += message['role'].upper() + ' ' + '-'*100 + '\n\n' - if 'name' in message: - log += f"Called function: {message['name']}(" - if 'args' in message: - log += '\n' - for k, v in message['args'].items(): - log += f"\t{k}={repr(v)},\n" - log += ')' - if 'content' in message: - log += '\nContent:\n' + message['content'] - elif 'function_call' in message: - log += f"Called function: {message['function_call'].get('name', 'UNKNOWN')}(\n" - log += ')' - else: - log += message["content"] log += '\n\n' with open(save_path, 'w') as f: From fe338e3c651f60eeb6fc393688e9acc64fde0ca2 Mon Sep 17 00:00:00 2001 From: Curtis Chong Date: Sun, 26 Nov 2023 20:19:04 -0500 Subject: [PATCH 3/6] reasoner works. haven't tested streams. also logger is broken --- .gitignore | 3 ++- chatgpt/chatgpt.py | 21 ++++++++++++++------- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index db809d5..551dc62 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ dist/ build/ -*.egg-info \ No newline at end of file +*.egg-info +__pycache__/ diff --git a/chatgpt/chatgpt.py b/chatgpt/chatgpt.py index 305b4f3..a0f022c 100644 --- a/chatgpt/chatgpt.py +++ b/chatgpt/chatgpt.py @@ -64,6 +64,11 @@ def complete(messages:list[ChatCompletionMessageParam]=None, model='gpt-4', temp temperature=temperature, **kwargs ) + stream = kwargs.get('stream', False) + # import pdb; pdb.set_trace() + if stream: + n = kwargs.get('n', 1) + return parse_stream(response, messages, n=n) return parse_response(response, messages, **kwargs) @@ -80,19 +85,21 @@ async def acomplete(messages:list[ChatCompletionMessageParam]=None, model='gpt-4 temperature=temperature, **kwargs ) + stream = kwargs.get('stream', False) + if stream: + n = kwargs.get('n', 1) + return await parse_stream(response, messages, n=n) return await parse_response(response, messages, **kwargs) -async def parse_response(response: ChatCompletion | Stream[ChatCompletionChunk] | AsyncStream[ChatCompletionChunk], messages:list[ChatCompletionMessageParam], **kwargs): +def parse_response(response: ChatCompletion, messages:list[ChatCompletionMessageParam], **kwargs): n = kwargs.get('n', 1) - stream = kwargs.get('stream', False) - if stream: - return await parse_stream(response, messages, n=n) + results = [] for choice in response.choices: message = choice.message - if kwargs.get('functions', None) and 'function_call' in message: + if kwargs.get('functions', None) and message.function_call: name = message.function_call.name try: args = json.loads(message.function_call.arguments) @@ -105,7 +112,7 @@ async def parse_response(response: ChatCompletion | Stream[ChatCompletionChunk] results.append(message.content) log_completion(messages + [message]) - output = results if n > 1 else results[0] + output = results if n > 1 else results[0] cache.set(get_key(messages), output) return output @@ -134,11 +141,11 @@ async def parse_stream(response: Stream[ChatCompletionChunk] | AsyncStream[ChatC def log_completion(messages:list[ChatCompletionMessageParam], completionMessage: ChatCompletionMessage = None): + return timestamp = datetime.now().strftime('%Y-%m-%d_%H-%M-%S-%f') save_path = os.path.join(logs_dir, timestamp + '.txt') os.makedirs(os.path.dirname(save_path), exist_ok=True) - # import pdb; pdb.set_trace() # first print the ChatCompletionMessageParam # log = "" From f0a46db42d04640d5fb696619c9fb7d5ccd6190a Mon Sep 17 00:00:00 2001 From: Curtis Chong Date: Sun, 26 Nov 2023 20:45:49 -0500 Subject: [PATCH 4/6] fixed logger --- chatgpt/chatgpt.py | 89 +++++++++++++++++++++++++++++----------------- 1 file changed, 56 insertions(+), 33 deletions(-) diff --git a/chatgpt/chatgpt.py b/chatgpt/chatgpt.py index a0f022c..3010136 100644 --- a/chatgpt/chatgpt.py +++ b/chatgpt/chatgpt.py @@ -88,7 +88,7 @@ async def acomplete(messages:list[ChatCompletionMessageParam]=None, model='gpt-4 stream = kwargs.get('stream', False) if stream: n = kwargs.get('n', 1) - return await parse_stream(response, messages, n=n) + return await parse_astream(response, messages, n=n) return await parse_response(response, messages, **kwargs) @@ -107,17 +107,37 @@ def parse_response(response: ChatCompletion, messages:list[ChatCompletionMessage print('ERROR: OpenAI returned invalid JSON for function call arguments') raise e results.append({'role': 'function', 'name': name, 'args': args}) - log_completion(messages + [results[-1]]) + log_completion(messages, results[-1]) else: results.append(message.content) - log_completion(messages + [message]) + log_completion(messages, None) output = results if n > 1 else results[0] cache.set(get_key(messages), output) return output +def parse_stream(response: Stream[ChatCompletionChunk], messages:list[ChatCompletionMessageParam], n=1): + results = ['' for _ in range(n)] + chunk: ChatCompletionChunk + for chunk in response: + for choice in chunk.choices: + if not choice.delta: + continue + text = choice.delta.content + if not text: + continue + idx = choice.index + results[idx] += text + if n == 1: + yield text + else: + yield (text, idx) + + for r in results: + log_completion(messages, r) + cache.set(get_key(messages), results) -async def parse_stream(response: Stream[ChatCompletionChunk] | AsyncStream[ChatCompletionChunk], messages:list[ChatCompletionMessageParam], n=1): +async def parse_astream(response: AsyncStream[ChatCompletionChunk], messages:list[ChatCompletionMessageParam], n=1): results = ['' for _ in range(n)] chunk: ChatCompletionChunk async for chunk in response: @@ -135,43 +155,46 @@ async def parse_stream(response: Stream[ChatCompletionChunk] | AsyncStream[ChatC yield (text, idx) for r in results: - # log_completion(messages + [{'role': 'assistant', 'content': r}]) log_completion(messages, r) cache.set(get_key(messages), results) - -def log_completion(messages:list[ChatCompletionMessageParam], completionMessage: ChatCompletionMessage = None): - return +def log_completion(messages: list[ChatCompletionMessageParam], result: ChatCompletionMessage = None): timestamp = datetime.now().strftime('%Y-%m-%d_%H-%M-%S-%f') save_path = os.path.join(logs_dir, timestamp + '.txt') os.makedirs(os.path.dirname(save_path), exist_ok=True) - # first print the ChatCompletionMessageParam - # log = "" - # log += role.upper() + ' ' + '-'*100 + '\n\n' - - - # then print the result, which is the ChatCompletionMessage - if not isinstance(completionMessage, ChatCompletionMessage): - raise TypeError(f"Expected ChatCompletionMessage, got {type(completionMessage)}") - role = completionMessage.role - log += role.upper() + ' ' + '-'*100 + '\n\n' - if completionMessage.content: - log += '\nContent:\n' + completionMessage['content'] - if completionMessage.function_call: - log += '\nCalled function:\n' + completionMessage.function_call - args = completionMessage.function_call.arguments - log += f'{args}\n' - if completionMessage.tool_calls: - for tool in completionMessage.tool_calls: - log += f'\nCalled {tool.type}:\n' - if tool.type == 'function': - log += f'{tool.function.name}({tool.function.arguments}) id={tool.id}\n' - else: - raise NotImplementedError(f"Tool type {tool.type} not implemented in logger") - - log += '\n\n' + log = "" + for message in messages: + log += message['role'].upper() + ' ' + '-'*100 + '\n\n' + if "content" in message: + log += '\nContent:\n' + message['content'] + if "function_call" in message: # TODO: remove later since function_call is deprecated + log += f'Call function\n:{message["function_call"]["name"]}({message["function_call"]["arguments"]})\n' + if "tool_calls" in message: + for tool in message["tool_calls"]: + log += f'\nCall {tool["type"]}:\n' + if tool["type"] == 'function': + log += f'{tool["function"]["name"]}({tool["function"]["arguments"]}) id={tool["id"]}\n' + else: + raise NotImplementedError(f'Tool type {tool["type"]} not implemented in logger') + + + if result: + log += result.role.upper() + ' ' + '-'*100 + '\n\n' + if result.content: + log += '\nContent:\n' + result['content'] + if result.function_call: + log += f'Called function:\n{result.function_call.name}({result.function_call.arguments})\n' + if result.tool_calls: + for tool in result.tool_calls: + log += f'\nCalled {tool.type}:\n' + if tool.type == 'function': + log += f'{tool.function.name}({tool.function.arguments}) id={tool.id}\n' + else: + raise NotImplementedError(f"Tool type {tool.type} not implemented in logger") + + log += '\n\n' with open(save_path, 'w') as f: f.write(log) From b0cb05e582340aeef358209016f42dd948636bd4 Mon Sep 17 00:00:00 2001 From: Curtis Chong Date: Sun, 26 Nov 2023 20:50:48 -0500 Subject: [PATCH 5/6] improved newlines in logs --- chatgpt/chatgpt.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/chatgpt/chatgpt.py b/chatgpt/chatgpt.py index 3010136..cdafff4 100644 --- a/chatgpt/chatgpt.py +++ b/chatgpt/chatgpt.py @@ -166,9 +166,9 @@ def log_completion(messages: list[ChatCompletionMessageParam], result: ChatCompl log = "" for message in messages: - log += message['role'].upper() + ' ' + '-'*100 + '\n\n' + log += message['role'].upper() + ' ' + '-'*100 + '\n' if "content" in message: - log += '\nContent:\n' + message['content'] + log += 'Content:\n' + message['content'] + "\n" if "function_call" in message: # TODO: remove later since function_call is deprecated log += f'Call function\n:{message["function_call"]["name"]}({message["function_call"]["arguments"]})\n' if "tool_calls" in message: @@ -178,12 +178,13 @@ def log_completion(messages: list[ChatCompletionMessageParam], result: ChatCompl log += f'{tool["function"]["name"]}({tool["function"]["arguments"]}) id={tool["id"]}\n' else: raise NotImplementedError(f'Tool type {tool["type"]} not implemented in logger') + log += '\n\n' if result: - log += result.role.upper() + ' ' + '-'*100 + '\n\n' + log += result.role.upper() + ' ' + '-'*100 + '\n' if result.content: - log += '\nContent:\n' + result['content'] + log += 'Content:\n' + result['content'] if result.function_call: log += f'Called function:\n{result.function_call.name}({result.function_call.arguments})\n' if result.tool_calls: From 3715d401b06ec554a281e56cca5f8d3b8908941e Mon Sep 17 00:00:00 2001 From: Curtis Chong Date: Sun, 26 Nov 2023 21:14:02 -0500 Subject: [PATCH 6/6] no longer pass dicts into log_completion --- chatgpt/chatgpt.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/chatgpt/chatgpt.py b/chatgpt/chatgpt.py index cdafff4..3f0165c 100644 --- a/chatgpt/chatgpt.py +++ b/chatgpt/chatgpt.py @@ -106,11 +106,10 @@ def parse_response(response: ChatCompletion, messages:list[ChatCompletionMessage except json.decoder.JSONDecodeError as e: print('ERROR: OpenAI returned invalid JSON for function call arguments') raise e - results.append({'role': 'function', 'name': name, 'args': args}) - log_completion(messages, results[-1]) - else: - results.append(message.content) - log_completion(messages, None) + # results.append({'role': 'function', 'name': name, 'args': args}) + # log_completion(messages, results[-1]) + results.append(message) + log_completion(messages, message) output = results if n > 1 else results[0] cache.set(get_key(messages), output)