voicerun_completions 0.1.2__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.
@@ -0,0 +1,142 @@
1
+ from typing import Optional
2
+ from google.genai.types import (
3
+ Content as GoogleMessageContent,
4
+ Part as GoogleMessagePart,
5
+ FunctionDeclaration as GoogleFunctionDefinition,
6
+ Tool as GoogleToolDefinition,
7
+ ToolConfig as GoogleToolChoice,
8
+ FunctionCallingConfig as GoogleFunctionChoice,
9
+ )
10
+ from primfunctions.completions.messages import (
11
+ ConversationHistory,
12
+ UserMessage,
13
+ AssistantMessage,
14
+ SystemMessage,
15
+ ToolResultMessage,
16
+ )
17
+ from primfunctions.completions.request import ToolChoice, ToolDefinition
18
+
19
+
20
+ def denormalize_conversation_history(normalized_messages: ConversationHistory) -> tuple[list[GoogleMessageContent], Optional[str]]:
21
+ """Convert normalized Message objects to Google Content format.
22
+
23
+ Returns:
24
+ Tuple of (contents, system_instruction) since Google handles system prompts separately
25
+ """
26
+ contents: list[GoogleMessageContent] = []
27
+ system_messages: list[str] = []
28
+
29
+ # TODO: thought signatures required https://ai.google.dev/gemini-api/docs/thought-signatures skip_thought_signature_validator?
30
+
31
+ for msg in normalized_messages:
32
+ match msg:
33
+ case SystemMessage():
34
+ system_messages.append(msg.content)
35
+ case UserMessage():
36
+ parts = []
37
+ if msg.content:
38
+ parts.append(GoogleMessagePart.from_text(text=msg.content))
39
+
40
+ content = GoogleMessageContent(
41
+ role="user",
42
+ parts=parts
43
+ )
44
+ contents.append(content)
45
+ case AssistantMessage():
46
+ # Assistant messages (model role in Google)
47
+ parts = []
48
+
49
+ # Add text content if present
50
+ if msg.content:
51
+ parts.append(GoogleMessagePart.from_text(text=msg.content))
52
+
53
+ # Add function calls if present
54
+ if msg.tool_calls:
55
+ for tc in msg.tool_calls:
56
+ # function_call = GoogleFunctionCall(
57
+ # id=tc.id,
58
+ # name=tc.function.name,
59
+ # args=tc.function.arguments
60
+ # )
61
+ parts.append(GoogleMessagePart.from_function_call(
62
+ name=tc.function.name,
63
+ args=tc.function.arguments
64
+ ))
65
+
66
+ content = GoogleMessageContent(
67
+ role="model",
68
+ parts=parts
69
+ )
70
+ contents.append(content)
71
+ case ToolResultMessage():
72
+ # Tool results become function_response parts in user messages
73
+ # Need to find the corresponding function name from tool_call_id
74
+ # For now, we'll need to track function names separately or get from context
75
+ if msg.tool_call_id and msg.content:
76
+ # TODO: We need the function name from the original tool call
77
+ # This requires tracking the function name when processing assistant messages
78
+ # function_response = GoogleFunctionResponse(
79
+ # id=msg.tool_call_id,
80
+ # name=msg.name or "unknown_function", # Name is required
81
+ # response={"output": msg.content}
82
+ # )
83
+
84
+ content = GoogleMessageContent(
85
+ role="user",
86
+ parts=[GoogleMessagePart.from_function_response(
87
+ name=msg.name or "unknown_function",
88
+ response={"output": msg.content}
89
+ )]
90
+ )
91
+ contents.append(content)
92
+
93
+ # Combine system messages with newlines
94
+ system_instruction: Optional[str] = "\n".join(system_messages) if system_messages else None
95
+
96
+ return contents, system_instruction
97
+
98
+
99
+ def denormalize_tools(normalized_tools: Optional[list[ToolDefinition]]) -> Optional[list[GoogleToolDefinition]]:
100
+ """Convert normalized Tool objects to Google Tool format."""
101
+
102
+ function_defs: Optional[list[GoogleFunctionDefinition]] = None
103
+ if normalized_tools:
104
+ function_defs = []
105
+ for tool in normalized_tools:
106
+ function_declaration = GoogleFunctionDefinition(
107
+ name=tool.function.name,
108
+ description=tool.function.description,
109
+ parameters=tool.function.parameters
110
+ )
111
+ function_defs.append(function_declaration)
112
+
113
+ # All google function calling tools are included in a single Google Tool
114
+ return [GoogleToolDefinition(function_declarations=function_defs)] if function_defs else None
115
+
116
+
117
+ def denormalize_tool_choice(normalized_tool_choice: Optional[ToolChoice]) -> Optional[GoogleToolChoice]:
118
+ """Convert normalized ToolChoice to Google GoogleToolChoice format."""
119
+ if not normalized_tool_choice:
120
+ return None
121
+
122
+ # Google uses ToolConfig with FunctionCallingConfig
123
+ if normalized_tool_choice == "auto":
124
+ return GoogleToolChoice(
125
+ function_calling_config=GoogleFunctionChoice(mode="AUTO")
126
+ )
127
+ elif normalized_tool_choice == "none":
128
+ return GoogleToolChoice(
129
+ function_calling_config=GoogleFunctionChoice(mode="NONE")
130
+ )
131
+ elif normalized_tool_choice == "required":
132
+ return GoogleToolChoice(
133
+ function_calling_config=GoogleFunctionChoice(mode="ANY")
134
+ )
135
+ else:
136
+ # Specific tool name - use allowed_function_names
137
+ return GoogleToolChoice(
138
+ function_calling_config=GoogleFunctionChoice(
139
+ mode="ANY",
140
+ allowed_function_names=[normalized_tool_choice]
141
+ )
142
+ )
@@ -0,0 +1,159 @@
1
+ from typing import Any, Optional, AsyncIterable
2
+ from openai import AsyncOpenAI
3
+ from openai.types.chat import (
4
+ ChatCompletion,
5
+ ChatCompletionMessageParam,
6
+ ChatCompletionFunctionToolParam,
7
+ ChatCompletionToolChoiceOptionParam,
8
+ )
9
+ from openai.types.chat.chat_completion import (
10
+ Choice as OpenAiChoice,
11
+ ChatCompletionMessage as OpenAiChatCompletionMessage,
12
+ )
13
+ from openai.types.chat.chat_completion_chunk import ChatCompletionChunk as OpenAiCompletionChunk
14
+ from primfunctions.completions.messages import AssistantMessage
15
+ from primfunctions.completions.response import ChatCompletionResponse
16
+ from primfunctions.completions.request import ChatCompletionRequest, StreamOptions
17
+
18
+ from .streaming import OpenAiStreamProcessor
19
+ from ..base import CompletionClient
20
+ from .utils import (
21
+ denormalize_conversation_history,
22
+ denormalize_tools,
23
+ denormalize_tool_choice,
24
+ normalize_tool_calls,
25
+ )
26
+
27
+
28
+ class OpenAiCompletionClient(CompletionClient):
29
+
30
+ def _denormalize_request(
31
+ self,
32
+ request: ChatCompletionRequest,
33
+ ) -> dict[str, Any]:
34
+ """Convert ChatCompletionRequest to kwargs for _get_completion."""
35
+
36
+ kwargs = {
37
+ "api_key": request.api_key,
38
+ "model": request.model,
39
+ "messages": denormalize_conversation_history(request.messages),
40
+ "tools": denormalize_tools(request.tools),
41
+ "tool_choice": denormalize_tool_choice(request.tool_choice),
42
+ "temperature": request.temperature if request.temperature else None,
43
+ }
44
+
45
+ if request.max_tokens is not None:
46
+ kwargs["max_tokens"] = request.max_tokens
47
+
48
+ if request.timeout is not None:
49
+ kwargs["timeout"] = request.timeout
50
+
51
+ return kwargs
52
+
53
+
54
+ def _normalize_response(
55
+ self,
56
+ response: ChatCompletion,
57
+ ) -> ChatCompletionResponse:
58
+ """Convert OpenAI ChatCompletion to normalized ChatCompletionResponse."""
59
+ # Take only first choice
60
+ choice: OpenAiChoice = response.choices[0]
61
+ openai_message: OpenAiChatCompletionMessage = choice.message
62
+
63
+ normalized_message = AssistantMessage(
64
+ content=openai_message.content,
65
+ tool_calls=normalize_tool_calls(openai_message.tool_calls)
66
+ )
67
+
68
+ return ChatCompletionResponse(
69
+ message=normalized_message,
70
+ finish_reason=choice.finish_reason,
71
+ usage=response.usage.model_dump() if response.usage else None
72
+ )
73
+
74
+
75
+ async def _get_completion(
76
+ self,
77
+ api_key: str,
78
+ model: str,
79
+ messages: list[ChatCompletionMessageParam],
80
+ tools: Optional[list[ChatCompletionFunctionToolParam]] = None,
81
+ tool_choice: Optional[ChatCompletionToolChoiceOptionParam] = None,
82
+ temperature: Optional[float] = None,
83
+ max_tokens: Optional[int] = None,
84
+ timeout: Optional[float] = None,
85
+ ) -> ChatCompletion:
86
+ """TODO.
87
+
88
+ [Client](https://github.com/openai/openai-python)
89
+ """
90
+ async with AsyncOpenAI(api_key=api_key) as client:
91
+ # Build kwargs dict with only non-None values
92
+ kwargs = {
93
+ "model": model,
94
+ "messages": messages,
95
+ "stream": False,
96
+ # TODO: support reasoning by model
97
+ # "reasoning_effort": "none", # Disable reasoning
98
+ }
99
+
100
+ # Only add optional parameters if they're provided
101
+ if tools is not None:
102
+ kwargs["tools"] = tools
103
+ if tool_choice is not None:
104
+ kwargs["tool_choice"] = tool_choice
105
+ if temperature is not None:
106
+ kwargs["temperature"] = temperature
107
+ if max_tokens is not None:
108
+ kwargs["max_tokens"] = max_tokens
109
+ if timeout is not None:
110
+ kwargs["timeout"] = timeout
111
+
112
+ return await client.chat.completions.create(**kwargs)
113
+
114
+
115
+ def _get_stream_processor(
116
+ self,
117
+ stream_options: Optional[StreamOptions] = None,
118
+ ) -> OpenAiStreamProcessor:
119
+ """Get openai-specific StreamProcessor."""
120
+ return OpenAiStreamProcessor(stream_options=stream_options)
121
+
122
+
123
+ async def _get_completion_stream(
124
+ self,
125
+ api_key: str,
126
+ model: str,
127
+ messages: list[ChatCompletionMessageParam],
128
+ tools: Optional[list[ChatCompletionFunctionToolParam]] = None,
129
+ tool_choice: Optional[ChatCompletionToolChoiceOptionParam] = None,
130
+ temperature: Optional[float] = None,
131
+ max_tokens: Optional[int] = None,
132
+ timeout: Optional[float] = None,
133
+ ) -> AsyncIterable[OpenAiCompletionChunk]:
134
+ """Stream chat completion chunks from OpenAI."""
135
+ client = AsyncOpenAI(api_key=api_key)
136
+
137
+ # Build kwargs dict with only non-None values
138
+ kwargs = {
139
+ "model": model,
140
+ "messages": messages,
141
+ "stream": True, # Enable streaming
142
+ "stream_options": {"include_usage": True}, # Include usage information in stream
143
+ # TODO: support reasoning by model
144
+ # "reasoning_effort": "none", # Disable reasoning
145
+ }
146
+
147
+ # Only add optional parameters if they're provided
148
+ if tools is not None:
149
+ kwargs["tools"] = tools
150
+ if tool_choice is not None:
151
+ kwargs["tool_choice"] = tool_choice
152
+ if temperature is not None:
153
+ kwargs["temperature"] = temperature
154
+ if max_tokens is not None:
155
+ kwargs["max_tokens"] = max_tokens
156
+ if timeout is not None:
157
+ kwargs["timeout"] = timeout
158
+
159
+ return await client.chat.completions.create(**kwargs)
@@ -0,0 +1,182 @@
1
+ from typing import Any, AsyncIterable, Dict, List, Optional
2
+ from openai.types.chat.chat_completion_chunk import (
3
+ Choice as OpenAiChunkChoice,
4
+ ChoiceDelta as OpenAiChoiceDelta,
5
+ ChatCompletionChunk as OpenAiCompletionChunk,
6
+ ChoiceDeltaToolCall as OpenAiToolDelta,
7
+ )
8
+ from primfunctions.completions.messages import ToolCall, AssistantMessage
9
+ from primfunctions.completions.response import ChatCompletionResponse
10
+ from primfunctions.completions.streaming import (
11
+ ChatCompletionChunk,
12
+ AssistantMessageDeltaChunk,
13
+ AssistantMessageSentenceChunk,
14
+ FinishReasonChunk,
15
+ ToolCallChunk,
16
+ UsageChunk,
17
+ FinalResponseChunk,
18
+ )
19
+ from primfunctions.utils.streaming import update_sentence_buffer, clean_text_for_speech
20
+ from primfunctions.completions.request import StreamOptions
21
+
22
+ from ..base import StreamProcessor, PartialToolCall
23
+
24
+
25
+ class OpenAiStreamProcessor(StreamProcessor):
26
+ """Processes OpenAI chat completion stream yielding normalized chunks."""
27
+
28
+
29
+ def __init__(
30
+ self,
31
+ stream_options: Optional[StreamOptions] = None,
32
+ ):
33
+ self.stream_sentences: bool = False
34
+ self.clean_sentences: bool = True
35
+ self.min_sentence_length: int = 6
36
+ self.punctuation_marks: Optional[list[str]] = None
37
+ self.punctuation_language: Optional[str] = None
38
+
39
+ # Override stream options defaults
40
+ if stream_options:
41
+ self.stream_sentences = stream_options.stream_sentences
42
+ self.clean_sentences = stream_options.clean_sentences
43
+ self.min_sentence_length = stream_options.min_sentence_length
44
+ self.punctuation_marks = stream_options.punctuation_marks
45
+ self.punctuation_language = stream_options.punctuation_language
46
+
47
+ self.active_calls: Dict[int, PartialToolCall] = {}
48
+ self.content: str = ""
49
+ self.tool_calls: List[ToolCall] = []
50
+ self.finish_reason: str = ""
51
+ self.usage: Dict[str, Any] = {}
52
+ self.sentence_buffer = ""
53
+
54
+
55
+ def _process_tool_deltas(self, tool_call_deltas: List[OpenAiToolDelta]) -> List[ToolCallChunk]:
56
+ """Process a tool call delta and return any completed tool call chunks."""
57
+ chunks: List[ToolCall] = []
58
+
59
+ for delta in tool_call_deltas:
60
+ # Tool call already in buffer
61
+ if delta.index in self.active_calls:
62
+ partial_call = self.active_calls[delta.index]
63
+ if delta.function and delta.function.arguments:
64
+ partial_call.arguments_buffer += delta.function.arguments
65
+
66
+ # Check if tool call complete
67
+ if partial_call.is_complete():
68
+ # Complete tool call, remove from active calls and return
69
+ completed_call = partial_call.to_tool_call()
70
+ del self.active_calls[delta.index]
71
+
72
+ # Append full tool call to chunks to stream
73
+ chunks.append(ToolCallChunk(
74
+ tool_call=completed_call
75
+ ))
76
+
77
+ # Add tool call to accumulated response
78
+ self.tool_calls.append(completed_call)
79
+
80
+ # New tool call
81
+ else:
82
+ self.active_calls[delta.index] = PartialToolCall(
83
+ id=delta.id,
84
+ type=delta.type,
85
+ function_name=delta.function.name,
86
+ arguments_buffer=delta.function.arguments or "",
87
+ index=delta.index,
88
+ )
89
+
90
+ return chunks
91
+
92
+
93
+ def _process_chunk(
94
+ self,
95
+ chunk: OpenAiCompletionChunk,
96
+ ) -> List[ChatCompletionChunk]:
97
+ """Convert OpenAI ChatCompletionChunk to individual typed chunks."""
98
+ chunks = []
99
+
100
+ # Handle usage chunk
101
+ if chunk.usage:
102
+ self.usage = chunk.usage.model_dump()
103
+
104
+ # Handle no choices
105
+ if not chunk.choices:
106
+ return chunks
107
+
108
+ # Take only first choice (OpenAI pattern)
109
+ choice: OpenAiChunkChoice = chunk.choices[0]
110
+ delta: OpenAiChoiceDelta = choice.delta
111
+
112
+ # Handle content
113
+ if delta.content:
114
+ if self.stream_sentences:
115
+ # Append delta to sentence buffer
116
+ sentence_buffer, complete_sentence = update_sentence_buffer(
117
+ content=delta.content,
118
+ sentence_buffer=self.sentence_buffer,
119
+ punctuation_marks=self.punctuation_marks,
120
+ clean_text=self.clean_sentences,
121
+ # TODO: this completely drops messages like "Sure."
122
+ min_sentence_length=self.min_sentence_length,
123
+ )
124
+ self.sentence_buffer = sentence_buffer
125
+
126
+ if complete_sentence:
127
+ chunks.append(AssistantMessageSentenceChunk(
128
+ sentence=complete_sentence
129
+ ))
130
+
131
+ else:
132
+ # Otherwise stream content delta directly
133
+ chunks.append(AssistantMessageDeltaChunk(
134
+ content=delta.content
135
+ ))
136
+
137
+ # Add content delta to accumulated response
138
+ self.content += delta.content
139
+
140
+ # Handle tool calls
141
+ if delta.tool_calls:
142
+ chunks.extend(self._process_tool_deltas(delta.tool_calls))
143
+
144
+ # Capture finish reason
145
+ if choice.finish_reason:
146
+ self.finish_reason = choice.finish_reason
147
+
148
+ return chunks
149
+
150
+ async def process_stream(
151
+ self,
152
+ stream: AsyncIterable[OpenAiCompletionChunk],
153
+ ) -> AsyncIterable[ChatCompletionChunk]:
154
+ async for chunk in stream:
155
+ for normalized_chunk in self._process_chunk(chunk):
156
+ yield normalized_chunk
157
+
158
+ if self.stream_sentences and self.sentence_buffer:
159
+ # Clean text for speech if requested
160
+ complete_sentence = clean_text_for_speech(self.sentence_buffer) if self.clean_sentences else self.sentence_buffer
161
+ # Handle any remaining text in sentence buffer
162
+ yield AssistantMessageSentenceChunk(
163
+ sentence=complete_sentence
164
+ )
165
+
166
+ # Yield the finish chunk (or default)
167
+ yield FinishReasonChunk(finish_reason=self.finish_reason or "stop")
168
+
169
+ # Yield the usage chunk
170
+ yield UsageChunk(usage=self.usage)
171
+
172
+ # Yield aggregated chat completion response as final chunk
173
+ yield FinalResponseChunk(
174
+ response = ChatCompletionResponse(
175
+ message=AssistantMessage(
176
+ content=self.content or None,
177
+ tool_calls=self.tool_calls or None
178
+ ),
179
+ usage=self.usage,
180
+ finish_reason=self.finish_reason,
181
+ )
182
+ )
@@ -0,0 +1,135 @@
1
+ import json
2
+ from typing import List, Optional
3
+ from openai.types.chat import (
4
+ ChatCompletionToolChoiceOptionParam as OpenAiToolChoice,
5
+ ChatCompletionNamedToolChoiceParam as OpenAiNamedToolChoice,
6
+ ChatCompletionFunctionToolParam as OpenAiToolDefinition,
7
+ ChatCompletionMessageParam as OpenAiMessage,
8
+ ChatCompletionUserMessageParam as OpenAiUserMessage,
9
+ ChatCompletionAssistantMessageParam as OpenAiAssistantMessage,
10
+ ChatCompletionSystemMessageParam as OpenAiSystemMessage,
11
+ ChatCompletionToolMessageParam as OpenAiToolResultMessage,
12
+ ChatCompletionMessageFunctionToolCallParam as OpenAiToolCall,
13
+ )
14
+ from openai.types.shared_params import FunctionDefinition as OpenAiFunctionDefinition
15
+ from openai.types.chat.chat_completion_message_function_tool_call_param import Function as OpenAiFunctionCall
16
+ from primfunctions.completions.messages import (
17
+ ConversationHistory,
18
+ UserMessage,
19
+ AssistantMessage,
20
+ SystemMessage,
21
+ ToolResultMessage,
22
+ ToolCall,
23
+ FunctionCall,
24
+ )
25
+ from primfunctions.completions.request import ToolChoice, ToolDefinition
26
+
27
+
28
+ def denormalize_tool_calls(normalized_tool_calls: Optional[List[ToolCall]]) -> list[OpenAiToolCall]:
29
+ """TODO"""
30
+ tool_calls: Optional[list[OpenAiToolCall]] = None
31
+ if normalized_tool_calls:
32
+ tool_calls = []
33
+ for tc in normalized_tool_calls:
34
+ function_call: OpenAiFunctionCall = {
35
+ "name": tc.function.name,
36
+ "arguments": json.dumps(tc.function.arguments),
37
+ }
38
+ tool_call: OpenAiToolCall = {
39
+ "id": tc.id,
40
+ "type": tc.type,
41
+ "function": function_call
42
+ }
43
+ tool_calls.append(tool_call)
44
+
45
+ return tool_calls
46
+
47
+
48
+ def denormalize_conversation_history(normalized_messages: ConversationHistory) -> list[OpenAiMessage]:
49
+ # Convert conversation history to ChatCompletionMessageParams
50
+ messages: list[OpenAiMessage] = []
51
+ for msg in normalized_messages:
52
+ match msg:
53
+ case UserMessage():
54
+ messages.append(OpenAiUserMessage(
55
+ role="user",
56
+ content=msg.content,
57
+ ))
58
+ case AssistantMessage():
59
+ messages.append(OpenAiAssistantMessage(
60
+ role="assistant",
61
+ content=msg.content,
62
+ tool_calls=denormalize_tool_calls(msg.tool_calls),
63
+ ))
64
+ case SystemMessage():
65
+ messages.append(OpenAiSystemMessage(
66
+ role="system",
67
+ content=msg.content,
68
+ ))
69
+ case ToolResultMessage():
70
+ tool_result: OpenAiToolResultMessage = {
71
+ "role": "tool",
72
+ "content": json.dumps(msg.content),
73
+ "tool_call_id": msg.tool_call_id,
74
+ }
75
+ messages.append(tool_result)
76
+
77
+ return messages
78
+
79
+
80
+ def denormalize_tools(normalized_tools: Optional[list[ToolDefinition]]) -> Optional[list[OpenAiToolDefinition]]:
81
+ # Convert tools to ChatCompletionFunctionToolParam format if provided
82
+ tools: Optional[list[OpenAiToolDefinition]] = None
83
+ if normalized_tools:
84
+ tools = []
85
+ for tool in normalized_tools:
86
+ function_def: OpenAiFunctionDefinition = {
87
+ "name": tool.function.name,
88
+ "description": tool.function.description,
89
+ "parameters": tool.function.parameters,
90
+ "strict": tool.function.strict,
91
+ }
92
+ tool_def: OpenAiToolDefinition = {
93
+ "type": tool.type,
94
+ "function": function_def,
95
+ }
96
+ tools.append(tool_def)
97
+
98
+ return tools
99
+
100
+
101
+ def denormalize_tool_choice(normalized_tool_choice: Optional[ToolChoice]) -> Optional[OpenAiToolChoice]:
102
+ """Convert normalized ToolChoice to OpenAI ChatCompletionToolChoiceOptionParam format."""
103
+ tool_choice: Optional[OpenAiToolChoice] = None
104
+ if normalized_tool_choice:
105
+ # Handle literal values that are directly compatible
106
+ if normalized_tool_choice in ["none", "auto", "required"]:
107
+ tool_choice = normalized_tool_choice
108
+ else:
109
+ # TODO: support ChatCompletionNamedToolChoiceCustomParam
110
+ # Assume tool name
111
+ tool_choice = OpenAiNamedToolChoice(
112
+ type="function",
113
+ function={"name": normalized_tool_choice}
114
+ )
115
+
116
+ return tool_choice
117
+
118
+
119
+ def normalize_tool_calls(denormalized_tool_calls: Optional[List[OpenAiToolCall]]) -> list[ToolCall]:
120
+ """Convert denormalized ChatCompletionMessageFunctionToolCallParam to ToolCall format."""
121
+ tool_calls: Optional[list[ToolCall]] = None
122
+ if denormalized_tool_calls:
123
+ tool_calls = []
124
+ for index, tc in enumerate(denormalized_tool_calls):
125
+ tool_calls.append(ToolCall(
126
+ id=tc.id,
127
+ type=tc.type,
128
+ function=FunctionCall(
129
+ name=tc.function.name,
130
+ arguments=json.loads(tc.function.arguments)
131
+ ),
132
+ index=index,
133
+ ))
134
+
135
+ return tool_calls
@@ -0,0 +1,46 @@
1
+ Metadata-Version: 2.4
2
+ Name: voicerun_completions
3
+ Version: 0.1.2
4
+ Summary: VoiceRun Completions
5
+ Author: VoiceRun
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: primfunctions
9
+ Requires-Dist: black>=23.3.0
10
+ Requires-Dist: openai==2.8.0
11
+ Requires-Dist: anthropic==0.74.0
12
+ Requires-Dist: google-genai==1.52.0
13
+
14
+ # voicerun_completions
15
+
16
+ A comprehensive SDK for using with the VoiceRun Completions functionality.
17
+
18
+ ## Table of Contents
19
+ - [Prerequisites](#prerequisites)
20
+ - [Installation](#installation)
21
+
22
+ ## Prerequisites
23
+
24
+ - Python 3.12+
25
+ - [uv](https://docs.astral.sh/uv/)
26
+
27
+ If you do not have a uv virtual environment, you can create one with:
28
+ ```bash
29
+ uv venv --python 3.12
30
+ ```
31
+
32
+ **Note:** We recommend using uv to install the package due to the incredible speed of the package manager. The package can still be installed via pip directly, but it will be slower.
33
+
34
+ ## Installation
35
+
36
+ ### From PyPI
37
+
38
+ ```bash
39
+ pip install voicerun_completions
40
+ ```
41
+
42
+ ### From Source
43
+
44
+ ```bash
45
+ uv pip install .
46
+ ```
@@ -0,0 +1,16 @@
1
+ voicerun_completions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ voicerun_completions/client.py,sha256=SyYXcByOiqFkhVn9HkMgNJNjsH3ERXiy3uI1fylA5jw,5827
3
+ voicerun_completions/providers/base.py,sha256=XCkWBXmvsXcDcMS6akksPNhPkKQXwpxNqJYkteT9RnM,4080
4
+ voicerun_completions/providers/anthropic/anthropic_client.py,sha256=bBb3_bAO1qSDo_onVL3M7H1ak97We6Y0-24ImAzxMZ0,6659
5
+ voicerun_completions/providers/anthropic/streaming.py,sha256=qGYOAqgmqeKzoj4vNw7-2TCFQU6IsmFpdUVWlx4xznw,7754
6
+ voicerun_completions/providers/anthropic/utils.py,sha256=Xwk7nv6N4LXseqU6r0Qh6k5Fna7kZY4QfKm7cTCq93U,7467
7
+ voicerun_completions/providers/google/google_client.py,sha256=XljdWabr-K8THUDDhC4YQ4HII6ElZJNrUeyRSCPJ8gw,5641
8
+ voicerun_completions/providers/google/streaming.py,sha256=VLcMKvDRVmGd8G95xJfw_rwFvElssFINoai-v26a7w0,6630
9
+ voicerun_completions/providers/google/utils.py,sha256=jfumtF2H-wp6pUDQpidOyqWcG68IFuyebb-Wr6BWYuo,5787
10
+ voicerun_completions/providers/openai/openai_client.py,sha256=uu3jdNl9WcLDkABhI68FI9xd_igF8CyLT91JLuHPzu4,5659
11
+ voicerun_completions/providers/openai/streaming.py,sha256=TwxGYxoyplmTrA5U5Pg3GRhm9d8Q1CApmDbo5g3v_aY,6824
12
+ voicerun_completions/providers/openai/utils.py,sha256=VU3WYXF-bhanbQQnzzKrkH3jslvEhZYa3RTJCJCNwG4,5193
13
+ voicerun_completions-0.1.2.dist-info/METADATA,sha256=7-evbAWVPbRBJz5BRCO1_qK_WvWQR7ZR4x7451hVWGI,1002
14
+ voicerun_completions-0.1.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
15
+ voicerun_completions-0.1.2.dist-info/top_level.txt,sha256=R9R2ek4izV-Jznppy4FVlmBAZs6VEQo-PeqpkaI4xzg,21
16
+ voicerun_completions-0.1.2.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ voicerun_completions