From 67393ebe4c35217761c235b16822d453ce2fa7cd Mon Sep 17 00:00:00 2001 From: henrycunh Date: Mon, 28 Aug 2023 11:15:15 -0300 Subject: [PATCH] feat: add Pydantic V2 and OpenRouter.ai support (#8) Co-authored-by: Cyrus Nouroozi --- README.md | 38 ++- cursive/cursive.py | 599 +++++++++++++++++++++------------------- cursive/custom_types.py | 98 ++++--- cursive/pricing.py | 34 ++- cursive/usage/openai.py | 7 +- cursive/utils.py | 4 + cursive/vendor/index.py | 18 +- poetry.lock | 518 ++++++++++++++++++++++++++++++---- pyproject.toml | 14 +- 9 files changed, 914 insertions(+), 416 deletions(-) diff --git a/README.md b/README.md index e3a9a2f..c02d692 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ cursive = Cursive() def add(a: float, b: float): """ Adds two numbers. - + a: The first number. b: The second number. """ @@ -101,7 +101,7 @@ The functions' result will automatically be fed into the conversation and anothe def create_character(name: str, age: str): """ Creates a character. - + name: The name of the character. age: The age of the character. """ @@ -156,7 +156,7 @@ cursive = Cursive( expand={ 'enable': True, 'defaults_to': 'gpt-3.5-turbo-16k', - 'model_mapping': { + 'resolve_model': { 'gpt-3.5-turbo': 'gpt-3.5-turbo-16k', 'gpt-4': 'claude-2', }, @@ -180,6 +180,34 @@ You can pass your OpenAI API key to `Cursive`'s constructor, or set the `OPENAI_ - `claude-instant-1.2` - Any other model version +#### OpenRouter + +OpenRouter is a service that gives you access to leading language models in an OpenAI-compatible API, including function calling! + +- `anthropic/claude-instant-1.2` +- `anthropic/claude-2` +- `openai/gpt-4-32k` +- `google/palm-2-codechat-bison` +- `nousresearch/nous-hermes-llama2-13b` +- Any model version from https://openrouter.ai/docs#models + +###### Credentials + +```python +import openai +import requests + +openai.api_key = "sk-or-..." +openai.api_base = "https://openrouter.ai/api/v1" +openai.requestssession = requests.Session() +openai.requestssession.headers.update({"HTTP-Referer": "https://appurl.com", "X-Title": "YOUR_APP_NAME"}) + +from cursive import Cursive + +cursive = Cursive() +cursive.ask(prompt="What is the meaning of life?", model="anthropic/claude-instant-1.2") +``` + ###### Credentials You can pass your Anthropic API key to `Cursive`'s constructor, or set the `ANTHROPIC_API_KEY` environment variable. @@ -208,6 +236,6 @@ You can pass your Replicate API key to `Cursive`'s constructor, or set the `REPL ### vendor support - [x] Anthropic - [x] Cohere -- [x] Replicate +- [x] Replicate - [ ] Azure OpenAI models -- [ ] Huggingface +- [ ] Huggingface diff --git a/cursive/cursive.py b/cursive/cursive.py index 19b3cee..bcb0ca3 100644 --- a/cursive/cursive.py +++ b/cursive/cursive.py @@ -7,6 +7,7 @@ import openai as openai_client from anthropic import APIError +import requests from cursive.build_input import get_function_call_directives from cursive.custom_function_call import parse_custom_function_call @@ -37,7 +38,7 @@ from cursive.pricing import resolve_pricing from cursive.usage.anthropic import get_anthropic_usage from cursive.usage.openai import get_openai_usage -from cursive.utils import filter_null_values, random_id, resguard +from cursive.utils import delete_keys_from_dict, filter_null_values, random_id, resguard from cursive.vendor.anthropic import ( AnthropicClient, process_anthropic_stream, @@ -46,6 +47,7 @@ from cursive.vendor.openai import process_openai_stream from cursive.vendor.replicate import ReplicateClient + # TODO: Improve implementation architecture, this was a quick and dirty class Cursive: options: CursiveSetupOptions @@ -59,37 +61,59 @@ def __init__( anthropic: Optional[dict[str, Any]] = None, cohere: Optional[dict[str, Any]] = None, replicate: Optional[dict[str, Any]] = None, + openrouter: Optional[dict[str, Any]] = None, ): - openai_client.api_key = (openai or {}).get('api_key') or \ - os.environ.get('OPENAI_API_KEY') - anthropic_client = AnthropicClient((anthropic or {}).get('api_key') or \ - os.environ.get('ANTHROPIC_API_KEY')) - cohere_client = CohereClient((cohere or {}).get('api_key') or \ - os.environ.get('CO_API_KEY', '---')) - replicate_client = ReplicateClient((replicate or {}).get('api_key') or \ - os.environ.get('REPLICATE_API_TOKEN', '---')) - + self._hooks = create_hooks() - self._vendor = CursiveVendors( - openai=openai_client, - anthropic=anthropic_client, - cohere=cohere_client, - replicate=replicate_client, - ) self.options = CursiveSetupOptions( max_retries=max_retries, expand=expand, debug=debug, ) - if debug: - self._debugger = create_debugger(self._hooks, { 'tag': 'cursive' }) + self._debugger = create_debugger(self._hooks, {"tag": "cursive"}) + + openai_client.api_key = (openai or {}).get("api_key") or os.environ.get( + "OPENAI_API_KEY" + ) + anthropic_client = AnthropicClient( + (anthropic or {}).get("api_key") or os.environ.get("ANTHROPIC_API_KEY") + ) + cohere_client = CohereClient( + (cohere or {}).get("api_key") or os.environ.get("CO_API_KEY", "---") + ) + replicate_client = ReplicateClient( + (replicate or {}).get("api_key") + or os.environ.get("REPLICATE_API_TOKEN", "---") + ) + + openrouter_api_key = (openrouter or {}).get("api_key") or os.environ.get( + "OPENROUTER_API_KEY" + ) + if openrouter_api_key: + openai_client.api_base = "https://openrouter.ai/api/v1" + openai_client.api_key = openrouter_api_key + self.options.is_using_openrouter = True + app_url = openrouter.get("app_url", "https://cursive.meistrari.com") + app_title = openrouter.get("app_title", "Cursive") + openai_client.requestssession = requests.Session() + openai_client.requestssession.headers.update({ + 'HTTP-Referer': app_url, + 'X-Title': app_title, + }) + + + self._vendor = CursiveVendors( + openai=openai_client, + anthropic=anthropic_client, + cohere=cohere_client, + replicate=replicate_client, + ) def on(self, event: CursiveHook, callback: Callable): self._hooks.hook(event, callback) - - + def ask( self, model: Optional[str | CursiveModel] = None, @@ -112,7 +136,7 @@ def ask( prompt: Optional[str] = None, ): model = model.value if isinstance(model, CursiveModel) else model - + result = build_answer( cursive=self, model=model, @@ -140,9 +164,9 @@ def ask( new_messages = [ *(result and result.messages or []), CompletionMessage( - role='assistant', - content=result and result.answer or '', - ) + role="assistant", + content=result and result.answer or "", + ), ] return CursiveAnswer( @@ -150,54 +174,52 @@ def ask( messages=new_messages, cursive=self, ) - - + def embed(self, content: str): options = { - 'model': 'text-embedding-ada-002', - 'input': content, + "model": "text-embedding-ada-002", + "input": content, } - self._hooks.call_hook('embedding:before', CursiveHookPayload(data=options)) + self._hooks.call_hook("embedding:before", CursiveHookPayload(data=options)) start = time.time() try: data = self._vendor.openai.Embedding.create( - input=options['input'], - model='text-embedding-ada-002' + input=options["input"], model="text-embedding-ada-002" ) - + result = { - 'embedding': data['data'][0]['embedding'], # type: ignore + "embedding": data["data"][0]["embedding"], # type: ignore } - self._hooks.call_hook('embedding:success', CursiveHookPayload( - data=result, - time=time.time() - start, - )) - self._hooks.call_hook('embedding:after', CursiveHookPayload( - data=result, - duration=time.time() - start - )) - - return result['embedding'] + self._hooks.call_hook( + "embedding:success", + CursiveHookPayload( + data=result, + time=time.time() - start, + ), + ) + self._hooks.call_hook( + "embedding:after", + CursiveHookPayload(data=result, duration=time.time() - start), + ) + + return result["embedding"] except self._vendor.openai.OpenAIError as e: error = CursiveError( - message=str(e), - details=e, - code=CursiveErrorCode.embedding_error + message=str(e), details=e, code=CursiveErrorCode.embedding_error + ) + self._hooks.call_hook( + "embedding:error", + CursiveHookPayload( + data=error, error=error, duration=time.time() - start + ), + ) + self._hooks.call_hook( + "embedding:after", + CursiveHookPayload( + data=error, error=error, duration=time.time() - start + ), ) - self._hooks.call_hook('embedding:error', CursiveHookPayload( - data=error, - error=error, - duration=time.time() - start - )) - self._hooks.call_hook('embedding:after', CursiveHookPayload( - data=error, - error=error, - duration=time.time() - start - )) raise error - - - def resolve_options( @@ -219,78 +241,87 @@ def resolve_options( stream: Optional[bool] = None, messages: Optional[list[CompletionMessage]] = None, prompt: Optional[str] = None, + cursive: Cursive = None, ): functions = functions or [] messages = messages or [] - model = model or 'gpt-3.5-turbo' + + # Resolve default model + model = model or ( + "openai/gpt-3.5-turbo" if cursive.options.is_using_openrouter else "gpt-3.5-turbo" + ) # TODO: Add support for function call resolving - vendor = resolve_vendor_from_model(model) - resolved_system_message = '' - if vendor in ['anthropic', 'cohere', 'replicate'] and len(functions) > 0: + vendor = "openrouter" if cursive.options.is_using_openrouter \ + else resolve_vendor_from_model(model) + + resolved_system_message = "" + + if vendor in ["anthropic", "cohere", "replicate"] and len(functions) > 0: resolved_system_message = ( - (system_message or '') - + '\n\n' - + get_function_call_directives(functions) + (system_message or "") + "\n\n" + get_function_call_directives(functions) ) - query_messages: list[CompletionMessage] = [message for message in [ - resolved_system_message and CompletionMessage( - role='system', - content=resolved_system_message - ), + query_messages: list[CompletionMessage] = [ + message + for message in [ + resolved_system_message + and CompletionMessage(role="system", content=resolved_system_message), *messages, - prompt and CompletionMessage(role='user', content=prompt), - ] if message + prompt and CompletionMessage(role="user", content=prompt), + ] + if message ] resolved_function_call = ( - { 'name': function_call.function_schema['name'] } + ( + {"name": function_call.function_schema["name"]} if isinstance(function_call, CursiveFunction) else function_call - ) if function_call else None - - options = filter_null_values({ - 'on_token': on_token, - 'max_tokens': max_tokens, - 'stop': stop, - 'temperature': temperature, - 'top_p': top_p, - 'presence_penalty': presence_penalty, - 'frequency_penalty': frequency_penalty, - 'best_of': best_of, - 'n': n, - 'logit_bias': logit_bias, - 'user': user, - 'stream': stream, - 'model': model, - 'messages': list( - map( - lambda message: filter_null_values(dict(message)), - query_messages - ) - ), - 'function_call': resolved_function_call, - }) + ) + if function_call + else None + ) + options = filter_null_values( + { + "on_token": on_token, + "max_tokens": max_tokens, + "stop": stop, + "temperature": temperature, + "top_p": top_p, + "presence_penalty": presence_penalty, + "frequency_penalty": frequency_penalty, + "best_of": best_of, + "n": n, + "logit_bias": logit_bias, + "user": user, + "stream": stream, + "model": model, + "messages": list( + map(lambda message: filter_null_values(dict(message)), query_messages) + ), + "function_call": resolved_function_call, + } + ) payload = CompletionPayload(**options) resolved_options = { - 'on_token': on_token, - 'max_tokens': max_tokens, - 'stop': stop, - 'temperature': temperature, - 'top_p': top_p, - 'presence_penalty': presence_penalty, - 'frequency_penalty': frequency_penalty, - 'best_of': best_of, - 'n': n, - 'logit_bias': logit_bias, - 'user': user, - 'stream': stream, - 'model': model, - 'messages': query_messages + "on_token": on_token, + "max_tokens": max_tokens, + "stop": stop, + "temperature": temperature, + "top_p": top_p, + "presence_penalty": presence_penalty, + "frequency_penalty": frequency_penalty, + "best_of": best_of, + "n": n, + "logit_bias": logit_bias, + "user": user, + "stream": stream, + "model": model, + "messages": query_messages, } return payload, resolved_options @@ -301,26 +332,26 @@ def create_completion( cursive: Cursive, on_token: Optional[CursiveAskOnToken] = None, ) -> CreateChatCompletionResponseExtended: - cursive._hooks.call_hook('completion:before', CursiveHookPayload(data=payload)) + cursive._hooks.call_hook("completion:before", CursiveHookPayload(data=payload)) data = {} start = time.time() - vendor = resolve_vendor_from_model(payload.model) + vendor = "openrouter" if cursive.options.is_using_openrouter \ + else resolve_vendor_from_model(payload.model) # TODO: Improve the completion creation based on model to vendor matching - if vendor == 'openai': - payload.messages = list( - map( - lambda message: { - k: v for k, v in message.dict().items() if k != 'id' and v is not None - }, - payload.messages - ) - ) - resolved_payload = filter_null_values(payload.dict()) - response = cursive._vendor.openai.ChatCompletion.create( - **resolved_payload - ) + if vendor in ["openai", "openrouter"]: + + resolved_payload = filter_null_values(payload.model_dump()) + + # Remove the ID from the messages before sending to OpenAI + resolved_payload["messages"] = [ + filter_null_values( + delete_keys_from_dict(message, ["id"]) + ) for message in resolved_payload["messages"] + ] + + response = cursive._vendor.openai.ChatCompletion.create(**resolved_payload) if payload.stream: data = process_openai_stream( payload=payload, @@ -328,34 +359,40 @@ def create_completion( response=response, on_token=on_token, ) - content = ''.join(list(map(lambda choice: choice['message']['content'], data['choices']))) - data['usage']['completion_tokens'] = get_openai_usage(content) - data['usage']['total_tokens'] = data['usage']['completion_tokens'] + data['usage']['prompt_tokens'] + content = "".join( + list(map(lambda choice: choice["message"]["content"], data["choices"])) + ) + data["usage"]["completion_tokens"] = get_openai_usage(content) + data["usage"]["total_tokens"] = ( + data["usage"]["completion_tokens"] + data["usage"]["prompt_tokens"] + ) else: data = response - - data['cost'] = resolve_pricing( - vendor='openai', - usage=CursiveAskUsage( - completion_tokens=data['usage']['completion_tokens'], - prompt_tokens=data['usage']['prompt_tokens'], - total_tokens=data['usage']['total_tokens'], - ), - model=data['model'] - ) - elif vendor == 'anthropic': + + # If the user is using OpenRouter, there's no usage data + if "usage" in data: + data["cost"] = resolve_pricing( + vendor="openai", + usage=CursiveAskUsage( + completion_tokens=data["usage"]["completion_tokens"], + prompt_tokens=data["usage"]["prompt_tokens"], + total_tokens=data["usage"]["total_tokens"], + ), + model=data["model"], + ) + + elif vendor == "anthropic": response, error = resguard( - lambda: cursive._vendor.anthropic.create_completion(payload), - APIError + lambda: cursive._vendor.anthropic.create_completion(payload), APIError ) if error: raise CursiveError( message=error.message, details=error, - code=CursiveErrorCode.completion_error + code=CursiveErrorCode.completion_error, ) - + if payload.stream: data = process_anthropic_stream( payload=payload, @@ -364,30 +401,30 @@ def create_completion( ) else: data = { - 'choices': [{ 'message': { 'content': response.completion.lstrip() } }], - 'model': payload.model, - 'id': random_id(), - 'usage': {}, + "choices": [{"message": {"content": response.completion.lstrip()}}], + "model": payload.model, + "id": random_id(), + "usage": {}, } parse_custom_function_call(data, payload, get_anthropic_usage) - data['cost'] = resolve_pricing( - vendor='anthropic', + data["cost"] = resolve_pricing( + vendor="anthropic", usage=CursiveAskUsage( - completion_tokens=data['usage']['completion_tokens'], - prompt_tokens=data['usage']['prompt_tokens'], - total_tokens=data['usage']['total_tokens'], + completion_tokens=data["usage"]["completion_tokens"], + prompt_tokens=data["usage"]["prompt_tokens"], + total_tokens=data["usage"]["total_tokens"], ), - model=data['model'] + model=data["model"], ) - elif vendor == 'cohere': + elif vendor == "cohere": response, error = cursive._vendor.cohere.create_completion(payload) if error: raise CursiveError( message=error.message, details=error, - code=CursiveErrorCode.completion_error + code=CursiveErrorCode.completion_error, ) if payload.stream: # TODO: Implement stream processing for Cohere @@ -398,32 +435,28 @@ def create_completion( ) else: data = { - 'choices': [ - { 'message': { 'content': response.data[0].text.lstrip() } } - ], - 'model': payload.model, - 'id': random_id(), - 'usage': {}, + "choices": [{"message": {"content": response.data[0].text.lstrip()}}], + "model": payload.model, + "id": random_id(), + "usage": {}, } parse_custom_function_call(data, payload, get_cohere_usage) - data['cost'] = resolve_pricing( - vendor='cohere', + data["cost"] = resolve_pricing( + vendor="cohere", usage=CursiveAskUsage( - completion_tokens=data['usage']['completion_tokens'], - prompt_tokens=data['usage']['prompt_tokens'], - total_tokens=data['usage']['total_tokens'], + completion_tokens=data["usage"]["completion_tokens"], + prompt_tokens=data["usage"]["prompt_tokens"], + total_tokens=data["usage"]["total_tokens"], ), - model=data['model'] + model=data["model"], ) - elif vendor == 'replicate': + elif vendor == "replicate": response, error = cursive._vendor.replicate.create_completion(payload) if error: raise CursiveError( - message=error, - details=error, - code=CursiveErrorCode.completion_error + message=error, details=error, code=CursiveErrorCode.completion_error ) # TODO: Implement stream processing for Replicate stream_transformer = StreamTransformer( @@ -435,29 +468,36 @@ def create_completion( def get_current_token(part): part.value = part.value - stream_transformer.on('get_current_token', get_current_token) + stream_transformer.on("get_current_token", get_current_token) data = stream_transformer.process() parse_custom_function_call(data, payload) + else: + raise CursiveError( + message="Unknown model", + details=None, + code=CursiveErrorCode.completion_error, + ) end = time.time() - if data.get('error'): + if data.get("error"): error = CursiveError( - message=data['error'].message, - details=data['error'], - code=CursiveErrorCode.completion_error + message=data["error"].message, + details=data["error"], + code=CursiveErrorCode.completion_error, ) hook_payload = CursiveHookPayload(data=None, error=error, duration=end - start) - cursive._hooks.call_hook('completion:error', hook_payload) - cursive._hooks.call_hook('completion:after', hook_payload) + cursive._hooks.call_hook("completion:error", hook_payload) + cursive._hooks.call_hook("completion:after", hook_payload) raise error hook_payload = CursiveHookPayload(data=data, error=None, duration=end - start) - cursive._hooks.call_hook('completion:success', hook_payload) - cursive._hooks.call_hook('completion:after', hook_payload) + cursive._hooks.call_hook("completion:success", hook_payload) + cursive._hooks.call_hook("completion:after", hook_payload) return CreateChatCompletionResponseExtended(**data) + def ask_model( cursive, model: Optional[str | CursiveModel] = None, @@ -478,7 +518,7 @@ def ask_model( stream: Optional[bool] = None, messages: Optional[list[CompletionMessage]] = None, prompt: Optional[str] = None, -) -> CursiveAskModelResponse: +) -> CursiveAskModelResponse: payload, resolved_options = resolve_options( model=model, system_message=system_message, @@ -498,12 +538,13 @@ def ask_model( stream=stream, messages=messages, prompt=prompt, + cursive=cursive, ) start = time.time() functions = functions or [] - if (type(function_call) == CursiveFunction): + if type(function_call) == CursiveFunction: functions.append(function_call) function_schemas = list(map(lambda function: function.function_schema, functions)) @@ -511,60 +552,52 @@ def ask_model( if len(function_schemas) > 0: payload.functions = function_schemas - completion, error = resguard(lambda: create_completion( - payload=payload, - cursive=cursive, - on_token=on_token, - ), CursiveError) + completion, error = resguard( + lambda: create_completion( + payload=payload, + cursive=cursive, + on_token=on_token, + ), + CursiveError, + ) if error: - if (not error.details): + if not error.details: raise CursiveError( - message=f'Unknown error: {error.message}', + message=f"Unknown error: {error.message}", details=error, - code=CursiveErrorCode.unknown_error + code=CursiveErrorCode.unknown_error, ) from error try: cause = error.details.code or error.details.type - if (cause == 'context_length_exceeded'): - if ( - not cursive.expand - or (cursive.expand and cursive.expand.enabled) - ): + if cause == "context_length_exceeded": + if not cursive.expand or (cursive.expand and cursive.expand.enabled): default_model = ( - ( - cursive.expand - and cursive.expand.defaultsTo - ) - or 'gpt-3.5-turbo-16k' - ) + cursive.expand and cursive.expand.defaultsTo + ) or "gpt-3.5-turbo-16k" model_mapping = ( - ( - cursive.expand - and cursive.expand.model_mapping - ) - or {} - ) + cursive.expand and cursive.expand.model_mapping + ) or {} resolved_model = model_mapping[model] or default_model completion, error = resguard( lambda: create_completion( - payload={ **payload, 'model': resolved_model }, + payload={**payload, "model": resolved_model}, cursive=cursive, on_token=on_token, ), CursiveError, ) - elif cause == 'invalid_request_error': + elif cause == "invalid_request_error": raise CursiveError( - message='Invalid request', + message="Invalid request", details=error.details, code=CursiveErrorCode.invalid_request_error, ) except Exception as e: error = CursiveError( - message=f'Unknown error: {e}', + message=f"Unknown error: {e}", details=e, - code=CursiveErrorCode.unknown_error + code=CursiveErrorCode.unknown_error, ) # TODO: Handle other errors @@ -577,7 +610,7 @@ def ask_model( cursive=cursive, on_token=on_token, ), - CursiveError + CursiveError, ) if error: @@ -587,58 +620,63 @@ def ask_model( if error: error = CursiveError( - message='Error while completing request', + message="Error while completing request", details=error.details, - code=CursiveErrorCode.completion_error + code=CursiveErrorCode.completion_error, ) hook_payload = CursiveHookPayload(error=error) - cursive._hooks.call_hook('ask:error', hook_payload) - cursive._hooks.call_hook('ask:after', hook_payload) + cursive._hooks.call_hook("ask:error", hook_payload) + cursive._hooks.call_hook("ask:after", hook_payload) raise error if ( completion and completion.choices and len(completion.choices) > 0 - and completion.choices[0].get('message') - and completion.choices[0]['message'].get('function_call') + and completion.choices[0].get("message") + and completion.choices[0]["message"].get("function_call") ): - - payload.messages.append({ - 'role': 'assistant', - 'function_call': completion.choices[0]['message'].get('function_call'), - 'content': '', - }) - func_call = completion.choices[0]['message'].get('function_call') + payload.messages.append( + { + "role": "assistant", + "function_call": completion.choices[0]["message"].get("function_call"), + "content": "", + } + ) + func_call = completion.choices[0]["message"].get("function_call") function_definition = next( - (f for f in functions if f.function_schema['name'] == func_call['name']), - None + (f for f in functions if f.function_schema["name"] == func_call["name"]), + None, ) if not function_definition: - return ask_model(**{ - **resolved_options, - 'function_call': 'none', - 'messages': payload.messages, - 'cursive': cursive, - }) + return ask_model( + **{ + **resolved_options, + "function_call": "none", + "messages": payload.messages, + "cursive": cursive, + } + ) - name = func_call['name'] - called_function = next((func for func in payload.functions if func['name'] == name), None) - arguments = json.loads(func_call['arguments'] or '{}') + name = func_call["name"] + called_function = next( + (func for func in payload.functions if func["name"] == name), None + ) + arguments = json.loads(func_call["arguments"] or "{}") if called_function: - props = called_function['parameters']['properties'] + props = called_function["parameters"]["properties"] for k, v in props.items(): if k in arguments: try: - arg_type = v['type'] - if arg_type == 'string': + arg_type = v["type"] + if arg_type == "string": arguments[k] = str(arguments[k]) - elif arg_type == 'number': + elif arg_type == "number": arguments[k] = float(arguments[k]) - elif arg_type == 'integer': + elif arg_type == "integer": arguments[k] = int(arguments[k]) - elif arg_type == 'boolean': + elif arg_type == "boolean": arguments[k] = bool(arguments[k]) except Exception: pass @@ -661,31 +699,34 @@ def ask_model( messages = payload.messages or [] - messages.append(CompletionMessage( - role='function', - name=func_call['name'], - content=json.dumps(function_result or ''), - )) + messages.append( + CompletionMessage( + role="function", + name=func_call["name"], + content=json.dumps(function_result or ""), + ) + ) if function_definition.pause: - completion.function_result = function_result return CursiveAskModelResponse( - answer=CreateChatCompletionResponseExtended(**completion.dict()), + answer=CreateChatCompletionResponseExtended(**completion.model_dump()), messages=messages, ) else: - return ask_model(**{ - **resolved_options, - 'functions': functions, - 'messages': messages, - 'cursive': cursive, - }) + return ask_model( + **{ + **resolved_options, + "functions": functions, + "messages": messages, + "cursive": cursive, + } + ) end = time.time() hook_payload = CursiveHookPayload(data=completion, duration=end - start) - cursive._hooks.call_hook('ask:after', hook_payload) - cursive._hooks.call_hook('ask:success', hook_payload) + cursive._hooks.call_hook("ask:after", hook_payload) + cursive._hooks.call_hook("ask:success", hook_payload) return CursiveAskModelResponse( answer=completion, @@ -736,14 +777,14 @@ def build_answer( messages=messages, prompt=prompt, ), - CursiveError + CursiveError, ) if error: return CursiveEnrichedAnswer( error=error, usage=None, - model=model or 'gpt-3.5-turbo', + model=model or "gpt-3.5-turbo", id=None, choices=None, function_result=None, @@ -752,11 +793,15 @@ def build_answer( cost=None, ) else: - usage = CursiveAskUsage( - completion_tokens=result.answer.usage['completion_tokens'], - prompt_tokens=result.answer.usage['prompt_tokens'], - total_tokens=result.answer.usage['total_tokens'], - ) if result.answer.usage else None + usage = ( + CursiveAskUsage( + completion_tokens=result.answer.usage["completion_tokens"], + prompt_tokens=result.answer.usage["prompt_tokens"], + total_tokens=result.answer.usage["total_tokens"], + ) + if result.answer.usage + else None + ) return CursiveEnrichedAnswer( error=None, @@ -765,23 +810,21 @@ def build_answer( usage=usage, cost=result.answer.cost, choices=list( - map(lambda choice: choice['message']['content'], result.answer.choices) + map(lambda choice: choice["message"]["content"], result.answer.choices) ), function_result=result.answer.function_result or None, - answer=result.answer.choices[-1]['message']['content'], + answer=result.answer.choices[-1]["message"]["content"], messages=result.messages, ) - class CursiveConversation: _cursive: Cursive messages: list[CompletionMessage] - + def __init__(self, messages: list[CompletionMessage]): self.messages = messages - - + def ask( self, model: Optional[str | CursiveModel] = None, @@ -802,7 +845,7 @@ def ask( stream: Optional[bool] = None, prompt: Optional[str] = None, ): - messages=[ + messages = [ *self.messages, ] @@ -833,7 +876,7 @@ def ask( new_messages = [ *(result and result.messages or []), - CompletionMessage(role='assistant', content=result and result.answer or ''), + CompletionMessage(role="assistant", content=result and result.answer or ""), ] return CursiveAnswer[None]( @@ -861,6 +904,7 @@ def use_cursive( E = TypeVar("E", None, CursiveError) + class CursiveAnswer(Generic[E]): choices: Optional[list[str]] id: Optional[str] @@ -875,7 +919,7 @@ class CursiveAnswer(Generic[E]): conversation: Optional[CursiveConversation] def __init__( - self, + self, result: Optional[Any] = None, error: Optional[E] = None, messages: Optional[list[CompletionMessage]] = None, @@ -903,7 +947,7 @@ def __init__( if messages: conversation = CursiveConversation(messages) if cursive: - conversation._cursive = cursive + conversation._cursive = cursive self.conversation = conversation def __str__(self): @@ -916,6 +960,7 @@ def __str__(self): f"answer={self.answer}\n\tconversation={self.conversation}\n)" ) + class CursiveVendors(BaseModel): openai: Optional[Any] = None anthropic: Optional[AnthropicClient] = None diff --git a/cursive/custom_types.py b/cursive/custom_types.py index 928ab96..a656b20 100644 --- a/cursive/custom_types.py +++ b/cursive/custom_types.py @@ -1,14 +1,18 @@ from enum import Enum from typing import Any, Callable, Literal, Optional -from pydantic import BaseModel as PydanticBaseModel +from pydantic import ConfigDict, BaseModel as PydanticBaseModel from cursive.function import CursiveFunction from cursive.utils import random_id + class BaseModel(PydanticBaseModel): - class Config: - arbitrary_types_allowed = True + model_config = ConfigDict( + arbitrary_types_allowed=True, + protected_namespaces=() + ) + class CursiveModel(Enum): GPT4 = "gpt4" @@ -17,35 +21,37 @@ class CursiveModel(Enum): GPT3_5_TURBO_16K = "gpt-3.5-turbo-16k" CLAUDE_2 = "claude-2" CLAUDE_INSTANT_1_2 = "claude-instant-1.2" - CLAUDE_INSTANT_1 = "claude-instant-2.2" + CLAUDE_INSTANT_1 = "claude-instant-1.2" COMMAND = "command" COMMAND_NIGHTLY = "command-nightly" + class CursiveAskUsage(BaseModel): completion_tokens: int prompt_tokens: int total_tokens: int - - class CursiveAskCost(BaseModel): completion: float prompt: float total: float version: str + Role = Literal[ - 'system', - 'user', - 'assistant', - 'function', + "system", + "user", + "assistant", + "function", ] + class ChatCompletionRequestMessageFunctionCall(BaseModel): name: Optional[str] = None arguments: Optional[str] = None + class CompletionMessage(BaseModel): id: Optional[str] = None role: Role @@ -58,21 +64,24 @@ def __init__(self, **data): if not self.id: self.id = random_id() + class CursiveSetupOptionsExpand(BaseModel): enabled: Optional[bool] = None defaults_to: Optional[str] = None model_mapping: Optional[dict[str, str]] = None + class CursiveErrorCode(Enum): - function_call_error = 'function_call_error', - completion_error = 'completion_error', - invalid_request_error = 'invalid_request_error', - embedding_error = 'embedding_error', - unknown_error = 'unknown_error', + function_call_error = ("function_call_error",) + completion_error = ("completion_error",) + invalid_request_error = ("invalid_request_error",) + embedding_error = ("embedding_error",) + unknown_error = ("unknown_error",) + class CursiveError(Exception): - name = 'CursiveError' - + name = "CursiveError" + def __init__( self, message: str, @@ -84,19 +93,22 @@ def __init__( self.code = code super().__init__(self.message) + class CursiveEnrichedAnswer(BaseModel): - error: CursiveError | None - usage: CursiveAskUsage | None + error: CursiveError | None = None + usage: CursiveAskUsage | None = None model: str - id: str | None + id: str | None = None choices: Optional[list[Any]] = None - function_result: Any | None - answer: str | None - messages: list[CompletionMessage] | None - cost: CursiveAskCost | None + function_result: Any | None = None + answer: str | None = None + messages: list[CompletionMessage] | None = None + cost: CursiveAskCost | None = None + CursiveAskOnToken = Callable[[dict[str, Any]], None] + class CursiveAskOptionsBase(BaseModel): model: Optional[str | CursiveModel] = None system_message: Optional[str] = None @@ -115,38 +127,47 @@ class CursiveAskOptionsBase(BaseModel): user: Optional[str] = None stream: Optional[bool] = None + class CreateChatCompletionResponse(BaseModel): id: str model: str choices: list[Any] usage: Optional[Any] = None + class CreateChatCompletionResponseExtended(CreateChatCompletionResponse): function_result: Optional[Any] = None cost: Optional[CursiveAskCost] = None error: Optional[CursiveError] = None + class CursiveAskModelResponse(BaseModel): answer: CreateChatCompletionResponseExtended messages: list[CompletionMessage] + class CursiveSetupOptions(BaseModel): max_retries: Optional[int] = None expand: Optional[CursiveSetupOptionsExpand] = None + is_using_openrouter: Optional[bool] = None + class CompletionRequestFunctionCall(BaseModel): name: str inputs: dict[str, Any] + class CompletionRequestStop(BaseModel): messages_seen: Optional[list[CompletionMessage]] = None max_turns: Optional[int] = None + class CompletionFunctions(BaseModel): name: str description: Optional[str] = None parameters: Optional[dict[str, Any]] = None + class CompletionPayload(BaseModel): model: str messages: list[CompletionMessage] @@ -164,22 +185,24 @@ class CompletionPayload(BaseModel): user: Optional[str] = None other: Optional[dict[str, Any]] = None + CursiveHook = Literal[ - 'embedding:before', - 'embedding:after', - 'embedding:error', - 'embedding:success', - 'completion:before', - 'completion:after', - 'completion:error', - 'completion:success', - 'ask:before', - 'ask:after', - 'ask:success', - 'ask:error', + "embedding:before", + "embedding:after", + "embedding:error", + "embedding:success", + "completion:before", + "completion:after", + "completion:error", + "completion:success", + "ask:before", + "ask:after", + "ask:success", + "ask:error", ] -class CursiveHookPayload(): + +class CursiveHookPayload: data: Optional[Any] error: Optional[CursiveError] duration: Optional[float] @@ -193,4 +216,3 @@ def __init__( self.data = data self.error = error self.duration = duration - diff --git a/cursive/pricing.py b/cursive/pricing.py index 6505132..91c1215 100644 --- a/cursive/pricing.py +++ b/cursive/pricing.py @@ -8,39 +8,37 @@ from .assets.price.cohere import COHERE_PRICING VENDOR_PRICING = { - 'openai': OPENAI_PRICING, - 'anthropic': ANTHROPIC_PRICING, - 'cohere': COHERE_PRICING + "openai": OPENAI_PRICING, + "anthropic": ANTHROPIC_PRICING, + "cohere": COHERE_PRICING, } + def resolve_pricing( - vendor: Literal['openai', 'anthropic'], - usage: CursiveAskUsage, - model: str + vendor: Literal["openai", "anthropic"], usage: CursiveAskUsage, model: str ): + if "/" in model: + vendor, model = model.split("/") + version: str prices: dict[str, dict[str, str]] version, prices = destructure_items( - keys=["version"], - dictionary=VENDOR_PRICING[vendor] + keys=["version"], dictionary=VENDOR_PRICING[vendor] ) models_available = list(prices.keys()) model_match = next((m for m in models_available if model.startswith(m)), None) - + if not model_match: - raise Exception(f'Unknown model {model}') - - model_price = prices[model_match] - completion = usage.completion_tokens * float(model_price["completion"]) / 1000 + raise Exception(f"Unknown model {model}") + + model_price = prices[model_match] + completion = usage.completion_tokens * float(model_price["completion"]) / 1000 prompt = usage.prompt_tokens * float(model_price["prompt"]) / 1000 - + cost = CursiveAskCost( - completion=completion, - prompt=prompt, - total=completion + prompt, - version=version + completion=completion, prompt=prompt, total=completion + prompt, version=version ) return cost diff --git a/cursive/usage/openai.py b/cursive/usage/openai.py index 44852dd..b2591b3 100644 --- a/cursive/usage/openai.py +++ b/cursive/usage/openai.py @@ -3,6 +3,8 @@ import tiktoken +from cursive.custom_types import CompletionMessage + def encode( text: str, @@ -18,7 +20,7 @@ def encode( ) -def get_openai_usage(content: str | list[dict]): +def get_openai_usage(content: str | list[CompletionMessage]): if type(content) == list: tokens = { "per_message": 3, @@ -28,8 +30,7 @@ def get_openai_usage(content: str | list[dict]): token_count = 3 for message in content: token_count += tokens['per_message'] - - for attribute, value in message.items(): + for attribute, value in message.model_dump().items(): if attribute == 'name': token_count += tokens['per_name'] diff --git a/cursive/utils.py b/cursive/utils.py index f81a637..c51d717 100644 --- a/cursive/utils.py +++ b/cursive/utils.py @@ -41,6 +41,10 @@ def trim(content: str) -> str: return content.strip() +def delete_keys_from_dict(dictionary: dict, keys: list[str]): + return { + k: v for k, v in dictionary.items() if k not in keys + } T = TypeVar('T', bound=Exception) diff --git a/cursive/vendor/index.py b/cursive/vendor/index.py index a799c7c..db3b309 100644 --- a/cursive/vendor/index.py +++ b/cursive/vendor/index.py @@ -1,15 +1,13 @@ - -model_suffix_to_vendor_mapping = { - 'openai': ['gpt-3.5', 'gpt-4'], - 'anthropic': ['claude-instant', 'claude-2'], - 'cohere': ['command'], - 'replicate': ['replicate'] +model_prefix_to_vendor = { + "openai": ["gpt-3.5", "gpt-4"], + "anthropic": ["claude-instant", "claude-2"], + "cohere": ["command"], + "replicate": ["replicate"], } def resolve_vendor_from_model(model: str): - for vendor, suffixes in model_suffix_to_vendor_mapping.items(): - if len([m for m in suffixes if model.startswith(m)]) > 0: + for vendor, prefixes in model_prefix_to_vendor.items(): + if any(model.startswith(m) for m in prefixes): return vendor - return '' - + return "" diff --git a/poetry.lock b/poetry.lock index 7a5c888..93e3177 100644 --- a/poetry.lock +++ b/poetry.lock @@ -122,24 +122,35 @@ files = [ [package.dependencies] frozenlist = ">=1.1.0" +[[package]] +name = "annotated-types" +version = "0.5.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.7" +files = [ + {file = "annotated_types-0.5.0-py3-none-any.whl", hash = "sha256:58da39888f92c276ad970249761ebea80ba544b77acddaa1a4d6cf78287d45fd"}, + {file = "annotated_types-0.5.0.tar.gz", hash = "sha256:47cdc3490d9ac1506ce92c7aaa76c579dc3509ff11e098fc867e5130ab7be802"}, +] + [[package]] name = "anthropic" -version = "0.3.8" +version = "0.3.10" description = "Client library for the anthropic API" optional = false python-versions = ">=3.7,<4.0" files = [ - {file = "anthropic-0.3.8-py3-none-any.whl", hash = "sha256:97ffe1bacc4214dc89b19f496cf2769746971e86f7c835a05aa21b76f260d279"}, - {file = "anthropic-0.3.8.tar.gz", hash = "sha256:6651099807456c3b95b3879f5ad7d00f7e7e4f7649a2394d18032ab8be54ef16"}, + {file = "anthropic-0.3.10-py3-none-any.whl", hash = "sha256:95cd73168296a4f91a5899a28660991e044322cf94442d07b99901d4ca74acd6"}, + {file = "anthropic-0.3.10.tar.gz", hash = "sha256:d1f66efc541fbff0ecfd37fd4d3690f9daaa748fc42d9ded5863a10815a5d97b"}, ] [package.dependencies] anyio = ">=3.5.0,<4" distro = ">=1.7.0,<2" httpx = ">=0.23.0,<1" -pydantic = ">=1.9.0,<2.0.0" +pydantic = ">=1.9.0,<3" tokenizers = ">=0.13.0" -typing-extensions = ">=4.1.1,<5" +typing-extensions = ">=4.5,<5" [[package]] name = "anyio" @@ -162,6 +173,34 @@ doc = ["Sphinx", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd- test = ["anyio[trio]", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] trio = ["trio (<0.22)"] +[[package]] +name = "appnope" +version = "0.1.3" +description = "Disable App Nap on macOS >= 10.9" +optional = false +python-versions = "*" +files = [ + {file = "appnope-0.1.3-py2.py3-none-any.whl", hash = "sha256:265a455292d0bd8a72453494fa24df5a11eb18373a60c7c0430889f22548605e"}, + {file = "appnope-0.1.3.tar.gz", hash = "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24"}, +] + +[[package]] +name = "asttokens" +version = "2.2.1" +description = "Annotate AST trees with source code positions" +optional = false +python-versions = "*" +files = [ + {file = "asttokens-2.2.1-py2.py3-none-any.whl", hash = "sha256:6b0ac9e93fb0335014d382b8fa9b3afa7df546984258005da0b9e7095b3deb1c"}, + {file = "asttokens-2.2.1.tar.gz", hash = "sha256:4622110b2a6f30b77e1473affaa97e711bc2f07d3f10848420ff1898edbe94f3"}, +] + +[package.dependencies] +six = "*" + +[package.extras] +test = ["astroid", "pytest"] + [[package]] name = "async-timeout" version = "4.0.3" @@ -191,6 +230,17 @@ docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib- tests = ["attrs[tests-no-zope]", "zope-interface"] tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +[[package]] +name = "backcall" +version = "0.2.0" +description = "Specifications for callback functions passed in to an API" +optional = false +python-versions = "*" +files = [ + {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, + {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, +] + [[package]] name = "backoff" version = "2.2.1" @@ -299,13 +349,13 @@ files = [ [[package]] name = "cohere" -version = "4.19.3" +version = "4.21" description = "" optional = false python-versions = ">=3.7,<4.0" files = [ - {file = "cohere-4.19.3-py3-none-any.whl", hash = "sha256:6c98f1e58b93b6316c824385c1d2032ed352280e9efa5695ba98306258abf84f"}, - {file = "cohere-4.19.3.tar.gz", hash = "sha256:c3aaa716c4da7d7a8ed68705fcdc92f1b1a2260b737cee6bd27af5c347f31496"}, + {file = "cohere-4.21-py3-none-any.whl", hash = "sha256:5eb81db62e78b3156e734421cc3e657054f9d9f1d68b9f38cf48fe3a8ae40dbc"}, + {file = "cohere-4.21.tar.gz", hash = "sha256:f611438f409dfc5d5a0a153a585349f5a80b169c7102b5994d9999ecf8440866"}, ] [package.dependencies] @@ -327,6 +377,17 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "decorator" +version = "5.1.1" +description = "Decorators for Humans" +optional = false +python-versions = ">=3.5" +files = [ + {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, + {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, +] + [[package]] name = "distro" version = "1.8.0" @@ -340,18 +401,32 @@ files = [ [[package]] name = "exceptiongroup" -version = "1.1.2" +version = "1.1.3" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.1.2-py3-none-any.whl", hash = "sha256:e346e69d186172ca7cf029c8c1d16235aa0e04035e5750b4b95039e65204328f"}, - {file = "exceptiongroup-1.1.2.tar.gz", hash = "sha256:12c3e887d6485d16943a309616de20ae5582633e0a2eda17f4e10fd61c1e8af5"}, + {file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"}, + {file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"}, ] [package.extras] test = ["pytest (>=6)"] +[[package]] +name = "executing" +version = "1.2.0" +description = "Get the currently executing AST node of a frame, and other information" +optional = false +python-versions = "*" +files = [ + {file = "executing-1.2.0-py2.py3-none-any.whl", hash = "sha256:0314a69e37426e3608aada02473b4161d4caf5a4b244d1d0c48072b8fee7bacc"}, + {file = "executing-1.2.0.tar.gz", hash = "sha256:19da64c18d2d851112f09c287f8d3dbbdf725ab0e569077efb6cdcbd3497c107"}, +] + +[package.extras] +tests = ["asttokens", "littleutils", "pytest", "rich"] + [[package]] name = "fastavro" version = "1.8.2" @@ -558,6 +633,93 @@ files = [ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] +[[package]] +name = "ipdb" +version = "0.13.13" +description = "IPython-enabled pdb" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "ipdb-0.13.13-py3-none-any.whl", hash = "sha256:45529994741c4ab6d2388bfa5d7b725c2cf7fe9deffabdb8a6113aa5ed449ed4"}, + {file = "ipdb-0.13.13.tar.gz", hash = "sha256:e3ac6018ef05126d442af680aad863006ec19d02290561ac88b8b1c0b0cfc726"}, +] + +[package.dependencies] +decorator = {version = "*", markers = "python_version > \"3.6\""} +ipython = {version = ">=7.31.1", markers = "python_version > \"3.6\""} +tomli = {version = "*", markers = "python_version > \"3.6\" and python_version < \"3.11\""} + +[[package]] +name = "ipython" +version = "8.14.0" +description = "IPython: Productive Interactive Computing" +optional = false +python-versions = ">=3.9" +files = [ + {file = "ipython-8.14.0-py3-none-any.whl", hash = "sha256:248aca623f5c99a6635bc3857677b7320b9b8039f99f070ee0d20a5ca5a8e6bf"}, + {file = "ipython-8.14.0.tar.gz", hash = "sha256:1d197b907b6ba441b692c48cf2a3a2de280dc0ac91a3405b39349a50272ca0a1"}, +] + +[package.dependencies] +appnope = {version = "*", markers = "sys_platform == \"darwin\""} +backcall = "*" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +decorator = "*" +jedi = ">=0.16" +matplotlib-inline = "*" +pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} +pickleshare = "*" +prompt-toolkit = ">=3.0.30,<3.0.37 || >3.0.37,<3.1.0" +pygments = ">=2.4.0" +stack-data = "*" +traitlets = ">=5" + +[package.extras] +all = ["black", "curio", "docrepr", "ipykernel", "ipyparallel", "ipywidgets", "matplotlib", "matplotlib (!=3.2.0)", "nbconvert", "nbformat", "notebook", "numpy (>=1.21)", "pandas", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio", "qtconsole", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "trio", "typing-extensions"] +black = ["black"] +doc = ["docrepr", "ipykernel", "matplotlib", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "typing-extensions"] +kernel = ["ipykernel"] +nbconvert = ["nbconvert"] +nbformat = ["nbformat"] +notebook = ["ipywidgets", "notebook"] +parallel = ["ipyparallel"] +qtconsole = ["qtconsole"] +test = ["pytest (<7.1)", "pytest-asyncio", "testpath"] +test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.21)", "pandas", "pytest (<7.1)", "pytest-asyncio", "testpath", "trio"] + +[[package]] +name = "jedi" +version = "0.19.0" +description = "An autocompletion tool for Python that can be used for text editors." +optional = false +python-versions = ">=3.6" +files = [ + {file = "jedi-0.19.0-py2.py3-none-any.whl", hash = "sha256:cb8ce23fbccff0025e9386b5cf85e892f94c9b822378f8da49970471335ac64e"}, + {file = "jedi-0.19.0.tar.gz", hash = "sha256:bcf9894f1753969cbac8022a8c2eaee06bfa3724e4192470aaffe7eb6272b0c4"}, +] + +[package.dependencies] +parso = ">=0.8.3,<0.9.0" + +[package.extras] +docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] +qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] +testing = ["Django (<3.1)", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] + +[[package]] +name = "matplotlib-inline" +version = "0.1.6" +description = "Inline Matplotlib backend for Jupyter" +optional = false +python-versions = ">=3.5" +files = [ + {file = "matplotlib-inline-0.1.6.tar.gz", hash = "sha256:f887e5f10ba98e8d2b150ddcf4702c1e5f8b3a20005eb0f74bfdbd360ee6f304"}, + {file = "matplotlib_inline-0.1.6-py3-none-any.whl", hash = "sha256:f1f41aab5328aa5aaea9b16d083b128102f8712542f819fe7e6a420ff581b311"}, +] + +[package.dependencies] +traitlets = "*" + [[package]] name = "multidict" version = "6.0.4" @@ -643,13 +805,13 @@ files = [ [[package]] name = "openai" -version = "0.27.8" +version = "0.27.9" description = "Python client library for the OpenAI API" optional = false python-versions = ">=3.7.1" files = [ - {file = "openai-0.27.8-py3-none-any.whl", hash = "sha256:e0a7c2f7da26bdbe5354b03c6d4b82a2f34bd4458c7a17ae1a7092c3e397e03c"}, - {file = "openai-0.27.8.tar.gz", hash = "sha256:2483095c7db1eee274cebac79e315a986c4e55207bb4fa7b82d185b3a2ed9536"}, + {file = "openai-0.27.9-py3-none-any.whl", hash = "sha256:6a3cf8e276d1a6262b50562fbc0cba7967cfebb78ed827d375986b48fdad6475"}, + {file = "openai-0.27.9.tar.gz", hash = "sha256:b687761c82f5ebb6f61efc791b2083d2d068277b94802d4d1369efe39851813d"}, ] [package.dependencies] @@ -674,72 +836,250 @@ files = [ {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, ] +[[package]] +name = "parso" +version = "0.8.3" +description = "A Python Parser" +optional = false +python-versions = ">=3.6" +files = [ + {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"}, + {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"}, +] + +[package.extras] +qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] +testing = ["docopt", "pytest (<6.0.0)"] + +[[package]] +name = "pexpect" +version = "4.8.0" +description = "Pexpect allows easy control of interactive console applications." +optional = false +python-versions = "*" +files = [ + {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, + {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, +] + +[package.dependencies] +ptyprocess = ">=0.5" + +[[package]] +name = "pickleshare" +version = "0.7.5" +description = "Tiny 'shelve'-like database with concurrency support" +optional = false +python-versions = "*" +files = [ + {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, + {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, +] + [[package]] name = "pluggy" -version = "1.2.0" +version = "1.3.0" description = "plugin and hook calling mechanisms for python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849"}, - {file = "pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"}, + {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, + {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, ] [package.extras] dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] +[[package]] +name = "prompt-toolkit" +version = "3.0.39" +description = "Library for building powerful interactive command lines in Python" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "prompt_toolkit-3.0.39-py3-none-any.whl", hash = "sha256:9dffbe1d8acf91e3de75f3b544e4842382fc06c6babe903ac9acb74dc6e08d88"}, + {file = "prompt_toolkit-3.0.39.tar.gz", hash = "sha256:04505ade687dc26dc4284b1ad19a83be2f2afe83e7a828ace0c72f3a1df72aac"}, +] + +[package.dependencies] +wcwidth = "*" + +[[package]] +name = "ptyprocess" +version = "0.7.0" +description = "Run a subprocess in a pseudo terminal" +optional = false +python-versions = "*" +files = [ + {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, + {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, +] + +[[package]] +name = "pure-eval" +version = "0.2.2" +description = "Safely evaluate AST nodes without side effects" +optional = false +python-versions = "*" +files = [ + {file = "pure_eval-0.2.2-py3-none-any.whl", hash = "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350"}, + {file = "pure_eval-0.2.2.tar.gz", hash = "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3"}, +] + +[package.extras] +tests = ["pytest"] + [[package]] name = "pydantic" -version = "1.10.12" -description = "Data validation and settings management using python type hints" +version = "2.3.0" +description = "Data validation using Python type hints" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pydantic-2.3.0-py3-none-any.whl", hash = "sha256:45b5e446c6dfaad9444819a293b921a40e1db1aa61ea08aede0522529ce90e81"}, + {file = "pydantic-2.3.0.tar.gz", hash = "sha256:1607cc106602284cd4a00882986570472f193fde9cb1259bceeaedb26aa79a6d"}, +] + +[package.dependencies] +annotated-types = ">=0.4.0" +pydantic-core = "2.6.3" +typing-extensions = ">=4.6.1" + +[package.extras] +email = ["email-validator (>=2.0.0)"] + +[[package]] +name = "pydantic-core" +version = "2.6.3" +description = "" optional = false python-versions = ">=3.7" files = [ - {file = "pydantic-1.10.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a1fcb59f2f355ec350073af41d927bf83a63b50e640f4dbaa01053a28b7a7718"}, - {file = "pydantic-1.10.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b7ccf02d7eb340b216ec33e53a3a629856afe1c6e0ef91d84a4e6f2fb2ca70fe"}, - {file = "pydantic-1.10.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fb2aa3ab3728d950bcc885a2e9eff6c8fc40bc0b7bb434e555c215491bcf48b"}, - {file = "pydantic-1.10.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:771735dc43cf8383959dc9b90aa281f0b6092321ca98677c5fb6125a6f56d58d"}, - {file = "pydantic-1.10.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ca48477862372ac3770969b9d75f1bf66131d386dba79506c46d75e6b48c1e09"}, - {file = "pydantic-1.10.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a5e7add47a5b5a40c49b3036d464e3c7802f8ae0d1e66035ea16aa5b7a3923ed"}, - {file = "pydantic-1.10.12-cp310-cp310-win_amd64.whl", hash = "sha256:e4129b528c6baa99a429f97ce733fff478ec955513630e61b49804b6cf9b224a"}, - {file = "pydantic-1.10.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b0d191db0f92dfcb1dec210ca244fdae5cbe918c6050b342d619c09d31eea0cc"}, - {file = "pydantic-1.10.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:795e34e6cc065f8f498c89b894a3c6da294a936ee71e644e4bd44de048af1405"}, - {file = "pydantic-1.10.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69328e15cfda2c392da4e713443c7dbffa1505bc9d566e71e55abe14c97ddc62"}, - {file = "pydantic-1.10.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2031de0967c279df0d8a1c72b4ffc411ecd06bac607a212892757db7462fc494"}, - {file = "pydantic-1.10.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ba5b2e6fe6ca2b7e013398bc7d7b170e21cce322d266ffcd57cca313e54fb246"}, - {file = "pydantic-1.10.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2a7bac939fa326db1ab741c9d7f44c565a1d1e80908b3797f7f81a4f86bc8d33"}, - {file = "pydantic-1.10.12-cp311-cp311-win_amd64.whl", hash = "sha256:87afda5539d5140cb8ba9e8b8c8865cb5b1463924d38490d73d3ccfd80896b3f"}, - {file = "pydantic-1.10.12-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:549a8e3d81df0a85226963611950b12d2d334f214436a19537b2efed61b7639a"}, - {file = "pydantic-1.10.12-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:598da88dfa127b666852bef6d0d796573a8cf5009ffd62104094a4fe39599565"}, - {file = "pydantic-1.10.12-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba5c4a8552bff16c61882db58544116d021d0b31ee7c66958d14cf386a5b5350"}, - {file = "pydantic-1.10.12-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c79e6a11a07da7374f46970410b41d5e266f7f38f6a17a9c4823db80dadf4303"}, - {file = "pydantic-1.10.12-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab26038b8375581dc832a63c948f261ae0aa21f1d34c1293469f135fa92972a5"}, - {file = "pydantic-1.10.12-cp37-cp37m-win_amd64.whl", hash = "sha256:e0a16d274b588767602b7646fa05af2782576a6cf1022f4ba74cbb4db66f6ca8"}, - {file = "pydantic-1.10.12-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6a9dfa722316f4acf4460afdf5d41d5246a80e249c7ff475c43a3a1e9d75cf62"}, - {file = "pydantic-1.10.12-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a73f489aebd0c2121ed974054cb2759af8a9f747de120acd2c3394cf84176ccb"}, - {file = "pydantic-1.10.12-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b30bcb8cbfccfcf02acb8f1a261143fab622831d9c0989707e0e659f77a18e0"}, - {file = "pydantic-1.10.12-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fcfb5296d7877af406ba1547dfde9943b1256d8928732267e2653c26938cd9c"}, - {file = "pydantic-1.10.12-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2f9a6fab5f82ada41d56b0602606a5506aab165ca54e52bc4545028382ef1c5d"}, - {file = "pydantic-1.10.12-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dea7adcc33d5d105896401a1f37d56b47d443a2b2605ff8a969a0ed5543f7e33"}, - {file = "pydantic-1.10.12-cp38-cp38-win_amd64.whl", hash = "sha256:1eb2085c13bce1612da8537b2d90f549c8cbb05c67e8f22854e201bde5d98a47"}, - {file = "pydantic-1.10.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ef6c96b2baa2100ec91a4b428f80d8f28a3c9e53568219b6c298c1125572ebc6"}, - {file = "pydantic-1.10.12-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6c076be61cd0177a8433c0adcb03475baf4ee91edf5a4e550161ad57fc90f523"}, - {file = "pydantic-1.10.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d5a58feb9a39f481eda4d5ca220aa8b9d4f21a41274760b9bc66bfd72595b86"}, - {file = "pydantic-1.10.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5f805d2d5d0a41633651a73fa4ecdd0b3d7a49de4ec3fadf062fe16501ddbf1"}, - {file = "pydantic-1.10.12-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:1289c180abd4bd4555bb927c42ee42abc3aee02b0fb2d1223fb7c6e5bef87dbe"}, - {file = "pydantic-1.10.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5d1197e462e0364906cbc19681605cb7c036f2475c899b6f296104ad42b9f5fb"}, - {file = "pydantic-1.10.12-cp39-cp39-win_amd64.whl", hash = "sha256:fdbdd1d630195689f325c9ef1a12900524dceb503b00a987663ff4f58669b93d"}, - {file = "pydantic-1.10.12-py3-none-any.whl", hash = "sha256:b749a43aa51e32839c9d71dc67eb1e4221bb04af1033a32e3923d46f9effa942"}, - {file = "pydantic-1.10.12.tar.gz", hash = "sha256:0fe8a415cea8f340e7a9af9c54fc71a649b43e8ca3cc732986116b3cb135d303"}, + {file = "pydantic_core-2.6.3-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:1a0ddaa723c48af27d19f27f1c73bdc615c73686d763388c8683fe34ae777bad"}, + {file = "pydantic_core-2.6.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5cfde4fab34dd1e3a3f7f3db38182ab6c95e4ea91cf322242ee0be5c2f7e3d2f"}, + {file = "pydantic_core-2.6.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5493a7027bfc6b108e17c3383959485087d5942e87eb62bbac69829eae9bc1f7"}, + {file = "pydantic_core-2.6.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:84e87c16f582f5c753b7f39a71bd6647255512191be2d2dbf49458c4ef024588"}, + {file = "pydantic_core-2.6.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:522a9c4a4d1924facce7270c84b5134c5cabcb01513213662a2e89cf28c1d309"}, + {file = "pydantic_core-2.6.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aaafc776e5edc72b3cad1ccedb5fd869cc5c9a591f1213aa9eba31a781be9ac1"}, + {file = "pydantic_core-2.6.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a750a83b2728299ca12e003d73d1264ad0440f60f4fc9cee54acc489249b728"}, + {file = "pydantic_core-2.6.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9e8b374ef41ad5c461efb7a140ce4730661aadf85958b5c6a3e9cf4e040ff4bb"}, + {file = "pydantic_core-2.6.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b594b64e8568cf09ee5c9501ede37066b9fc41d83d58f55b9952e32141256acd"}, + {file = "pydantic_core-2.6.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2a20c533cb80466c1d42a43a4521669ccad7cf2967830ac62c2c2f9cece63e7e"}, + {file = "pydantic_core-2.6.3-cp310-none-win32.whl", hash = "sha256:04fe5c0a43dec39aedba0ec9579001061d4653a9b53a1366b113aca4a3c05ca7"}, + {file = "pydantic_core-2.6.3-cp310-none-win_amd64.whl", hash = "sha256:6bf7d610ac8f0065a286002a23bcce241ea8248c71988bda538edcc90e0c39ad"}, + {file = "pydantic_core-2.6.3-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:6bcc1ad776fffe25ea5c187a028991c031a00ff92d012ca1cc4714087e575973"}, + {file = "pydantic_core-2.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:df14f6332834444b4a37685810216cc8fe1fe91f447332cd56294c984ecbff1c"}, + {file = "pydantic_core-2.6.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0b7486d85293f7f0bbc39b34e1d8aa26210b450bbd3d245ec3d732864009819"}, + {file = "pydantic_core-2.6.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a892b5b1871b301ce20d40b037ffbe33d1407a39639c2b05356acfef5536d26a"}, + {file = "pydantic_core-2.6.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:883daa467865e5766931e07eb20f3e8152324f0adf52658f4d302242c12e2c32"}, + {file = "pydantic_core-2.6.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4eb77df2964b64ba190eee00b2312a1fd7a862af8918ec70fc2d6308f76ac64"}, + {file = "pydantic_core-2.6.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ce8c84051fa292a5dc54018a40e2a1926fd17980a9422c973e3ebea017aa8da"}, + {file = "pydantic_core-2.6.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:22134a4453bd59b7d1e895c455fe277af9d9d9fbbcb9dc3f4a97b8693e7e2c9b"}, + {file = "pydantic_core-2.6.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:02e1c385095efbd997311d85c6021d32369675c09bcbfff3b69d84e59dc103f6"}, + {file = "pydantic_core-2.6.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d79f1f2f7ebdb9b741296b69049ff44aedd95976bfee38eb4848820628a99b50"}, + {file = "pydantic_core-2.6.3-cp311-none-win32.whl", hash = "sha256:430ddd965ffd068dd70ef4e4d74f2c489c3a313adc28e829dd7262cc0d2dd1e8"}, + {file = "pydantic_core-2.6.3-cp311-none-win_amd64.whl", hash = "sha256:84f8bb34fe76c68c9d96b77c60cef093f5e660ef8e43a6cbfcd991017d375950"}, + {file = "pydantic_core-2.6.3-cp311-none-win_arm64.whl", hash = "sha256:5a2a3c9ef904dcdadb550eedf3291ec3f229431b0084666e2c2aa8ff99a103a2"}, + {file = "pydantic_core-2.6.3-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:8421cf496e746cf8d6b677502ed9a0d1e4e956586cd8b221e1312e0841c002d5"}, + {file = "pydantic_core-2.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bb128c30cf1df0ab78166ded1ecf876620fb9aac84d2413e8ea1594b588c735d"}, + {file = "pydantic_core-2.6.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37a822f630712817b6ecc09ccc378192ef5ff12e2c9bae97eb5968a6cdf3b862"}, + {file = "pydantic_core-2.6.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:240a015102a0c0cc8114f1cba6444499a8a4d0333e178bc504a5c2196defd456"}, + {file = "pydantic_core-2.6.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f90e5e3afb11268628c89f378f7a1ea3f2fe502a28af4192e30a6cdea1e7d5e"}, + {file = "pydantic_core-2.6.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:340e96c08de1069f3d022a85c2a8c63529fd88709468373b418f4cf2c949fb0e"}, + {file = "pydantic_core-2.6.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1480fa4682e8202b560dcdc9eeec1005f62a15742b813c88cdc01d44e85308e5"}, + {file = "pydantic_core-2.6.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f14546403c2a1d11a130b537dda28f07eb6c1805a43dae4617448074fd49c282"}, + {file = "pydantic_core-2.6.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a87c54e72aa2ef30189dc74427421e074ab4561cf2bf314589f6af5b37f45e6d"}, + {file = "pydantic_core-2.6.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f93255b3e4d64785554e544c1c76cd32f4a354fa79e2eeca5d16ac2e7fdd57aa"}, + {file = "pydantic_core-2.6.3-cp312-none-win32.whl", hash = "sha256:f70dc00a91311a1aea124e5f64569ea44c011b58433981313202c46bccbec0e1"}, + {file = "pydantic_core-2.6.3-cp312-none-win_amd64.whl", hash = "sha256:23470a23614c701b37252618e7851e595060a96a23016f9a084f3f92f5ed5881"}, + {file = "pydantic_core-2.6.3-cp312-none-win_arm64.whl", hash = "sha256:1ac1750df1b4339b543531ce793b8fd5c16660a95d13aecaab26b44ce11775e9"}, + {file = "pydantic_core-2.6.3-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:a53e3195f134bde03620d87a7e2b2f2046e0e5a8195e66d0f244d6d5b2f6d31b"}, + {file = "pydantic_core-2.6.3-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:f2969e8f72c6236c51f91fbb79c33821d12a811e2a94b7aa59c65f8dbdfad34a"}, + {file = "pydantic_core-2.6.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:672174480a85386dd2e681cadd7d951471ad0bb028ed744c895f11f9d51b9ebe"}, + {file = "pydantic_core-2.6.3-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:002d0ea50e17ed982c2d65b480bd975fc41086a5a2f9c924ef8fc54419d1dea3"}, + {file = "pydantic_core-2.6.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3ccc13afee44b9006a73d2046068d4df96dc5b333bf3509d9a06d1b42db6d8bf"}, + {file = "pydantic_core-2.6.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:439a0de139556745ae53f9cc9668c6c2053444af940d3ef3ecad95b079bc9987"}, + {file = "pydantic_core-2.6.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d63b7545d489422d417a0cae6f9898618669608750fc5e62156957e609e728a5"}, + {file = "pydantic_core-2.6.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b44c42edc07a50a081672e25dfe6022554b47f91e793066a7b601ca290f71e42"}, + {file = "pydantic_core-2.6.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1c721bfc575d57305dd922e6a40a8fe3f762905851d694245807a351ad255c58"}, + {file = "pydantic_core-2.6.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:5e4a2cf8c4543f37f5dc881de6c190de08096c53986381daebb56a355be5dfe6"}, + {file = "pydantic_core-2.6.3-cp37-none-win32.whl", hash = "sha256:d9b4916b21931b08096efed090327f8fe78e09ae8f5ad44e07f5c72a7eedb51b"}, + {file = "pydantic_core-2.6.3-cp37-none-win_amd64.whl", hash = "sha256:a8acc9dedd304da161eb071cc7ff1326aa5b66aadec9622b2574ad3ffe225525"}, + {file = "pydantic_core-2.6.3-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:5e9c068f36b9f396399d43bfb6defd4cc99c36215f6ff33ac8b9c14ba15bdf6b"}, + {file = "pydantic_core-2.6.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e61eae9b31799c32c5f9b7be906be3380e699e74b2db26c227c50a5fc7988698"}, + {file = "pydantic_core-2.6.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85463560c67fc65cd86153a4975d0b720b6d7725cf7ee0b2d291288433fc21b"}, + {file = "pydantic_core-2.6.3-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9616567800bdc83ce136e5847d41008a1d602213d024207b0ff6cab6753fe645"}, + {file = "pydantic_core-2.6.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e9b65a55bbabda7fccd3500192a79f6e474d8d36e78d1685496aad5f9dbd92c"}, + {file = "pydantic_core-2.6.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f468d520f47807d1eb5d27648393519655eadc578d5dd862d06873cce04c4d1b"}, + {file = "pydantic_core-2.6.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9680dd23055dd874173a3a63a44e7f5a13885a4cfd7e84814be71be24fba83db"}, + {file = "pydantic_core-2.6.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a718d56c4d55efcfc63f680f207c9f19c8376e5a8a67773535e6f7e80e93170"}, + {file = "pydantic_core-2.6.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8ecbac050856eb6c3046dea655b39216597e373aa8e50e134c0e202f9c47efec"}, + {file = "pydantic_core-2.6.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:788be9844a6e5c4612b74512a76b2153f1877cd845410d756841f6c3420230eb"}, + {file = "pydantic_core-2.6.3-cp38-none-win32.whl", hash = "sha256:07a1aec07333bf5adebd8264047d3dc518563d92aca6f2f5b36f505132399efc"}, + {file = "pydantic_core-2.6.3-cp38-none-win_amd64.whl", hash = "sha256:621afe25cc2b3c4ba05fff53525156d5100eb35c6e5a7cf31d66cc9e1963e378"}, + {file = "pydantic_core-2.6.3-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:813aab5bfb19c98ae370952b6f7190f1e28e565909bfc219a0909db168783465"}, + {file = "pydantic_core-2.6.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:50555ba3cb58f9861b7a48c493636b996a617db1a72c18da4d7f16d7b1b9952b"}, + {file = "pydantic_core-2.6.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19e20f8baedd7d987bd3f8005c146e6bcbda7cdeefc36fad50c66adb2dd2da48"}, + {file = "pydantic_core-2.6.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b0a5d7edb76c1c57b95df719af703e796fc8e796447a1da939f97bfa8a918d60"}, + {file = "pydantic_core-2.6.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f06e21ad0b504658a3a9edd3d8530e8cea5723f6ea5d280e8db8efc625b47e49"}, + {file = "pydantic_core-2.6.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea053cefa008fda40f92aab937fb9f183cf8752e41dbc7bc68917884454c6362"}, + {file = "pydantic_core-2.6.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:171a4718860790f66d6c2eda1d95dd1edf64f864d2e9f9115840840cf5b5713f"}, + {file = "pydantic_core-2.6.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5ed7ceca6aba5331ece96c0e328cd52f0dcf942b8895a1ed2642de50800b79d3"}, + {file = "pydantic_core-2.6.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:acafc4368b289a9f291e204d2c4c75908557d4f36bd3ae937914d4529bf62a76"}, + {file = "pydantic_core-2.6.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1aa712ba150d5105814e53cb141412217146fedc22621e9acff9236d77d2a5ef"}, + {file = "pydantic_core-2.6.3-cp39-none-win32.whl", hash = "sha256:44b4f937b992394a2e81a5c5ce716f3dcc1237281e81b80c748b2da6dd5cf29a"}, + {file = "pydantic_core-2.6.3-cp39-none-win_amd64.whl", hash = "sha256:9b33bf9658cb29ac1a517c11e865112316d09687d767d7a0e4a63d5c640d1b17"}, + {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:d7050899026e708fb185e174c63ebc2c4ee7a0c17b0a96ebc50e1f76a231c057"}, + {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:99faba727727b2e59129c59542284efebbddade4f0ae6a29c8b8d3e1f437beb7"}, + {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fa159b902d22b283b680ef52b532b29554ea2a7fc39bf354064751369e9dbd7"}, + {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:046af9cfb5384f3684eeb3f58a48698ddab8dd870b4b3f67f825353a14441418"}, + {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:930bfe73e665ebce3f0da2c6d64455098aaa67e1a00323c74dc752627879fc67"}, + {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:85cc4d105747d2aa3c5cf3e37dac50141bff779545ba59a095f4a96b0a460e70"}, + {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b25afe9d5c4f60dcbbe2b277a79be114e2e65a16598db8abee2a2dcde24f162b"}, + {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e49ce7dc9f925e1fb010fc3d555250139df61fa6e5a0a95ce356329602c11ea9"}, + {file = "pydantic_core-2.6.3-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:2dd50d6a1aef0426a1d0199190c6c43ec89812b1f409e7fe44cb0fbf6dfa733c"}, + {file = "pydantic_core-2.6.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6595b0d8c8711e8e1dc389d52648b923b809f68ac1c6f0baa525c6440aa0daa"}, + {file = "pydantic_core-2.6.3-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ef724a059396751aef71e847178d66ad7fc3fc969a1a40c29f5aac1aa5f8784"}, + {file = "pydantic_core-2.6.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3c8945a105f1589ce8a693753b908815e0748f6279959a4530f6742e1994dcb6"}, + {file = "pydantic_core-2.6.3-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c8c6660089a25d45333cb9db56bb9e347241a6d7509838dbbd1931d0e19dbc7f"}, + {file = "pydantic_core-2.6.3-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:692b4ff5c4e828a38716cfa92667661a39886e71136c97b7dac26edef18767f7"}, + {file = "pydantic_core-2.6.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:f1a5d8f18877474c80b7711d870db0eeef9442691fcdb00adabfc97e183ee0b0"}, + {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:3796a6152c545339d3b1652183e786df648ecdf7c4f9347e1d30e6750907f5bb"}, + {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:b962700962f6e7a6bd77e5f37320cabac24b4c0f76afeac05e9f93cf0c620014"}, + {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56ea80269077003eaa59723bac1d8bacd2cd15ae30456f2890811efc1e3d4413"}, + {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75c0ebbebae71ed1e385f7dfd9b74c1cff09fed24a6df43d326dd7f12339ec34"}, + {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:252851b38bad3bfda47b104ffd077d4f9604a10cb06fe09d020016a25107bf98"}, + {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:6656a0ae383d8cd7cc94e91de4e526407b3726049ce8d7939049cbfa426518c8"}, + {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:d9140ded382a5b04a1c030b593ed9bf3088243a0a8b7fa9f071a5736498c5483"}, + {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d38bbcef58220f9c81e42c255ef0bf99735d8f11edef69ab0b499da77105158a"}, + {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:c9d469204abcca28926cbc28ce98f28e50e488767b084fb3fbdf21af11d3de26"}, + {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:48c1ed8b02ffea4d5c9c220eda27af02b8149fe58526359b3c07eb391cb353a2"}, + {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b2b1bfed698fa410ab81982f681f5b1996d3d994ae8073286515ac4d165c2e7"}, + {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf9d42a71a4d7a7c1f14f629e5c30eac451a6fc81827d2beefd57d014c006c4a"}, + {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4292ca56751aebbe63a84bbfc3b5717abb09b14d4b4442cc43fd7c49a1529efd"}, + {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:7dc2ce039c7290b4ef64334ec7e6ca6494de6eecc81e21cb4f73b9b39991408c"}, + {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:615a31b1629e12445c0e9fc8339b41aaa6cc60bd53bf802d5fe3d2c0cda2ae8d"}, + {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1fa1f6312fb84e8c281f32b39affe81984ccd484da6e9d65b3d18c202c666149"}, + {file = "pydantic_core-2.6.3.tar.gz", hash = "sha256:1508f37ba9e3ddc0189e6ff4e2228bd2d3c3a4641cbe8c07177162f76ed696c7"}, ] [package.dependencies] -typing-extensions = ">=4.2.0" +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + +[[package]] +name = "pygments" +version = "2.16.1" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.7" +files = [ + {file = "Pygments-2.16.1-py3-none-any.whl", hash = "sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692"}, + {file = "Pygments-2.16.1.tar.gz", hash = "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29"}, +] [package.extras] -dotenv = ["python-dotenv (>=0.10.4)"] -email = ["email-validator (>=1.0.3)"] +plugins = ["importlib-metadata"] [[package]] name = "pytest" @@ -900,6 +1240,17 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + [[package]] name = "sniffio" version = "1.3.0" @@ -911,6 +1262,25 @@ files = [ {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, ] +[[package]] +name = "stack-data" +version = "0.6.2" +description = "Extract data from python stack frames and tracebacks for informative displays" +optional = false +python-versions = "*" +files = [ + {file = "stack_data-0.6.2-py3-none-any.whl", hash = "sha256:cbb2a53eb64e5785878201a97ed7c7b94883f48b87bfb0bbe8b623c74679e4a8"}, + {file = "stack_data-0.6.2.tar.gz", hash = "sha256:32d2dd0376772d01b6cb9fc996f3c8b57a357089dec328ed4b6553d037eaf815"}, +] + +[package.dependencies] +asttokens = ">=2.1.0" +executing = ">=1.2.0" +pure-eval = "*" + +[package.extras] +tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] + [[package]] name = "tiktoken" version = "0.4.0" @@ -1041,6 +1411,21 @@ notebook = ["ipywidgets (>=6)"] slack = ["slack-sdk"] telegram = ["requests"] +[[package]] +name = "traitlets" +version = "5.9.0" +description = "Traitlets Python configuration system" +optional = false +python-versions = ">=3.7" +files = [ + {file = "traitlets-5.9.0-py3-none-any.whl", hash = "sha256:9e6ec080259b9a5940c797d58b613b5e31441c2257b87c2e795c5228ae80d2d8"}, + {file = "traitlets-5.9.0.tar.gz", hash = "sha256:f6cde21a9c68cf756af02035f72d5a723bf607e862e7be33ece505abf4a3bad9"}, +] + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] +test = ["argcomplete (>=2.0)", "pre-commit", "pytest", "pytest-mock"] + [[package]] name = "typing-extensions" version = "4.7.1" @@ -1069,6 +1454,17 @@ secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17. socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] +[[package]] +name = "wcwidth" +version = "0.2.6" +description = "Measures the displayed width of unicode strings in a terminal" +optional = false +python-versions = "*" +files = [ + {file = "wcwidth-0.2.6-py2.py3-none-any.whl", hash = "sha256:795b138f6875577cd91bba52baf9e445cd5118fd32723b460e30a0af30ea230e"}, + {file = "wcwidth-0.2.6.tar.gz", hash = "sha256:a5220780a404dbe3353789870978e472cfe477761f06ee55077256e509b156d0"}, +] + [[package]] name = "yarl" version = "1.9.2" @@ -1173,5 +1569,5 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" -python-versions = ">=3.10.9,<3.12" -content-hash = "864c2ea8a9810fb1c1c1637019a81cb45714e8d220338446f49ab817b23ab2aa" +python-versions = ">=3.10.0,<3.12" +content-hash = "b8fd02831f41f27163ca45812ad373f89e45c17b922c32b2588fa5eac6097002" diff --git a/pyproject.toml b/pyproject.toml index 79d4f21..4d88db1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,23 +1,29 @@ [tool.poetry] name = "cursivepy" -version = "0.5.0" +version = "0.6.0" description = "" -authors = ["Rodrigo Godinho ", "Henrique Cunha "] +authors = [ + "Rodrigo Godinho ", + "Henrique Cunha ", + "Cyrus Nouroozi " +] readme = "README.md" -packages = [{include = "cursive"}] +packages = [{ include = "cursive" }] [tool.poetry.dependencies] python = ">=3.10.0,<3.12" tiktoken = "^0.4.0" openai = "^0.27.8" anthropic = "^0.3.6" -pydantic = "^1.9.0" +pydantic = "^2.3" cohere = "^4.19.3" tokenizers = "^0.13.3" replicate = "^0.11.0" [tool.poetry.group.dev.dependencies] pytest = "^7.4.0" +ipython = "^8.14.0" +ipdb = "^0.13.13" [build-system] requires = ["poetry-core"]