grasp_agents 0.2.10__py3-none-any.whl → 0.3.1__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.
- grasp_agents/__init__.py +15 -14
- grasp_agents/cloud_llm.py +118 -131
- grasp_agents/comm_processor.py +201 -0
- grasp_agents/generics_utils.py +15 -7
- grasp_agents/llm.py +60 -31
- grasp_agents/llm_agent.py +229 -278
- grasp_agents/llm_agent_memory.py +58 -0
- grasp_agents/llm_policy_executor.py +482 -0
- grasp_agents/memory.py +20 -134
- grasp_agents/message_history.py +140 -0
- grasp_agents/openai/__init__.py +54 -36
- grasp_agents/openai/completion_chunk_converters.py +78 -0
- grasp_agents/openai/completion_converters.py +53 -30
- grasp_agents/openai/content_converters.py +13 -14
- grasp_agents/openai/converters.py +44 -68
- grasp_agents/openai/message_converters.py +58 -72
- grasp_agents/openai/openai_llm.py +101 -42
- grasp_agents/openai/tool_converters.py +24 -19
- grasp_agents/packet.py +24 -0
- grasp_agents/packet_pool.py +91 -0
- grasp_agents/printer.py +29 -15
- grasp_agents/processor.py +194 -0
- grasp_agents/prompt_builder.py +173 -176
- grasp_agents/run_context.py +21 -41
- grasp_agents/typing/completion.py +58 -12
- grasp_agents/typing/completion_chunk.py +173 -0
- grasp_agents/typing/converters.py +8 -12
- grasp_agents/typing/events.py +86 -0
- grasp_agents/typing/io.py +4 -13
- grasp_agents/typing/message.py +12 -50
- grasp_agents/typing/tool.py +52 -26
- grasp_agents/usage_tracker.py +6 -6
- grasp_agents/utils.py +3 -3
- grasp_agents/workflow/looped_workflow.py +132 -0
- grasp_agents/workflow/parallel_processor.py +95 -0
- grasp_agents/workflow/sequential_workflow.py +66 -0
- grasp_agents/workflow/workflow_processor.py +78 -0
- {grasp_agents-0.2.10.dist-info → grasp_agents-0.3.1.dist-info}/METADATA +41 -50
- grasp_agents-0.3.1.dist-info/RECORD +51 -0
- grasp_agents/agent_message.py +0 -27
- grasp_agents/agent_message_pool.py +0 -92
- grasp_agents/base_agent.py +0 -51
- grasp_agents/comm_agent.py +0 -217
- grasp_agents/llm_agent_state.py +0 -79
- grasp_agents/tool_orchestrator.py +0 -203
- grasp_agents/workflow/looped_agent.py +0 -120
- grasp_agents/workflow/sequential_agent.py +0 -63
- grasp_agents/workflow/workflow_agent.py +0 -73
- grasp_agents-0.2.10.dist-info/RECORD +0 -46
- {grasp_agents-0.2.10.dist-info → grasp_agents-0.3.1.dist-info}/WHEEL +0 -0
- {grasp_agents-0.2.10.dist-info → grasp_agents-0.3.1.dist-info}/licenses/LICENSE.md +0 -0
@@ -0,0 +1,140 @@
|
|
1
|
+
import logging
|
2
|
+
from collections.abc import Iterator, Sequence
|
3
|
+
from copy import deepcopy
|
4
|
+
|
5
|
+
from .typing.io import LLMPrompt
|
6
|
+
from .typing.message import Message, Messages, SystemMessage
|
7
|
+
|
8
|
+
logger = logging.getLogger(__name__)
|
9
|
+
|
10
|
+
|
11
|
+
class MessageHistory:
|
12
|
+
def __init__(self, sys_prompt: LLMPrompt | None = None) -> None:
|
13
|
+
self._sys_prompt = sys_prompt
|
14
|
+
self._conversations: list[Messages]
|
15
|
+
self.reset()
|
16
|
+
|
17
|
+
@property
|
18
|
+
def sys_prompt(self) -> LLMPrompt | None:
|
19
|
+
return self._sys_prompt
|
20
|
+
|
21
|
+
def add_message_batch(self, message_batch: Sequence[Message]) -> None:
|
22
|
+
"""
|
23
|
+
Adds a batch of messages to the current batched conversations.
|
24
|
+
This method verifies that the size of the input message batch matches
|
25
|
+
the expected batch size (self.batch_size).
|
26
|
+
If there is a mismatch, the method adjusts by duplicating either
|
27
|
+
the message or the conversation as necessary:
|
28
|
+
|
29
|
+
- If the message batch contains exactly one message and
|
30
|
+
self.batch_size > 1, the single message is duplicated to match
|
31
|
+
the batch size.
|
32
|
+
- If the message batch contains multiple messages but
|
33
|
+
self.batch_size == 1, the entire conversation is duplicated to
|
34
|
+
accommodate each message in the batch.
|
35
|
+
- If the message batch size does not match self.batch_size and none of
|
36
|
+
the above adjustments apply, a ValueError is raised.
|
37
|
+
|
38
|
+
Afterwards, each message in the batch is appended to its corresponding
|
39
|
+
conversation in the batched conversations.
|
40
|
+
|
41
|
+
Args:
|
42
|
+
message_batch: A sequence of Message objects
|
43
|
+
representing the batch of messages to be added. Must align with
|
44
|
+
or be adjusted to match the current batch size.
|
45
|
+
|
46
|
+
Raises:
|
47
|
+
ValueError: If the message batch size does not match the current
|
48
|
+
batch size and cannot be automatically adjusted.
|
49
|
+
|
50
|
+
"""
|
51
|
+
message_batch_size = len(message_batch)
|
52
|
+
|
53
|
+
if message_batch_size == 1 and self.batch_size > 1:
|
54
|
+
logger.info(
|
55
|
+
"Message batch size is 1, current batch size is "
|
56
|
+
f"{self.batch_size}: duplicating the message to match the "
|
57
|
+
"current batch size"
|
58
|
+
)
|
59
|
+
message_batch = self._duplicate_message_to_current_batch_size(message_batch)
|
60
|
+
message_batch_size = self.batch_size
|
61
|
+
elif message_batch_size > 1 and self.batch_size == 1:
|
62
|
+
logger.info(
|
63
|
+
f"Message batch size is {len(message_batch)}, current batch "
|
64
|
+
"size is 1: duplicating the conversation to match the message "
|
65
|
+
"batch size"
|
66
|
+
)
|
67
|
+
self._duplicate_conversation_to_target_batch_size(message_batch_size)
|
68
|
+
elif message_batch_size != self.batch_size:
|
69
|
+
raise ValueError(
|
70
|
+
f"Message batch size {message_batch_size} does not match "
|
71
|
+
f"current batch size {self.batch_size}"
|
72
|
+
)
|
73
|
+
|
74
|
+
for batch_id in range(message_batch_size):
|
75
|
+
self._conversations[batch_id].append(message_batch[batch_id])
|
76
|
+
|
77
|
+
def add_message_batches(self, message_batches: Sequence[Sequence[Message]]) -> None:
|
78
|
+
for message_batch in message_batches:
|
79
|
+
self.add_message_batch(message_batch)
|
80
|
+
|
81
|
+
def add_message(self, message: Message) -> None:
|
82
|
+
for conversation in self._conversations:
|
83
|
+
conversation.append(message)
|
84
|
+
|
85
|
+
def add_message_list(self, message_list: Sequence[Message]) -> None:
|
86
|
+
for message in message_list:
|
87
|
+
self.add_message(message)
|
88
|
+
|
89
|
+
def __len__(self) -> int:
|
90
|
+
return len(self._conversations[0])
|
91
|
+
|
92
|
+
def __repr__(self) -> str:
|
93
|
+
return f"{self.__class__.__name__}(len={len(self)}; bs={self.batch_size})"
|
94
|
+
|
95
|
+
def __getitem__(self, idx: int) -> tuple[Message, ...]:
|
96
|
+
return tuple(conversation[idx] for conversation in self._conversations)
|
97
|
+
|
98
|
+
def __iter__(self) -> Iterator[tuple[Message, ...]]:
|
99
|
+
for idx in range(len(self)):
|
100
|
+
yield tuple(conversation[idx] for conversation in self._conversations)
|
101
|
+
|
102
|
+
def _duplicate_message_to_current_batch_size(
|
103
|
+
self, message_batch: Sequence[Message]
|
104
|
+
) -> Sequence[Message]:
|
105
|
+
assert len(message_batch) == 1, (
|
106
|
+
"Message batch size must be 1 to duplicate to current batch size"
|
107
|
+
)
|
108
|
+
|
109
|
+
return [deepcopy(message_batch[0]) for _ in range(self.batch_size)]
|
110
|
+
|
111
|
+
def _duplicate_conversation_to_target_batch_size(
|
112
|
+
self, target_batch_size: int
|
113
|
+
) -> None:
|
114
|
+
assert self.batch_size == 1, "Batch size must be 1 to duplicate conversation"
|
115
|
+
self._conversations = [
|
116
|
+
deepcopy(self._conversations[0]) for _ in range(target_batch_size)
|
117
|
+
]
|
118
|
+
|
119
|
+
@property
|
120
|
+
def conversations(self) -> list[Messages]:
|
121
|
+
return self._conversations
|
122
|
+
|
123
|
+
@property
|
124
|
+
def batch_size(self) -> int:
|
125
|
+
return len(self._conversations)
|
126
|
+
|
127
|
+
def reset(
|
128
|
+
self, sys_prompt: LLMPrompt | None = None, *, batch_size: int = 1
|
129
|
+
) -> None:
|
130
|
+
if sys_prompt is not None:
|
131
|
+
self._sys_prompt = sys_prompt
|
132
|
+
|
133
|
+
conv: Messages = []
|
134
|
+
if self._sys_prompt is not None:
|
135
|
+
conv.append(SystemMessage(content=self._sys_prompt))
|
136
|
+
|
137
|
+
self._conversations = [deepcopy(conv) for _ in range(batch_size)]
|
138
|
+
|
139
|
+
def erase(self) -> None:
|
140
|
+
self._conversations = [[]]
|
grasp_agents/openai/__init__.py
CHANGED
@@ -1,87 +1,105 @@
|
|
1
1
|
# pyright: reportUnusedImport=false
|
2
2
|
|
3
3
|
from openai._streaming import (
|
4
|
-
AsyncStream as
|
4
|
+
AsyncStream as OpenAIAsyncStream, # type: ignore[import] # noqa: PLC2701
|
5
|
+
)
|
6
|
+
from openai.types import CompletionUsage as OpenAICompletionUsage
|
7
|
+
from openai.types.chat.chat_completion import ChatCompletion as OpenAICompletion
|
8
|
+
from openai.types.chat.chat_completion import (
|
9
|
+
ChoiceLogprobs as OpenAIChoiceLogprobs,
|
5
10
|
)
|
6
|
-
from openai.types import CompletionUsage as ChatCompletionUsage
|
7
|
-
from openai.types.chat.chat_completion import ChatCompletion
|
8
11
|
from openai.types.chat.chat_completion_assistant_message_param import (
|
9
|
-
ChatCompletionAssistantMessageParam,
|
12
|
+
ChatCompletionAssistantMessageParam as OpenAIAssistantMessageParam,
|
13
|
+
)
|
14
|
+
from openai.types.chat.chat_completion_chunk import (
|
15
|
+
ChatCompletionChunk as OpenAICompletionChunk,
|
16
|
+
)
|
17
|
+
from openai.types.chat.chat_completion_chunk import (
|
18
|
+
Choice as CompletionChunkChoice,
|
19
|
+
)
|
20
|
+
from openai.types.chat.chat_completion_chunk import (
|
21
|
+
Choice as OpenAIChunkChoice,
|
22
|
+
)
|
23
|
+
from openai.types.chat.chat_completion_chunk import (
|
24
|
+
ChoiceDelta as CompletionChunkChoiceDelta,
|
25
|
+
)
|
26
|
+
from openai.types.chat.chat_completion_chunk import (
|
27
|
+
ChoiceDelta as OpenAIChunkChoiceDelta,
|
28
|
+
)
|
29
|
+
from openai.types.chat.chat_completion_chunk import (
|
30
|
+
ChoiceDeltaToolCall as OpenAIChunkChoiceDeltaToolCall,
|
10
31
|
)
|
11
|
-
from openai.types.chat.chat_completion_chunk import ChatCompletionChunk
|
12
32
|
from openai.types.chat.chat_completion_content_part_image_param import (
|
13
|
-
ChatCompletionContentPartImageParam,
|
33
|
+
ChatCompletionContentPartImageParam as OpenAIContentPartImageParam,
|
14
34
|
)
|
15
35
|
from openai.types.chat.chat_completion_content_part_image_param import (
|
16
|
-
ImageURL as
|
36
|
+
ImageURL as OpenAIImageURL,
|
17
37
|
)
|
18
38
|
from openai.types.chat.chat_completion_content_part_param import (
|
19
|
-
ChatCompletionContentPartParam,
|
39
|
+
ChatCompletionContentPartParam as OpenAIContentPartParam,
|
20
40
|
)
|
21
41
|
from openai.types.chat.chat_completion_content_part_text_param import (
|
22
|
-
ChatCompletionContentPartTextParam,
|
42
|
+
ChatCompletionContentPartTextParam as OpenAIContentPartTextParam,
|
23
43
|
)
|
24
44
|
from openai.types.chat.chat_completion_developer_message_param import (
|
25
|
-
ChatCompletionDeveloperMessageParam,
|
45
|
+
ChatCompletionDeveloperMessageParam as OpenAIDeveloperMessageParam,
|
26
46
|
)
|
27
47
|
from openai.types.chat.chat_completion_function_message_param import (
|
28
|
-
ChatCompletionFunctionMessageParam,
|
48
|
+
ChatCompletionFunctionMessageParam as OpenAIFunctionMessageParam,
|
49
|
+
)
|
50
|
+
from openai.types.chat.chat_completion_message import (
|
51
|
+
ChatCompletionMessage as OpenAIMessage,
|
29
52
|
)
|
30
|
-
from openai.types.chat.chat_completion_message import ChatCompletionMessage
|
31
53
|
from openai.types.chat.chat_completion_message_param import (
|
32
|
-
ChatCompletionMessageParam,
|
54
|
+
ChatCompletionMessageParam as OpenAIMessageParam,
|
33
55
|
)
|
34
56
|
from openai.types.chat.chat_completion_message_tool_call_param import (
|
35
|
-
ChatCompletionMessageToolCallParam,
|
57
|
+
ChatCompletionMessageToolCallParam as OpenAIToolCallParam,
|
36
58
|
)
|
37
59
|
from openai.types.chat.chat_completion_message_tool_call_param import (
|
38
|
-
Function as
|
60
|
+
Function as OpenAIToolCallFunction,
|
39
61
|
)
|
40
62
|
from openai.types.chat.chat_completion_named_tool_choice_param import (
|
41
|
-
ChatCompletionNamedToolChoiceParam,
|
63
|
+
ChatCompletionNamedToolChoiceParam as OpenAINamedToolChoiceParam,
|
42
64
|
)
|
43
65
|
from openai.types.chat.chat_completion_named_tool_choice_param import (
|
44
|
-
Function as
|
66
|
+
Function as OpenAINamedToolChoiceFunction,
|
45
67
|
)
|
46
68
|
from openai.types.chat.chat_completion_prediction_content_param import (
|
47
|
-
ChatCompletionPredictionContentParam,
|
69
|
+
ChatCompletionPredictionContentParam as OpenAIPredictionContentParam,
|
48
70
|
)
|
49
71
|
from openai.types.chat.chat_completion_stream_options_param import (
|
50
|
-
ChatCompletionStreamOptionsParam,
|
72
|
+
ChatCompletionStreamOptionsParam as OpenAIStreamOptionsParam,
|
51
73
|
)
|
52
74
|
from openai.types.chat.chat_completion_system_message_param import (
|
53
|
-
ChatCompletionSystemMessageParam,
|
75
|
+
ChatCompletionSystemMessageParam as OpenAISystemMessageParam,
|
54
76
|
)
|
55
77
|
from openai.types.chat.chat_completion_tool_choice_option_param import (
|
56
|
-
ChatCompletionToolChoiceOptionParam,
|
78
|
+
ChatCompletionToolChoiceOptionParam as OpenAIToolChoiceOptionParam,
|
57
79
|
)
|
58
80
|
from openai.types.chat.chat_completion_tool_message_param import (
|
59
|
-
ChatCompletionToolMessageParam,
|
81
|
+
ChatCompletionToolMessageParam as OpenAIToolMessageParam,
|
60
82
|
)
|
61
83
|
from openai.types.chat.chat_completion_tool_param import (
|
62
|
-
ChatCompletionToolParam,
|
84
|
+
ChatCompletionToolParam as OpenAIToolParam,
|
63
85
|
)
|
64
86
|
from openai.types.chat.chat_completion_user_message_param import (
|
65
|
-
ChatCompletionUserMessageParam,
|
87
|
+
ChatCompletionUserMessageParam as OpenAIUserMessageParam,
|
66
88
|
)
|
67
89
|
from openai.types.chat.parsed_chat_completion import (
|
68
|
-
ParsedChatCompletion,
|
69
|
-
ParsedChatCompletionMessage,
|
70
|
-
ParsedChoice,
|
71
|
-
)
|
72
|
-
from openai.types.shared_params.function_definition import (
|
73
|
-
FunctionDefinition as ChatCompletionFunctionDefinition,
|
90
|
+
ParsedChatCompletion as OpenAIParsedCompletion,
|
74
91
|
)
|
75
|
-
from openai.types.
|
76
|
-
|
92
|
+
from openai.types.chat.parsed_chat_completion import (
|
93
|
+
ParsedChatCompletionMessage as OpenAIParsedMessage,
|
77
94
|
)
|
78
|
-
from openai.types.
|
79
|
-
|
95
|
+
from openai.types.chat.parsed_chat_completion import (
|
96
|
+
ParsedChoice as OpenAIParsedChoice,
|
80
97
|
)
|
81
|
-
from openai.types.shared_params.
|
82
|
-
|
98
|
+
from openai.types.shared_params.function_definition import (
|
99
|
+
FunctionDefinition as OpenAIFunctionDefinition,
|
83
100
|
)
|
84
101
|
|
102
|
+
# from openai.beta.types.chat.
|
85
103
|
from .openai_llm import OpenAILLM, OpenAILLMSettings
|
86
104
|
|
87
105
|
__all__ = ["OpenAILLM", "OpenAILLMSettings"]
|
@@ -0,0 +1,78 @@
|
|
1
|
+
from ..typing.completion_chunk import (
|
2
|
+
CompletionChunk,
|
3
|
+
CompletionChunkChoice,
|
4
|
+
CompletionChunkChoiceDelta,
|
5
|
+
CompletionChunkDeltaToolCall,
|
6
|
+
)
|
7
|
+
from . import OpenAICompletionChunk # type: ignore[import]
|
8
|
+
from .completion_converters import from_api_completion_usage
|
9
|
+
|
10
|
+
|
11
|
+
def from_api_completion_chunk(
|
12
|
+
api_completion_chunk: OpenAICompletionChunk, name: str | None = None
|
13
|
+
) -> CompletionChunk:
|
14
|
+
if api_completion_chunk.choices is None: # type: ignore
|
15
|
+
raise RuntimeError(
|
16
|
+
f"Completion chunk API error: "
|
17
|
+
f"{getattr(api_completion_chunk, 'error', None)}"
|
18
|
+
)
|
19
|
+
|
20
|
+
choices: list[CompletionChunkChoice] = []
|
21
|
+
|
22
|
+
for api_choice in api_completion_chunk.choices:
|
23
|
+
index = api_choice.index
|
24
|
+
finish_reason = api_choice.finish_reason
|
25
|
+
|
26
|
+
if api_choice.delta is None: # type: ignore
|
27
|
+
raise RuntimeError(
|
28
|
+
"API returned None for delta content in completion chunk "
|
29
|
+
f"with finish_reason: {finish_reason}."
|
30
|
+
)
|
31
|
+
# if api_choice.delta.content is None:
|
32
|
+
# raise RuntimeError(
|
33
|
+
# "API returned None for delta content in completion chunk "
|
34
|
+
# f"with finish_reason: {finish_reason}."
|
35
|
+
# )
|
36
|
+
|
37
|
+
delta = CompletionChunkChoiceDelta(
|
38
|
+
content=api_choice.delta.content,
|
39
|
+
refusal=api_choice.delta.refusal,
|
40
|
+
role=api_choice.delta.role,
|
41
|
+
tool_calls=[
|
42
|
+
CompletionChunkDeltaToolCall(
|
43
|
+
id=tool_call.id,
|
44
|
+
index=tool_call.index,
|
45
|
+
tool_name=tool_call.function.name,
|
46
|
+
tool_arguments=tool_call.function.arguments,
|
47
|
+
)
|
48
|
+
for tool_call in (api_choice.delta.tool_calls or [])
|
49
|
+
if tool_call.function
|
50
|
+
],
|
51
|
+
)
|
52
|
+
|
53
|
+
choice = CompletionChunkChoice(
|
54
|
+
index=index,
|
55
|
+
delta=delta,
|
56
|
+
finish_reason=finish_reason,
|
57
|
+
# OpenAI logprobs have identical, but separately defined, types for
|
58
|
+
# completion chunks and completions
|
59
|
+
logprobs=api_choice.logprobs,
|
60
|
+
)
|
61
|
+
|
62
|
+
choices.append(choice)
|
63
|
+
|
64
|
+
usage = (
|
65
|
+
from_api_completion_usage(api_completion_chunk.usage)
|
66
|
+
if api_completion_chunk.usage
|
67
|
+
else None
|
68
|
+
)
|
69
|
+
|
70
|
+
return CompletionChunk(
|
71
|
+
id=api_completion_chunk.id,
|
72
|
+
model=api_completion_chunk.model,
|
73
|
+
name=name,
|
74
|
+
created=api_completion_chunk.created,
|
75
|
+
system_fingerprint=api_completion_chunk.system_fingerprint,
|
76
|
+
choices=choices,
|
77
|
+
usage=usage,
|
78
|
+
)
|
@@ -1,18 +1,34 @@
|
|
1
|
-
from
|
2
|
-
|
3
|
-
from ..typing.completion import Completion, CompletionChoice, CompletionChunk
|
4
|
-
from . import (
|
5
|
-
ChatCompletion,
|
6
|
-
ChatCompletionAsyncStream, # type: ignore[import]
|
7
|
-
ChatCompletionChunk,
|
8
|
-
)
|
1
|
+
from ..typing.completion import Completion, CompletionChoice, Usage
|
2
|
+
from . import OpenAICompletion, OpenAICompletionUsage
|
9
3
|
from .message_converters import from_api_assistant_message
|
10
4
|
|
11
5
|
|
6
|
+
def from_api_completion_usage(api_usage: OpenAICompletionUsage) -> Usage:
|
7
|
+
reasoning_tokens = None
|
8
|
+
cached_tokens = None
|
9
|
+
|
10
|
+
if api_usage.completion_tokens_details is not None:
|
11
|
+
reasoning_tokens = api_usage.completion_tokens_details.reasoning_tokens
|
12
|
+
if api_usage.prompt_tokens_details is not None:
|
13
|
+
cached_tokens = api_usage.prompt_tokens_details.cached_tokens
|
14
|
+
|
15
|
+
input_tokens = api_usage.prompt_tokens - (cached_tokens or 0)
|
16
|
+
output_tokens = api_usage.completion_tokens - (reasoning_tokens or 0)
|
17
|
+
|
18
|
+
return Usage(
|
19
|
+
input_tokens=input_tokens,
|
20
|
+
output_tokens=output_tokens,
|
21
|
+
reasoning_tokens=reasoning_tokens,
|
22
|
+
cached_tokens=cached_tokens,
|
23
|
+
)
|
24
|
+
|
25
|
+
|
12
26
|
def from_api_completion(
|
13
|
-
api_completion:
|
27
|
+
api_completion: OpenAICompletion, name: str | None = None
|
14
28
|
) -> Completion:
|
15
29
|
choices: list[CompletionChoice] = []
|
30
|
+
usage: Usage | None = None
|
31
|
+
|
16
32
|
if api_completion.choices is None: # type: ignore
|
17
33
|
# Some providers return None for the choices when there is an error
|
18
34
|
# TODO: add custom error types
|
@@ -20,36 +36,43 @@ def from_api_completion(
|
|
20
36
|
f"Completion API error: {getattr(api_completion, 'error', None)}"
|
21
37
|
)
|
22
38
|
for api_choice in api_completion.choices:
|
23
|
-
|
39
|
+
index = api_choice.index
|
24
40
|
finish_reason = api_choice.finish_reason
|
41
|
+
|
25
42
|
# Some providers return None for the message when finish_reason is other than "stop"
|
26
43
|
if api_choice.message is None: # type: ignore
|
27
44
|
raise RuntimeError(
|
28
45
|
f"API returned None for message with finish_reason: {finish_reason}"
|
29
46
|
)
|
30
|
-
message = from_api_assistant_message(
|
31
|
-
api_choice.message, api_completion.usage, model_id=model_id
|
32
|
-
)
|
33
|
-
choices.append(CompletionChoice(message=message, finish_reason=finish_reason))
|
34
47
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
def to_api_completion(completion: Completion) -> ChatCompletion:
|
39
|
-
raise NotImplementedError
|
48
|
+
# usage = from_api_completion_usage(api_usage) if api_usage else None
|
40
49
|
|
50
|
+
usage = (
|
51
|
+
from_api_completion_usage(api_completion.usage)
|
52
|
+
if api_completion.usage
|
53
|
+
else None
|
54
|
+
)
|
55
|
+
message = from_api_assistant_message(api_choice.message, name=name)
|
41
56
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
57
|
+
choices.append(
|
58
|
+
CompletionChoice(
|
59
|
+
index=index,
|
60
|
+
message=message,
|
61
|
+
finish_reason=finish_reason,
|
62
|
+
logprobs=api_choice.logprobs,
|
63
|
+
)
|
64
|
+
)
|
46
65
|
|
47
|
-
return
|
66
|
+
return Completion(
|
67
|
+
id=api_completion.id,
|
68
|
+
created=api_completion.created,
|
69
|
+
system_fingerprint=api_completion.system_fingerprint,
|
70
|
+
usage=usage,
|
71
|
+
choices=choices,
|
72
|
+
model=api_completion.model,
|
73
|
+
name=name,
|
74
|
+
)
|
48
75
|
|
49
76
|
|
50
|
-
|
51
|
-
|
52
|
-
model_id: str | None = None,
|
53
|
-
) -> AsyncIterator[CompletionChunk]:
|
54
|
-
async for api_chunk in api_completion_chunk_iterator:
|
55
|
-
yield from_api_completion_chunk(api_chunk, model_id=model_id)
|
77
|
+
def to_api_completion(completion: Completion) -> OpenAICompletion:
|
78
|
+
raise NotImplementedError
|
@@ -8,10 +8,10 @@ from ..typing.content import (
|
|
8
8
|
ImageData,
|
9
9
|
)
|
10
10
|
from . import (
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
11
|
+
OpenAIContentPartImageParam,
|
12
|
+
OpenAIContentPartParam,
|
13
|
+
OpenAIContentPartTextParam,
|
14
|
+
OpenAIImageURL,
|
15
15
|
)
|
16
16
|
|
17
17
|
BASE64_PREFIX = "data:image/jpeg;base64,"
|
@@ -26,7 +26,7 @@ def image_data_to_str(image_data: ImageData) -> str:
|
|
26
26
|
|
27
27
|
|
28
28
|
def from_api_content(
|
29
|
-
api_content: str | Iterable[
|
29
|
+
api_content: str | Iterable[OpenAIContentPartParam],
|
30
30
|
) -> "Content":
|
31
31
|
if isinstance(api_content, str):
|
32
32
|
return Content(parts=[ContentPartText(data=api_content)])
|
@@ -48,7 +48,7 @@ def from_api_content(
|
|
48
48
|
detail=detail,
|
49
49
|
)
|
50
50
|
else:
|
51
|
-
image_data = ImageData.from_url(img_url=url, detail=detail)
|
51
|
+
image_data = ImageData.from_url(img_url=url, detail=detail)
|
52
52
|
content_part = ContentPartImage(data=image_data)
|
53
53
|
|
54
54
|
content_parts.append(content_part) # type: ignore
|
@@ -58,19 +58,18 @@ def from_api_content(
|
|
58
58
|
|
59
59
|
def to_api_content(
|
60
60
|
content: Content,
|
61
|
-
) -> Iterable[
|
62
|
-
api_content: list[
|
61
|
+
) -> Iterable[OpenAIContentPartParam]:
|
62
|
+
api_content: list[OpenAIContentPartParam] = []
|
63
63
|
for content_part in content.parts:
|
64
|
-
api_content_part:
|
64
|
+
api_content_part: OpenAIContentPartParam
|
65
65
|
if isinstance(content_part, ContentPartText):
|
66
|
-
api_content_part =
|
67
|
-
type="text",
|
68
|
-
text=content_part.data,
|
66
|
+
api_content_part = OpenAIContentPartTextParam(
|
67
|
+
type="text", text=content_part.data
|
69
68
|
)
|
70
69
|
else:
|
71
|
-
api_content_part =
|
70
|
+
api_content_part = OpenAIContentPartImageParam(
|
72
71
|
type="image_url",
|
73
|
-
image_url=
|
72
|
+
image_url=OpenAIImageURL(
|
74
73
|
url=image_data_to_str(content_part.data),
|
75
74
|
detail=content_part.data.detail,
|
76
75
|
),
|