not-again-ai 0.5.0__tar.gz → 0.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. {not_again_ai-0.5.0 → not_again_ai-0.6.0}/PKG-INFO +5 -5
  2. {not_again_ai-0.5.0 → not_again_ai-0.6.0}/README.md +1 -1
  3. {not_again_ai-0.5.0 → not_again_ai-0.6.0}/pyproject.toml +7 -4
  4. {not_again_ai-0.5.0 → not_again_ai-0.6.0}/src/not_again_ai/llm/chat_completion.py +6 -3
  5. {not_again_ai-0.5.0 → not_again_ai-0.6.0}/src/not_again_ai/llm/prompts.py +5 -61
  6. {not_again_ai-0.5.0 → not_again_ai-0.6.0}/src/not_again_ai/llm/tokens.py +2 -0
  7. not_again_ai-0.5.0/src/not_again_ai/llm/chat_completion_vision.py +0 -88
  8. {not_again_ai-0.5.0 → not_again_ai-0.6.0}/LICENSE +0 -0
  9. {not_again_ai-0.5.0 → not_again_ai-0.6.0}/src/not_again_ai/__init__.py +0 -0
  10. {not_again_ai-0.5.0 → not_again_ai-0.6.0}/src/not_again_ai/base/__init__.py +0 -0
  11. {not_again_ai-0.5.0 → not_again_ai-0.6.0}/src/not_again_ai/base/file_system.py +0 -0
  12. {not_again_ai-0.5.0 → not_again_ai-0.6.0}/src/not_again_ai/base/parallel.py +0 -0
  13. {not_again_ai-0.5.0 → not_again_ai-0.6.0}/src/not_again_ai/llm/__init__.py +0 -0
  14. {not_again_ai-0.5.0 → not_again_ai-0.6.0}/src/not_again_ai/llm/context_management.py +0 -0
  15. {not_again_ai-0.5.0 → not_again_ai-0.6.0}/src/not_again_ai/llm/embeddings.py +0 -0
  16. {not_again_ai-0.5.0 → not_again_ai-0.6.0}/src/not_again_ai/llm/openai_client.py +0 -0
  17. {not_again_ai-0.5.0 → not_again_ai-0.6.0}/src/not_again_ai/py.typed +0 -0
  18. {not_again_ai-0.5.0 → not_again_ai-0.6.0}/src/not_again_ai/statistics/__init__.py +0 -0
  19. {not_again_ai-0.5.0 → not_again_ai-0.6.0}/src/not_again_ai/statistics/dependence.py +0 -0
  20. {not_again_ai-0.5.0 → not_again_ai-0.6.0}/src/not_again_ai/viz/__init__.py +0 -0
  21. {not_again_ai-0.5.0 → not_again_ai-0.6.0}/src/not_again_ai/viz/barplots.py +0 -0
  22. {not_again_ai-0.5.0 → not_again_ai-0.6.0}/src/not_again_ai/viz/distributions.py +0 -0
  23. {not_again_ai-0.5.0 → not_again_ai-0.6.0}/src/not_again_ai/viz/scatterplot.py +0 -0
  24. {not_again_ai-0.5.0 → not_again_ai-0.6.0}/src/not_again_ai/viz/time_series.py +0 -0
  25. {not_again_ai-0.5.0 → not_again_ai-0.6.0}/src/not_again_ai/viz/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: not-again-ai
3
- Version: 0.5.0
3
+ Version: 0.6.0
4
4
  Summary: Designed to once and for all collect all the little things that come up over and over again in AI projects and put them in one place.
5
5
  Home-page: https://github.com/DaveCoDev/not-again-ai
6
6
  License: MIT
@@ -21,10 +21,10 @@ Provides-Extra: llm
21
21
  Provides-Extra: statistics
22
22
  Provides-Extra: viz
23
23
  Requires-Dist: numpy (>=1.26.4,<2.0.0) ; extra == "statistics" or extra == "viz"
24
- Requires-Dist: openai (>=1.16.2,<2.0.0) ; extra == "llm"
25
- Requires-Dist: pandas (>=2.2.1,<3.0.0) ; extra == "viz"
24
+ Requires-Dist: openai (>=1.23.2,<2.0.0) ; extra == "llm"
25
+ Requires-Dist: pandas (>=2.2.2,<3.0.0) ; extra == "viz"
26
26
  Requires-Dist: python-liquid (>=1.12.1,<2.0.0) ; extra == "llm"
27
- Requires-Dist: scikit-learn (>=1.4.1.post1,<2.0.0) ; extra == "statistics"
27
+ Requires-Dist: scikit-learn (>=1.4.2,<2.0.0) ; extra == "statistics"
28
28
  Requires-Dist: scipy (>=1.13.0,<2.0.0) ; extra == "statistics"
29
29
  Requires-Dist: seaborn (>=0.13.2,<0.14.0) ; extra == "viz"
30
30
  Requires-Dist: tiktoken (>=0.6.0,<0.7.0) ; extra == "llm"
@@ -76,7 +76,7 @@ The package is split into subpackages, so you can install only the parts you nee
76
76
  The base package includes only functions that have minimal external dependencies and are useful in a variety of situations such as parallelization and filesystem operations.
77
77
 
78
78
  ## LLM (Large Language Model)
79
- [README](https://github.com/DaveCoDev/not-again-ai/blob/main/readmes/llm.md)
79
+ [README](https://github.com/DaveCoDev/not-again-ai/blob/main/readmes/llm.md), [Example Notebooks](https://github.com/DaveCoDev/not-again-ai/blob/main/notebooks/llm/)
80
80
 
81
81
  Supports OpenAI chat completions and text embeddings. Includes functions for creating chat completion prompts, token management, and context management.
82
82
 
@@ -42,7 +42,7 @@ The package is split into subpackages, so you can install only the parts you nee
42
42
  The base package includes only functions that have minimal external dependencies and are useful in a variety of situations such as parallelization and filesystem operations.
43
43
 
44
44
  ## LLM (Large Language Model)
45
- [README](https://github.com/DaveCoDev/not-again-ai/blob/main/readmes/llm.md)
45
+ [README](https://github.com/DaveCoDev/not-again-ai/blob/main/readmes/llm.md), [Example Notebooks](https://github.com/DaveCoDev/not-again-ai/blob/main/notebooks/llm/)
46
46
 
47
47
  Supports OpenAI chat completions and text embeddings. Includes functions for creating chat completion prompts, token management, and context management.
48
48
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "not-again-ai"
3
- version = "0.5.0"
3
+ version = "0.6.0"
4
4
  description = "Designed to once and for all collect all the little things that come up over and over again in AI projects and put them in one place."
5
5
  authors = ["DaveCoDev <dave.co.dev@gmail.com>"]
6
6
  license = "MIT"
@@ -28,11 +28,11 @@ python = "^3.11, <3.13"
28
28
 
29
29
  # Optional dependencies are defined here, and groupings are defined below.
30
30
  numpy = { version = "^1.26.4", optional = true }
31
- openai = { version = "^1.16.2", optional = true }
32
- pandas = { version = "^2.2.1", optional = true }
31
+ openai = { version = "^1.23.2", optional = true }
32
+ pandas = { version = "^2.2.2", optional = true }
33
33
  python-liquid = { version = "^1.12.1", optional = true }
34
34
  scipy = { version = "^1.13.0", optional = true }
35
- scikit-learn = { version = "^1.4.1.post1", optional = true }
35
+ scikit-learn = { version = "^1.4.2", optional = true }
36
36
  seaborn = { version = "^0.13.2", optional = true }
37
37
  tiktoken = { version = "^0.6.0", optional = true }
38
38
 
@@ -41,6 +41,9 @@ llm = ["openai", "python-liquid", "tiktoken"]
41
41
  statistics = ["numpy", "scikit-learn", "scipy"]
42
42
  viz = ["numpy", "pandas", "seaborn"]
43
43
 
44
+ [tool.poetry.dev-dependencies]
45
+ ipykernel = "*"
46
+
44
47
  [tool.poetry.group.nox.dependencies]
45
48
  nox-poetry = "*"
46
49
 
@@ -21,6 +21,10 @@ def chat_completion(
21
21
  ) -> dict[str, Any]:
22
22
  """Get an OpenAI chat completion response: https://platform.openai.com/docs/api-reference/chat/create
23
23
 
24
+ NOTE: Depending on the model, certain parameters may not be supported,
25
+ particularly for older vision-enabled models like gpt-4-1106-vision-preview.
26
+ Be sure to check the documentation: https://platform.openai.com/docs/models/gpt-4-turbo-and-gpt-4
27
+
24
28
  Args:
25
29
  messages (list): A list of messages comprising the conversation so far.
26
30
  model (str): ID of the model to use. See the model endpoint compatibility table:
@@ -64,9 +68,8 @@ def chat_completion(
64
68
  this will be a list of dictionaries containing the token, logprob, and bytes for each token in the message.
65
69
  'choices' (list[dict], optional): A list of chat completion choices if n > 1 where each dict contains the above fields.
66
70
  'completion_tokens' (int): The number of tokens used by the model to generate the completion.
67
- NOTE: If n > 1 this is the sum of all completions and thus will be same value in each dict.
68
- 'prompt_tokens' (int): The number of tokens in the generated response.
69
- NOTE: If n > 1 this is the sum of all completions and thus will be same value in each dict.
71
+ NOTE: If n > 1 this is the sum of all completions.
72
+ 'prompt_tokens' (int): The number of tokens in the messages sent to the model.
70
73
  'system_fingerprint' (str, optional): If seed is set, a unique identifier for the model used to generate the response.
71
74
  """
72
75
  response_format = {"type": "json_object"} if json_mode else None
@@ -7,69 +7,13 @@ from typing import Any
7
7
  from liquid import Template
8
8
 
9
9
 
10
- def _validate_message(message: dict[str, str]) -> bool:
11
- """Valides that a message has valid fields and if the role is valid.
12
- See https://platform.openai.com/docs/api-reference/chat/create#chat-create-messages
13
- """
14
- valid_fields = ["role", "content", "name", "tool_call_id", "tool_calls"]
15
- # Check if the only keys in the message are in valid_fields
16
- if not all(key in valid_fields for key in message):
17
- raise ValueError(f"Message contains invalid fields: {message.keys()}")
18
-
19
- # Check if the only roles in the message are in valid_fields
20
- valid_roles = ["system", "user", "assistant", "tool"]
21
- if message["role"] not in valid_roles:
22
- raise ValueError(f"Message contains invalid role: {message['role']}")
23
-
24
- return True
25
-
26
-
27
- def chat_prompt(messages_unformatted: list[dict[str, str]], variables: dict[str, str]) -> list[dict[str, str]]:
28
- """
29
- Formats a list of messages for OpenAI's chat completion API using Liquid templating.
30
-
31
- Args:
32
- messages_unformatted: A list of dictionaries where each dictionary
33
- represents a message. Each message must have 'role' and 'content'
34
- keys with string values, where content is a Liquid template.
35
- variables: A dictionary where each key-value pair represents a variable
36
- name and its value for template rendering.
37
-
38
- Returns:
39
- A list of dictionaries with the same structure as `messages_unformatted`,
40
- but with the 'content' of each message with the provided `variables`.
41
-
42
- Examples:
43
- >>> messages = [
44
- ... {"role": "system", "content": "You are a helpful assistant."},
45
- ... {"role": "user", "content": "Help me {{task}}"}
46
- ... ]
47
- >>> vars = {"task": "write Python code for the fibonnaci sequence"}
48
- >>> chat_prompt(messages, vars)
49
- [
50
- {"role": "system", "content": "You are a helpful assistant."},
51
- {"role": "user", "content": "Help me write Python code for the fibonnaci sequence"}
52
- ]
53
- """
54
-
55
- messages_formatted = deepcopy(messages_unformatted)
56
- for message in messages_formatted:
57
- if not _validate_message(message):
58
- raise ValueError()
59
-
60
- liquid_template = Template(message["content"])
61
- message["content"] = liquid_template.render(**variables)
62
-
63
- return messages_formatted
64
-
65
-
66
10
  def _validate_message_vision(message: dict[str, list[dict[str, Path | str]] | str]) -> bool:
67
11
  """Validates that a message for a vision model is valid"""
68
- valid_fields = ["role", "content"]
12
+ valid_fields = ["role", "content", "name", "tool_call_id", "tool_calls"]
69
13
  if not all(key in valid_fields for key in message):
70
14
  raise ValueError(f"Message contains invalid fields: {message.keys()}")
71
15
 
72
- valid_roles = ["system", "user", "assistant"]
16
+ valid_roles = ["system", "user", "assistant", "tool"]
73
17
  if message["role"] not in valid_roles:
74
18
  raise ValueError(f"Message contains invalid role: {message['role']}")
75
19
 
@@ -126,13 +70,13 @@ def create_image_url(image_path: Path) -> str:
126
70
  return f"data:{mime_type};base64,{image_data}"
127
71
 
128
72
 
129
- def chat_prompt_vision(messages_unformatted: list[dict[str, Any]], variables: dict[str, str]) -> list[dict[str, Any]]:
130
- """Formats a list of messages for OpenAI's chat completion API for vision models only using Liquid templating.
73
+ def chat_prompt(messages_unformatted: list[dict[str, Any]], variables: dict[str, str]) -> list[dict[str, Any]]:
74
+ """Formats a list of messages for OpenAI's chat completion API,
75
+ including special syntax for vision models, using Liquid templating.
131
76
 
132
77
  Args:
133
78
  messages_unformatted (list[dict[str, list[dict[str, Path | str]] | str]]):
134
79
  A list of dictionaries where each dictionary represents a message.
135
- Each message must have 'role' and 'content' keys. `role` must be 'system', 'user', or 'assistant'.
136
80
  `content` can be a Liquid template string or a list of dictionaries where each dictionary
137
81
  represents a content part. Each content part can be a string or a dictionary with 'image' and 'detail' keys.
138
82
  The 'image' key must be a Path or a string representing a URL. The 'detail' key is optional and must be 'low' or 'high'.
@@ -80,6 +80,8 @@ def num_tokens_from_messages(messages: list[dict[str, str]], model: str = "gpt-3
80
80
  "gpt-4-1106-preview",
81
81
  "gpt-4-turbo-preview",
82
82
  "gpt-4-0125-preview",
83
+ "gpt-4-turbo",
84
+ "gpt-4-turbo-2024-04-09",
83
85
  }:
84
86
  tokens_per_message = 3 # every message follows <|start|>{role/name}\n{content}<|end|>\n
85
87
  tokens_per_name = 1 # if there's a name, the role is omitted
@@ -1,88 +0,0 @@
1
- from typing import Any
2
-
3
- from openai import OpenAI
4
-
5
-
6
- def chat_completion_vision(
7
- messages: list[dict[str, Any]],
8
- model: str,
9
- client: OpenAI,
10
- max_tokens: int | None = None,
11
- temperature: float = 0.7,
12
- seed: int | None = None,
13
- n: int = 1,
14
- **kwargs: Any,
15
- ) -> dict[str, Any]:
16
- """Get an OpenAI chat completion response for vision models only: https://platform.openai.com/docs/guides/vision
17
-
18
- Args:
19
- messages (list): A list of messages comprising the conversation so far.
20
- See https://platform.openai.com/docs/api-reference/chat/create for details on the format
21
- model (str): ID of the model to use for generating chat completions. Refer to OpenAI's documentation
22
- for details on available models.
23
- client (OpenAI): An instance of the OpenAI client, used to make requests to the API.
24
- max_tokens (int | None, optional): The maximum number of tokens to generate in the chat completion.
25
- If None, defaults to the model's maximum context length. Defaults to None.
26
- temperature (float, optional): Controls the randomness of the output. A higher temperature produces
27
- more varied results, whereas a lower temperature results in more deterministic and predictable text.
28
- Must be between 0 and 2. Defaults to 0.7.
29
- seed (int | None, optional): A seed used for deterministic generation. Providing a seed ensures that
30
- the same input will produce the same output across different runs. Defaults to None.
31
- n (int, optional): The number of chat completion choices to generate for each input message.
32
- Defaults to 1.
33
- **kwargs (Any): Additional keyword arguments to pass to the OpenAI client chat completion method.
34
-
35
- Returns:
36
- dict[str, Any]: A dictionary containing the generated responses and metadata. Key components include:
37
- 'finish_reason' (str): The reason the model stopped generating further tokens.
38
- Can be 'stop' or 'length'
39
- 'tool_names' (list[str], optional): The names of the tools called by the model.
40
- 'tool_args_list' (list[dict], optional): The arguments of the tools called by the model.
41
- 'message' (str | dict): The content of the generated assistant message.
42
- 'choices' (list[dict], optional): A list of chat completion choices if n > 1 where each dict contains the above fields.
43
- 'completion_tokens' (int): The number of tokens used by the model to generate the completion.
44
- NOTE: If n > 1 this is the sum of all completions and thus will be same value in each dict.
45
- 'prompt_tokens' (int): The number of tokens in the generated response.
46
- NOTE: If n > 1 this is the sum of all completions and thus will be same value in each dict.
47
- 'system_fingerprint' (str, optional): If seed is set, a unique identifier for the model used to generate the response.
48
- """
49
- kwargs.update(
50
- {
51
- "messages": messages,
52
- "model": model,
53
- "max_tokens": max_tokens,
54
- "temperature": temperature,
55
- "n": n,
56
- }
57
- )
58
-
59
- if seed is not None:
60
- kwargs["seed"] = seed
61
-
62
- response = client.chat.completions.create(**kwargs)
63
-
64
- response_data: dict[str, Any] = {"choices": []}
65
- for response_choice in response.choices:
66
- response_data_curr = {}
67
- finish_reason = response_choice.finish_reason
68
- response_data_curr["finish_reason"] = finish_reason
69
-
70
- if finish_reason == "stop" or finish_reason == "length":
71
- message = response_choice.message.content
72
- response_data_curr["message"] = message
73
-
74
- response_data["choices"].append(response_data_curr)
75
-
76
- usage = response.usage
77
- if usage is not None:
78
- response_data["completion_tokens"] = usage.completion_tokens
79
- response_data["prompt_tokens"] = usage.prompt_tokens
80
-
81
- if seed is not None and response.system_fingerprint is not None:
82
- response_data["system_fingerprint"] = response.system_fingerprint
83
-
84
- if len(response_data["choices"]) == 1:
85
- response_data.update(response_data["choices"][0])
86
- del response_data["choices"]
87
-
88
- return response_data
File without changes