not-again-ai 0.18.0__py3-none-any.whl → 0.19.0__py3-none-any.whl
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.
- not_again_ai/llm/chat_completion/interface.py +5 -2
- not_again_ai/llm/chat_completion/providers/anthropic_api.py +2 -2
- not_again_ai/llm/chat_completion/providers/gemini_api.py +237 -0
- not_again_ai/llm/chat_completion/types.py +1 -4
- not_again_ai/llm/image_gen/__init__.py +4 -0
- not_again_ai/llm/image_gen/interface.py +24 -0
- not_again_ai/llm/image_gen/providers/__init__.py +0 -0
- not_again_ai/llm/image_gen/providers/openai_api.py +144 -0
- not_again_ai/llm/image_gen/types.py +24 -0
- {not_again_ai-0.18.0.dist-info → not_again_ai-0.19.0.dist-info}/METADATA +8 -7
- {not_again_ai-0.18.0.dist-info → not_again_ai-0.19.0.dist-info}/RECORD +13 -7
- {not_again_ai-0.18.0.dist-info → not_again_ai-0.19.0.dist-info}/WHEEL +0 -0
- {not_again_ai-0.18.0.dist-info → not_again_ai-0.19.0.dist-info}/licenses/LICENSE +0 -0
@@ -2,6 +2,7 @@ from collections.abc import AsyncGenerator, Callable
|
|
2
2
|
from typing import Any
|
3
3
|
|
4
4
|
from not_again_ai.llm.chat_completion.providers.anthropic_api import anthropic_chat_completion
|
5
|
+
from not_again_ai.llm.chat_completion.providers.gemini_api import gemini_chat_completion
|
5
6
|
from not_again_ai.llm.chat_completion.providers.ollama_api import ollama_chat_completion, ollama_chat_completion_stream
|
6
7
|
from not_again_ai.llm.chat_completion.providers.openai_api import openai_chat_completion, openai_chat_completion_stream
|
7
8
|
from not_again_ai.llm.chat_completion.types import ChatCompletionChunk, ChatCompletionRequest, ChatCompletionResponse
|
@@ -16,6 +17,8 @@ def chat_completion(
|
|
16
17
|
- `openai` - OpenAI
|
17
18
|
- `azure_openai` - Azure OpenAI
|
18
19
|
- `ollama` - Ollama
|
20
|
+
- `anthropic` - Anthropic
|
21
|
+
- `gemini` - Gemini
|
19
22
|
|
20
23
|
Args:
|
21
24
|
request: Request parameter object
|
@@ -31,6 +34,8 @@ def chat_completion(
|
|
31
34
|
return ollama_chat_completion(request, client)
|
32
35
|
elif provider == "anthropic":
|
33
36
|
return anthropic_chat_completion(request, client)
|
37
|
+
elif provider == "gemini":
|
38
|
+
return gemini_chat_completion(request, client)
|
34
39
|
else:
|
35
40
|
raise ValueError(f"Provider {provider} not supported")
|
36
41
|
|
@@ -43,8 +48,6 @@ async def chat_completion_stream(
|
|
43
48
|
"""Stream a chat completion response from the given provider. Currently supported providers:
|
44
49
|
- `openai` - OpenAI
|
45
50
|
- `azure_openai` - Azure OpenAI
|
46
|
-
- `ollama` - Ollama
|
47
|
-
- `anthropic` - Anthropic
|
48
51
|
|
49
52
|
Args:
|
50
53
|
request: Request parameter object
|
@@ -103,12 +103,12 @@ def anthropic_chat_completion(request: ChatCompletionRequest, client: Callable[.
|
|
103
103
|
elif tool_choice_value in ["auto", "any"]:
|
104
104
|
tool_choice["type"] = "auto"
|
105
105
|
if kwargs.get("parallel_tool_calls") is not None:
|
106
|
-
tool_choice["disable_parallel_tool_use"] =
|
106
|
+
tool_choice["disable_parallel_tool_use"] = not kwargs["parallel_tool_calls"] # type: ignore
|
107
107
|
else:
|
108
108
|
tool_choice["name"] = tool_choice_value
|
109
109
|
tool_choice["type"] = "tool"
|
110
110
|
if kwargs.get("parallel_tool_calls") is not None:
|
111
|
-
tool_choice["disable_parallel_tool_use"] =
|
111
|
+
tool_choice["disable_parallel_tool_use"] = not kwargs["parallel_tool_calls"] # type: ignore
|
112
112
|
kwargs["tool_choice"] = tool_choice
|
113
113
|
kwargs.pop("parallel_tool_calls", None)
|
114
114
|
|
@@ -0,0 +1,237 @@
|
|
1
|
+
import base64
|
2
|
+
from collections.abc import Callable
|
3
|
+
import os
|
4
|
+
import time
|
5
|
+
from typing import Any
|
6
|
+
|
7
|
+
from google import genai
|
8
|
+
from google.genai import types
|
9
|
+
from google.genai.types import FunctionCall, FunctionCallingConfigMode, GenerateContentResponse
|
10
|
+
|
11
|
+
from not_again_ai.llm.chat_completion.types import (
|
12
|
+
AssistantMessage,
|
13
|
+
ChatCompletionChoice,
|
14
|
+
ChatCompletionRequest,
|
15
|
+
ChatCompletionResponse,
|
16
|
+
Function,
|
17
|
+
ImageContent,
|
18
|
+
Role,
|
19
|
+
TextContent,
|
20
|
+
ToolCall,
|
21
|
+
)
|
22
|
+
|
23
|
+
# This should be all of the options we want to support in types.GenerateContentConfig, that are not handled otherwise
|
24
|
+
GEMINI_PARAMETER_MAP = {
|
25
|
+
"max_completion_tokens": "max_output_tokens",
|
26
|
+
"temperature": "temperature",
|
27
|
+
"top_p": "top_p",
|
28
|
+
"top_k": "top_k",
|
29
|
+
}
|
30
|
+
|
31
|
+
GEMINI_FINISH_REASON_MAP = {
|
32
|
+
"STOP": "stop",
|
33
|
+
"MAX_TOKENS": "max_tokens",
|
34
|
+
"SAFETY": "safety",
|
35
|
+
"RECITATION": "recitation",
|
36
|
+
"LANGUAGE": "language",
|
37
|
+
"OTHER": "other",
|
38
|
+
"BLOCKLIST": "blocklist",
|
39
|
+
"PROHIBITED_CONTENT": "prohibited_content",
|
40
|
+
"SPII": "spii",
|
41
|
+
"MALFORMED_FUNCTION_CALL": "malformed_function_call",
|
42
|
+
"IMAGE_SAFETY": "image_safety",
|
43
|
+
}
|
44
|
+
|
45
|
+
|
46
|
+
def gemini_chat_completion(request: ChatCompletionRequest, client: Callable[..., Any]) -> ChatCompletionResponse:
|
47
|
+
"""Experimental Gemini chat completion function."""
|
48
|
+
# Handle messages
|
49
|
+
# Any system messages need to be removed from messages and concatenated into a single string (in order)
|
50
|
+
system = ""
|
51
|
+
contents = []
|
52
|
+
for message in request.messages:
|
53
|
+
if message.role == "system":
|
54
|
+
# Handle both string content and structured content
|
55
|
+
if isinstance(message.content, str):
|
56
|
+
system += message.content + "\n"
|
57
|
+
else:
|
58
|
+
# If it's a list of content parts, extract text content
|
59
|
+
for part in message.content:
|
60
|
+
if hasattr(part, "text"):
|
61
|
+
system += part.text + "\n"
|
62
|
+
elif message.role == "tool":
|
63
|
+
tool_name = message.name if message.name is not None else ""
|
64
|
+
function_response_part = types.Part.from_function_response(
|
65
|
+
name=tool_name,
|
66
|
+
response={"result": message.content},
|
67
|
+
)
|
68
|
+
contents.append(
|
69
|
+
types.Content(
|
70
|
+
role="user",
|
71
|
+
parts=[function_response_part],
|
72
|
+
)
|
73
|
+
)
|
74
|
+
elif message.role == "assistant":
|
75
|
+
if message.content and isinstance(message.content, str):
|
76
|
+
contents.append(types.Content(role="model", parts=[types.Part(text=message.content)]))
|
77
|
+
function_parts = []
|
78
|
+
if isinstance(message, AssistantMessage) and message.tool_calls:
|
79
|
+
for tool_call in message.tool_calls:
|
80
|
+
function_call_part = types.Part(
|
81
|
+
function_call=FunctionCall(
|
82
|
+
id=tool_call.id,
|
83
|
+
name=tool_call.function.name,
|
84
|
+
args=tool_call.function.arguments,
|
85
|
+
)
|
86
|
+
)
|
87
|
+
function_parts.append(function_call_part)
|
88
|
+
if function_parts:
|
89
|
+
contents.append(types.Content(role="model", parts=function_parts))
|
90
|
+
elif message.role == "user":
|
91
|
+
if isinstance(message.content, str):
|
92
|
+
contents.append(types.Content(role="user", parts=[types.Part(text=message.content)]))
|
93
|
+
elif isinstance(message.content, list):
|
94
|
+
parts = []
|
95
|
+
for part in message.content:
|
96
|
+
if isinstance(part, TextContent):
|
97
|
+
parts.append(types.Part(text=part.text))
|
98
|
+
elif isinstance(part, ImageContent):
|
99
|
+
# Extract MIME type and data from data URI
|
100
|
+
uri_parts = part.image_url.url.split(",", 1)
|
101
|
+
if len(uri_parts) == 2:
|
102
|
+
mime_type = uri_parts[0].split(":")[1].split(";")[0]
|
103
|
+
base64_data = uri_parts[1]
|
104
|
+
image_data = base64.b64decode(base64_data)
|
105
|
+
parts.append(types.Part.from_bytes(mime_type=mime_type, data=image_data))
|
106
|
+
contents.append(types.Content(role="user", parts=parts))
|
107
|
+
|
108
|
+
kwargs: dict[str, Any] = {}
|
109
|
+
kwargs["contents"] = contents
|
110
|
+
kwargs["model"] = request.model
|
111
|
+
config: dict[str, Any] = {}
|
112
|
+
config["system_instruction"] = system.rstrip()
|
113
|
+
config["automatic_function_calling"] = {"disable": True}
|
114
|
+
|
115
|
+
# Handle the possible tool choice options
|
116
|
+
if request.tool_choice:
|
117
|
+
tool_choice = request.tool_choice
|
118
|
+
if tool_choice == "auto":
|
119
|
+
config["tool_config"] = types.FunctionCallingConfig(mode=FunctionCallingConfigMode.AUTO)
|
120
|
+
elif tool_choice == "any":
|
121
|
+
config["tool_config"] = types.FunctionCallingConfig(mode=FunctionCallingConfigMode.ANY)
|
122
|
+
elif tool_choice == "none":
|
123
|
+
config["tool_config"] = types.FunctionCallingConfig(mode=FunctionCallingConfigMode.NONE)
|
124
|
+
elif isinstance(tool_choice, list):
|
125
|
+
config["tool_config"] = types.FunctionCallingConfig(
|
126
|
+
mode=FunctionCallingConfigMode.ANY, allowed_function_names=tool_choice
|
127
|
+
)
|
128
|
+
elif tool_choice not in (None, "auto", "any", "none"):
|
129
|
+
config["tool_config"] = types.FunctionCallingConfig(
|
130
|
+
mode=FunctionCallingConfigMode.ANY, allowed_function_names=[tool_choice]
|
131
|
+
)
|
132
|
+
|
133
|
+
# Handle tools
|
134
|
+
tools = []
|
135
|
+
for tool in request.tools or []:
|
136
|
+
tools.append(types.Tool(function_declarations=[tool])) # type: ignore
|
137
|
+
if tools:
|
138
|
+
config["tools"] = tools
|
139
|
+
|
140
|
+
# Everything else defined in GEMINI_PARAMETER_MAP goes into kwargs["config"]
|
141
|
+
request_kwargs = request.model_dump(mode="json", exclude_none=True)
|
142
|
+
for key, value in GEMINI_PARAMETER_MAP.items():
|
143
|
+
if value is not None and key in request_kwargs:
|
144
|
+
config[value] = request_kwargs.pop(key)
|
145
|
+
|
146
|
+
kwargs["config"] = types.GenerateContentConfig(**config)
|
147
|
+
|
148
|
+
start_time = time.time()
|
149
|
+
response: GenerateContentResponse = client(**kwargs)
|
150
|
+
end_time = time.time()
|
151
|
+
response_duration = round(end_time - start_time, 4)
|
152
|
+
|
153
|
+
finish_reason = "other"
|
154
|
+
if response.candidates and response.candidates[0].finish_reason:
|
155
|
+
finish_reason_str = str(response.candidates[0].finish_reason)
|
156
|
+
finish_reason = GEMINI_FINISH_REASON_MAP.get(finish_reason_str, "other")
|
157
|
+
|
158
|
+
tool_calls: list[ToolCall] = []
|
159
|
+
tool_call_objs = response.function_calls
|
160
|
+
if tool_call_objs:
|
161
|
+
for tool_call_obj in tool_call_objs:
|
162
|
+
tool_call_id = tool_call_obj.id if tool_call_obj.id else ""
|
163
|
+
tool_calls.append(
|
164
|
+
ToolCall(
|
165
|
+
id=tool_call_id,
|
166
|
+
function=Function(
|
167
|
+
name=tool_call_obj.name if tool_call_obj.name is not None else "",
|
168
|
+
arguments=tool_call_obj.args if tool_call_obj.args is not None else {},
|
169
|
+
),
|
170
|
+
)
|
171
|
+
)
|
172
|
+
|
173
|
+
assistant_message = ""
|
174
|
+
if (
|
175
|
+
response.candidates
|
176
|
+
and response.candidates[0].content
|
177
|
+
and response.candidates[0].content.parts
|
178
|
+
and response.candidates[0].content.parts[0].text
|
179
|
+
):
|
180
|
+
assistant_message = response.candidates[0].content.parts[0].text
|
181
|
+
|
182
|
+
choice = ChatCompletionChoice(
|
183
|
+
message=AssistantMessage(
|
184
|
+
role=Role.ASSISTANT,
|
185
|
+
content=assistant_message,
|
186
|
+
tool_calls=tool_calls,
|
187
|
+
),
|
188
|
+
finish_reason=finish_reason,
|
189
|
+
)
|
190
|
+
|
191
|
+
completion_tokens = 0
|
192
|
+
# Add null check for usage_metadata
|
193
|
+
if response.usage_metadata is not None:
|
194
|
+
if response.usage_metadata.thoughts_token_count:
|
195
|
+
completion_tokens = response.usage_metadata.thoughts_token_count
|
196
|
+
if response.usage_metadata.candidates_token_count:
|
197
|
+
completion_tokens += response.usage_metadata.candidates_token_count
|
198
|
+
|
199
|
+
# Set safe default for prompt_tokens
|
200
|
+
prompt_tokens = 0
|
201
|
+
if response.usage_metadata is not None and response.usage_metadata.prompt_token_count:
|
202
|
+
prompt_tokens = response.usage_metadata.prompt_token_count
|
203
|
+
|
204
|
+
chat_completion_response = ChatCompletionResponse(
|
205
|
+
choices=[choice],
|
206
|
+
completion_tokens=completion_tokens,
|
207
|
+
prompt_tokens=prompt_tokens,
|
208
|
+
response_duration=response_duration,
|
209
|
+
)
|
210
|
+
return chat_completion_response
|
211
|
+
|
212
|
+
|
213
|
+
def create_client_callable(client_class: type[genai.Client], **client_args: Any) -> Callable[..., Any]:
|
214
|
+
"""Creates a callable that instantiates and uses a Google genai client.
|
215
|
+
|
216
|
+
Args:
|
217
|
+
client_class: The Google genai client class to instantiate
|
218
|
+
**client_args: Arguments to pass to the client constructor
|
219
|
+
|
220
|
+
Returns:
|
221
|
+
A callable that creates a client and returns completion results
|
222
|
+
"""
|
223
|
+
filtered_args = {k: v for k, v in client_args.items() if v is not None}
|
224
|
+
|
225
|
+
def client_callable(**kwargs: Any) -> Any:
|
226
|
+
client = client_class(**filtered_args)
|
227
|
+
completion = client.models.generate_content(**kwargs)
|
228
|
+
return completion
|
229
|
+
|
230
|
+
return client_callable
|
231
|
+
|
232
|
+
|
233
|
+
def gemini_client(api_key: str | None = None) -> Callable[..., Any]:
|
234
|
+
if not api_key:
|
235
|
+
api_key = os.environ.get("GEMINI_API_KEY")
|
236
|
+
client_callable = create_client_callable(genai.Client, api_key=api_key)
|
237
|
+
return client_callable
|
@@ -138,10 +138,7 @@ class ChatCompletionRequest(BaseModel):
|
|
138
138
|
|
139
139
|
class ChatCompletionChoice(BaseModel):
|
140
140
|
message: AssistantMessage
|
141
|
-
finish_reason:
|
142
|
-
"stop", "length", "tool_calls", "content_filter", "end_turn", "max_tokens", "stop_sequence", "tool_use"
|
143
|
-
]
|
144
|
-
|
141
|
+
finish_reason: str
|
145
142
|
json_message: dict[str, Any] | None = Field(default=None)
|
146
143
|
logprobs: list[dict[str, Any] | list[dict[str, Any]]] | None = Field(default=None)
|
147
144
|
|
@@ -0,0 +1,24 @@
|
|
1
|
+
from collections.abc import Callable
|
2
|
+
from typing import Any
|
3
|
+
|
4
|
+
from not_again_ai.llm.image_gen.providers.openai_api import openai_create_image
|
5
|
+
from not_again_ai.llm.image_gen.types import ImageGenRequest, ImageGenResponse
|
6
|
+
|
7
|
+
|
8
|
+
def create_image(request: ImageGenRequest, provider: str, client: Callable[..., Any]) -> ImageGenResponse:
|
9
|
+
"""Get a image response from the given provider. Currently supported providers:
|
10
|
+
- `openai` - OpenAI
|
11
|
+
- `azure_openai` - Azure OpenAI
|
12
|
+
|
13
|
+
Args:
|
14
|
+
request: Request parameter object
|
15
|
+
provider: The supported provider name
|
16
|
+
client: Client information, see the provider's implementation for what can be provided
|
17
|
+
|
18
|
+
Returns:
|
19
|
+
ImageGenResponse: The image generation response.
|
20
|
+
"""
|
21
|
+
if provider == "openai" or provider == "azure_openai":
|
22
|
+
return openai_create_image(request, client)
|
23
|
+
else:
|
24
|
+
raise ValueError(f"Provider {provider} not supported")
|
File without changes
|
@@ -0,0 +1,144 @@
|
|
1
|
+
import base64
|
2
|
+
from collections.abc import Callable
|
3
|
+
import time
|
4
|
+
from typing import Any, Literal
|
5
|
+
|
6
|
+
from azure.identity import DefaultAzureCredential, get_bearer_token_provider
|
7
|
+
from openai import AzureOpenAI, OpenAI
|
8
|
+
from openai.types.images_response import ImagesResponse
|
9
|
+
|
10
|
+
from not_again_ai.llm.image_gen.types import ImageGenRequest, ImageGenResponse
|
11
|
+
|
12
|
+
|
13
|
+
def openai_create_image(request: ImageGenRequest, client: Callable[..., Any]) -> ImageGenResponse:
|
14
|
+
"""Create an image using OpenAI API.
|
15
|
+
|
16
|
+
Args:
|
17
|
+
request (ImageGenRequest): The request object containing parameters for image generation.
|
18
|
+
client (Callable[..., Any]): The OpenAI client callable.
|
19
|
+
|
20
|
+
Returns:
|
21
|
+
ImageGenResponse: The response object containing the generated image and metadata.
|
22
|
+
"""
|
23
|
+
kwargs = request.model_dump(exclude_none=True)
|
24
|
+
if kwargs.get("images"):
|
25
|
+
kwargs["image"] = kwargs.pop("images", None)
|
26
|
+
|
27
|
+
start_time = time.time()
|
28
|
+
response: ImagesResponse = client(**kwargs)
|
29
|
+
end_time = time.time()
|
30
|
+
response_duration = round(end_time - start_time, 4)
|
31
|
+
|
32
|
+
images: list[bytes] = []
|
33
|
+
if response.data:
|
34
|
+
for data in response.data:
|
35
|
+
images.append(base64.b64decode(data.b64_json or ""))
|
36
|
+
|
37
|
+
input_tokens = response.usage.input_tokens if response.usage else -1
|
38
|
+
output_tokens = response.usage.output_tokens if response.usage else -1
|
39
|
+
input_tokens_details = response.usage.input_tokens_details.to_dict() if response.usage else {}
|
40
|
+
image_gen_response = ImageGenResponse(
|
41
|
+
images=images,
|
42
|
+
input_tokens=input_tokens,
|
43
|
+
output_tokens=output_tokens,
|
44
|
+
input_tokens_details=input_tokens_details,
|
45
|
+
response_duration=response_duration,
|
46
|
+
)
|
47
|
+
return image_gen_response
|
48
|
+
|
49
|
+
|
50
|
+
def create_client_callable(client_class: type[OpenAI | AzureOpenAI], **client_args: Any) -> Callable[..., Any]:
|
51
|
+
"""
|
52
|
+
Creates the correct callable depending on the parameters provided.
|
53
|
+
"""
|
54
|
+
filtered_args = {k: v for k, v in client_args.items() if v is not None}
|
55
|
+
|
56
|
+
def client_callable(**kwargs: Any) -> Any:
|
57
|
+
client = client_class(**filtered_args)
|
58
|
+
# If mask or image is not none, use client.images.edit instead of client.images.generate
|
59
|
+
if kwargs.get("mask") or kwargs.get("image"):
|
60
|
+
completion = client.images.edit(**kwargs)
|
61
|
+
else:
|
62
|
+
completion = client.images.generate(**kwargs)
|
63
|
+
return completion
|
64
|
+
|
65
|
+
return client_callable
|
66
|
+
|
67
|
+
|
68
|
+
class InvalidOAIAPITypeError(Exception):
|
69
|
+
"""Raised when an invalid OAIAPIType string is provided."""
|
70
|
+
|
71
|
+
|
72
|
+
def openai_client(
|
73
|
+
api_type: Literal["openai", "azure_openai"] = "openai",
|
74
|
+
api_key: str | None = None,
|
75
|
+
organization: str | None = None,
|
76
|
+
aoai_api_version: str = "2024-06-01",
|
77
|
+
azure_endpoint: str | None = None,
|
78
|
+
timeout: float | None = None,
|
79
|
+
max_retries: int | None = None,
|
80
|
+
) -> Callable[..., Any]:
|
81
|
+
"""Create an OpenAI or Azure OpenAI client instance based on the specified API type and other provided parameters.
|
82
|
+
|
83
|
+
It is preferred to use RBAC authentication for Azure OpenAI. You must be signed in with the Azure CLI and have correct role assigned.
|
84
|
+
See https://techcommunity.microsoft.com/t5/microsoft-developer-community/using-keyless-authentication-with-azure-openai/ba-p/4111521
|
85
|
+
|
86
|
+
Args:
|
87
|
+
api_type (str, optional): Type of the API to be used. Accepted values are 'openai' or 'azure_openai'.
|
88
|
+
Defaults to 'openai'.
|
89
|
+
api_key (str, optional): The API key to authenticate the client. If not provided,
|
90
|
+
OpenAI automatically uses `OPENAI_API_KEY` from the environment.
|
91
|
+
If provided for Azure OpenAI, it will be used for authentication instead of the Azure AD token provider.
|
92
|
+
organization (str, optional): The ID of the organization. If not provided,
|
93
|
+
OpenAI automotically uses `OPENAI_ORG_ID` from the environment.
|
94
|
+
aoai_api_version (str, optional): Only applicable if using Azure OpenAI https://learn.microsoft.com/azure/ai-services/openai/reference#rest-api-versioning
|
95
|
+
azure_endpoint (str, optional): The endpoint to use for Azure OpenAI.
|
96
|
+
timeout (float, optional): By default requests time out after 10 minutes.
|
97
|
+
max_retries (int, optional): Certain errors are automatically retried 2 times by default,
|
98
|
+
with a short exponential backoff. Connection errors (for example, due to a network connectivity problem),
|
99
|
+
408 Request Timeout, 409 Conflict, 429 Rate Limit, and >=500 Internal errors are all retried by default.
|
100
|
+
|
101
|
+
Returns:
|
102
|
+
Callable[..., Any]: A callable that creates a client and returns completion results
|
103
|
+
|
104
|
+
|
105
|
+
Raises:
|
106
|
+
InvalidOAIAPITypeError: If an invalid API type string is provided.
|
107
|
+
NotImplementedError: If the specified API type is recognized but not yet supported (e.g., 'azure_openai').
|
108
|
+
"""
|
109
|
+
if api_type not in ["openai", "azure_openai"]:
|
110
|
+
raise InvalidOAIAPITypeError(f"Invalid OAIAPIType: {api_type}. Must be 'openai' or 'azure_openai'.")
|
111
|
+
|
112
|
+
if api_type == "openai":
|
113
|
+
return create_client_callable(
|
114
|
+
OpenAI,
|
115
|
+
api_key=api_key,
|
116
|
+
organization=organization,
|
117
|
+
timeout=timeout,
|
118
|
+
max_retries=max_retries,
|
119
|
+
)
|
120
|
+
elif api_type == "azure_openai":
|
121
|
+
if api_key:
|
122
|
+
return create_client_callable(
|
123
|
+
AzureOpenAI,
|
124
|
+
api_version=aoai_api_version,
|
125
|
+
azure_endpoint=azure_endpoint,
|
126
|
+
api_key=api_key,
|
127
|
+
timeout=timeout,
|
128
|
+
max_retries=max_retries,
|
129
|
+
)
|
130
|
+
else:
|
131
|
+
azure_credential = DefaultAzureCredential()
|
132
|
+
ad_token_provider = get_bearer_token_provider(
|
133
|
+
azure_credential, "https://cognitiveservices.azure.com/.default"
|
134
|
+
)
|
135
|
+
return create_client_callable(
|
136
|
+
AzureOpenAI,
|
137
|
+
api_version=aoai_api_version,
|
138
|
+
azure_endpoint=azure_endpoint,
|
139
|
+
azure_ad_token_provider=ad_token_provider,
|
140
|
+
timeout=timeout,
|
141
|
+
max_retries=max_retries,
|
142
|
+
)
|
143
|
+
else:
|
144
|
+
raise NotImplementedError(f"API type '{api_type}' is invalid.")
|
@@ -0,0 +1,24 @@
|
|
1
|
+
from pathlib import Path
|
2
|
+
from typing import Any
|
3
|
+
|
4
|
+
from pydantic import BaseModel, Field
|
5
|
+
|
6
|
+
|
7
|
+
class ImageGenRequest(BaseModel):
|
8
|
+
prompt: str
|
9
|
+
model: str
|
10
|
+
images: list[Path] | None = Field(default=None)
|
11
|
+
mask: Path | None = Field(default=None)
|
12
|
+
n: int = Field(default=1)
|
13
|
+
quality: str | None = Field(default=None)
|
14
|
+
size: str | None = Field(default=None)
|
15
|
+
background: str | None = Field(default=None)
|
16
|
+
moderation: str | None = Field(default=None)
|
17
|
+
|
18
|
+
|
19
|
+
class ImageGenResponse(BaseModel):
|
20
|
+
images: list[bytes]
|
21
|
+
input_tokens: int
|
22
|
+
output_tokens: int
|
23
|
+
response_duration: float
|
24
|
+
input_tokens_details: dict[str, Any] | None = Field(default=None)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: not-again-ai
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.19.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
|
Project-URL: Homepage, https://github.com/DaveCoDev/not-again-ai
|
6
6
|
Project-URL: Documentation, https://davecodev.github.io/not-again-ai/
|
@@ -19,16 +19,17 @@ Classifier: Programming Language :: Python :: 3.11
|
|
19
19
|
Classifier: Programming Language :: Python :: 3.12
|
20
20
|
Classifier: Typing :: Typed
|
21
21
|
Requires-Python: >=3.11
|
22
|
-
Requires-Dist: loguru
|
23
|
-
Requires-Dist: pydantic
|
22
|
+
Requires-Dist: loguru<1.0,>=0.7
|
23
|
+
Requires-Dist: pydantic<3.0,>=2.11
|
24
24
|
Provides-Extra: data
|
25
25
|
Requires-Dist: playwright<2.0,>=1.51; extra == 'data'
|
26
26
|
Requires-Dist: pytest-playwright<1.0,>=0.7; extra == 'data'
|
27
27
|
Provides-Extra: llm
|
28
|
-
Requires-Dist: anthropic<1.0,>=0.
|
28
|
+
Requires-Dist: anthropic<1.0,>=0.50; extra == 'llm'
|
29
29
|
Requires-Dist: azure-identity<2.0,>=1.21; extra == 'llm'
|
30
|
+
Requires-Dist: google-genai<2.0,>1.12; extra == 'llm'
|
30
31
|
Requires-Dist: ollama<1.0,>=0.4; extra == 'llm'
|
31
|
-
Requires-Dist: openai<2.0,>=1.
|
32
|
+
Requires-Dist: openai<2.0,>=1.76; extra == 'llm'
|
32
33
|
Requires-Dist: python-liquid<3.0,>=2.0; extra == 'llm'
|
33
34
|
Requires-Dist: tiktoken<1.0,>=0.9; extra == 'llm'
|
34
35
|
Provides-Extra: statistics
|
@@ -62,7 +63,7 @@ It is encouraged to also **a)** use this as a template for your own Python packa
|
|
62
63
|
**b)** instead of installing the package, copy and paste functions into your own projects.
|
63
64
|
We make this easier by limiting the number of dependencies and use an MIT license.
|
64
65
|
|
65
|
-
**Documentation** available within individual **[notebooks](notebooks)
|
66
|
+
**Documentation** available within individual **[notebooks](notebooks)** or docstrings within the source code.
|
66
67
|
|
67
68
|
# Installation
|
68
69
|
|
@@ -138,7 +139,7 @@ all machines that use the project, both during development and in production.
|
|
138
139
|
To install all dependencies into an isolated virtual environment:
|
139
140
|
|
140
141
|
```shell
|
141
|
-
uv sync --all-extras
|
142
|
+
uv sync --all-extras --all-groups
|
142
143
|
```
|
143
144
|
|
144
145
|
To upgrade all dependencies to their latest versions:
|
@@ -7,10 +7,11 @@ not_again_ai/data/__init__.py,sha256=1jF6mwvtB2PT7IEc3xpbRtZm3g3Lyf8zUqH4AEE4qlQ
|
|
7
7
|
not_again_ai/data/web.py,sha256=wjx9cc33jcoJBGonYCIpwygPBFOwz7F-dx_ominmbnI,1838
|
8
8
|
not_again_ai/llm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
9
9
|
not_again_ai/llm/chat_completion/__init__.py,sha256=HozawvdRkTFgq8XR16GJUHN1ukEa4Ya68wVPVrl-afs,250
|
10
|
-
not_again_ai/llm/chat_completion/interface.py,sha256=
|
11
|
-
not_again_ai/llm/chat_completion/types.py,sha256=
|
10
|
+
not_again_ai/llm/chat_completion/interface.py,sha256=OU6ghG7RlveahkHZWdRHFg0uzbSrSh2Dz7u5-4rrypA,2700
|
11
|
+
not_again_ai/llm/chat_completion/types.py,sha256=0pBo1Fgm__JU3NyMShGouAIolcANPpTfXn8WJHODlQw,5472
|
12
12
|
not_again_ai/llm/chat_completion/providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
13
|
-
not_again_ai/llm/chat_completion/providers/anthropic_api.py,sha256=
|
13
|
+
not_again_ai/llm/chat_completion/providers/anthropic_api.py,sha256=Eix3_GgQvDyPr6pKfSDrfqRg_bTb0pqMI8fdQrx9e84,6211
|
14
|
+
not_again_ai/llm/chat_completion/providers/gemini_api.py,sha256=ovyTssfN3achMr2Laa2Hu557CjYN6o7UkO5IGXg6lzk,9461
|
14
15
|
not_again_ai/llm/chat_completion/providers/ollama_api.py,sha256=Puo2eE2VynvZOoqrUlNYtPgRGCRMVa8syc3TfBxS1hs,10661
|
15
16
|
not_again_ai/llm/chat_completion/providers/openai_api.py,sha256=1wdeV50KYX_KIf2uofsICKYoHVSvj4kTRpS1Vuw3NSQ,17887
|
16
17
|
not_again_ai/llm/embedding/__init__.py,sha256=wscUfROukvw0M0vYccfaVTdXV0P-eICAT5mqM0LaHHc,182
|
@@ -19,6 +20,11 @@ not_again_ai/llm/embedding/types.py,sha256=J4FFLx35Aow2kOaafDReeY9cUNqhWMjaAk5gX
|
|
19
20
|
not_again_ai/llm/embedding/providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
20
21
|
not_again_ai/llm/embedding/providers/ollama_api.py,sha256=m-OCis9WAUT2baGsGVPzejlive40eSNyO6tHmPh6joM,3201
|
21
22
|
not_again_ai/llm/embedding/providers/openai_api.py,sha256=JFFqbq0O5snIEnr9VESdp5xehikQBPbs7nwyE6acFsY,5441
|
23
|
+
not_again_ai/llm/image_gen/__init__.py,sha256=v31PgYdTxMQRRxXPFl40BW5Y8RSHrZuwabuD-yC9gfI,170
|
24
|
+
not_again_ai/llm/image_gen/interface.py,sha256=XGE0aDvQwe-aWRuGNLMECO6KnMiK8qLv2APvr0hZ0tY,930
|
25
|
+
not_again_ai/llm/image_gen/types.py,sha256=Qhdov5azWwmmbqqE3Ln7t-Fb_Ipyp8r3z0_80omyASc,672
|
26
|
+
not_again_ai/llm/image_gen/providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
27
|
+
not_again_ai/llm/image_gen/providers/openai_api.py,sha256=3IEhdId1UU_imvDebytTt0dSCjEEyPHf2o4vVt6RqSE,6198
|
22
28
|
not_again_ai/llm/prompting/__init__.py,sha256=7YnHro1yH01FLGnao27WyqQDFjNYf9npE5UxoR9YrUU,84
|
23
29
|
not_again_ai/llm/prompting/compile_prompt.py,sha256=uBn655yTiQ325z1CUgnkU2k7ICIvaYRJOm50B7w2lSs,4683
|
24
30
|
not_again_ai/llm/prompting/interface.py,sha256=SMKYabmu3zTWbEDukU6aLU_JQ88apeBWWOF_qZ0s3ww,1783
|
@@ -33,7 +39,7 @@ not_again_ai/viz/distributions.py,sha256=OyWwJaNI6lMRm_iSrhq-CORLNvXfeuLSgDtVo3u
|
|
33
39
|
not_again_ai/viz/scatterplot.py,sha256=5CUOWeknbBOaZPeX9oPin5sBkRKEwk8qeFH45R-9LlY,2292
|
34
40
|
not_again_ai/viz/time_series.py,sha256=pOGZqXp_2nd6nKo-PUQNCtmMh__69jxQ6bQibTGLwZA,5212
|
35
41
|
not_again_ai/viz/utils.py,sha256=hN7gwxtBt3U6jQni2K8j5m5pCXpaJDoNzGhBBikEU28,238
|
36
|
-
not_again_ai-0.
|
37
|
-
not_again_ai-0.
|
38
|
-
not_again_ai-0.
|
39
|
-
not_again_ai-0.
|
42
|
+
not_again_ai-0.19.0.dist-info/METADATA,sha256=LeCIas912YMtvKEJcChoPqYrM3ay_EurUZehvlQ9t8o,12004
|
43
|
+
not_again_ai-0.19.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
44
|
+
not_again_ai-0.19.0.dist-info/licenses/LICENSE,sha256=btjOgNGpp-ux5xOo1Gx1MddxeWtT9sof3s3Nui29QfA,1071
|
45
|
+
not_again_ai-0.19.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|