meshagent-openai 0.18.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.
- meshagent/openai/__init__.py +16 -0
- meshagent/openai/proxy/__init__.py +3 -0
- meshagent/openai/proxy/proxy.py +79 -0
- meshagent/openai/tools/__init__.py +18 -0
- meshagent/openai/tools/apply_patch.py +344 -0
- meshagent/openai/tools/completions_adapter.py +437 -0
- meshagent/openai/tools/responses_adapter.py +2369 -0
- meshagent/openai/tools/schema.py +253 -0
- meshagent/openai/tools/stt.py +118 -0
- meshagent/openai/tools/stt_test.py +87 -0
- meshagent/openai/version.py +1 -0
- meshagent_openai-0.18.0.dist-info/METADATA +50 -0
- meshagent_openai-0.18.0.dist-info/RECORD +16 -0
- meshagent_openai-0.18.0.dist-info/WHEEL +5 -0
- meshagent_openai-0.18.0.dist-info/licenses/LICENSE +201 -0
- meshagent_openai-0.18.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
def validate_response_format(response_format) -> str | None:
|
|
2
|
+
"""
|
|
3
|
+
Validates a response format according to the OpenAI Structured Outputs specification.
|
|
4
|
+
|
|
5
|
+
See https://platform.openai.com/docs/guides/structured-outputs for details.
|
|
6
|
+
|
|
7
|
+
Note: This code is up to date as of January 21, 2024
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
# Check that response_format is a dictionary
|
|
11
|
+
if not isinstance(response_format, dict):
|
|
12
|
+
return "Error: Response format must be a dictionary."
|
|
13
|
+
|
|
14
|
+
# Check that response_format contains exactly "type" and "json_schema" keys
|
|
15
|
+
if set(response_format.keys()) != {"type", "json_schema"}:
|
|
16
|
+
return (
|
|
17
|
+
"Error: Response format must contain exactly 'type' and 'json_schema' keys."
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
# Check that response format has type=json_schema
|
|
21
|
+
if "type" not in response_format or response_format["type"] != "json_schema":
|
|
22
|
+
return "Error: Response format must have type 'json_schema'."
|
|
23
|
+
|
|
24
|
+
# Check that the "json_schema" is a dict
|
|
25
|
+
if "json_schema" not in response_format or not isinstance(
|
|
26
|
+
response_format["json_schema"], dict
|
|
27
|
+
):
|
|
28
|
+
return "Error: 'json_schema' key must be a dictionary."
|
|
29
|
+
|
|
30
|
+
# Check that "json_schema" contains exactly "name" and "schema" keys, and optionally "description" and "strict" keys
|
|
31
|
+
required_keys = {"name", "schema"}
|
|
32
|
+
optional_keys = {"description", "strict"}
|
|
33
|
+
if set(response_format["json_schema"].keys()) != required_keys.union(optional_keys):
|
|
34
|
+
return "Error: 'json_schema' key must contain exactly 'name', 'schema', and optionally 'description' and 'strict' keys."
|
|
35
|
+
|
|
36
|
+
# Check that "json_schema" contains a "name" string
|
|
37
|
+
if "name" not in response_format["json_schema"] or not isinstance(
|
|
38
|
+
response_format["json_schema"]["name"], str
|
|
39
|
+
):
|
|
40
|
+
return "Error: 'name' key must be a string."
|
|
41
|
+
|
|
42
|
+
# Check that "description" is a string if present
|
|
43
|
+
if "description" in response_format["json_schema"] and not isinstance(
|
|
44
|
+
response_format["json_schema"]["description"], str
|
|
45
|
+
):
|
|
46
|
+
return "Error: 'description' key must be a string."
|
|
47
|
+
|
|
48
|
+
# Check that "json_schema" contains a "schema" dict
|
|
49
|
+
if "schema" not in response_format["json_schema"] or not isinstance(
|
|
50
|
+
response_format["json_schema"]["schema"], dict
|
|
51
|
+
):
|
|
52
|
+
return "Error: 'schema' key must be a dictionary."
|
|
53
|
+
|
|
54
|
+
# Check that "strict" is a bool if present
|
|
55
|
+
if "strict" in response_format["json_schema"] and not isinstance(
|
|
56
|
+
response_format["json_schema"]["strict"], bool
|
|
57
|
+
):
|
|
58
|
+
return "Error: 'strict' key must be a boolean."
|
|
59
|
+
|
|
60
|
+
return validate_schema(response_format["json_schema"]["schema"])
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def validate_schema(schema, path="root", depth=0, stats=None):
|
|
64
|
+
"""
|
|
65
|
+
Validates a JSON schema according to the OpenAI Structured Outputs specification.
|
|
66
|
+
|
|
67
|
+
See https://platform.openai.com/docs/guides/structured-outputs for details.
|
|
68
|
+
|
|
69
|
+
Note: This code is up to date as of January 21, 2024
|
|
70
|
+
"""
|
|
71
|
+
print(f"Validating schema at {path}...")
|
|
72
|
+
|
|
73
|
+
# Initialize stats
|
|
74
|
+
if stats is None:
|
|
75
|
+
stats = {
|
|
76
|
+
"total_properties": 0,
|
|
77
|
+
"total_enum_values": 0,
|
|
78
|
+
"total_enum_string_length": 0,
|
|
79
|
+
"total_string_length": 0,
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
# Check root object type
|
|
83
|
+
if path == "root" and schema.get("type") != "object":
|
|
84
|
+
return f"Error at {path}: Root schema must be of type 'object'."
|
|
85
|
+
|
|
86
|
+
# Check for anyOf at root
|
|
87
|
+
if path == "root" and "anyOf" in schema:
|
|
88
|
+
return f"Error at {path}: Root schema must not use 'anyOf'."
|
|
89
|
+
|
|
90
|
+
# Check for required fields
|
|
91
|
+
if schema.get("type") == "object" and "properties" in schema:
|
|
92
|
+
if "required" not in schema or set(schema["required"]) != set(
|
|
93
|
+
schema["properties"].keys()
|
|
94
|
+
):
|
|
95
|
+
missing_keys = set(schema["properties"].keys()) - set(
|
|
96
|
+
schema.get("required", [])
|
|
97
|
+
)
|
|
98
|
+
return f"Error at {path}: All object properties must be required. Missing keys: {missing_keys}."
|
|
99
|
+
if (
|
|
100
|
+
"additionalProperties" not in schema
|
|
101
|
+
or schema["additionalProperties"] is not False
|
|
102
|
+
):
|
|
103
|
+
return f"Error at {path}: 'additionalProperties' must be set to false."
|
|
104
|
+
|
|
105
|
+
# Check for supported type
|
|
106
|
+
valid_types = {
|
|
107
|
+
"string",
|
|
108
|
+
"number",
|
|
109
|
+
"boolean",
|
|
110
|
+
"integer",
|
|
111
|
+
"object",
|
|
112
|
+
"array",
|
|
113
|
+
"enum",
|
|
114
|
+
"anyOf",
|
|
115
|
+
}
|
|
116
|
+
if "type" in schema:
|
|
117
|
+
schema_type = schema["type"]
|
|
118
|
+
if isinstance(schema_type, list):
|
|
119
|
+
if (
|
|
120
|
+
(len(schema_type) != 2)
|
|
121
|
+
or ("null" not in schema_type)
|
|
122
|
+
or not any(t in valid_types for t in schema_type if t != "null")
|
|
123
|
+
):
|
|
124
|
+
return f"Error at {path}: Invalid type list {schema_type}. Must contain exactly one valid type and None."
|
|
125
|
+
null_allowed = True
|
|
126
|
+
elif schema_type not in valid_types:
|
|
127
|
+
return f"Error at {path}: Invalid type '{schema_type}'. Must be one of {valid_types}."
|
|
128
|
+
else:
|
|
129
|
+
null_allowed = False
|
|
130
|
+
|
|
131
|
+
# Check that enum matches specified type
|
|
132
|
+
if "enum" in schema:
|
|
133
|
+
for enum in schema["enum"]:
|
|
134
|
+
if null_allowed and enum is None:
|
|
135
|
+
continue
|
|
136
|
+
if not null_allowed and enum is None:
|
|
137
|
+
return f"Error at {path}: Enum value cannot be null unless type is [..., null]."
|
|
138
|
+
|
|
139
|
+
schema_type = schema.get("type")
|
|
140
|
+
if isinstance(schema_type, list):
|
|
141
|
+
valid_type = next(t for t in schema_type if t != "null")
|
|
142
|
+
else:
|
|
143
|
+
valid_type = schema_type
|
|
144
|
+
|
|
145
|
+
if valid_type == "integer" and not isinstance(enum, int):
|
|
146
|
+
return f"Error at {path}: Enum value '{enum}' does not match type 'integer'."
|
|
147
|
+
if valid_type == "number" and not isinstance(enum, (int, float)):
|
|
148
|
+
return f"Error at {path}: Enum value '{enum}' does not match type 'number'."
|
|
149
|
+
if valid_type == "string" and not isinstance(enum, str):
|
|
150
|
+
return f"Error at {path}: Enum value '{enum}' does not match type 'string'."
|
|
151
|
+
if valid_type == "boolean" and not isinstance(enum, bool):
|
|
152
|
+
return f"Error at {path}: Enum value '{enum}' does not match type 'boolean'."
|
|
153
|
+
if valid_type == "object" and not isinstance(enum, dict):
|
|
154
|
+
return f"Error at {path}: Enum value '{enum}' does not match type 'object'."
|
|
155
|
+
if valid_type == "array" and not isinstance(enum, list):
|
|
156
|
+
return (
|
|
157
|
+
f"Error at {path}: Enum value '{enum}' does not match type 'array'."
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
# Check for unsupported keywords based on type
|
|
161
|
+
unsupported_keywords_by_type = {
|
|
162
|
+
"string": ["minLength", "maxLength", "pattern", "format"],
|
|
163
|
+
"number": ["minimum", "maximum", "multipleOf"],
|
|
164
|
+
"integer": ["minimum", "maximum", "multipleOf"],
|
|
165
|
+
"object": [
|
|
166
|
+
"patternProperties",
|
|
167
|
+
"unevaluatedProperties",
|
|
168
|
+
"propertyNames",
|
|
169
|
+
"minProperties",
|
|
170
|
+
"maxProperties",
|
|
171
|
+
],
|
|
172
|
+
"array": [
|
|
173
|
+
"unevaluatedItems",
|
|
174
|
+
"contains",
|
|
175
|
+
"minContains",
|
|
176
|
+
"maxContains",
|
|
177
|
+
"minItems",
|
|
178
|
+
"maxItems",
|
|
179
|
+
"uniqueItems",
|
|
180
|
+
],
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
schema_type = schema.get("type")
|
|
184
|
+
if isinstance(schema_type, list):
|
|
185
|
+
schema_type = next(t for t in schema_type if t != "null")
|
|
186
|
+
|
|
187
|
+
if schema_type in unsupported_keywords_by_type:
|
|
188
|
+
for keyword in unsupported_keywords_by_type[schema_type]:
|
|
189
|
+
if keyword in schema:
|
|
190
|
+
return f"Error at {path}: Unsupported keyword '{keyword}' found for type '{schema_type}'."
|
|
191
|
+
|
|
192
|
+
# Check for nesting depth
|
|
193
|
+
if depth > 5:
|
|
194
|
+
return f"Error at {path}: Exceeded maximum nesting depth of 5."
|
|
195
|
+
|
|
196
|
+
# Check for total properties
|
|
197
|
+
if schema.get("type") == "object":
|
|
198
|
+
stats["total_properties"] += len(schema.get("properties", {}))
|
|
199
|
+
if stats["total_properties"] > 100:
|
|
200
|
+
return "Error: Exceeded maximum of 100 object properties."
|
|
201
|
+
|
|
202
|
+
# Check for total string length
|
|
203
|
+
for key in schema.get("properties", {}):
|
|
204
|
+
stats["total_string_length"] += len(key)
|
|
205
|
+
for enum in schema.get("enum", []):
|
|
206
|
+
stats["total_enum_values"] += 1
|
|
207
|
+
stats["total_enum_string_length"] += len(str(enum)) if enum is not None else 4
|
|
208
|
+
if stats["total_string_length"] > 15000:
|
|
209
|
+
return "Error: Exceeded maximum total string length of 15,000 characters."
|
|
210
|
+
if stats["total_enum_values"] > 500:
|
|
211
|
+
return "Error: Exceeded maximum of 500 enum values."
|
|
212
|
+
if stats["total_enum_string_length"] > 7500 and stats["total_enum_values"] > 250:
|
|
213
|
+
return "Error: Exceeded maximum total enum string length of 7,500 characters for more than 250 enum values."
|
|
214
|
+
|
|
215
|
+
# Recursively validate nested schemas
|
|
216
|
+
if "properties" in schema:
|
|
217
|
+
for prop, subschema in schema["properties"].items():
|
|
218
|
+
result = validate_schema(
|
|
219
|
+
subschema, path=f"{path}.{prop}", depth=depth + 1, stats=stats
|
|
220
|
+
)
|
|
221
|
+
if result:
|
|
222
|
+
return result
|
|
223
|
+
|
|
224
|
+
if "anyOf" in schema:
|
|
225
|
+
for index, subschema in enumerate(schema["anyOf"]):
|
|
226
|
+
result = validate_schema(
|
|
227
|
+
subschema, path=f"{path}.anyOf[{index}]", depth=depth + 1, stats=stats
|
|
228
|
+
)
|
|
229
|
+
if result:
|
|
230
|
+
return result
|
|
231
|
+
|
|
232
|
+
if "$defs" in schema:
|
|
233
|
+
for def_name, subschema in schema["$defs"].items():
|
|
234
|
+
result = validate_schema(
|
|
235
|
+
subschema, path=f"{path}.$defs.{def_name}", depth=depth + 1, stats=stats
|
|
236
|
+
)
|
|
237
|
+
if result:
|
|
238
|
+
return result
|
|
239
|
+
|
|
240
|
+
if "items" in schema:
|
|
241
|
+
result = validate_schema(
|
|
242
|
+
schema["items"], path=f"{path}.items", depth=depth + 1, stats=stats
|
|
243
|
+
)
|
|
244
|
+
if result:
|
|
245
|
+
return result
|
|
246
|
+
|
|
247
|
+
return None
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def validate_strict_schema(schema) -> str | None:
|
|
251
|
+
return validate_response_format(
|
|
252
|
+
{"type": "json_schema", "name": "schema", "strict": True, "json_schema": schema}
|
|
253
|
+
)
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
from meshagent.tools import ToolContext, Tool, Toolkit, JsonResponse, TextResponse
|
|
2
|
+
from openai import AsyncOpenAI
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
from meshagent.openai.proxy import get_client
|
|
5
|
+
from typing import Optional
|
|
6
|
+
import io
|
|
7
|
+
import pathlib
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
async def _transcribe(
|
|
11
|
+
*,
|
|
12
|
+
client: AsyncOpenAI,
|
|
13
|
+
data: bytes,
|
|
14
|
+
model: str,
|
|
15
|
+
filename: str,
|
|
16
|
+
response_format: str,
|
|
17
|
+
timestamp_granularities: list[str] = None,
|
|
18
|
+
prompt: Optional[str] = None,
|
|
19
|
+
language: Optional[str] = None,
|
|
20
|
+
):
|
|
21
|
+
buf = io.BytesIO(data)
|
|
22
|
+
buf.name = filename
|
|
23
|
+
transcript: BaseModel = await client.audio.transcriptions.create(
|
|
24
|
+
model=model,
|
|
25
|
+
response_format=response_format,
|
|
26
|
+
file=buf,
|
|
27
|
+
prompt=prompt,
|
|
28
|
+
language=language,
|
|
29
|
+
timestamp_granularities=timestamp_granularities,
|
|
30
|
+
stream=False,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
if isinstance(transcript, str):
|
|
34
|
+
return TextResponse(text=transcript)
|
|
35
|
+
|
|
36
|
+
return JsonResponse(json=transcript.model_dump(mode="json"))
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class OpenAIAudioFileSTT(Tool):
|
|
40
|
+
def __init__(self, *, client: Optional[AsyncOpenAI] = None):
|
|
41
|
+
super().__init__(
|
|
42
|
+
name="openai-file-stt",
|
|
43
|
+
input_schema={
|
|
44
|
+
"type": "object",
|
|
45
|
+
"additionalProperties": False,
|
|
46
|
+
"required": [
|
|
47
|
+
"model",
|
|
48
|
+
"path",
|
|
49
|
+
"response_format",
|
|
50
|
+
"timestamp_granularities",
|
|
51
|
+
"prompt",
|
|
52
|
+
],
|
|
53
|
+
"properties": {
|
|
54
|
+
"path": {
|
|
55
|
+
"type": "string",
|
|
56
|
+
"description": "the path to a file in the room storage",
|
|
57
|
+
},
|
|
58
|
+
"prompt": {
|
|
59
|
+
"type": "string",
|
|
60
|
+
"description": "a prompt. can improve the accuracy of the transcript",
|
|
61
|
+
},
|
|
62
|
+
"model": {
|
|
63
|
+
"type": "string",
|
|
64
|
+
"enum": [
|
|
65
|
+
"whisper-1",
|
|
66
|
+
"gpt-4o-mini-transcribe",
|
|
67
|
+
"gpt-4o-transcribe",
|
|
68
|
+
],
|
|
69
|
+
},
|
|
70
|
+
"response_format": {
|
|
71
|
+
"type": "string",
|
|
72
|
+
"description": "text and json are supported for all models, srt, verbose_json, and vtt are only supported for whisper-1",
|
|
73
|
+
"enum": ["text", "json", "srt", "verbose_json", "vtt"],
|
|
74
|
+
},
|
|
75
|
+
"timestamp_granularities": {
|
|
76
|
+
"description": "timestamp_granularities are only valid with whisper-1",
|
|
77
|
+
"type": "array",
|
|
78
|
+
"items": {"type": "string", "enum": ["word", "segment"]},
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
title="OpenAI audio file STT",
|
|
83
|
+
description="transcribes an audio file to text",
|
|
84
|
+
)
|
|
85
|
+
self.client = client
|
|
86
|
+
|
|
87
|
+
async def execute(
|
|
88
|
+
self,
|
|
89
|
+
context: ToolContext,
|
|
90
|
+
*,
|
|
91
|
+
model: str,
|
|
92
|
+
prompt: str,
|
|
93
|
+
path: str,
|
|
94
|
+
response_format: str,
|
|
95
|
+
timestamp_granularities: list,
|
|
96
|
+
):
|
|
97
|
+
file_data = await context.room.storage.download(path=path)
|
|
98
|
+
client = self.client
|
|
99
|
+
if client is None:
|
|
100
|
+
client = get_client(room=context.room)
|
|
101
|
+
|
|
102
|
+
return await _transcribe(
|
|
103
|
+
client=client,
|
|
104
|
+
data=file_data.data,
|
|
105
|
+
model=model,
|
|
106
|
+
prompt=prompt,
|
|
107
|
+
filename=pathlib.Path(path).name,
|
|
108
|
+
response_format=response_format,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class OpenAISTTToolkit(Toolkit):
|
|
113
|
+
def __init__(self):
|
|
114
|
+
super().__init__(
|
|
115
|
+
name="openai-stt",
|
|
116
|
+
description="tools for speech to text using openai",
|
|
117
|
+
tools=[OpenAIAudioFileSTT()],
|
|
118
|
+
)
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import asyncio
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from openai import AsyncOpenAI
|
|
6
|
+
from meshagent.tools import JsonResponse, TextResponse
|
|
7
|
+
|
|
8
|
+
from .tts import _transcribe
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
################################################################################
|
|
12
|
+
# Fixtures
|
|
13
|
+
################################################################################
|
|
14
|
+
@pytest.fixture(scope="session")
|
|
15
|
+
def client() -> AsyncOpenAI:
|
|
16
|
+
"""Real async OpenAI client – no mocks, hits the network."""
|
|
17
|
+
return AsyncOpenAI(api_key=os.getenv("OPENAI_API_KEY"))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@pytest.fixture(scope="session")
|
|
21
|
+
def audio_bytes() -> bytes:
|
|
22
|
+
"""Loads the test clip only once per session."""
|
|
23
|
+
with open("harvard.wav", "rb") as fp:
|
|
24
|
+
return fp.read()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
################################################################################
|
|
28
|
+
# Tests – one for “text”, one for “json”. Add more if you need other formats.
|
|
29
|
+
################################################################################
|
|
30
|
+
@pytest.mark.asyncio
|
|
31
|
+
async def test_transcribe_text(client, audio_bytes):
|
|
32
|
+
"""_transcribe should return non-empty TextResponse for plain-text format."""
|
|
33
|
+
result = await asyncio.wait_for(
|
|
34
|
+
_transcribe(
|
|
35
|
+
client=client,
|
|
36
|
+
data=audio_bytes,
|
|
37
|
+
filename="harvard.wav",
|
|
38
|
+
model="gpt-4o-mini-transcribe",
|
|
39
|
+
prompt="",
|
|
40
|
+
response_format="text",
|
|
41
|
+
),
|
|
42
|
+
timeout=90,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
# Basic sanity checks
|
|
46
|
+
assert isinstance(result, TextResponse)
|
|
47
|
+
assert result.text.strip() != ""
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@pytest.mark.asyncio
|
|
51
|
+
async def test_transcribe_json(client, audio_bytes):
|
|
52
|
+
"""_transcribe should return a well-formed JsonResponse for JSON format."""
|
|
53
|
+
result = await asyncio.wait_for(
|
|
54
|
+
_transcribe(
|
|
55
|
+
client=client,
|
|
56
|
+
data=audio_bytes,
|
|
57
|
+
filename="harvard.wav",
|
|
58
|
+
model="gpt-4o-mini-transcribe",
|
|
59
|
+
prompt="",
|
|
60
|
+
response_format="json",
|
|
61
|
+
),
|
|
62
|
+
timeout=90,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# Basic sanity checks
|
|
66
|
+
assert isinstance(result, JsonResponse)
|
|
67
|
+
assert isinstance(result.json["text"], str)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@pytest.mark.asyncio
|
|
71
|
+
async def test_transcribe_verbose_json(client, audio_bytes):
|
|
72
|
+
"""_transcribe should return a well-formed JsonResponse for JSON format."""
|
|
73
|
+
result = await asyncio.wait_for(
|
|
74
|
+
_transcribe(
|
|
75
|
+
client=client,
|
|
76
|
+
data=audio_bytes,
|
|
77
|
+
filename="harvard.wav",
|
|
78
|
+
model="whisper-1",
|
|
79
|
+
prompt="",
|
|
80
|
+
response_format="verbose_json",
|
|
81
|
+
),
|
|
82
|
+
timeout=90,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
# Basic sanity checks
|
|
86
|
+
assert isinstance(result, JsonResponse)
|
|
87
|
+
assert isinstance(result.json["segments"], list)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.18.0"
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: meshagent-openai
|
|
3
|
+
Version: 0.18.0
|
|
4
|
+
Summary: OpenAI Building Blocks for Meshagent
|
|
5
|
+
License-Expression: Apache-2.0
|
|
6
|
+
Project-URL: Documentation, https://docs.meshagent.com
|
|
7
|
+
Project-URL: Website, https://www.meshagent.com
|
|
8
|
+
Project-URL: Source, https://www.meshagent.com
|
|
9
|
+
Requires-Python: >=3.13
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: pyjwt~=2.10
|
|
13
|
+
Requires-Dist: pytest~=8.4
|
|
14
|
+
Requires-Dist: pytest-asyncio~=0.26
|
|
15
|
+
Requires-Dist: openai~=2.6.0
|
|
16
|
+
Requires-Dist: meshagent-api~=0.18.0
|
|
17
|
+
Requires-Dist: meshagent-agents~=0.18.0
|
|
18
|
+
Requires-Dist: meshagent-tools~=0.18.0
|
|
19
|
+
Dynamic: license-file
|
|
20
|
+
|
|
21
|
+
# [Meshagent](https://www.meshagent.com)
|
|
22
|
+
|
|
23
|
+
## MeshAgent OpenAI
|
|
24
|
+
The ``meshagent.openai`` package provides adapters to integrate OpenAI models with MeshAgent tools and agents.
|
|
25
|
+
|
|
26
|
+
### Completions Adapter and Responses Adapter
|
|
27
|
+
MeshAgent supports both the OpenAI Chat Completions API and Responses API. It is recommended to use the Responses adapter given the newer OpenAI models and functionality use the Responses adapter.
|
|
28
|
+
|
|
29
|
+
- ``OpenAICompletionsAdapter``: wraps the OpenAI Chat Completions API. It turns Toolkit objects into OpenAI-style tool definitions and processes tool calls appropriately.
|
|
30
|
+
- ``OpenAIResponsesAdapter``: wraps the newer OpenAI Responses API. It collects tools, handles streaming events, and provides callbacks for advanced features like image generation or web search.
|
|
31
|
+
|
|
32
|
+
```Python Python
|
|
33
|
+
from meshagent.openai import OpenAIResponsesAdapter
|
|
34
|
+
from openai import AsyncOpenAI
|
|
35
|
+
|
|
36
|
+
# Use an OpenAI client inside a MeshAgent LLMAdapter
|
|
37
|
+
adapter = OpenAIResponsesAdapter(client=AsyncOpenAI(api_key="sk-..."))
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Tool Response Adapter
|
|
41
|
+
The ``OpenAICompletionsToolResponseAdapter`` and ``OpenAIResponsesToolResponseAdapter``convert a tool's structured response into plain text or JSOn that can beinserted into an OpenAI chat context.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
### Learn more about MeshAgent on our website or check out the docs for additional examples!
|
|
45
|
+
|
|
46
|
+
**Website**: [www.meshagent.com](https://www.meshagent.com/)
|
|
47
|
+
|
|
48
|
+
**Documentation**: [docs.meshagent.com](https://docs.meshagent.com/)
|
|
49
|
+
|
|
50
|
+
---
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
meshagent/openai/__init__.py,sha256=g4RSQWfL2El6HQ8i2Aw8wwBEJVC861Z61S0GqkFnBys,369
|
|
2
|
+
meshagent/openai/version.py,sha256=0EHw4xygmgkGSyfwNfEoMlQyN0uHxjHtlSFF79s6120,23
|
|
3
|
+
meshagent/openai/proxy/__init__.py,sha256=PkOCHmUptsbuX5sNlWJk5bMxnSzyg5AZhPtooEPV7XE,54
|
|
4
|
+
meshagent/openai/proxy/proxy.py,sha256=iTgk6ONcYUiOGjEownWW3JeeJ-zCyX28faUZ3oFu6fM,2635
|
|
5
|
+
meshagent/openai/tools/__init__.py,sha256=cLXoB9CBqKbCGhZMAJTIX6-yv_UO8AxpaH8vQQ1e8VY,467
|
|
6
|
+
meshagent/openai/tools/apply_patch.py,sha256=iSkZpyq4jaMYHs1lLZ8pkocqmDeuhPKxgzHHCsd7euU,10195
|
|
7
|
+
meshagent/openai/tools/completions_adapter.py,sha256=dBRXuWxc2LiWaTpA8agMhwxhRvbxbMnggvv_9QtK-HA,15946
|
|
8
|
+
meshagent/openai/tools/responses_adapter.py,sha256=x4XJLXLDeVHWt9QkPqHK0eSPd21L8C2KAuJhUQpt5RY,88900
|
|
9
|
+
meshagent/openai/tools/schema.py,sha256=YaP0iEL9Lf2qS4xZy8VILjr1IS52XS9LEcn_cskNreo,10079
|
|
10
|
+
meshagent/openai/tools/stt.py,sha256=H3YusIjigJwxfEdkrK5qZ6DHbjQagaLNj7q_-fTfwy4,3845
|
|
11
|
+
meshagent/openai/tools/stt_test.py,sha256=XE4qZBlNeEWdJW5NjBGyaJmuCKN0ZLlJ2b_GBp7MzVk,2651
|
|
12
|
+
meshagent_openai-0.18.0.dist-info/licenses/LICENSE,sha256=eTt0SPW-sVNdkZe9PS_S8WfCIyLjRXRl7sUBWdlteFg,10254
|
|
13
|
+
meshagent_openai-0.18.0.dist-info/METADATA,sha256=odoFXoF4tXmkqchyIO58E0gX7MWVjNKO9zPnRc2cBdw,2108
|
|
14
|
+
meshagent_openai-0.18.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
15
|
+
meshagent_openai-0.18.0.dist-info/top_level.txt,sha256=GlcXnHtRP6m7zlG3Df04M35OsHtNXy_DY09oFwWrH74,10
|
|
16
|
+
meshagent_openai-0.18.0.dist-info/RECORD,,
|