sequa 0.0.1__tar.gz → 0.0.2__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.
- {sequa-0.0.1 → sequa-0.0.2}/PKG-INFO +4 -1
- {sequa-0.0.1 → sequa-0.0.2}/pyproject.toml +4 -1
- {sequa-0.0.1 → sequa-0.0.2}/src/sequa/cassette.py +26 -0
- {sequa-0.0.1 → sequa-0.0.2}/src/sequa/llm/adapters/__init__.py +6 -0
- sequa-0.0.2/src/sequa/llm/adapters/anthropic.py +193 -0
- sequa-0.0.2/src/sequa/llm/adapters/chat.py +393 -0
- sequa-0.0.2/src/sequa/llm/adapters/patch_anthropic.py +70 -0
- sequa-0.0.2/src/sequa/llm/adapters/patch_openai.py +70 -0
- sequa-0.0.1/src/sequa/llm/adapters/chat.py +0 -238
- sequa-0.0.1/src/sequa/py.typed +0 -0
- {sequa-0.0.1 → sequa-0.0.2}/README.md +0 -0
- {sequa-0.0.1 → sequa-0.0.2}/src/sequa/__init__.py +0 -0
- {sequa-0.0.1 → sequa-0.0.2}/src/sequa/cli/main.py +0 -0
- {sequa-0.0.1 → sequa-0.0.2}/src/sequa/llm/adapters/base.py +0 -0
- {sequa-0.0.1 → sequa-0.0.2}/src/sequa/llm/adapters/monkey_patch.py +0 -0
- {sequa-0.0.1 → sequa-0.0.2}/src/sequa/llm/adapters/patch_groq.py +0 -0
- {sequa-0.0.1 → sequa-0.0.2}/src/sequa/matcher.py +0 -0
- {sequa-0.0.1 → sequa-0.0.2}/src/sequa/models.py +0 -0
- {sequa-0.0.1 → sequa-0.0.2}/src/sequa/recorder.py +0 -0
- {sequa-0.0.1 → sequa-0.0.2}/src/sequa/storage.py +0 -0
- {sequa-0.0.1 → sequa-0.0.2}/src/sequa/utils.py +0 -0
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: sequa
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.2
|
|
4
4
|
Summary: Add your description here
|
|
5
5
|
Author: thetechnoadvisor
|
|
6
6
|
Author-email: thetechnoadvisor <thetechnoadvisor@gmail.com>
|
|
7
7
|
Requires-Dist: python-dotenv>=1.0.0
|
|
8
|
+
Requires-Dist: langchain-core>=0.1.0
|
|
8
9
|
Requires-Dist: langchain-groq>=0.3.0
|
|
10
|
+
Requires-Dist: openai>=1.0.0
|
|
11
|
+
Requires-Dist: anthropic>=0.18.0
|
|
9
12
|
Requires-Python: >=3.12
|
|
10
13
|
Description-Content-Type: text/markdown
|
|
11
14
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sequa"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.2"
|
|
4
4
|
description = "Add your description here"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
@@ -9,7 +9,10 @@ authors = [
|
|
|
9
9
|
requires-python = ">=3.12"
|
|
10
10
|
dependencies = [
|
|
11
11
|
"python-dotenv>=1.0.0",
|
|
12
|
+
"langchain-core>=0.1.0",
|
|
12
13
|
"langchain-groq>=0.3.0",
|
|
14
|
+
"openai>=1.0.0",
|
|
15
|
+
"anthropic>=0.18.0",
|
|
13
16
|
]
|
|
14
17
|
|
|
15
18
|
[project.scripts]
|
|
@@ -50,6 +50,7 @@ class cassette:
|
|
|
50
50
|
normalizer=self.normalizer,
|
|
51
51
|
)
|
|
52
52
|
self.original_methods: list[tuple[type, str, Any]] = []
|
|
53
|
+
self.patchers: list[Any] = []
|
|
53
54
|
|
|
54
55
|
def __enter__(self) -> cassette:
|
|
55
56
|
# 1. Push self.engine onto thread-local context stack
|
|
@@ -103,6 +104,26 @@ class cassette:
|
|
|
103
104
|
except ImportError:
|
|
104
105
|
pass
|
|
105
106
|
|
|
107
|
+
# 3. Patch OpenAI if installed
|
|
108
|
+
try:
|
|
109
|
+
import openai
|
|
110
|
+
from sequa.llm.adapters.patch_openai import OpenAIMonkeyPatch
|
|
111
|
+
op_patcher = OpenAIMonkeyPatch()
|
|
112
|
+
op_patcher.patch()
|
|
113
|
+
self.patchers.append(op_patcher)
|
|
114
|
+
except ImportError:
|
|
115
|
+
pass
|
|
116
|
+
|
|
117
|
+
# 4. Patch Anthropic if installed
|
|
118
|
+
try:
|
|
119
|
+
import anthropic
|
|
120
|
+
from sequa.llm.adapters.patch_anthropic import AnthropicMonkeyPatch
|
|
121
|
+
ant_patcher = AnthropicMonkeyPatch()
|
|
122
|
+
ant_patcher.patch()
|
|
123
|
+
self.patchers.append(ant_patcher)
|
|
124
|
+
except ImportError:
|
|
125
|
+
pass
|
|
126
|
+
|
|
106
127
|
return self
|
|
107
128
|
|
|
108
129
|
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
@@ -115,6 +136,11 @@ class cassette:
|
|
|
115
136
|
setattr(cls, attr, original)
|
|
116
137
|
self.original_methods.clear()
|
|
117
138
|
|
|
139
|
+
# 3. Restore OpenAI and Anthropic patchers
|
|
140
|
+
for patcher in self.patchers:
|
|
141
|
+
patcher.restore()
|
|
142
|
+
self.patchers.clear()
|
|
143
|
+
|
|
118
144
|
def intercept(
|
|
119
145
|
self, fn: Callable[..., Any], *args: Any, **kwargs: Any
|
|
120
146
|
) -> dict[str, Any]:
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
from .base import AdapterRegistry, ProviderAdapter, CanonicalRequest, CanonicalResponse
|
|
2
2
|
from .chat import OpenAIAdapter, LangChainGroqAdapter
|
|
3
|
+
from .anthropic import AnthropicAdapter
|
|
3
4
|
from .monkey_patch import LangChainGroqMonkeyPatch
|
|
5
|
+
from .patch_openai import OpenAIMonkeyPatch
|
|
6
|
+
from .patch_anthropic import AnthropicMonkeyPatch
|
|
4
7
|
from ...cassette import cassette
|
|
5
8
|
|
|
6
9
|
|
|
@@ -15,7 +18,10 @@ __all__ = [
|
|
|
15
18
|
"CanonicalResponse",
|
|
16
19
|
"OpenAIAdapter",
|
|
17
20
|
"LangChainGroqAdapter",
|
|
21
|
+
"AnthropicAdapter",
|
|
18
22
|
"LangChainGroqMonkeyPatch",
|
|
23
|
+
"OpenAIMonkeyPatch",
|
|
24
|
+
"AnthropicMonkeyPatch",
|
|
19
25
|
"cassette",
|
|
20
26
|
"RequestInterceptor",
|
|
21
27
|
]
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
from sequa.llm.adapters.base import CanonicalRequest, CanonicalResponse, ProviderAdapter
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class AnthropicAdapter(ProviderAdapter):
|
|
8
|
+
provider_name: str = "anthropic"
|
|
9
|
+
|
|
10
|
+
def to_canonical_request(self, request: Any, **kwargs: Any) -> CanonicalRequest:
|
|
11
|
+
model = kwargs.get("model")
|
|
12
|
+
messages = kwargs.get("messages") or []
|
|
13
|
+
temperature = kwargs.get("temperature")
|
|
14
|
+
|
|
15
|
+
params = {k: v for k, v in kwargs.items() if k not in ("model", "messages", "temperature")}
|
|
16
|
+
|
|
17
|
+
normalized_messages = []
|
|
18
|
+
for msg in messages:
|
|
19
|
+
if isinstance(msg, dict):
|
|
20
|
+
normalized_messages.append({"role": msg.get("role"), "content": msg.get("content")})
|
|
21
|
+
else:
|
|
22
|
+
role = getattr(msg, "role", "user")
|
|
23
|
+
content = getattr(msg, "content", "")
|
|
24
|
+
normalized_messages.append({"role": role, "content": content})
|
|
25
|
+
|
|
26
|
+
return CanonicalRequest(
|
|
27
|
+
provider=self.provider_name,
|
|
28
|
+
model=model,
|
|
29
|
+
temperature=temperature,
|
|
30
|
+
messages=normalized_messages,
|
|
31
|
+
params=params,
|
|
32
|
+
metadata={"raw_request": kwargs},
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
def to_canonical_response(self, response: Any, **kwargs: Any) -> CanonicalResponse:
|
|
36
|
+
# Check if response is a dict or Anthropic Message object
|
|
37
|
+
if isinstance(response, dict):
|
|
38
|
+
resp_id = response.get("id")
|
|
39
|
+
model = response.get("model") or kwargs.get("model")
|
|
40
|
+
role = response.get("role", "assistant")
|
|
41
|
+
stop_reason = response.get("stop_reason")
|
|
42
|
+
stop_sequence = response.get("stop_sequence")
|
|
43
|
+
raw_content = response.get("content") or []
|
|
44
|
+
|
|
45
|
+
# Extract content string
|
|
46
|
+
content_text = ""
|
|
47
|
+
for item in raw_content:
|
|
48
|
+
if isinstance(item, dict):
|
|
49
|
+
if item.get("type") == "text":
|
|
50
|
+
content_text += item.get("text", "")
|
|
51
|
+
else:
|
|
52
|
+
content_text += getattr(item, "text", "")
|
|
53
|
+
|
|
54
|
+
# Extract usage
|
|
55
|
+
raw_usage = response.get("usage") or {}
|
|
56
|
+
usage = {
|
|
57
|
+
"input_tokens": raw_usage.get("input_tokens") or raw_usage.get("prompt_tokens") or 0,
|
|
58
|
+
"output_tokens": raw_usage.get("output_tokens") or raw_usage.get("completion_tokens") or 0,
|
|
59
|
+
}
|
|
60
|
+
else:
|
|
61
|
+
# It's an Anthropic Message object
|
|
62
|
+
resp_id = getattr(response, "id", None)
|
|
63
|
+
model = getattr(response, "model", None) or kwargs.get("model")
|
|
64
|
+
role = getattr(response, "role", "assistant")
|
|
65
|
+
stop_reason = getattr(response, "stop_reason", None)
|
|
66
|
+
stop_sequence = getattr(response, "stop_sequence", None)
|
|
67
|
+
raw_content = getattr(response, "content", [])
|
|
68
|
+
|
|
69
|
+
content_text = ""
|
|
70
|
+
for item in raw_content:
|
|
71
|
+
content_text += getattr(item, "text", "")
|
|
72
|
+
|
|
73
|
+
usage_obj = getattr(response, "usage", None)
|
|
74
|
+
usage = None
|
|
75
|
+
if usage_obj:
|
|
76
|
+
usage = {
|
|
77
|
+
"input_tokens": getattr(usage_obj, "input_tokens", 0),
|
|
78
|
+
"output_tokens": getattr(usage_obj, "output_tokens", 0),
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
# Format content block list for serialization
|
|
82
|
+
content_serializable = []
|
|
83
|
+
if isinstance(raw_content, list):
|
|
84
|
+
for item in raw_content:
|
|
85
|
+
if isinstance(item, dict):
|
|
86
|
+
content_serializable.append({
|
|
87
|
+
"type": item.get("type", "text"),
|
|
88
|
+
"text": item.get("text", ""),
|
|
89
|
+
})
|
|
90
|
+
else:
|
|
91
|
+
content_serializable.append({
|
|
92
|
+
"type": getattr(item, "type", "text"),
|
|
93
|
+
"text": getattr(item, "text", ""),
|
|
94
|
+
})
|
|
95
|
+
else:
|
|
96
|
+
content_serializable.append({
|
|
97
|
+
"type": "text",
|
|
98
|
+
"text": content_text,
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
return CanonicalResponse(
|
|
102
|
+
provider=self.provider_name,
|
|
103
|
+
model=model,
|
|
104
|
+
output=content_text,
|
|
105
|
+
usage=usage,
|
|
106
|
+
metadata={
|
|
107
|
+
"raw_response": {
|
|
108
|
+
"id": resp_id,
|
|
109
|
+
"model": model,
|
|
110
|
+
"role": role,
|
|
111
|
+
"type": "message",
|
|
112
|
+
"stop_reason": stop_reason,
|
|
113
|
+
"stop_sequence": stop_sequence,
|
|
114
|
+
"content": content_serializable,
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
def from_canonical_response(self, response: CanonicalResponse, request: Any) -> Any:
|
|
120
|
+
raw_resp = response.metadata.get("raw_response", {})
|
|
121
|
+
resp_id = raw_resp.get("id") or "replayed-response"
|
|
122
|
+
content_data = raw_resp.get("content") or []
|
|
123
|
+
usage_data = response.usage or {}
|
|
124
|
+
|
|
125
|
+
try:
|
|
126
|
+
from anthropic.types import Message, TextBlock, Usage
|
|
127
|
+
|
|
128
|
+
content_blocks = []
|
|
129
|
+
for item in content_data:
|
|
130
|
+
content_blocks.append(TextBlock(
|
|
131
|
+
text=item.get("text", response.output or ""),
|
|
132
|
+
type="text"
|
|
133
|
+
))
|
|
134
|
+
|
|
135
|
+
usage_obj = None
|
|
136
|
+
if usage_data:
|
|
137
|
+
usage_obj = Usage(
|
|
138
|
+
input_tokens=usage_data.get("input_tokens", 0),
|
|
139
|
+
output_tokens=usage_data.get("output_tokens", 0),
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
return Message(
|
|
143
|
+
id=resp_id,
|
|
144
|
+
content=content_blocks,
|
|
145
|
+
model=response.model or "replayed-model",
|
|
146
|
+
role=raw_resp.get("role", "assistant"),
|
|
147
|
+
stop_reason=raw_resp.get("stop_reason", "end_turn"),
|
|
148
|
+
stop_sequence=raw_resp.get("stop_sequence"),
|
|
149
|
+
type="message",
|
|
150
|
+
usage=usage_obj,
|
|
151
|
+
)
|
|
152
|
+
except ImportError:
|
|
153
|
+
class MockTextBlock:
|
|
154
|
+
def __init__(self, text: str):
|
|
155
|
+
self.text = text
|
|
156
|
+
self.type = "text"
|
|
157
|
+
|
|
158
|
+
class MockUsage:
|
|
159
|
+
def __init__(self, input_t: int, output_t: int):
|
|
160
|
+
self.input_tokens = input_t
|
|
161
|
+
self.output_tokens = output_t
|
|
162
|
+
|
|
163
|
+
class MockMessage:
|
|
164
|
+
def __init__(self, id: str, content: list[MockTextBlock], model: str, role: str, stop_reason: str, stop_sequence: str | None, usage: MockUsage | None):
|
|
165
|
+
self.id = id
|
|
166
|
+
self.content = content
|
|
167
|
+
self.model = model
|
|
168
|
+
self.role = role
|
|
169
|
+
self.stop_reason = stop_reason
|
|
170
|
+
self.stop_sequence = stop_sequence
|
|
171
|
+
self.type = "message"
|
|
172
|
+
self.usage = usage
|
|
173
|
+
|
|
174
|
+
content_blocks = []
|
|
175
|
+
for item in content_data:
|
|
176
|
+
content_blocks.append(MockTextBlock(text=item.get("text", response.output or "")))
|
|
177
|
+
|
|
178
|
+
usage_obj = None
|
|
179
|
+
if usage_data:
|
|
180
|
+
usage_obj = MockUsage(
|
|
181
|
+
usage_data.get("input_tokens") or usage_data.get("prompt_tokens") or 0,
|
|
182
|
+
usage_data.get("output_tokens") or usage_data.get("completion_tokens") or 0,
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
return MockMessage(
|
|
186
|
+
id=resp_id,
|
|
187
|
+
content=content_blocks,
|
|
188
|
+
model=response.model or "replayed-model",
|
|
189
|
+
role=raw_resp.get("role", "assistant"),
|
|
190
|
+
stop_reason=raw_resp.get("stop_reason", "end_turn"),
|
|
191
|
+
stop_sequence=raw_resp.get("stop_sequence"),
|
|
192
|
+
usage=usage_obj,
|
|
193
|
+
)
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from .base import CanonicalRequest, CanonicalResponse, ProviderAdapter
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class LangChainGroqAdapter(ProviderAdapter):
|
|
9
|
+
"""A generic chat-completions adapter that can be configured per provider."""
|
|
10
|
+
|
|
11
|
+
def __init__(
|
|
12
|
+
self, provider_name: str = "langchain_groq", response_object: str | None = None
|
|
13
|
+
) -> None:
|
|
14
|
+
self.provider_name = provider_name
|
|
15
|
+
self._response_object = response_object
|
|
16
|
+
|
|
17
|
+
def to_canonical_request(self, request: Any, **kwargs: Any) -> CanonicalRequest:
|
|
18
|
+
if isinstance(request, (tuple, list)) and len(request) > 0:
|
|
19
|
+
first_arg = request[0]
|
|
20
|
+
if hasattr(first_arg, "model_name"):
|
|
21
|
+
payload = first_arg
|
|
22
|
+
messages = request[1] if len(request) > 1 else []
|
|
23
|
+
else:
|
|
24
|
+
payload = first_arg
|
|
25
|
+
messages = []
|
|
26
|
+
else:
|
|
27
|
+
payload = request or {}
|
|
28
|
+
messages = []
|
|
29
|
+
|
|
30
|
+
model = getattr(payload, "model_name", None) or kwargs.get("model")
|
|
31
|
+
temperature = getattr(payload, "temperature", None) or kwargs.get("temperature")
|
|
32
|
+
|
|
33
|
+
if isinstance(payload, dict):
|
|
34
|
+
raw_payload = payload
|
|
35
|
+
if not messages:
|
|
36
|
+
messages = payload.get("messages") or kwargs.get("messages") or []
|
|
37
|
+
else:
|
|
38
|
+
raw_payload = {"input": payload}
|
|
39
|
+
if not messages:
|
|
40
|
+
messages = kwargs.get("messages") or []
|
|
41
|
+
|
|
42
|
+
if isinstance(messages, (list, tuple)):
|
|
43
|
+
messages_list = list(messages)
|
|
44
|
+
elif messages:
|
|
45
|
+
messages_list = [messages]
|
|
46
|
+
else:
|
|
47
|
+
messages_list = []
|
|
48
|
+
|
|
49
|
+
return CanonicalRequest(
|
|
50
|
+
provider=self.provider_name,
|
|
51
|
+
model=model,
|
|
52
|
+
temperature=temperature,
|
|
53
|
+
messages=messages_list,
|
|
54
|
+
params=kwargs,
|
|
55
|
+
metadata={"raw_request": raw_payload},
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
def to_canonical_response(self, response: Any, **kwargs: Any) -> CanonicalResponse:
|
|
59
|
+
payload = response or {}
|
|
60
|
+
if hasattr(payload, "content") and not isinstance(payload, dict):
|
|
61
|
+
content = getattr(payload, "content", None)
|
|
62
|
+
return CanonicalResponse(
|
|
63
|
+
provider=self.provider_name,
|
|
64
|
+
output=content,
|
|
65
|
+
model=payload.response_metadata.get("model_name"),
|
|
66
|
+
tool_calls=payload.tool_calls,
|
|
67
|
+
invalid_tool_calls=payload.invalid_tool_calls,
|
|
68
|
+
latency=payload.response_metadata.get("total_time"),
|
|
69
|
+
reasoning=(
|
|
70
|
+
payload.response_metadata.get("token_usage", {}).get("reasoning_content")
|
|
71
|
+
if payload.response_metadata.get("token_usage")
|
|
72
|
+
else None
|
|
73
|
+
),
|
|
74
|
+
usage=payload.usage_metadata,
|
|
75
|
+
metadata={
|
|
76
|
+
"raw_response": {
|
|
77
|
+
"id": getattr(payload, "id", None),
|
|
78
|
+
"response_metadata": getattr(payload, "response_metadata", {}),
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
choices = payload.get("choices", [])
|
|
84
|
+
first_choice = choices[0] if choices else {}
|
|
85
|
+
message = first_choice.get("message", {})
|
|
86
|
+
|
|
87
|
+
return CanonicalResponse(
|
|
88
|
+
provider=self.provider_name,
|
|
89
|
+
model=kwargs.get("model"),
|
|
90
|
+
output=message.get("content"),
|
|
91
|
+
usage=payload.get("usage"),
|
|
92
|
+
metadata={"raw_response": payload},
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
def from_canonical_response(self, response: CanonicalResponse, request: Any) -> Any:
|
|
96
|
+
raw_resp = response.metadata.get("raw_response")
|
|
97
|
+
resp_id = "replayed-response"
|
|
98
|
+
finish_reason = "stop"
|
|
99
|
+
if raw_resp:
|
|
100
|
+
if isinstance(raw_resp, dict):
|
|
101
|
+
resp_id = raw_resp.get("id", "replayed-response")
|
|
102
|
+
choices = raw_resp.get("choices", [])
|
|
103
|
+
if choices:
|
|
104
|
+
finish_reason = choices[0].get("finish_reason", "stop")
|
|
105
|
+
else:
|
|
106
|
+
resp_id = getattr(raw_resp, "id", "replayed-response")
|
|
107
|
+
response_metadata = getattr(raw_resp, "response_metadata", {})
|
|
108
|
+
finish_reason = response_metadata.get("finish_reason", "stop")
|
|
109
|
+
|
|
110
|
+
# Determine if we should return a LangChain AIMessage object
|
|
111
|
+
is_langchain = False
|
|
112
|
+
if request and isinstance(request, (tuple, list)) and len(request) > 0:
|
|
113
|
+
first_arg = request[0]
|
|
114
|
+
# If the class represents ChatGroq or a LangChain chat model
|
|
115
|
+
if hasattr(first_arg, "model_name") or hasattr(first_arg, "invoke"):
|
|
116
|
+
is_langchain = True
|
|
117
|
+
|
|
118
|
+
if raw_resp and hasattr(raw_resp, "content") and not isinstance(raw_resp, dict):
|
|
119
|
+
is_langchain = True
|
|
120
|
+
|
|
121
|
+
if is_langchain:
|
|
122
|
+
from langchain_core.messages import AIMessage
|
|
123
|
+
|
|
124
|
+
response_metadata = {}
|
|
125
|
+
if raw_resp:
|
|
126
|
+
if isinstance(raw_resp, dict):
|
|
127
|
+
response_metadata = dict(raw_resp.get("response_metadata", {}))
|
|
128
|
+
elif hasattr(raw_resp, "response_metadata"):
|
|
129
|
+
response_metadata = dict(getattr(raw_resp, "response_metadata", {}))
|
|
130
|
+
|
|
131
|
+
if not response_metadata:
|
|
132
|
+
response_metadata = {
|
|
133
|
+
"model_name": response.model,
|
|
134
|
+
"finish_reason": finish_reason,
|
|
135
|
+
}
|
|
136
|
+
if response.latency is not None:
|
|
137
|
+
response_metadata["total_time"] = response.latency
|
|
138
|
+
|
|
139
|
+
aimsg_kwargs: dict[str, Any] = {
|
|
140
|
+
"content": response.output or "",
|
|
141
|
+
"response_metadata": response_metadata,
|
|
142
|
+
"id": resp_id,
|
|
143
|
+
"tool_calls": response.tool_calls or [],
|
|
144
|
+
"invalid_tool_calls": response.invalid_tool_calls or [],
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if response.usage and isinstance(response.usage, dict):
|
|
148
|
+
input_t = response.usage.get("input_tokens") or response.usage.get("prompt_tokens") or 0
|
|
149
|
+
output_t = response.usage.get("output_tokens") or response.usage.get("completion_tokens") or 0
|
|
150
|
+
total_t = response.usage.get("total_tokens") or (input_t + output_t)
|
|
151
|
+
aimsg_kwargs["usage_metadata"] = {
|
|
152
|
+
"input_tokens": input_t,
|
|
153
|
+
"output_tokens": output_t,
|
|
154
|
+
"total_tokens": total_t,
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return AIMessage(**aimsg_kwargs)
|
|
158
|
+
|
|
159
|
+
payload: dict[str, Any] = {
|
|
160
|
+
"id": resp_id,
|
|
161
|
+
"choices": [
|
|
162
|
+
{
|
|
163
|
+
"message": {
|
|
164
|
+
"role": "assistant",
|
|
165
|
+
"content": response.output,
|
|
166
|
+
},
|
|
167
|
+
"finish_reason": finish_reason,
|
|
168
|
+
}
|
|
169
|
+
],
|
|
170
|
+
"usage": response.usage or {},
|
|
171
|
+
"model": response.model or "replayed-model",
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if self._response_object is not None:
|
|
175
|
+
payload["object"] = self._response_object
|
|
176
|
+
|
|
177
|
+
return payload
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class OpenAIAdapter(ProviderAdapter):
|
|
181
|
+
"""An OpenAI chat completions adapter."""
|
|
182
|
+
|
|
183
|
+
def __init__(self, response_object: str | None = "chat.completion") -> None:
|
|
184
|
+
self.provider_name = "openai"
|
|
185
|
+
self._response_object = response_object
|
|
186
|
+
|
|
187
|
+
def to_canonical_request(self, request: Any, **kwargs: Any) -> CanonicalRequest:
|
|
188
|
+
payload = request or {}
|
|
189
|
+
if not isinstance(payload, dict):
|
|
190
|
+
if isinstance(payload, (list, tuple)) and len(payload) > 0:
|
|
191
|
+
payload = payload[0] if isinstance(payload[0], dict) else {}
|
|
192
|
+
else:
|
|
193
|
+
payload = {}
|
|
194
|
+
|
|
195
|
+
model = kwargs.get("model") or payload.get("model")
|
|
196
|
+
messages = kwargs.get("messages") or payload.get("messages") or []
|
|
197
|
+
temperature = kwargs.get("temperature") or payload.get("temperature")
|
|
198
|
+
|
|
199
|
+
params = {k: v for k, v in kwargs.items() if k not in ("model", "messages")}
|
|
200
|
+
for k, v in payload.items():
|
|
201
|
+
if k not in ("model", "messages") and k not in params:
|
|
202
|
+
params[k] = v
|
|
203
|
+
|
|
204
|
+
normalized_messages = []
|
|
205
|
+
for msg in messages:
|
|
206
|
+
if isinstance(msg, dict):
|
|
207
|
+
normalized_messages.append({"role": msg.get("role"), "content": msg.get("content")})
|
|
208
|
+
else:
|
|
209
|
+
role = getattr(msg, "role", "user")
|
|
210
|
+
content = getattr(msg, "content", "")
|
|
211
|
+
normalized_messages.append({"role": role, "content": content})
|
|
212
|
+
|
|
213
|
+
return CanonicalRequest(
|
|
214
|
+
provider=self.provider_name,
|
|
215
|
+
model=model,
|
|
216
|
+
temperature=temperature,
|
|
217
|
+
messages=normalized_messages,
|
|
218
|
+
params=params,
|
|
219
|
+
metadata={"raw_request": kwargs},
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
def to_canonical_response(self, response: Any, **kwargs: Any) -> CanonicalResponse:
|
|
223
|
+
if isinstance(response, dict):
|
|
224
|
+
payload = response
|
|
225
|
+
choices = payload.get("choices", [])
|
|
226
|
+
first_choice = choices[0] if choices else {}
|
|
227
|
+
message = first_choice.get("message", {})
|
|
228
|
+
output = message.get("content")
|
|
229
|
+
model = payload.get("model") or kwargs.get("model")
|
|
230
|
+
usage = payload.get("usage")
|
|
231
|
+
resp_id = payload.get("id")
|
|
232
|
+
tool_calls = message.get("tool_calls") or []
|
|
233
|
+
else:
|
|
234
|
+
# It's a ChatCompletion object
|
|
235
|
+
resp_id = getattr(response, "id", None)
|
|
236
|
+
model = getattr(response, "model", None) or kwargs.get("model")
|
|
237
|
+
usage_obj = getattr(response, "usage", None)
|
|
238
|
+
usage = None
|
|
239
|
+
if usage_obj:
|
|
240
|
+
usage = {
|
|
241
|
+
"prompt_tokens": getattr(usage_obj, "prompt_tokens", 0),
|
|
242
|
+
"completion_tokens": getattr(usage_obj, "completion_tokens", 0),
|
|
243
|
+
"total_tokens": getattr(usage_obj, "total_tokens", 0),
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
choices = getattr(response, "choices", [])
|
|
247
|
+
first_choice = choices[0] if choices else None
|
|
248
|
+
output = None
|
|
249
|
+
tool_calls = []
|
|
250
|
+
if first_choice:
|
|
251
|
+
message_obj = getattr(first_choice, "message", None)
|
|
252
|
+
if message_obj:
|
|
253
|
+
output = getattr(message_obj, "content", None)
|
|
254
|
+
tool_calls = getattr(message_obj, "tool_calls", None) or []
|
|
255
|
+
|
|
256
|
+
# Convert choices list to serializable dictionary format
|
|
257
|
+
choices_serializable = []
|
|
258
|
+
for c in choices:
|
|
259
|
+
if isinstance(c, dict):
|
|
260
|
+
msg = c.get("message") or {}
|
|
261
|
+
choices_serializable.append({
|
|
262
|
+
"finish_reason": c.get("finish_reason", "stop"),
|
|
263
|
+
"message": {
|
|
264
|
+
"role": msg.get("role", "assistant"),
|
|
265
|
+
"content": msg.get("content", ""),
|
|
266
|
+
}
|
|
267
|
+
})
|
|
268
|
+
else:
|
|
269
|
+
msg_obj = getattr(c, "message", None)
|
|
270
|
+
choices_serializable.append({
|
|
271
|
+
"finish_reason": getattr(c, "finish_reason", "stop"),
|
|
272
|
+
"message": {
|
|
273
|
+
"role": getattr(msg_obj, "role", "assistant") if msg_obj else "assistant",
|
|
274
|
+
"content": getattr(msg_obj, "content", "") if msg_obj else "",
|
|
275
|
+
}
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
return CanonicalResponse(
|
|
279
|
+
provider=self.provider_name,
|
|
280
|
+
model=model,
|
|
281
|
+
output=output,
|
|
282
|
+
usage=usage,
|
|
283
|
+
tool_calls=list(tool_calls),
|
|
284
|
+
metadata={
|
|
285
|
+
"raw_response": {
|
|
286
|
+
"id": resp_id,
|
|
287
|
+
"model": model,
|
|
288
|
+
"choices": choices_serializable,
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
def from_canonical_response(self, response: CanonicalResponse, request: Any) -> Any:
|
|
294
|
+
raw_resp = response.metadata.get("raw_response", {})
|
|
295
|
+
resp_id = raw_resp.get("id") or "replayed-response"
|
|
296
|
+
choices_data = raw_resp.get("choices") or []
|
|
297
|
+
|
|
298
|
+
choices_list = []
|
|
299
|
+
for i, choice in enumerate(choices_data):
|
|
300
|
+
msg_data = choice.get("message", {})
|
|
301
|
+
choices_list.append({
|
|
302
|
+
"index": i,
|
|
303
|
+
"finish_reason": choice.get("finish_reason", "stop"),
|
|
304
|
+
"message": {
|
|
305
|
+
"role": msg_data.get("role", "assistant"),
|
|
306
|
+
"content": msg_data.get("content", response.output),
|
|
307
|
+
}
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
usage_data = response.usage or {}
|
|
311
|
+
|
|
312
|
+
try:
|
|
313
|
+
from openai.types.chat import ChatCompletion
|
|
314
|
+
from openai.types.chat.chat_completion import Choice, ChoiceMessage
|
|
315
|
+
from openai.types import CompletionUsage
|
|
316
|
+
|
|
317
|
+
choices = []
|
|
318
|
+
for item in choices_list:
|
|
319
|
+
msg = item["message"]
|
|
320
|
+
choices.append(Choice(
|
|
321
|
+
finish_reason=item["finish_reason"],
|
|
322
|
+
index=item["index"],
|
|
323
|
+
message=ChoiceMessage(
|
|
324
|
+
content=msg["content"],
|
|
325
|
+
role=msg["role"],
|
|
326
|
+
)
|
|
327
|
+
))
|
|
328
|
+
|
|
329
|
+
usage = None
|
|
330
|
+
if usage_data:
|
|
331
|
+
usage = CompletionUsage(
|
|
332
|
+
prompt_tokens=usage_data.get("prompt_tokens", 0),
|
|
333
|
+
completion_tokens=usage_data.get("completion_tokens", 0),
|
|
334
|
+
total_tokens=usage_data.get("total_tokens", 0),
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
return ChatCompletion(
|
|
338
|
+
id=resp_id,
|
|
339
|
+
choices=choices,
|
|
340
|
+
created=123456789,
|
|
341
|
+
model=response.model or "replayed-model",
|
|
342
|
+
object="chat.completion",
|
|
343
|
+
usage=usage,
|
|
344
|
+
)
|
|
345
|
+
except ImportError:
|
|
346
|
+
class MockMessage:
|
|
347
|
+
def __init__(self, role: str, content: str):
|
|
348
|
+
self.role = role
|
|
349
|
+
self.content = content
|
|
350
|
+
|
|
351
|
+
class MockChoice:
|
|
352
|
+
def __init__(self, index: int, finish_reason: str, message: MockMessage):
|
|
353
|
+
self.index = index
|
|
354
|
+
self.finish_reason = finish_reason
|
|
355
|
+
self.message = message
|
|
356
|
+
|
|
357
|
+
class MockUsage:
|
|
358
|
+
def __init__(self, prompt: int, completion: int, total: int):
|
|
359
|
+
self.prompt_tokens = prompt
|
|
360
|
+
self.completion_tokens = completion
|
|
361
|
+
self.total_tokens = total
|
|
362
|
+
|
|
363
|
+
class MockChatCompletion:
|
|
364
|
+
def __init__(self, id: str, choices: list[MockChoice], model: str, usage: MockUsage | None):
|
|
365
|
+
self.id = id
|
|
366
|
+
self.choices = choices
|
|
367
|
+
self.model = model
|
|
368
|
+
self.usage = usage
|
|
369
|
+
self.object = "chat.completion"
|
|
370
|
+
|
|
371
|
+
choices = []
|
|
372
|
+
for item in choices_list:
|
|
373
|
+
msg = item["message"]
|
|
374
|
+
choices.append(MockChoice(
|
|
375
|
+
index=item["index"],
|
|
376
|
+
finish_reason=item["finish_reason"],
|
|
377
|
+
message=MockMessage(role=msg["role"], content=msg["content"])
|
|
378
|
+
))
|
|
379
|
+
|
|
380
|
+
usage = None
|
|
381
|
+
if usage_data:
|
|
382
|
+
usage = MockUsage(
|
|
383
|
+
usage_data.get("prompt_tokens") or usage_data.get("input_tokens") or 0,
|
|
384
|
+
usage_data.get("completion_tokens") or usage_data.get("output_tokens") or 0,
|
|
385
|
+
usage_data.get("total_tokens") or 0,
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
return MockChatCompletion(
|
|
389
|
+
id=resp_id,
|
|
390
|
+
choices=choices,
|
|
391
|
+
model=response.model or "replayed-model",
|
|
392
|
+
usage=usage,
|
|
393
|
+
)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
from sequa.llm.adapters.anthropic import AnthropicAdapter
|
|
5
|
+
from sequa.cassette import get_active_engine
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class AnthropicMonkeyPatch:
|
|
9
|
+
def __init__(self) -> None:
|
|
10
|
+
self.adapter = AnthropicAdapter()
|
|
11
|
+
self.original_create = None
|
|
12
|
+
self.original_async_create = None
|
|
13
|
+
self.patched = False
|
|
14
|
+
|
|
15
|
+
def patch(self) -> None:
|
|
16
|
+
if self.patched:
|
|
17
|
+
return
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
from anthropic.resources.messages import Messages, AsyncMessages
|
|
21
|
+
|
|
22
|
+
# Sync Patch
|
|
23
|
+
self.original_create = Messages.create
|
|
24
|
+
|
|
25
|
+
def wrapped_create(self_obj: Messages, *args: Any, **kwargs: Any) -> Any:
|
|
26
|
+
active_engine = get_active_engine()
|
|
27
|
+
if active_engine is None:
|
|
28
|
+
return self.original_create(self_obj, *args, **kwargs)
|
|
29
|
+
|
|
30
|
+
def make_live_call(*a: Any, **kw: Any) -> Any:
|
|
31
|
+
return self.original_create(self_obj, *a, **kw)
|
|
32
|
+
|
|
33
|
+
return active_engine.handle_call(self.adapter, make_live_call, self_obj, *args, **kwargs)
|
|
34
|
+
|
|
35
|
+
wrapped_create.__llmcassette_patched__ = True
|
|
36
|
+
Messages.create = wrapped_create
|
|
37
|
+
|
|
38
|
+
# Async Patch
|
|
39
|
+
self.original_async_create = AsyncMessages.create
|
|
40
|
+
|
|
41
|
+
async def wrapped_async_create(self_obj: AsyncMessages, *args: Any, **kwargs: Any) -> Any:
|
|
42
|
+
active_engine = get_active_engine()
|
|
43
|
+
if active_engine is None:
|
|
44
|
+
return await self.original_async_create(self_obj, *args, **kwargs)
|
|
45
|
+
|
|
46
|
+
async def make_live_call(*a: Any, **kw: Any) -> Any:
|
|
47
|
+
return await self.original_async_create(self_obj, *a, **kw)
|
|
48
|
+
|
|
49
|
+
return await active_engine.handle_call_async(self.adapter, make_live_call, self_obj, *args, **kwargs)
|
|
50
|
+
|
|
51
|
+
wrapped_async_create.__llmcassette_patched__ = True
|
|
52
|
+
AsyncMessages.create = wrapped_async_create
|
|
53
|
+
|
|
54
|
+
self.patched = True
|
|
55
|
+
except ImportError:
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
def restore(self) -> None:
|
|
59
|
+
if not self.patched:
|
|
60
|
+
return
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
from anthropic.resources.messages import Messages, AsyncMessages
|
|
64
|
+
if self.original_create:
|
|
65
|
+
Messages.create = self.original_create
|
|
66
|
+
if self.original_async_create:
|
|
67
|
+
AsyncMessages.create = self.original_async_create
|
|
68
|
+
self.patched = False
|
|
69
|
+
except ImportError:
|
|
70
|
+
pass
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
from sequa.llm.adapters.chat import OpenAIAdapter
|
|
5
|
+
from sequa.cassette import get_active_engine
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class OpenAIMonkeyPatch:
|
|
9
|
+
def __init__(self) -> None:
|
|
10
|
+
self.adapter = OpenAIAdapter()
|
|
11
|
+
self.original_create = None
|
|
12
|
+
self.original_async_create = None
|
|
13
|
+
self.patched = False
|
|
14
|
+
|
|
15
|
+
def patch(self) -> None:
|
|
16
|
+
if self.patched:
|
|
17
|
+
return
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
from openai.resources.chat.completions import Completions, AsyncCompletions
|
|
21
|
+
|
|
22
|
+
# Sync Patch
|
|
23
|
+
self.original_create = Completions.create
|
|
24
|
+
|
|
25
|
+
def wrapped_create(self_obj: Completions, *args: Any, **kwargs: Any) -> Any:
|
|
26
|
+
active_engine = get_active_engine()
|
|
27
|
+
if active_engine is None:
|
|
28
|
+
return self.original_create(self_obj, *args, **kwargs)
|
|
29
|
+
|
|
30
|
+
def make_live_call(*a: Any, **kw: Any) -> Any:
|
|
31
|
+
return self.original_create(self_obj, *a, **kw)
|
|
32
|
+
|
|
33
|
+
return active_engine.handle_call(self.adapter, make_live_call, self_obj, *args, **kwargs)
|
|
34
|
+
|
|
35
|
+
wrapped_create.__llmcassette_patched__ = True
|
|
36
|
+
Completions.create = wrapped_create
|
|
37
|
+
|
|
38
|
+
# Async Patch
|
|
39
|
+
self.original_async_create = AsyncCompletions.create
|
|
40
|
+
|
|
41
|
+
async def wrapped_async_create(self_obj: AsyncCompletions, *args: Any, **kwargs: Any) -> Any:
|
|
42
|
+
active_engine = get_active_engine()
|
|
43
|
+
if active_engine is None:
|
|
44
|
+
return await self.original_async_create(self_obj, *args, **kwargs)
|
|
45
|
+
|
|
46
|
+
async def make_live_call(*a: Any, **kw: Any) -> Any:
|
|
47
|
+
return await self.original_async_create(self_obj, *a, **kw)
|
|
48
|
+
|
|
49
|
+
return await active_engine.handle_call_async(self.adapter, make_live_call, self_obj, *args, **kwargs)
|
|
50
|
+
|
|
51
|
+
wrapped_async_create.__llmcassette_patched__ = True
|
|
52
|
+
AsyncCompletions.create = wrapped_async_create
|
|
53
|
+
|
|
54
|
+
self.patched = True
|
|
55
|
+
except ImportError:
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
def restore(self) -> None:
|
|
59
|
+
if not self.patched:
|
|
60
|
+
return
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
from openai.resources.chat.completions import Completions, AsyncCompletions
|
|
64
|
+
if self.original_create:
|
|
65
|
+
Completions.create = self.original_create
|
|
66
|
+
if self.original_async_create:
|
|
67
|
+
AsyncCompletions.create = self.original_async_create
|
|
68
|
+
self.patched = False
|
|
69
|
+
except ImportError:
|
|
70
|
+
pass
|
|
@@ -1,238 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from typing import Any
|
|
4
|
-
|
|
5
|
-
from .base import CanonicalRequest, CanonicalResponse, ProviderAdapter
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class LangChainGroqAdapter(ProviderAdapter):
|
|
9
|
-
"""A generic chat-completions adapter that can be configured per provider."""
|
|
10
|
-
|
|
11
|
-
def __init__(
|
|
12
|
-
self, provider_name: str = "langchain_groq", response_object: str | None = None
|
|
13
|
-
) -> None:
|
|
14
|
-
self.provider_name = provider_name
|
|
15
|
-
self._response_object = response_object
|
|
16
|
-
|
|
17
|
-
def to_canonical_request(self, request: Any, **kwargs: Any) -> CanonicalRequest:
|
|
18
|
-
if isinstance(request, (tuple, list)) and len(request) > 0:
|
|
19
|
-
first_arg = request[0]
|
|
20
|
-
if hasattr(first_arg, "model_name"):
|
|
21
|
-
payload = first_arg
|
|
22
|
-
messages = request[1] if len(request) > 1 else []
|
|
23
|
-
else:
|
|
24
|
-
payload = first_arg
|
|
25
|
-
messages = []
|
|
26
|
-
else:
|
|
27
|
-
payload = request or {}
|
|
28
|
-
messages = []
|
|
29
|
-
|
|
30
|
-
model = getattr(payload, "model_name", None) or kwargs.get("model")
|
|
31
|
-
temperature = getattr(payload, "temperature", None) or kwargs.get("temperature")
|
|
32
|
-
|
|
33
|
-
if isinstance(payload, dict):
|
|
34
|
-
raw_payload = payload
|
|
35
|
-
if not messages:
|
|
36
|
-
messages = payload.get("messages") or kwargs.get("messages") or []
|
|
37
|
-
else:
|
|
38
|
-
raw_payload = {"input": payload}
|
|
39
|
-
if not messages:
|
|
40
|
-
messages = kwargs.get("messages") or []
|
|
41
|
-
|
|
42
|
-
if isinstance(messages, (list, tuple)):
|
|
43
|
-
messages_list = list(messages)
|
|
44
|
-
elif messages:
|
|
45
|
-
messages_list = [messages]
|
|
46
|
-
else:
|
|
47
|
-
messages_list = []
|
|
48
|
-
|
|
49
|
-
return CanonicalRequest(
|
|
50
|
-
provider=self.provider_name,
|
|
51
|
-
model=model,
|
|
52
|
-
temperature=temperature,
|
|
53
|
-
messages=messages_list,
|
|
54
|
-
params=kwargs,
|
|
55
|
-
metadata={"raw_request": raw_payload},
|
|
56
|
-
)
|
|
57
|
-
|
|
58
|
-
def to_canonical_response(self, response: Any, **kwargs: Any) -> CanonicalResponse:
|
|
59
|
-
payload = response or {}
|
|
60
|
-
if hasattr(payload, "content") and not isinstance(payload, dict):
|
|
61
|
-
content = getattr(payload, "content", None)
|
|
62
|
-
return CanonicalResponse(
|
|
63
|
-
provider=self.provider_name,
|
|
64
|
-
output=content,
|
|
65
|
-
model=payload.response_metadata.get("model_name"),
|
|
66
|
-
tool_calls=payload.tool_calls,
|
|
67
|
-
invalid_tool_calls=payload.invalid_tool_calls,
|
|
68
|
-
latency=payload.response_metadata.get("total_time"),
|
|
69
|
-
reasoning=(
|
|
70
|
-
payload.response_metadata.get("token_usage", {}).get("reasoning_content")
|
|
71
|
-
if payload.response_metadata.get("token_usage")
|
|
72
|
-
else None
|
|
73
|
-
),
|
|
74
|
-
usage=payload.usage_metadata,
|
|
75
|
-
metadata={
|
|
76
|
-
"raw_response": {
|
|
77
|
-
"id": getattr(payload, "id", None),
|
|
78
|
-
"response_metadata": getattr(payload, "response_metadata", {}),
|
|
79
|
-
}
|
|
80
|
-
},
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
choices = payload.get("choices", [])
|
|
84
|
-
first_choice = choices[0] if choices else {}
|
|
85
|
-
message = first_choice.get("message", {})
|
|
86
|
-
|
|
87
|
-
return CanonicalResponse(
|
|
88
|
-
provider=self.provider_name,
|
|
89
|
-
model=kwargs.get("model"),
|
|
90
|
-
output=message.get("content"),
|
|
91
|
-
usage=payload.get("usage"),
|
|
92
|
-
metadata={"raw_response": payload},
|
|
93
|
-
)
|
|
94
|
-
|
|
95
|
-
def from_canonical_response(self, response: CanonicalResponse, request: Any) -> Any:
|
|
96
|
-
raw_resp = response.metadata.get("raw_response")
|
|
97
|
-
resp_id = "replayed-response"
|
|
98
|
-
finish_reason = "stop"
|
|
99
|
-
if raw_resp:
|
|
100
|
-
if isinstance(raw_resp, dict):
|
|
101
|
-
resp_id = raw_resp.get("id", "replayed-response")
|
|
102
|
-
choices = raw_resp.get("choices", [])
|
|
103
|
-
if choices:
|
|
104
|
-
finish_reason = choices[0].get("finish_reason", "stop")
|
|
105
|
-
else:
|
|
106
|
-
resp_id = getattr(raw_resp, "id", "replayed-response")
|
|
107
|
-
response_metadata = getattr(raw_resp, "response_metadata", {})
|
|
108
|
-
finish_reason = response_metadata.get("finish_reason", "stop")
|
|
109
|
-
|
|
110
|
-
# Determine if we should return a LangChain AIMessage object
|
|
111
|
-
is_langchain = False
|
|
112
|
-
if request and isinstance(request, (tuple, list)) and len(request) > 0:
|
|
113
|
-
first_arg = request[0]
|
|
114
|
-
# If the class represents ChatGroq or a LangChain chat model
|
|
115
|
-
if hasattr(first_arg, "model_name") or hasattr(first_arg, "invoke"):
|
|
116
|
-
is_langchain = True
|
|
117
|
-
|
|
118
|
-
if raw_resp and hasattr(raw_resp, "content") and not isinstance(raw_resp, dict):
|
|
119
|
-
is_langchain = True
|
|
120
|
-
|
|
121
|
-
if is_langchain:
|
|
122
|
-
from langchain_core.messages import AIMessage
|
|
123
|
-
|
|
124
|
-
response_metadata = {}
|
|
125
|
-
if raw_resp:
|
|
126
|
-
if isinstance(raw_resp, dict):
|
|
127
|
-
response_metadata = dict(raw_resp.get("response_metadata", {}))
|
|
128
|
-
elif hasattr(raw_resp, "response_metadata"):
|
|
129
|
-
response_metadata = dict(getattr(raw_resp, "response_metadata", {}))
|
|
130
|
-
|
|
131
|
-
if not response_metadata:
|
|
132
|
-
response_metadata = {
|
|
133
|
-
"model_name": response.model,
|
|
134
|
-
"finish_reason": finish_reason,
|
|
135
|
-
}
|
|
136
|
-
if response.latency is not None:
|
|
137
|
-
response_metadata["total_time"] = response.latency
|
|
138
|
-
|
|
139
|
-
aimsg_kwargs: dict[str, Any] = {
|
|
140
|
-
"content": response.output or "",
|
|
141
|
-
"response_metadata": response_metadata,
|
|
142
|
-
"id": resp_id,
|
|
143
|
-
"tool_calls": response.tool_calls or [],
|
|
144
|
-
"invalid_tool_calls": response.invalid_tool_calls or [],
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
if response.usage and isinstance(response.usage, dict):
|
|
148
|
-
input_t = response.usage.get("input_tokens") or response.usage.get("prompt_tokens") or 0
|
|
149
|
-
output_t = response.usage.get("output_tokens") or response.usage.get("completion_tokens") or 0
|
|
150
|
-
total_t = response.usage.get("total_tokens") or (input_t + output_t)
|
|
151
|
-
aimsg_kwargs["usage_metadata"] = {
|
|
152
|
-
"input_tokens": input_t,
|
|
153
|
-
"output_tokens": output_t,
|
|
154
|
-
"total_tokens": total_t,
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
return AIMessage(**aimsg_kwargs)
|
|
158
|
-
|
|
159
|
-
payload: dict[str, Any] = {
|
|
160
|
-
"id": resp_id,
|
|
161
|
-
"choices": [
|
|
162
|
-
{
|
|
163
|
-
"message": {
|
|
164
|
-
"role": "assistant",
|
|
165
|
-
"content": response.output,
|
|
166
|
-
},
|
|
167
|
-
"finish_reason": finish_reason,
|
|
168
|
-
}
|
|
169
|
-
],
|
|
170
|
-
"usage": response.usage or {},
|
|
171
|
-
"model": response.model or "replayed-model",
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
if self._response_object is not None:
|
|
175
|
-
payload["object"] = self._response_object
|
|
176
|
-
|
|
177
|
-
return payload
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
class OpenAIAdapter(ProviderAdapter):
|
|
181
|
-
"""An OpenAI chat completions adapter."""
|
|
182
|
-
|
|
183
|
-
def __init__(self, response_object: str | None = "chat.completion") -> None:
|
|
184
|
-
self.provider_name = "openai"
|
|
185
|
-
self._response_object = response_object
|
|
186
|
-
|
|
187
|
-
def to_canonical_request(self, request: Any, **kwargs: Any) -> CanonicalRequest:
|
|
188
|
-
payload = request or {}
|
|
189
|
-
messages = payload.get("messages", [])
|
|
190
|
-
return CanonicalRequest(
|
|
191
|
-
provider=self.provider_name,
|
|
192
|
-
model=kwargs.get("model"),
|
|
193
|
-
messages=list(messages),
|
|
194
|
-
params=kwargs,
|
|
195
|
-
)
|
|
196
|
-
|
|
197
|
-
def to_canonical_response(self, response: Any, **kwargs: Any) -> CanonicalResponse:
|
|
198
|
-
payload = response or {}
|
|
199
|
-
choices = payload.get("choices", [])
|
|
200
|
-
first_choice = choices[0] if choices else {}
|
|
201
|
-
message = first_choice.get("message", {})
|
|
202
|
-
return CanonicalResponse(
|
|
203
|
-
provider=self.provider_name,
|
|
204
|
-
model=kwargs.get("model"),
|
|
205
|
-
output=message.get("content"),
|
|
206
|
-
usage=payload.get("usage"),
|
|
207
|
-
metadata={"raw_response": payload},
|
|
208
|
-
)
|
|
209
|
-
|
|
210
|
-
def from_canonical_response(self, response: CanonicalResponse, request: Any) -> Any:
|
|
211
|
-
raw_resp = response.metadata.get("raw_response")
|
|
212
|
-
resp_id = "replayed-response"
|
|
213
|
-
finish_reason = "stop"
|
|
214
|
-
if raw_resp and isinstance(raw_resp, dict):
|
|
215
|
-
resp_id = raw_resp.get("id", "replayed-response")
|
|
216
|
-
choices = raw_resp.get("choices", [])
|
|
217
|
-
if choices:
|
|
218
|
-
finish_reason = choices[0].get("finish_reason", "stop")
|
|
219
|
-
|
|
220
|
-
payload: dict[str, Any] = {
|
|
221
|
-
"id": resp_id,
|
|
222
|
-
"choices": [
|
|
223
|
-
{
|
|
224
|
-
"message": {
|
|
225
|
-
"role": "assistant",
|
|
226
|
-
"content": response.output,
|
|
227
|
-
},
|
|
228
|
-
"finish_reason": finish_reason,
|
|
229
|
-
}
|
|
230
|
-
],
|
|
231
|
-
"usage": response.usage or {},
|
|
232
|
-
"model": response.model or "replayed-model",
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
if self._response_object is not None:
|
|
236
|
-
payload["object"] = self._response_object
|
|
237
|
-
|
|
238
|
-
return payload
|
sequa-0.0.1/src/sequa/py.typed
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|