not-again-ai 0.3.1__tar.gz → 0.4.1__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.3.1 → not_again_ai-0.4.1}/LICENSE +1 -1
  2. {not_again_ai-0.3.1 → not_again_ai-0.4.1}/PKG-INFO +25 -21
  3. {not_again_ai-0.3.1 → not_again_ai-0.4.1}/README.md +16 -11
  4. {not_again_ai-0.3.1 → not_again_ai-0.4.1}/pyproject.toml +10 -26
  5. {not_again_ai-0.3.1 → not_again_ai-0.4.1}/src/not_again_ai/llm/__init__.py +1 -1
  6. not_again_ai-0.4.1/src/not_again_ai/llm/chat_completion.py +102 -0
  7. {not_again_ai-0.3.1 → not_again_ai-0.4.1}/src/not_again_ai/llm/context_management.py +2 -2
  8. not_again_ai-0.4.1/src/not_again_ai/llm/embeddings.py +62 -0
  9. {not_again_ai-0.3.1 → not_again_ai-0.4.1}/src/not_again_ai/llm/openai_client.py +6 -4
  10. {not_again_ai-0.3.1 → not_again_ai-0.4.1}/src/not_again_ai/llm/prompts.py +9 -8
  11. {not_again_ai-0.3.1 → not_again_ai-0.4.1}/src/not_again_ai/llm/tokens.py +15 -10
  12. {not_again_ai-0.3.1 → not_again_ai-0.4.1}/src/not_again_ai/viz/time_series.py +5 -3
  13. not_again_ai-0.3.1/src/not_again_ai/llm/chat_completion.py +0 -77
  14. {not_again_ai-0.3.1 → not_again_ai-0.4.1}/src/not_again_ai/__init__.py +0 -0
  15. {not_again_ai-0.3.1 → not_again_ai-0.4.1}/src/not_again_ai/base/__init__.py +0 -0
  16. {not_again_ai-0.3.1 → not_again_ai-0.4.1}/src/not_again_ai/base/file_system.py +0 -0
  17. {not_again_ai-0.3.1 → not_again_ai-0.4.1}/src/not_again_ai/base/parallel.py +0 -0
  18. {not_again_ai-0.3.1 → not_again_ai-0.4.1}/src/not_again_ai/py.typed +0 -0
  19. {not_again_ai-0.3.1 → not_again_ai-0.4.1}/src/not_again_ai/statistics/__init__.py +0 -0
  20. {not_again_ai-0.3.1 → not_again_ai-0.4.1}/src/not_again_ai/statistics/dependence.py +0 -0
  21. {not_again_ai-0.3.1 → not_again_ai-0.4.1}/src/not_again_ai/viz/__init__.py +0 -0
  22. {not_again_ai-0.3.1 → not_again_ai-0.4.1}/src/not_again_ai/viz/barplots.py +0 -0
  23. {not_again_ai-0.3.1 → not_again_ai-0.4.1}/src/not_again_ai/viz/distributions.py +0 -0
  24. {not_again_ai-0.3.1 → not_again_ai-0.4.1}/src/not_again_ai/viz/scatterplot.py +0 -0
  25. {not_again_ai-0.3.1 → not_again_ai-0.4.1}/src/not_again_ai/viz/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2022-2023 DaveCoDev
3
+ Copyright (c) 2022-2024 DaveCoDev
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: not-again-ai
3
- Version: 0.3.1
3
+ Version: 0.4.1
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
7
7
  Author: DaveCoDev
8
8
  Author-email: dave.co.dev@gmail.com
9
- Requires-Python: >=3.10,<3.13
9
+ Requires-Python: >=3.11,<3.13
10
10
  Classifier: Development Status :: 3 - Alpha
11
11
  Classifier: Intended Audience :: Developers
12
12
  Classifier: Intended Audience :: Science/Research
@@ -14,7 +14,6 @@ Classifier: License :: OSI Approved :: MIT License
14
14
  Classifier: Operating System :: OS Independent
15
15
  Classifier: Programming Language :: Python
16
16
  Classifier: Programming Language :: Python :: 3
17
- Classifier: Programming Language :: Python :: 3.10
18
17
  Classifier: Programming Language :: Python :: 3.11
19
18
  Classifier: Programming Language :: Python :: 3.12
20
19
  Classifier: Programming Language :: Python :: 3 :: Only
@@ -22,13 +21,13 @@ Classifier: Typing :: Typed
22
21
  Provides-Extra: llm
23
22
  Provides-Extra: statistics
24
23
  Provides-Extra: viz
25
- Requires-Dist: jinja2 (>=3.1.2,<4.0.0) ; extra == "llm"
26
- Requires-Dist: numpy (>=1.26.2,<2.0.0) ; extra == "statistics" or extra == "viz"
27
- Requires-Dist: openai (>=1.6.1,<2.0.0) ; extra == "llm"
28
- Requires-Dist: pandas (>=2.1.4,<3.0.0) ; extra == "viz"
29
- Requires-Dist: scikit-learn (>=1.3.2,<2.0.0) ; extra == "statistics"
30
- Requires-Dist: scipy (>=1.11.4,<2.0.0) ; extra == "statistics"
31
- Requires-Dist: seaborn (>=0.13.0,<0.14.0) ; extra == "viz"
24
+ Requires-Dist: numpy (>=1.26.3,<2.0.0) ; extra == "statistics" or extra == "viz"
25
+ Requires-Dist: openai (>=1.10.0,<2.0.0) ; extra == "llm"
26
+ Requires-Dist: pandas (>=2.2.0,<3.0.0) ; extra == "viz"
27
+ Requires-Dist: python-liquid (>=1.10.2,<2.0.0) ; extra == "llm"
28
+ Requires-Dist: scikit-learn (>=1.4.0,<2.0.0) ; extra == "statistics"
29
+ Requires-Dist: scipy (>=1.12.0,<2.0.0) ; extra == "statistics"
30
+ Requires-Dist: seaborn (>=0.13.2,<0.14.0) ; extra == "viz"
32
31
  Requires-Dist: tiktoken (>=0.5.2,<0.6.0) ; extra == "llm"
33
32
  Project-URL: Documentation, https://github.com/DaveCoDev/not-again-ai
34
33
  Project-URL: Repository, https://github.com/DaveCoDev/not-again-ai
@@ -39,7 +38,6 @@ Description-Content-Type: text/markdown
39
38
  [![GitHub Actions][github-actions-badge]](https://github.com/johnthagen/python-blueprint/actions)
40
39
  [![Packaged with Poetry][poetry-badge]](https://python-poetry.org/)
41
40
  [![Nox][nox-badge]](https://github.com/wntrblm/nox)
42
- [![Code style: Black][black-badge]](https://github.com/psf/black)
43
41
  [![Ruff][ruff-badge]](https://github.com/astral-sh/ruff)
44
42
  [![Type checked with mypy][mypy-badge]](https://mypy-lang.org/)
45
43
 
@@ -56,7 +54,7 @@ Description-Content-Type: text/markdown
56
54
 
57
55
  # Installation
58
56
 
59
- Requires: Python 3.10, 3.11, or 3.12
57
+ Requires: Python 3.11, or 3.12
60
58
 
61
59
  Install the entire package from [PyPI](https://pypi.org/project/not-again-ai/) with:
62
60
 
@@ -81,6 +79,15 @@ The base package includes only functions that have minimal external dependencies
81
79
  ## LLM (Large Language Model)
82
80
  [README](https://github.com/DaveCoDev/not-again-ai/blob/main/readmes/llm.md)
83
81
 
82
+ Supports OpenAI chat completions and text embeddings. Includes functions for creating chat completion prompts, token management, and context management.
83
+
84
+ One example:
85
+ ```python
86
+ client = openai_client()
87
+ messages = [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": "Hello!"}]
88
+ response = chat_completion(messages=messages, model="gpt-3.5-turbo", max_tokens=100, client=client)["message"]
89
+ >>> "Hello! How can I help you today?"
90
+ ```
84
91
 
85
92
  ## Statistics
86
93
  [README](https://github.com/DaveCoDev/not-again-ai/blob/main/readmes/statistics.md)
@@ -266,9 +273,11 @@ To pass arguments to `pytest` through `nox`:
266
273
 
267
274
  ## Code Style Checking
268
275
 
269
- [PEP 8](https://peps.python.org/pep-0008/) is the universally accepted style guide for
270
- Python code. PEP 8 code compliance is verified using [Ruff](https://github.com/astral-sh/ruff).
271
- Ruff is configured in the `[tool.ruff]` section of `pyproject.toml`.
276
+ [PEP 8](https://peps.python.org/pep-0008/) is the universally accepted style guide for Python
277
+ code. PEP 8 code compliance is verified using [Ruff][Ruff]. Ruff is configured in the
278
+ `[tool.ruff]` section of [`pyproject.toml`](./pyproject.toml).
279
+
280
+ [Ruff]: https://github.com/astral-sh/ruff
272
281
 
273
282
  To lint code, run:
274
283
 
@@ -284,12 +293,7 @@ To automatically fix fixable lint errors, run:
284
293
 
285
294
  ## Automated Code Formatting
286
295
 
287
- Code is automatically formatted using [black](https://github.com/psf/black). Imports are
288
- automatically sorted and grouped using [Ruff](https://github.com/astral-sh/ruff).
289
-
290
- These tools are configured by:
291
-
292
- - [`pyproject.toml`](./pyproject.toml)
296
+ [Ruff][Ruff] is used to automatically format code and group and sort imports.
293
297
 
294
298
  To automatically format code, run:
295
299
 
@@ -3,7 +3,6 @@
3
3
  [![GitHub Actions][github-actions-badge]](https://github.com/johnthagen/python-blueprint/actions)
4
4
  [![Packaged with Poetry][poetry-badge]](https://python-poetry.org/)
5
5
  [![Nox][nox-badge]](https://github.com/wntrblm/nox)
6
- [![Code style: Black][black-badge]](https://github.com/psf/black)
7
6
  [![Ruff][ruff-badge]](https://github.com/astral-sh/ruff)
8
7
  [![Type checked with mypy][mypy-badge]](https://mypy-lang.org/)
9
8
 
@@ -20,7 +19,7 @@
20
19
 
21
20
  # Installation
22
21
 
23
- Requires: Python 3.10, 3.11, or 3.12
22
+ Requires: Python 3.11, or 3.12
24
23
 
25
24
  Install the entire package from [PyPI](https://pypi.org/project/not-again-ai/) with:
26
25
 
@@ -45,6 +44,15 @@ The base package includes only functions that have minimal external dependencies
45
44
  ## LLM (Large Language Model)
46
45
  [README](https://github.com/DaveCoDev/not-again-ai/blob/main/readmes/llm.md)
47
46
 
47
+ Supports OpenAI chat completions and text embeddings. Includes functions for creating chat completion prompts, token management, and context management.
48
+
49
+ One example:
50
+ ```python
51
+ client = openai_client()
52
+ messages = [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": "Hello!"}]
53
+ response = chat_completion(messages=messages, model="gpt-3.5-turbo", max_tokens=100, client=client)["message"]
54
+ >>> "Hello! How can I help you today?"
55
+ ```
48
56
 
49
57
  ## Statistics
50
58
  [README](https://github.com/DaveCoDev/not-again-ai/blob/main/readmes/statistics.md)
@@ -230,9 +238,11 @@ To pass arguments to `pytest` through `nox`:
230
238
 
231
239
  ## Code Style Checking
232
240
 
233
- [PEP 8](https://peps.python.org/pep-0008/) is the universally accepted style guide for
234
- Python code. PEP 8 code compliance is verified using [Ruff](https://github.com/astral-sh/ruff).
235
- Ruff is configured in the `[tool.ruff]` section of `pyproject.toml`.
241
+ [PEP 8](https://peps.python.org/pep-0008/) is the universally accepted style guide for Python
242
+ code. PEP 8 code compliance is verified using [Ruff][Ruff]. Ruff is configured in the
243
+ `[tool.ruff]` section of [`pyproject.toml`](./pyproject.toml).
244
+
245
+ [Ruff]: https://github.com/astral-sh/ruff
236
246
 
237
247
  To lint code, run:
238
248
 
@@ -248,12 +258,7 @@ To automatically fix fixable lint errors, run:
248
258
 
249
259
  ## Automated Code Formatting
250
260
 
251
- Code is automatically formatted using [black](https://github.com/psf/black). Imports are
252
- automatically sorted and grouped using [Ruff](https://github.com/astral-sh/ruff).
253
-
254
- These tools are configured by:
255
-
256
- - [`pyproject.toml`](./pyproject.toml)
261
+ [Ruff][Ruff] is used to automatically format code and group and sort imports.
257
262
 
258
263
  To automatically format code, run:
259
264
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "not-again-ai"
3
- version = "0.3.1"
3
+ version = "0.4.1"
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"
@@ -16,7 +16,6 @@ classifiers = [
16
16
  "Programming Language :: Python",
17
17
  "Programming Language :: Python :: 3",
18
18
  "Programming Language :: Python :: 3 :: Only",
19
- "Programming Language :: Python :: 3.10",
20
19
  "Programming Language :: Python :: 3.11",
21
20
  "Programming Language :: Python :: 3.12",
22
21
  "Typing :: Typed",
@@ -26,28 +25,25 @@ classifiers = [
26
25
  # Some packages, such as scipy, constrain their upper bound of Python versions they support.
27
26
  # Without also constraining the upper bound here, Poetry will not select those versions and will
28
27
  # result in an old version being resolved/locked.
29
- python = "^3.10, <3.13"
28
+ python = "^3.11, <3.13"
30
29
 
31
30
  # Optional dependencies are defined here, and groupings are defined below.
32
- jinja2 = { version = "^3.1.2", optional = true }
33
- numpy = { version = "^1.26.2", optional = true }
34
- openai = { version = "^1.6.1", optional = true }
35
- pandas = { version = "^2.1.4", optional = true }
36
- scipy = { version = "^1.11.4", optional = true }
37
- scikit-learn = { version = "^1.3.2", optional = true }
38
- seaborn = { version = "^0.13.0", optional = true }
31
+ numpy = { version = "^1.26.3", optional = true }
32
+ openai = { version = "^1.10.0", optional = true }
33
+ pandas = { version = "^2.2.0", optional = true }
34
+ python-liquid = { version = "^1.10.2", optional = true }
35
+ scipy = { version = "^1.12.0", optional = true }
36
+ scikit-learn = { version = "^1.4.0", optional = true }
37
+ seaborn = { version = "^0.13.2", optional = true }
39
38
  tiktoken = { version = "^0.5.2", optional = true }
40
39
 
41
40
  [tool.poetry.extras]
42
- llm = ["jinja2", "openai", "tiktoken"]
41
+ llm = ["openai", "python-liquid", "tiktoken"]
43
42
  statistics = ["numpy", "scikit-learn", "scipy"]
44
43
  viz = ["numpy", "pandas", "seaborn"]
45
44
 
46
45
  [tool.poetry.group.nox.dependencies]
47
46
  nox-poetry = "*"
48
- # TODO: Remove this after virtualenv supports platformdirs 4.
49
- # https://github.com/pypa/virtualenv/issues/2666
50
- platformdirs = "<4"
51
47
 
52
48
  [tool.poetry.group.test.dependencies]
53
49
  pytest = "*"
@@ -65,9 +61,6 @@ mypy = "*"
65
61
  [tool.poetry.group.lint.dependencies]
66
62
  ruff = "*"
67
63
 
68
- [tool.poetry.group.fmt.dependencies]
69
- black = "*"
70
-
71
64
  [tool.poetry.group.docs.dependencies]
72
65
  mkdocs-material = "*"
73
66
  mkdocs-htmlproofer-plugin = "*"
@@ -111,8 +104,6 @@ unfixable = ["F401"]
111
104
  [tool.ruff.isort]
112
105
  force-sort-within-sections = true
113
106
  split-on-trailing-comma = false
114
- # For non-src directory projects, explicitly set top level package names:
115
- # known-first-party = ["my-app"]
116
107
 
117
108
  [tool.ruff.flake8-tidy-imports]
118
109
  ban-relative-imports = "all"
@@ -120,13 +111,6 @@ ban-relative-imports = "all"
120
111
  [tool.ruff.flake8-bugbear]
121
112
  extend-immutable-calls = ["typer.Argument"]
122
113
 
123
- [tool.black]
124
- line-length = 120
125
- target-version = ["py39", "py310", "py311", "py312"]
126
- # black will automatically exclude all files listed in .gitignore
127
- # If you need to exclude additional folders, consider using extend-exclude to avoid disabling the
128
- # default .gitignore behaviour.
129
-
130
114
  [tool.pytest.ini_options]
131
115
  addopts = [
132
116
  "--strict-config",
@@ -1,5 +1,5 @@
1
1
  try:
2
- import jinja2 # noqa
2
+ import liquid # noqa
3
3
  import openai # noqa
4
4
  import tiktoken # noqa
5
5
  except ImportError:
@@ -0,0 +1,102 @@
1
+ import contextlib
2
+ import json
3
+ from typing import Any
4
+
5
+ from openai import OpenAI
6
+
7
+
8
+ def chat_completion(
9
+ messages: list[dict[str, str]],
10
+ model: str,
11
+ client: OpenAI,
12
+ tools: list[dict[str, Any]] | None = None,
13
+ tool_choice: str = "auto",
14
+ max_tokens: int | None = None,
15
+ temperature: float = 0.7,
16
+ json_mode: bool = False,
17
+ **kwargs: Any,
18
+ ) -> dict[str, Any]:
19
+ """Get an OpenAI chat completion response: https://platform.openai.com/docs/api-reference/chat/create
20
+
21
+ Args:
22
+ messages (list): A list of messages comprising the conversation so far.
23
+ model (str): ID of the model to use. See the model endpoint compatibility table:
24
+ https://platform.openai.com/docs/models/model-endpoint-compatibility
25
+ for details on which models work with the Chat API.
26
+ client (OpenAI): An instance of the OpenAI client.
27
+ tools (list[dict[str, Any]], optional): A list of tools the model may generate JSON inputs for.
28
+ Defaults to None.
29
+ tool_choice (str, optional): The tool choice to use. Can be "auto", "none", or a specific function name.
30
+ Defaults to "auto".
31
+ max_tokens (int, optional): The maximum number of tokens to generate in the chat completion.
32
+ Defaults to None, which automatically limits to the model's maximum context length.
33
+ temperature (float, optional): What sampling temperature to use, between 0 and 2.
34
+ Higher values like 0.8 will make the output more random,
35
+ while lower values like 0.2 will make it more focused and deterministic. Defaults to 0.7.
36
+ json_mode (bool, optional): When JSON mode is enabled, the model is constrained to only
37
+ generate strings that parse into valid JSON object and will return a dictionary.
38
+ See https://platform.openai.com/docs/guides/text-generation/json-mode
39
+ **kwargs: Additional keyword arguments to pass to the OpenAI client chat completion.
40
+
41
+ Returns:
42
+ dict: A dictionary containing the following keys:
43
+ - "finish_reason" (str): The reason the model stopped generating further tokens.
44
+ Can be "stop", "length", or "tool_calls".
45
+ - "tool_names" (list[str], optional): The names of the tools called by the model.
46
+ - "tool_args_list" (list[dict], optional): The arguments of the tools called by the model.
47
+ - "message" (str | dict): The content of the generated assistant message.
48
+ If json_mode is True, this will be a dictionary.
49
+ - "completion_tokens" (int): The number of tokens used by the model to generate the completion.
50
+ - "prompt_tokens" (int): The number of tokens in the generated response.
51
+ """
52
+ response_format = {"type": "json_object"} if json_mode else None
53
+
54
+ kwargs.update(
55
+ {
56
+ "messages": messages,
57
+ "model": model,
58
+ "tools": tools,
59
+ "max_tokens": max_tokens,
60
+ "temperature": temperature,
61
+ "response_format": response_format,
62
+ "n": 1,
63
+ }
64
+ )
65
+
66
+ if tools is not None:
67
+ if tool_choice not in ["none", "auto"]:
68
+ kwargs["tool_choice"] = {"type": "function", "function": {"name": tool_choice}}
69
+ else:
70
+ kwargs["tool_choice"] = tool_choice
71
+
72
+ # Call the function with the set parameters
73
+ response = client.chat.completions.create(**kwargs)
74
+
75
+ response_data = {}
76
+ finish_reason = response.choices[0].finish_reason
77
+ response_data["finish_reason"] = finish_reason
78
+
79
+ # Not checking finish_reason=="tool_calls" here because when a user providea function name as tool_choice,
80
+ # the finish reason is "stop", not "tool_calls"
81
+ tool_calls = response.choices[0].message.tool_calls
82
+ if tool_calls:
83
+ tool_names = []
84
+ tool_args_list = []
85
+ for tool_call in tool_calls:
86
+ tool_names.append(tool_call.function.name)
87
+ tool_args_list.append(json.loads(tool_call.function.arguments))
88
+ response_data["tool_names"] = tool_names
89
+ response_data["tool_args_list"] = tool_args_list
90
+ elif finish_reason == "stop" or finish_reason == "length":
91
+ message = response.choices[0].message.content
92
+ if json_mode:
93
+ with contextlib.suppress(json.JSONDecodeError):
94
+ message = json.loads(message)
95
+ response_data["message"] = message
96
+
97
+ usage = response.usage
98
+ if usage is not None:
99
+ response_data["completion_tokens"] = usage.completion_tokens
100
+ response_data["prompt_tokens"] = usage.prompt_tokens
101
+
102
+ return response_data
@@ -18,7 +18,7 @@ def priority_truncation(
18
18
  variables: dict[str, str],
19
19
  priority: list[str],
20
20
  token_limit: int,
21
- model: str = "gpt-3.5-turbo-0613",
21
+ model: str = "gpt-3.5-turbo-1106",
22
22
  ) -> list[dict[str, str]]:
23
23
  """Formats messages_unformatted and injects variables into the messages in the order of priority, truncating the messages to fit the token limit.
24
24
 
@@ -37,7 +37,7 @@ def priority_truncation(
37
37
  variables: A dictionary where each key-value pair represents a variable name and its value to inject.
38
38
  priority: A list of variable names in their order of priority.
39
39
  token_limit: The maximum number of tokens allowed in the messages.
40
- model: The model to use for tokenization. Defaults to "gpt-3.5-turbo-0613".
40
+ model: The model to use for tokenization. Defaults to "gpt-3.5-turbo-1106".
41
41
  """
42
42
 
43
43
  # Check if all variables in the priority list are in the variables dict.
@@ -0,0 +1,62 @@
1
+ from typing import Any
2
+
3
+ from openai import OpenAI
4
+
5
+
6
+ def embed_text(
7
+ text: str | list[str],
8
+ client: OpenAI,
9
+ model: str = "text-embedding-3-large",
10
+ dimensions: int | None = None,
11
+ encoding_format: str = "float",
12
+ **kwargs: Any,
13
+ ) -> list[float] | str | list[list[float]] | list[str]:
14
+ """Generates an embedding vector for a given text using OpenAI's API.
15
+
16
+ Args:
17
+ text (str | list[str]): The input text to be embedded. Each text should not exceed 8191 tokens, which is the max for V2 and V3 models
18
+ client (OpenAI): The OpenAI client used to interact with the API.
19
+ model (str, optional): The ID of the model to use for embedding.
20
+ Defaults to "text-embedding-3-large".
21
+ Choose from text-embedding-3-small, text-embedding-3-large, text-embedding-ada-002.
22
+ See https://platform.openai.com/docs/models/embeddings for more details.
23
+ dimensions (int | None, optional): The number of dimensions for the output embeddings.
24
+ This is only supported in "text-embedding-3" and later models. Defaults to None.
25
+ encoding_format (str, optional): The format for the returned embeddings. Can be either "float" or "base64".
26
+ Defaults to "float".
27
+
28
+ Returns:
29
+ list[float] | str | list[list[float]] | list[str]: The embedding vector represented as a list of floats or base64 encoded string.
30
+ If multiple text inputs are provided, a list of embedding vectors is returned.
31
+ The length and format of the vector depend on the model, encoding_format, and dimensions.
32
+
33
+ Raises:
34
+ ValueError: If 'text-embedding-ada-002' model is used and dimensions are specified,
35
+ as this model does not support specifying dimensions.
36
+
37
+ Example:
38
+ client = OpenAI()
39
+ embedding = embed_text("Example text", client, model="text-embedding-ada-002")
40
+ """
41
+ if model == "text-embedding-ada-002" and dimensions:
42
+ # text-embedding-ada-002 does not support dimensions
43
+ raise ValueError("text-embedding-ada-002 does not support dimensions")
44
+
45
+ kwargs = {
46
+ "model": model,
47
+ "input": text,
48
+ "encoding_format": encoding_format,
49
+ }
50
+ if dimensions:
51
+ kwargs["dimensions"] = dimensions
52
+
53
+ response = client.embeddings.create(**kwargs)
54
+
55
+ responses = []
56
+ for embedding in response.data:
57
+ responses.append(embedding.embedding)
58
+
59
+ if len(responses) == 1:
60
+ return responses[0]
61
+
62
+ return responses
@@ -21,10 +21,12 @@ def openai_client(
21
21
  Defaults to 'openai'.
22
22
  api_key (str, optional): The API key to authenticate the client. If not provided,
23
23
  OpenAI automatically uses `OPENAI_API_KEY` from the environment.
24
- organization (str, optional): The ID of the organization (for enterprise users). If not provided,
24
+ organization (str, optional): The ID of the organization. If not provided,
25
25
  OpenAI automotically uses `OPENAI_ORG_ID` from the environment.
26
- timeout (float, optional): TBD
27
- max_retries (int, optional): TBD
26
+ timeout (float, optional): By default requests time out after 10 minutes.
27
+ max_retries (int, optional): Certain errors are automatically retried 2 times by default,
28
+ with a short exponential backoff. Connection errors (for example, due to a network connectivity problem),
29
+ 408 Request Timeout, 409 Conflict, 429 Rate Limit, and >=500 Internal errors are all retried by default.
28
30
 
29
31
  Returns:
30
32
  OpenAI: An instance of the OpenAI client.
@@ -34,7 +36,7 @@ def openai_client(
34
36
  NotImplementedError: If the specified API type is recognized but not yet supported (e.g., 'azure_openai').
35
37
 
36
38
  Examples:
37
- >>> client = oai_client(api_type="openai", api_key="YOUR_API_KEY")
39
+ >>> client = openai_client(api_type="openai", api_key="YOUR_API_KEY")
38
40
  """
39
41
  if api_type not in ["openai", "azure_openai"]:
40
42
  raise InvalidOAIAPITypeError(f"Invalid OAIAPIType: {api_type}. Must be 'openai' or 'azure_openai'.")
@@ -1,4 +1,4 @@
1
- import jinja2
1
+ from liquid import Template
2
2
 
3
3
 
4
4
  def _validate_message(message: dict[str, str]) -> bool:
@@ -19,15 +19,13 @@ def _validate_message(message: dict[str, str]) -> bool:
19
19
 
20
20
 
21
21
  def chat_prompt(messages_unformatted: list[dict[str, str]], variables: dict[str, str]) -> list[dict[str, str]]:
22
- """Formats a list of messages for OpenAI's chat completion API using Jinja2 templating.
23
-
24
- The content of each message is treated as a Jinja2 template that is rendered
25
- with the provided variables.
22
+ """
23
+ Formats a list of messages for OpenAI's chat completion API using Liquid templating.
26
24
 
27
25
  Args:
28
26
  messages_unformatted: A list of dictionaries where each dictionary
29
27
  represents a message. Each message must have 'role' and 'content'
30
- keys with string values, where content is a Jinja2 template.
28
+ keys with string values, where content is a Liquid template.
31
29
  variables: A dictionary where each key-value pair represents a variable
32
30
  name and its value for template rendering.
33
31
 
@@ -47,10 +45,13 @@ def chat_prompt(messages_unformatted: list[dict[str, str]], variables: dict[str,
47
45
  {"role": "user", "content": "Help me write Python code for the fibonnaci sequence"}
48
46
  ]
49
47
  """
48
+
50
49
  messages_formatted = messages_unformatted.copy()
51
50
  for message in messages_formatted:
52
- # Validate each message and return a ValueError if any message is invalid
53
51
  if not _validate_message(message):
54
52
  raise ValueError(f"Invalid message: {message}")
55
- message["content"] = jinja2.Template(message["content"]).render(**variables)
53
+
54
+ liquid_template = Template(message["content"])
55
+ message["content"] = liquid_template.render(**variables)
56
+
56
57
  return messages_formatted
@@ -1,15 +1,14 @@
1
1
  import tiktoken
2
2
 
3
3
 
4
- def truncate_str(text: str, max_len: int, model: str = "gpt-3.5-turbo-0613") -> str:
4
+ def truncate_str(text: str, max_len: int, model: str = "gpt-3.5-turbo-1106") -> str:
5
5
  """Truncates a string to a maximum token length.
6
6
 
7
7
  Args:
8
8
  text: The string to truncate.
9
9
  max_len: The maximum number of tokens to keep.
10
- model: The model to use for tokenization. Defaults to "gpt-3.5-turbo-0613".
11
- See https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb
12
- for a list of OpenAI models.
10
+ model: The model to use for tokenization. Defaults to "gpt-3.5-turbo-1106".
11
+ See https://platform.openai.com/docs/models for a list of OpenAI models.
13
12
 
14
13
  Returns:
15
14
  The truncated string.
@@ -30,12 +29,13 @@ def truncate_str(text: str, max_len: int, model: str = "gpt-3.5-turbo-0613") ->
30
29
  return text
31
30
 
32
31
 
33
- def num_tokens_in_string(text: str, model: str = "gpt-3.5-turbo-0613") -> int:
32
+ def num_tokens_in_string(text: str, model: str = "gpt-3.5-turbo-1106") -> int:
34
33
  """Return the number of tokens in a string.
35
34
 
36
35
  Args:
37
36
  text: The string to count the tokens.
38
- model: The model to use for tokenization. Defaults to "gpt-3.5-turbo-0613".
37
+ model: The model to use for tokenization. Defaults to "gpt-3.5-turbo-1106".
38
+ See https://platform.openai.com/docs/models for a list of OpenAI models.
39
39
 
40
40
  Returns:
41
41
  The number of tokens in the string.
@@ -48,17 +48,17 @@ def num_tokens_in_string(text: str, model: str = "gpt-3.5-turbo-0613") -> int:
48
48
  return len(encoding.encode(text))
49
49
 
50
50
 
51
- def num_tokens_from_messages(messages: list[dict[str, str]], model: str = "gpt-3.5-turbo-0613") -> int:
51
+ def num_tokens_from_messages(messages: list[dict[str, str]], model: str = "gpt-3.5-turbo-1106") -> int:
52
52
  """Return the number of tokens used by a list of messages.
53
53
  NOTE: Does not support counting tokens used by function calling.
54
54
  Reference: # https://github.com/openai/openai-cookbook/blob/main/examples/How_to_format_inputs_to_ChatGPT_models.ipynb
55
+ and https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb
55
56
 
56
57
  Args:
57
58
  messages: A list of messages to count the tokens
58
59
  should ideally be the result after calling llm.prompts.chat_prompt.
59
- model: The model to use for tokenization. Defaults to "gpt-3.5-turbo-0613".
60
- See https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb
61
- for a list of OpenAI models.
60
+ model: The model to use for tokenization. Defaults to "gpt-3.5-turbo-1106".
61
+ See https://platform.openai.com/docs/models for a list of OpenAI models.
62
62
 
63
63
  Returns:
64
64
  The number of tokens used by the messages.
@@ -71,16 +71,21 @@ def num_tokens_from_messages(messages: list[dict[str, str]], model: str = "gpt-3
71
71
  if model in {
72
72
  "gpt-3.5-turbo-0613",
73
73
  "gpt-3.5-turbo-16k-0613",
74
+ "gpt-3.5-turbo-1106",
74
75
  "gpt-4-0314",
75
76
  "gpt-4-32k-0314",
76
77
  "gpt-4-0613",
77
78
  "gpt-4-32k-0613",
79
+ "gpt-4-1106-preview",
80
+ "gpt-4-turbo-preview",
81
+ "gpt-4-0125-preview",
78
82
  }:
79
83
  tokens_per_message = 3 # every message follows <|start|>{role/name}\n{content}<|end|>\n
80
84
  tokens_per_name = 1 # if there's a name, the role is omitted
81
85
  elif model == "gpt-3.5-turbo-0301":
82
86
  tokens_per_message = 4
83
87
  tokens_per_name = -1
88
+ # Approximate catch-all. Assumes future versions of 3.5 and 4 will have the same token counts as the 0613 versions.
84
89
  elif "gpt-3.5-turbo" in model:
85
90
  return num_tokens_from_messages(messages, model="gpt-3.5-turbo-0613")
86
91
  elif "gpt-4" in model:
@@ -13,9 +13,11 @@ from not_again_ai.viz.utils import reset_plot_libs
13
13
  def ts_lineplot(
14
14
  ts_data: list[float] | (npt.NDArray[np.float64] | npt.NDArray[np.int64]),
15
15
  save_pathname: str,
16
- ts_x: list[float]
17
- | (npt.NDArray[np.float64] | (npt.NDArray[np.datetime64] | (npt.NDArray[np.int64] | pd.Series)))
18
- | None = None,
16
+ ts_x: (
17
+ list[float]
18
+ | (npt.NDArray[np.float64] | (npt.NDArray[np.datetime64] | (npt.NDArray[np.int64] | pd.Series)))
19
+ | None
20
+ ) = None,
19
21
  ts_names: list[str] | None = None,
20
22
  title: str | None = None,
21
23
  xlabel: str | None = "Time",
@@ -1,77 +0,0 @@
1
- import json
2
- from typing import Any
3
-
4
- from openai import OpenAI
5
-
6
-
7
- def chat_completion(
8
- messages: list[dict[str, str]],
9
- model: str,
10
- client: OpenAI,
11
- functions: list[dict[str, Any]] | None = None,
12
- max_tokens: int | None = None,
13
- temperature: float = 0.7,
14
- **kwargs: Any,
15
- ) -> dict[str, Any]:
16
- """Get an OpenAI chat completion response: https://platform.openai.com/docs/api-reference/chat/create
17
-
18
- Args:
19
- messages (list): A list of messages comprising the conversation so far.
20
- model (str): ID of the model to use. See the model endpoint compatibility table:
21
- https://platform.openai.com/docs/models/model-endpoint-compatibility
22
- for details on which models work with the Chat API.
23
- client (OpenAI): An instance of the OpenAI client.
24
- functions (list, optional): A list of functions the model may generate JSON inputs for. Defaults to None.
25
- max_tokens (int, optional): The maximum number of tokens to generate in the chat completion.
26
- Defaults to limited to the model's context length.
27
- temperature (float, optional): What sampling temperature to use, between 0 and 2.
28
- Higher values like 0.8 will make the output more random,
29
- while lower values like 0.2 will make it more focused and deterministic. Defaults to 0.7.
30
- **kwargs: Additional keyword arguments to pass to the OpenAI client chat completion.
31
-
32
- Returns:
33
- dict: A dictionary containing the following keys:
34
- - "finish_reason" (str): The reason the model stopped generating further tokens. Can be "stop" or "function_call".
35
- - "function_name" (str, optional): The name of the function called by the model, present only if "finish_reason" is "function_call".
36
- - "function_args" (dict, optional): The arguments of the function called by the model, present only if "finish_reason" is "function_call".
37
- - "message" (str, optional): The content of the generated assistant message, present only if "finish_reason" is "stop".
38
- - "completion_tokens" (int): The number of tokens used by the model to generate the completion.
39
- - "prompt_tokens" (int): The number of tokens in the generated response.
40
- """
41
- if functions is None:
42
- response = client.chat.completions.create(
43
- messages=messages, # type: ignore
44
- model=model,
45
- max_tokens=max_tokens,
46
- temperature=temperature,
47
- n=1,
48
- **kwargs,
49
- )
50
- else:
51
- response = client.chat.completions.create( # type: ignore
52
- messages=messages,
53
- model=model,
54
- functions=functions,
55
- function_call="auto",
56
- max_tokens=max_tokens,
57
- temperature=temperature,
58
- n=1,
59
- **kwargs,
60
- )
61
-
62
- response_data = {}
63
- finish_reason = response.choices[0].finish_reason
64
- response_data["finish_reason"] = finish_reason
65
- if finish_reason == "function_call":
66
- function_call = response.choices[0].message.function_call
67
- if function_call is not None:
68
- response_data["function_name"] = function_call.name # type: ignore
69
- response_data["function_args"] = json.loads(function_call.arguments)
70
- elif finish_reason == "stop" or finish_reason == "length":
71
- message = response.choices[0].message
72
- response_data["message"] = message.content # type: ignore
73
- usage = response.usage
74
- if usage is not None:
75
- response_data["completion_tokens"] = usage.completion_tokens # type: ignore
76
- response_data["prompt_tokens"] = usage.prompt_tokens # type: ignore
77
- return response_data