meshagent-openai 0.0.36__py3-none-any.whl → 0.0.38__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.
Potentially problematic release.
This version of meshagent-openai might be problematic. Click here for more details.
- meshagent/openai/__init__.py +16 -2
- meshagent/openai/proxy/__init__.py +2 -0
- meshagent/openai/proxy/proxy.py +7 -10
- meshagent/openai/tools/__init__.py +17 -2
- meshagent/openai/tools/completions_adapter.py +226 -139
- meshagent/openai/tools/responses_adapter.py +1028 -500
- meshagent/openai/tools/schema.py +100 -49
- meshagent/openai/tools/stt.py +67 -38
- meshagent/openai/tools/stt_test.py +6 -4
- meshagent/openai/version.py +1 -1
- meshagent_openai-0.0.38.dist-info/METADATA +50 -0
- meshagent_openai-0.0.38.dist-info/RECORD +15 -0
- meshagent_openai-0.0.36.dist-info/METADATA +0 -21
- meshagent_openai-0.0.36.dist-info/RECORD +0 -15
- {meshagent_openai-0.0.36.dist-info → meshagent_openai-0.0.38.dist-info}/WHEEL +0 -0
- {meshagent_openai-0.0.36.dist-info → meshagent_openai-0.0.38.dist-info}/licenses/LICENSE +0 -0
- {meshagent_openai-0.0.36.dist-info → meshagent_openai-0.0.38.dist-info}/top_level.txt +0 -0
meshagent/openai/tools/schema.py
CHANGED
|
@@ -1,60 +1,71 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
1
|
def validate_response_format(response_format) -> str | None:
|
|
4
2
|
"""
|
|
5
3
|
Validates a response format according to the OpenAI Structured Outputs specification.
|
|
6
|
-
|
|
4
|
+
|
|
7
5
|
See https://platform.openai.com/docs/guides/structured-outputs for details.
|
|
8
|
-
|
|
6
|
+
|
|
9
7
|
Note: This code is up to date as of January 21, 2024
|
|
10
8
|
"""
|
|
11
9
|
|
|
12
10
|
# Check that response_format is a dictionary
|
|
13
11
|
if not isinstance(response_format, dict):
|
|
14
12
|
return "Error: Response format must be a dictionary."
|
|
15
|
-
|
|
13
|
+
|
|
16
14
|
# Check that response_format contains exactly "type" and "json_schema" keys
|
|
17
15
|
if set(response_format.keys()) != {"type", "json_schema"}:
|
|
18
|
-
return
|
|
19
|
-
|
|
16
|
+
return (
|
|
17
|
+
"Error: Response format must contain exactly 'type' and 'json_schema' keys."
|
|
18
|
+
)
|
|
19
|
+
|
|
20
20
|
# Check that response format has type=json_schema
|
|
21
21
|
if "type" not in response_format or response_format["type"] != "json_schema":
|
|
22
22
|
return "Error: Response format must have type 'json_schema'."
|
|
23
|
-
|
|
23
|
+
|
|
24
24
|
# Check that the "json_schema" is a dict
|
|
25
|
-
if "json_schema" not in response_format or not isinstance(
|
|
25
|
+
if "json_schema" not in response_format or not isinstance(
|
|
26
|
+
response_format["json_schema"], dict
|
|
27
|
+
):
|
|
26
28
|
return "Error: 'json_schema' key must be a dictionary."
|
|
27
|
-
|
|
29
|
+
|
|
28
30
|
# Check that "json_schema" contains exactly "name" and "schema" keys, and optionally "description" and "strict" keys
|
|
29
31
|
required_keys = {"name", "schema"}
|
|
30
32
|
optional_keys = {"description", "strict"}
|
|
31
33
|
if set(response_format["json_schema"].keys()) != required_keys.union(optional_keys):
|
|
32
34
|
return "Error: 'json_schema' key must contain exactly 'name', 'schema', and optionally 'description' and 'strict' keys."
|
|
33
|
-
|
|
35
|
+
|
|
34
36
|
# Check that "json_schema" contains a "name" string
|
|
35
|
-
if "name" not in response_format["json_schema"] or not isinstance(
|
|
37
|
+
if "name" not in response_format["json_schema"] or not isinstance(
|
|
38
|
+
response_format["json_schema"]["name"], str
|
|
39
|
+
):
|
|
36
40
|
return "Error: 'name' key must be a string."
|
|
37
|
-
|
|
41
|
+
|
|
38
42
|
# Check that "description" is a string if present
|
|
39
|
-
if "description" in response_format["json_schema"] and not isinstance(
|
|
43
|
+
if "description" in response_format["json_schema"] and not isinstance(
|
|
44
|
+
response_format["json_schema"]["description"], str
|
|
45
|
+
):
|
|
40
46
|
return "Error: 'description' key must be a string."
|
|
41
|
-
|
|
47
|
+
|
|
42
48
|
# Check that "json_schema" contains a "schema" dict
|
|
43
|
-
if "schema" not in response_format["json_schema"] or not isinstance(
|
|
49
|
+
if "schema" not in response_format["json_schema"] or not isinstance(
|
|
50
|
+
response_format["json_schema"]["schema"], dict
|
|
51
|
+
):
|
|
44
52
|
return "Error: 'schema' key must be a dictionary."
|
|
45
|
-
|
|
53
|
+
|
|
46
54
|
# Check that "strict" is a bool if present
|
|
47
|
-
if "strict" in response_format["json_schema"] and not isinstance(
|
|
55
|
+
if "strict" in response_format["json_schema"] and not isinstance(
|
|
56
|
+
response_format["json_schema"]["strict"], bool
|
|
57
|
+
):
|
|
48
58
|
return "Error: 'strict' key must be a boolean."
|
|
49
59
|
|
|
50
60
|
return validate_schema(response_format["json_schema"]["schema"])
|
|
51
61
|
|
|
62
|
+
|
|
52
63
|
def validate_schema(schema, path="root", depth=0, stats=None):
|
|
53
64
|
"""
|
|
54
65
|
Validates a JSON schema according to the OpenAI Structured Outputs specification.
|
|
55
|
-
|
|
66
|
+
|
|
56
67
|
See https://platform.openai.com/docs/guides/structured-outputs for details.
|
|
57
|
-
|
|
68
|
+
|
|
58
69
|
Note: This code is up to date as of January 21, 2024
|
|
59
70
|
"""
|
|
60
71
|
print(f"Validating schema at {path}...")
|
|
@@ -65,7 +76,7 @@ def validate_schema(schema, path="root", depth=0, stats=None):
|
|
|
65
76
|
"total_properties": 0,
|
|
66
77
|
"total_enum_values": 0,
|
|
67
78
|
"total_enum_string_length": 0,
|
|
68
|
-
"total_string_length": 0
|
|
79
|
+
"total_string_length": 0,
|
|
69
80
|
}
|
|
70
81
|
|
|
71
82
|
# Check root object type
|
|
@@ -78,25 +89,45 @@ def validate_schema(schema, path="root", depth=0, stats=None):
|
|
|
78
89
|
|
|
79
90
|
# Check for required fields
|
|
80
91
|
if schema.get("type") == "object" and "properties" in schema:
|
|
81
|
-
if "required" not in schema or set(schema["required"]) != set(
|
|
82
|
-
|
|
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
|
+
)
|
|
83
98
|
return f"Error at {path}: All object properties must be required. Missing keys: {missing_keys}."
|
|
84
|
-
if
|
|
99
|
+
if (
|
|
100
|
+
"additionalProperties" not in schema
|
|
101
|
+
or schema["additionalProperties"] is not False
|
|
102
|
+
):
|
|
85
103
|
return f"Error at {path}: 'additionalProperties' must be set to false."
|
|
86
|
-
|
|
104
|
+
|
|
87
105
|
# Check for supported type
|
|
88
|
-
valid_types = {
|
|
106
|
+
valid_types = {
|
|
107
|
+
"string",
|
|
108
|
+
"number",
|
|
109
|
+
"boolean",
|
|
110
|
+
"integer",
|
|
111
|
+
"object",
|
|
112
|
+
"array",
|
|
113
|
+
"enum",
|
|
114
|
+
"anyOf",
|
|
115
|
+
}
|
|
89
116
|
if "type" in schema:
|
|
90
117
|
schema_type = schema["type"]
|
|
91
118
|
if isinstance(schema_type, list):
|
|
92
|
-
if (
|
|
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
|
+
):
|
|
93
124
|
return f"Error at {path}: Invalid type list {schema_type}. Must contain exactly one valid type and None."
|
|
94
125
|
null_allowed = True
|
|
95
126
|
elif schema_type not in valid_types:
|
|
96
127
|
return f"Error at {path}: Invalid type '{schema_type}'. Must be one of {valid_types}."
|
|
97
128
|
else:
|
|
98
129
|
null_allowed = False
|
|
99
|
-
|
|
130
|
+
|
|
100
131
|
# Check that enum matches specified type
|
|
101
132
|
if "enum" in schema:
|
|
102
133
|
for enum in schema["enum"]:
|
|
@@ -104,13 +135,13 @@ def validate_schema(schema, path="root", depth=0, stats=None):
|
|
|
104
135
|
continue
|
|
105
136
|
if not null_allowed and enum is None:
|
|
106
137
|
return f"Error at {path}: Enum value cannot be null unless type is [..., null]."
|
|
107
|
-
|
|
138
|
+
|
|
108
139
|
schema_type = schema.get("type")
|
|
109
140
|
if isinstance(schema_type, list):
|
|
110
141
|
valid_type = next(t for t in schema_type if t != "null")
|
|
111
142
|
else:
|
|
112
143
|
valid_type = schema_type
|
|
113
|
-
|
|
144
|
+
|
|
114
145
|
if valid_type == "integer" and not isinstance(enum, int):
|
|
115
146
|
return f"Error at {path}: Enum value '{enum}' does not match type 'integer'."
|
|
116
147
|
if valid_type == "number" and not isinstance(enum, (int, float)):
|
|
@@ -122,15 +153,31 @@ def validate_schema(schema, path="root", depth=0, stats=None):
|
|
|
122
153
|
if valid_type == "object" and not isinstance(enum, dict):
|
|
123
154
|
return f"Error at {path}: Enum value '{enum}' does not match type 'object'."
|
|
124
155
|
if valid_type == "array" and not isinstance(enum, list):
|
|
125
|
-
return
|
|
156
|
+
return (
|
|
157
|
+
f"Error at {path}: Enum value '{enum}' does not match type 'array'."
|
|
158
|
+
)
|
|
126
159
|
|
|
127
160
|
# Check for unsupported keywords based on type
|
|
128
161
|
unsupported_keywords_by_type = {
|
|
129
162
|
"string": ["minLength", "maxLength", "pattern", "format"],
|
|
130
163
|
"number": ["minimum", "maximum", "multipleOf"],
|
|
131
164
|
"integer": ["minimum", "maximum", "multipleOf"],
|
|
132
|
-
"object": [
|
|
133
|
-
|
|
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
|
+
],
|
|
134
181
|
}
|
|
135
182
|
|
|
136
183
|
schema_type = schema.get("type")
|
|
@@ -150,7 +197,7 @@ def validate_schema(schema, path="root", depth=0, stats=None):
|
|
|
150
197
|
if schema.get("type") == "object":
|
|
151
198
|
stats["total_properties"] += len(schema.get("properties", {}))
|
|
152
199
|
if stats["total_properties"] > 100:
|
|
153
|
-
return
|
|
200
|
+
return "Error: Exceeded maximum of 100 object properties."
|
|
154
201
|
|
|
155
202
|
# Check for total string length
|
|
156
203
|
for key in schema.get("properties", {}):
|
|
@@ -159,44 +206,48 @@ def validate_schema(schema, path="root", depth=0, stats=None):
|
|
|
159
206
|
stats["total_enum_values"] += 1
|
|
160
207
|
stats["total_enum_string_length"] += len(str(enum)) if enum is not None else 4
|
|
161
208
|
if stats["total_string_length"] > 15000:
|
|
162
|
-
return
|
|
209
|
+
return "Error: Exceeded maximum total string length of 15,000 characters."
|
|
163
210
|
if stats["total_enum_values"] > 500:
|
|
164
|
-
return
|
|
211
|
+
return "Error: Exceeded maximum of 500 enum values."
|
|
165
212
|
if stats["total_enum_string_length"] > 7500 and stats["total_enum_values"] > 250:
|
|
166
|
-
return
|
|
213
|
+
return "Error: Exceeded maximum total enum string length of 7,500 characters for more than 250 enum values."
|
|
167
214
|
|
|
168
215
|
# Recursively validate nested schemas
|
|
169
216
|
if "properties" in schema:
|
|
170
217
|
for prop, subschema in schema["properties"].items():
|
|
171
|
-
result = validate_schema(
|
|
218
|
+
result = validate_schema(
|
|
219
|
+
subschema, path=f"{path}.{prop}", depth=depth + 1, stats=stats
|
|
220
|
+
)
|
|
172
221
|
if result:
|
|
173
222
|
return result
|
|
174
223
|
|
|
175
224
|
if "anyOf" in schema:
|
|
176
225
|
for index, subschema in enumerate(schema["anyOf"]):
|
|
177
|
-
result = validate_schema(
|
|
226
|
+
result = validate_schema(
|
|
227
|
+
subschema, path=f"{path}.anyOf[{index}]", depth=depth + 1, stats=stats
|
|
228
|
+
)
|
|
178
229
|
if result:
|
|
179
230
|
return result
|
|
180
231
|
|
|
181
232
|
if "$defs" in schema:
|
|
182
233
|
for def_name, subschema in schema["$defs"].items():
|
|
183
|
-
result = validate_schema(
|
|
234
|
+
result = validate_schema(
|
|
235
|
+
subschema, path=f"{path}.$defs.{def_name}", depth=depth + 1, stats=stats
|
|
236
|
+
)
|
|
184
237
|
if result:
|
|
185
238
|
return result
|
|
186
239
|
|
|
187
240
|
if "items" in schema:
|
|
188
|
-
result = validate_schema(
|
|
241
|
+
result = validate_schema(
|
|
242
|
+
schema["items"], path=f"{path}.items", depth=depth + 1, stats=stats
|
|
243
|
+
)
|
|
189
244
|
if result:
|
|
190
245
|
return result
|
|
191
|
-
|
|
246
|
+
|
|
192
247
|
return None
|
|
193
248
|
|
|
194
249
|
|
|
195
250
|
def validate_strict_schema(schema) -> str | None:
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
"name" : "schema",
|
|
200
|
-
"strict" : True,
|
|
201
|
-
"json_schema" : schema
|
|
202
|
-
})
|
|
251
|
+
return validate_response_format(
|
|
252
|
+
{"type": "json_schema", "name": "schema", "strict": True, "json_schema": schema}
|
|
253
|
+
)
|
meshagent/openai/tools/stt.py
CHANGED
|
@@ -6,76 +6,107 @@ from typing import Optional
|
|
|
6
6
|
import io
|
|
7
7
|
import pathlib
|
|
8
8
|
|
|
9
|
-
async def _transcribe(*, client: AsyncOpenAI, data: bytes, model: str, filename: str, response_format: str, timestamp_granularities: list[str] = None, prompt: Optional[str] = None, language: Optional[str] = None):
|
|
10
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
|
+
):
|
|
11
21
|
buf = io.BytesIO(data)
|
|
12
22
|
buf.name = filename
|
|
13
|
-
transcript
|
|
23
|
+
transcript: BaseModel = await client.audio.transcriptions.create(
|
|
14
24
|
model=model,
|
|
15
25
|
response_format=response_format,
|
|
16
26
|
file=buf,
|
|
17
27
|
prompt=prompt,
|
|
18
28
|
language=language,
|
|
19
29
|
timestamp_granularities=timestamp_granularities,
|
|
20
|
-
stream=False
|
|
30
|
+
stream=False,
|
|
21
31
|
)
|
|
22
32
|
|
|
23
33
|
if isinstance(transcript, str):
|
|
24
34
|
return TextResponse(text=transcript)
|
|
25
|
-
|
|
35
|
+
|
|
26
36
|
return JsonResponse(json=transcript.model_dump(mode="json"))
|
|
27
37
|
|
|
38
|
+
|
|
28
39
|
class OpenAIAudioFileSTT(Tool):
|
|
29
40
|
def __init__(self, *, client: Optional[AsyncOpenAI] = None):
|
|
30
41
|
super().__init__(
|
|
31
42
|
name="openai-file-stt",
|
|
32
43
|
input_schema={
|
|
33
|
-
"type"
|
|
34
|
-
"additionalProperties"
|
|
35
|
-
"required"
|
|
36
|
-
|
|
37
|
-
"path"
|
|
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": {
|
|
38
55
|
"type": "string",
|
|
39
|
-
"description"
|
|
56
|
+
"description": "the path to a file in the room storage",
|
|
40
57
|
},
|
|
41
|
-
"prompt"
|
|
58
|
+
"prompt": {
|
|
42
59
|
"type": "string",
|
|
43
|
-
"description"
|
|
60
|
+
"description": "a prompt. can improve the accuracy of the transcript",
|
|
44
61
|
},
|
|
45
|
-
"model"
|
|
46
|
-
"type"
|
|
47
|
-
"enum"
|
|
48
|
-
"whisper-1",
|
|
49
|
-
|
|
62
|
+
"model": {
|
|
63
|
+
"type": "string",
|
|
64
|
+
"enum": [
|
|
65
|
+
"whisper-1",
|
|
66
|
+
"gpt-4o-mini-transcribe",
|
|
67
|
+
"gpt-4o-transcribe",
|
|
68
|
+
],
|
|
50
69
|
},
|
|
51
|
-
"response_format"
|
|
52
|
-
"type"
|
|
53
|
-
"description"
|
|
54
|
-
"enum"
|
|
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"],
|
|
55
74
|
},
|
|
56
|
-
"timestamp_granularities"
|
|
57
|
-
"description"
|
|
58
|
-
"type"
|
|
59
|
-
"items" :
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
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
|
+
},
|
|
65
81
|
},
|
|
66
82
|
title="OpenAI audio file STT",
|
|
67
|
-
description="transcribes an audio file to text"
|
|
83
|
+
description="transcribes an audio file to text",
|
|
68
84
|
)
|
|
69
85
|
self.client = client
|
|
70
86
|
|
|
71
|
-
async def execute(
|
|
72
|
-
|
|
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
|
+
):
|
|
73
97
|
file_data = await context.room.storage.download(path=path)
|
|
74
98
|
client = self.client
|
|
75
|
-
if client
|
|
99
|
+
if client is None:
|
|
76
100
|
client = get_client(room=context.room)
|
|
77
101
|
|
|
78
|
-
return await _transcribe(
|
|
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
|
+
)
|
|
79
110
|
|
|
80
111
|
|
|
81
112
|
class OpenAISTTToolkit(Toolkit):
|
|
@@ -83,7 +114,5 @@ class OpenAISTTToolkit(Toolkit):
|
|
|
83
114
|
super().__init__(
|
|
84
115
|
name="openai-stt",
|
|
85
116
|
description="tools for speech to text using openai",
|
|
86
|
-
tools=[
|
|
87
|
-
OpenAIAudioFileSTT()
|
|
88
|
-
]
|
|
117
|
+
tools=[OpenAIAudioFileSTT()],
|
|
89
118
|
)
|
|
@@ -7,6 +7,7 @@ from meshagent.tools import JsonResponse, TextResponse
|
|
|
7
7
|
|
|
8
8
|
from .tts import _transcribe
|
|
9
9
|
|
|
10
|
+
|
|
10
11
|
################################################################################
|
|
11
12
|
# Fixtures
|
|
12
13
|
################################################################################
|
|
@@ -45,6 +46,7 @@ async def test_transcribe_text(client, audio_bytes):
|
|
|
45
46
|
assert isinstance(result, TextResponse)
|
|
46
47
|
assert result.text.strip() != ""
|
|
47
48
|
|
|
49
|
+
|
|
48
50
|
@pytest.mark.asyncio
|
|
49
51
|
async def test_transcribe_json(client, audio_bytes):
|
|
50
52
|
"""_transcribe should return a well-formed JsonResponse for JSON format."""
|
|
@@ -55,7 +57,7 @@ async def test_transcribe_json(client, audio_bytes):
|
|
|
55
57
|
filename="harvard.wav",
|
|
56
58
|
model="gpt-4o-mini-transcribe",
|
|
57
59
|
prompt="",
|
|
58
|
-
response_format="json"
|
|
60
|
+
response_format="json",
|
|
59
61
|
),
|
|
60
62
|
timeout=90,
|
|
61
63
|
)
|
|
@@ -63,7 +65,8 @@ async def test_transcribe_json(client, audio_bytes):
|
|
|
63
65
|
# Basic sanity checks
|
|
64
66
|
assert isinstance(result, JsonResponse)
|
|
65
67
|
assert isinstance(result.json["text"], str)
|
|
66
|
-
|
|
68
|
+
|
|
69
|
+
|
|
67
70
|
@pytest.mark.asyncio
|
|
68
71
|
async def test_transcribe_verbose_json(client, audio_bytes):
|
|
69
72
|
"""_transcribe should return a well-formed JsonResponse for JSON format."""
|
|
@@ -74,7 +77,7 @@ async def test_transcribe_verbose_json(client, audio_bytes):
|
|
|
74
77
|
filename="harvard.wav",
|
|
75
78
|
model="whisper-1",
|
|
76
79
|
prompt="",
|
|
77
|
-
response_format="verbose_json"
|
|
80
|
+
response_format="verbose_json",
|
|
78
81
|
),
|
|
79
82
|
timeout=90,
|
|
80
83
|
)
|
|
@@ -82,4 +85,3 @@ async def test_transcribe_verbose_json(client, audio_bytes):
|
|
|
82
85
|
# Basic sanity checks
|
|
83
86
|
assert isinstance(result, JsonResponse)
|
|
84
87
|
assert isinstance(result.json["segments"], list)
|
|
85
|
-
|
meshagent/openai/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.0.
|
|
1
|
+
__version__ = "0.0.38"
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: meshagent-openai
|
|
3
|
+
Version: 0.0.38
|
|
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.12
|
|
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~=1.84
|
|
16
|
+
Requires-Dist: meshagent-api~=0.0.38
|
|
17
|
+
Requires-Dist: meshagent-agents~=0.0.38
|
|
18
|
+
Requires-Dist: meshagent-tools~=0.0.38
|
|
19
|
+
Dynamic: license-file
|
|
20
|
+
|
|
21
|
+
## MeshAgent OpenAI
|
|
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,15 @@
|
|
|
1
|
+
meshagent/openai/__init__.py,sha256=g4RSQWfL2El6HQ8i2Aw8wwBEJVC861Z61S0GqkFnBys,369
|
|
2
|
+
meshagent/openai/version.py,sha256=R5QxTjVaID7odO0eBWpOnyCjNQxBZ7cpyruM_NMOoDc,23
|
|
3
|
+
meshagent/openai/proxy/__init__.py,sha256=PkOCHmUptsbuX5sNlWJk5bMxnSzyg5AZhPtooEPV7XE,54
|
|
4
|
+
meshagent/openai/proxy/proxy.py,sha256=EC9LMQ6iv8bWnoGVFx78Sx7C_oaCi8Oq4bcMagleTiY,845
|
|
5
|
+
meshagent/openai/tools/__init__.py,sha256=cLXoB9CBqKbCGhZMAJTIX6-yv_UO8AxpaH8vQQ1e8VY,467
|
|
6
|
+
meshagent/openai/tools/completions_adapter.py,sha256=GEI0DcAnHH-FJBuWfxdd0xIseBDRq8szIT3n-mT0290,16546
|
|
7
|
+
meshagent/openai/tools/responses_adapter.py,sha256=wBcsFTk1BedWXqxBbO5HCRzi42XhD4EeUCrruuRTQWE,64600
|
|
8
|
+
meshagent/openai/tools/schema.py,sha256=YaP0iEL9Lf2qS4xZy8VILjr1IS52XS9LEcn_cskNreo,10079
|
|
9
|
+
meshagent/openai/tools/stt.py,sha256=H3YusIjigJwxfEdkrK5qZ6DHbjQagaLNj7q_-fTfwy4,3845
|
|
10
|
+
meshagent/openai/tools/stt_test.py,sha256=XE4qZBlNeEWdJW5NjBGyaJmuCKN0ZLlJ2b_GBp7MzVk,2651
|
|
11
|
+
meshagent_openai-0.0.38.dist-info/licenses/LICENSE,sha256=eTt0SPW-sVNdkZe9PS_S8WfCIyLjRXRl7sUBWdlteFg,10254
|
|
12
|
+
meshagent_openai-0.0.38.dist-info/METADATA,sha256=vCMh3a7WlEuVuvsMF9dirWwIVFCSFtAbNd-7uzwVsso,2086
|
|
13
|
+
meshagent_openai-0.0.38.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
14
|
+
meshagent_openai-0.0.38.dist-info/top_level.txt,sha256=GlcXnHtRP6m7zlG3Df04M35OsHtNXy_DY09oFwWrH74,10
|
|
15
|
+
meshagent_openai-0.0.38.dist-info/RECORD,,
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: meshagent-openai
|
|
3
|
-
Version: 0.0.36
|
|
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.12
|
|
10
|
-
Description-Content-Type: text/markdown
|
|
11
|
-
License-File: LICENSE
|
|
12
|
-
Requires-Dist: pyjwt~=2.10
|
|
13
|
-
Requires-Dist: pytest~=8.3
|
|
14
|
-
Requires-Dist: pytest-asyncio~=0.26
|
|
15
|
-
Requires-Dist: openai~=1.84
|
|
16
|
-
Requires-Dist: meshagent-api~=0.0.36
|
|
17
|
-
Requires-Dist: meshagent-agents~=0.0.36
|
|
18
|
-
Requires-Dist: meshagent-tools~=0.0.36
|
|
19
|
-
Dynamic: license-file
|
|
20
|
-
|
|
21
|
-
### Meshagent OpenAI
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
meshagent/openai/__init__.py,sha256=4JRby-ltGfJzrNYhJkMNIpVc2ml2zL_JkkFC0T1_8Vk,174
|
|
2
|
-
meshagent/openai/version.py,sha256=8XOR9xXboOEdDoZvWO2gEX-ufe6IVa50eWNDhT4ctHI,22
|
|
3
|
-
meshagent/openai/proxy/__init__.py,sha256=SqoueAmMXHbDKd8O4EeqGkI0gEiC3xLTLlpESGxySPU,30
|
|
4
|
-
meshagent/openai/proxy/proxy.py,sha256=JG3I6doIJXPkeZUWb6h93xEv5i1GO4I1cSuWDfLlbf8,883
|
|
5
|
-
meshagent/openai/tools/__init__.py,sha256=SRJpWc_L9jv1c8aBLULflDg8co1kaw2Ffnr6hDkYEwg,240
|
|
6
|
-
meshagent/openai/tools/completions_adapter.py,sha256=M8PpyaLu02QwrYkLB3c1h72J3wlmrK3UdfNKx6yUDJk,14483
|
|
7
|
-
meshagent/openai/tools/responses_adapter.py,sha256=ZsfWcmiasdpbiz27eaGpHN-_df2dD6Xpf4iCGbGNcGg,53608
|
|
8
|
-
meshagent/openai/tools/schema.py,sha256=7WvWFWK65G123G6ADxR27wA8vVpB_Twc3ZXlrYulMZg,9572
|
|
9
|
-
meshagent/openai/tools/stt.py,sha256=6Ig8h-0wO0OCG6WKikp15HGqIVBKAWrP8HLzQimuvNk,3611
|
|
10
|
-
meshagent/openai/tools/stt_test.py,sha256=FCTWZ7bI0vUnTRjRivO_5QEZqHaTE0ehNp1QQkx8iJ0,2651
|
|
11
|
-
meshagent_openai-0.0.36.dist-info/licenses/LICENSE,sha256=eTt0SPW-sVNdkZe9PS_S8WfCIyLjRXRl7sUBWdlteFg,10254
|
|
12
|
-
meshagent_openai-0.0.36.dist-info/METADATA,sha256=o34A3RrATuAbp3PasT_aiI4qN-Ytci5MPcvOoNuDCWQ,652
|
|
13
|
-
meshagent_openai-0.0.36.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
14
|
-
meshagent_openai-0.0.36.dist-info/top_level.txt,sha256=GlcXnHtRP6m7zlG3Df04M35OsHtNXy_DY09oFwWrH74,10
|
|
15
|
-
meshagent_openai-0.0.36.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|