interfaze 1.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
interfaze/__init__.py ADDED
@@ -0,0 +1,69 @@
1
+ """Official Interfaze SDK for Python."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from openai import (
6
+ APIConnectionError,
7
+ APIError,
8
+ APIStatusError,
9
+ APITimeoutError,
10
+ AuthenticationError,
11
+ BadRequestError,
12
+ ConflictError,
13
+ InternalServerError,
14
+ NotFoundError,
15
+ OpenAIError,
16
+ PermissionDeniedError,
17
+ RateLimitError,
18
+ UnprocessableEntityError,
19
+ )
20
+
21
+ from . import _inputs as inputs
22
+ from ._client import AsyncInterfaze, Interfaze
23
+ from ._constants import GUARD_CODES, INTERFAZE_BASE_URL, INTERFAZE_MODEL, TASK_NAMES
24
+ from ._errors import InterfazeError
25
+ from ._schema import empty_task_schema, response_format
26
+ from ._stream import AsyncInterfazeStream, InterfazeStream
27
+ from ._types import (
28
+ GuardCode,
29
+ InterfazeChatCompletion,
30
+ Precontext,
31
+ ReasoningEffort,
32
+ TaskName,
33
+ )
34
+
35
+ __version__ = "1.0.1"
36
+
37
+ __all__ = [
38
+ "Interfaze",
39
+ "AsyncInterfaze",
40
+ "InterfazeError",
41
+ "InterfazeChatCompletion",
42
+ "InterfazeStream",
43
+ "AsyncInterfazeStream",
44
+ "Precontext",
45
+ "TaskName",
46
+ "GuardCode",
47
+ "ReasoningEffort",
48
+ "response_format",
49
+ "empty_task_schema",
50
+ "inputs",
51
+ "TASK_NAMES",
52
+ "GUARD_CODES",
53
+ "INTERFAZE_MODEL",
54
+ "INTERFAZE_BASE_URL",
55
+ # openai re-exports
56
+ "OpenAIError",
57
+ "APIError",
58
+ "APIStatusError",
59
+ "APIConnectionError",
60
+ "APITimeoutError",
61
+ "BadRequestError",
62
+ "AuthenticationError",
63
+ "NotFoundError",
64
+ "ConflictError",
65
+ "RateLimitError",
66
+ "InternalServerError",
67
+ "PermissionDeniedError",
68
+ "UnprocessableEntityError",
69
+ ]
interfaze/_chat.py ADDED
@@ -0,0 +1,284 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Dict, Iterable, List, Literal, Optional, Union, cast, overload
4
+
5
+ from openai import AsyncOpenAI, AsyncStream, OpenAI, Stream
6
+ from openai.types.chat import ChatCompletion, ChatCompletionChunk, ParsedChatCompletion
7
+
8
+ from ._constants import INTERFAZE_MODEL
9
+ from ._errors import InterfazeError
10
+ from ._guard import guard_tag
11
+ from ._schema import empty_task_schema
12
+ from ._stream import AsyncInterfazeStream, InterfazeStream, strip_json_fence
13
+ from ._types import GuardCode, InterfazeChatCompletion, TaskName
14
+
15
+
16
+ def _is_non_empty_schema(rf: Any) -> bool:
17
+ schema = (rf or {}).get("json_schema", {}).get("schema", {}) if isinstance(rf, dict) else {}
18
+ props = schema.get("properties") if isinstance(schema, dict) else None
19
+ return bool(props)
20
+
21
+
22
+ def prepare(
23
+ messages: Iterable[Any],
24
+ model: Optional[str],
25
+ task: Optional[TaskName],
26
+ guard: Optional[List[GuardCode]],
27
+ response_format: Optional[Dict[str, Any]],
28
+ ) -> "tuple[List[Any], str, Optional[Dict[str, Any]], bool]":
29
+ rf = response_format
30
+ if task:
31
+ if rf and _is_non_empty_schema(rf):
32
+ raise InterfazeError(
33
+ "A non-empty `response_format` cannot be combined with `task` "
34
+ "(Interfaze runs tasks with raw output)."
35
+ )
36
+ rf = empty_task_schema()
37
+ tags = " ".join(
38
+ t for t in (f"<task>{task}</task>" if task else None, guard_tag(guard) if guard else None) if t
39
+ )
40
+ msgs = list(messages)
41
+ if tags:
42
+ idx = next(
43
+ (i for i, m in enumerate(msgs) if isinstance(m, dict) and m.get("role") == "system"),
44
+ None,
45
+ )
46
+ if idx is not None and isinstance(msgs[idx].get("content"), str):
47
+ merged = dict(msgs[idx])
48
+ existing = merged["content"]
49
+ merged["content"] = f"{tags}\n{existing}" if existing else tags
50
+ msgs[idx] = merged
51
+ else:
52
+ msgs = [{"role": "system", "content": tags}, *msgs]
53
+ strip = isinstance(rf, dict) and rf.get("type") == "json_object"
54
+ return msgs, (model or INTERFAZE_MODEL), rf, strip
55
+
56
+
57
+ def to_interfaze(raw: ChatCompletion, strip_fence: bool) -> InterfazeChatCompletion:
58
+ data = raw.model_dump()
59
+ if strip_fence:
60
+ try:
61
+ msg = data["choices"][0]["message"]
62
+ if isinstance(msg.get("content"), str):
63
+ msg["content"] = strip_json_fence(msg["content"])
64
+ except (KeyError, IndexError, TypeError):
65
+ pass
66
+ result = InterfazeChatCompletion.model_validate(data)
67
+ request_id = getattr(raw, "_request_id", None)
68
+ if request_id is not None:
69
+ result._request_id = request_id
70
+ return result
71
+
72
+
73
+ class _CompletionsBase:
74
+ def _kwargs(
75
+ self,
76
+ messages: Iterable[Any],
77
+ model: Optional[str],
78
+ task: Optional[TaskName],
79
+ guard: Optional[List[GuardCode]],
80
+ response_format: Optional[Dict[str, Any]],
81
+ extra: Dict[str, Any],
82
+ ) -> "tuple[Dict[str, Any], bool]":
83
+ msgs, mdl, rf, strip = prepare(messages, model, task, guard, response_format)
84
+ kw: Dict[str, Any] = {"model": mdl, "messages": msgs, **extra}
85
+ if rf is not None:
86
+ kw["response_format"] = rf
87
+ return kw, strip
88
+
89
+
90
+ class Completions(_CompletionsBase):
91
+ """`interfaze.chat.completions` (sync)."""
92
+
93
+ def __init__(self, client: OpenAI) -> None:
94
+ self._client = client
95
+
96
+ @overload
97
+ def create(
98
+ self,
99
+ *,
100
+ messages: Iterable[Any],
101
+ stream: Literal[False] = False,
102
+ model: str = ...,
103
+ task: Optional[TaskName] = ...,
104
+ guard: Optional[List[GuardCode]] = ...,
105
+ response_format: Optional[Dict[str, Any]] = ...,
106
+ **kwargs: Any,
107
+ ) -> InterfazeChatCompletion: ...
108
+ @overload
109
+ def create(
110
+ self,
111
+ *,
112
+ messages: Iterable[Any],
113
+ stream: Literal[True],
114
+ model: str = ...,
115
+ task: Optional[TaskName] = ...,
116
+ guard: Optional[List[GuardCode]] = ...,
117
+ response_format: Optional[Dict[str, Any]] = ...,
118
+ **kwargs: Any,
119
+ ) -> Stream[ChatCompletionChunk]: ...
120
+
121
+ def create(
122
+ self,
123
+ *,
124
+ messages: Iterable[Any],
125
+ stream: bool = False,
126
+ model: str = INTERFAZE_MODEL,
127
+ task: Optional[TaskName] = None,
128
+ guard: Optional[List[GuardCode]] = None,
129
+ response_format: Optional[Dict[str, Any]] = None,
130
+ **kwargs: Any,
131
+ ) -> "Union[InterfazeChatCompletion, Stream[ChatCompletionChunk]]":
132
+ kw, strip = self._kwargs(messages, model, task, guard, response_format, kwargs)
133
+ if stream:
134
+ return cast(
135
+ "Stream[ChatCompletionChunk]", self._client.chat.completions.create(stream=True, **kw)
136
+ )
137
+ raw = self._client.chat.completions.create(**kw)
138
+ return to_interfaze(cast(ChatCompletion, raw), strip)
139
+
140
+ def stream(
141
+ self,
142
+ *,
143
+ messages: Iterable[Any],
144
+ model: str = INTERFAZE_MODEL,
145
+ task: Optional[TaskName] = None,
146
+ guard: Optional[List[GuardCode]] = None,
147
+ response_format: Optional[Dict[str, Any]] = None,
148
+ **kwargs: Any,
149
+ ) -> InterfazeStream:
150
+ kw, strip = self._kwargs(messages, model, task, guard, response_format, kwargs)
151
+ return InterfazeStream(self._client, kw, strip)
152
+
153
+ def parse(
154
+ self,
155
+ *,
156
+ messages: Iterable[Any],
157
+ response_format: Any,
158
+ model: str = INTERFAZE_MODEL,
159
+ guard: Optional[List[GuardCode]] = None,
160
+ **kwargs: Any,
161
+ ) -> "ParsedChatCompletion[Any]":
162
+ """Structured output via a Pydantic model (delegates to the OpenAI client's ``parse``).
163
+
164
+ Interfaze extras (``vcache``/``precontext``) are present but untyped here; use ``create`` for
165
+ the typed extended completion.
166
+ """
167
+ msgs = prepare(messages, model, None, guard, None)[0]
168
+ return self._client.chat.completions.parse(
169
+ model=model, messages=msgs, response_format=response_format, **kwargs
170
+ )
171
+
172
+ @property
173
+ def with_raw_response(self) -> Any:
174
+ """Raw-HTTP escape hatch (delegates to the OpenAI client; bypasses task/guard preprocessing)."""
175
+ return self._client.chat.completions.with_raw_response
176
+
177
+ @property
178
+ def with_streaming_response(self) -> Any:
179
+ """Streaming raw-HTTP escape hatch (delegates to the OpenAI client)."""
180
+ return self._client.chat.completions.with_streaming_response
181
+
182
+
183
+ class AsyncCompletions(_CompletionsBase):
184
+ """`interfaze.chat.completions` (async)."""
185
+
186
+ def __init__(self, client: AsyncOpenAI) -> None:
187
+ self._client = client
188
+
189
+ @overload
190
+ async def create(
191
+ self,
192
+ *,
193
+ messages: Iterable[Any],
194
+ stream: Literal[False] = False,
195
+ model: str = ...,
196
+ task: Optional[TaskName] = ...,
197
+ guard: Optional[List[GuardCode]] = ...,
198
+ response_format: Optional[Dict[str, Any]] = ...,
199
+ **kwargs: Any,
200
+ ) -> InterfazeChatCompletion: ...
201
+ @overload
202
+ async def create(
203
+ self,
204
+ *,
205
+ messages: Iterable[Any],
206
+ stream: Literal[True],
207
+ model: str = ...,
208
+ task: Optional[TaskName] = ...,
209
+ guard: Optional[List[GuardCode]] = ...,
210
+ response_format: Optional[Dict[str, Any]] = ...,
211
+ **kwargs: Any,
212
+ ) -> AsyncStream[ChatCompletionChunk]: ...
213
+
214
+ async def create(
215
+ self,
216
+ *,
217
+ messages: Iterable[Any],
218
+ stream: bool = False,
219
+ model: str = INTERFAZE_MODEL,
220
+ task: Optional[TaskName] = None,
221
+ guard: Optional[List[GuardCode]] = None,
222
+ response_format: Optional[Dict[str, Any]] = None,
223
+ **kwargs: Any,
224
+ ) -> "Union[InterfazeChatCompletion, AsyncStream[ChatCompletionChunk]]":
225
+ kw, strip = self._kwargs(messages, model, task, guard, response_format, kwargs)
226
+ if stream:
227
+ return cast(
228
+ "AsyncStream[ChatCompletionChunk]",
229
+ await self._client.chat.completions.create(stream=True, **kw),
230
+ )
231
+ raw = await self._client.chat.completions.create(**kw)
232
+ return to_interfaze(cast(ChatCompletion, raw), strip)
233
+
234
+ def stream(
235
+ self,
236
+ *,
237
+ messages: Iterable[Any],
238
+ model: str = INTERFAZE_MODEL,
239
+ task: Optional[TaskName] = None,
240
+ guard: Optional[List[GuardCode]] = None,
241
+ response_format: Optional[Dict[str, Any]] = None,
242
+ **kwargs: Any,
243
+ ) -> AsyncInterfazeStream:
244
+ kw, strip = self._kwargs(messages, model, task, guard, response_format, kwargs)
245
+ return AsyncInterfazeStream(self._client, kw, strip)
246
+
247
+ async def parse(
248
+ self,
249
+ *,
250
+ messages: Iterable[Any],
251
+ response_format: Any,
252
+ model: str = INTERFAZE_MODEL,
253
+ guard: Optional[List[GuardCode]] = None,
254
+ **kwargs: Any,
255
+ ) -> "ParsedChatCompletion[Any]":
256
+ """Structured output via a Pydantic model (delegates to the OpenAI client's ``parse``).
257
+
258
+ Interfaze extras (``vcache``/``precontext``) are present but untyped here; use ``create`` for
259
+ the typed extended completion.
260
+ """
261
+ msgs = prepare(messages, model, None, guard, None)[0]
262
+ return await self._client.chat.completions.parse(
263
+ model=model, messages=msgs, response_format=response_format, **kwargs
264
+ )
265
+
266
+ @property
267
+ def with_raw_response(self) -> Any:
268
+ """Raw-HTTP escape hatch (delegates to the OpenAI client; bypasses task/guard preprocessing)."""
269
+ return self._client.chat.completions.with_raw_response
270
+
271
+ @property
272
+ def with_streaming_response(self) -> Any:
273
+ """Streaming raw-HTTP escape hatch (delegates to the OpenAI client)."""
274
+ return self._client.chat.completions.with_streaming_response
275
+
276
+
277
+ class Chat:
278
+ def __init__(self, client: OpenAI) -> None:
279
+ self.completions = Completions(client)
280
+
281
+
282
+ class AsyncChat:
283
+ def __init__(self, client: AsyncOpenAI) -> None:
284
+ self.completions = AsyncCompletions(client)
interfaze/_client.py ADDED
@@ -0,0 +1,108 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from typing import Any, Dict, Optional
5
+
6
+ from openai import AsyncOpenAI, OpenAI
7
+
8
+ from ._chat import AsyncChat, Chat
9
+ from ._constants import (
10
+ DEFAULT_TIMEOUT,
11
+ HEADER_ADMIN_KEY,
12
+ HEADER_BYPASS_CACHE,
13
+ HEADER_BYPASS_MOE,
14
+ HEADER_SHOW_ADDITIONAL_INFO,
15
+ INTERFAZE_BASE_URL,
16
+ )
17
+ from ._errors import InterfazeError
18
+ from ._tasks import AsyncTasks, Tasks
19
+
20
+
21
+ def _resolve_key(api_key: Optional[str]) -> str:
22
+ key = api_key or os.environ.get("INTERFAZE_API_KEY")
23
+ if not key:
24
+ raise InterfazeError(
25
+ "Missing API key. Pass Interfaze(api_key=...) or set the INTERFAZE_API_KEY environment variable."
26
+ )
27
+ return key
28
+
29
+
30
+ def _build_headers(
31
+ default_headers: Optional[Dict[str, str]],
32
+ show_additional_info: bool,
33
+ bypass_moe: bool,
34
+ bypass_cache: bool,
35
+ admin_key: Optional[str],
36
+ ) -> Dict[str, str]:
37
+ headers: Dict[str, str] = dict(default_headers or {})
38
+ if show_additional_info:
39
+ headers[HEADER_SHOW_ADDITIONAL_INFO] = "true"
40
+ if bypass_moe:
41
+ headers[HEADER_BYPASS_MOE] = "true"
42
+ if bypass_cache:
43
+ headers[HEADER_BYPASS_CACHE] = "true"
44
+ if admin_key:
45
+ headers[HEADER_ADMIN_KEY] = admin_key
46
+ return headers
47
+
48
+
49
+ class Interfaze:
50
+ """Synchronous Interfaze client — a curated wrapper over ``openai.OpenAI``.
51
+
52
+ Exposes the endpoints Interfaze implements (``chat.completions``, ``models``) plus task
53
+ helpers (``tasks.*``). The underlying client is available at ``.openai``.
54
+ """
55
+
56
+ def __init__(
57
+ self,
58
+ *,
59
+ api_key: Optional[str] = None,
60
+ base_url: Optional[str] = None,
61
+ show_additional_info: bool = False,
62
+ bypass_moe: bool = False,
63
+ bypass_cache: bool = False,
64
+ admin_key: Optional[str] = None,
65
+ default_headers: Optional[Dict[str, str]] = None,
66
+ **kwargs: Any,
67
+ ) -> None:
68
+ kwargs.setdefault("timeout", DEFAULT_TIMEOUT)
69
+ self.openai = OpenAI(
70
+ api_key=_resolve_key(api_key),
71
+ base_url=base_url or INTERFAZE_BASE_URL,
72
+ default_headers=_build_headers(
73
+ default_headers, show_additional_info, bypass_moe, bypass_cache, admin_key
74
+ ),
75
+ **kwargs,
76
+ )
77
+ self.chat = Chat(self.openai)
78
+ self.models = self.openai.models
79
+ self.tasks = Tasks(self.chat.completions)
80
+
81
+
82
+ class AsyncInterfaze:
83
+ """Asynchronous Interfaze client — a curated wrapper over ``openai.AsyncOpenAI``."""
84
+
85
+ def __init__(
86
+ self,
87
+ *,
88
+ api_key: Optional[str] = None,
89
+ base_url: Optional[str] = None,
90
+ show_additional_info: bool = False,
91
+ bypass_moe: bool = False,
92
+ bypass_cache: bool = False,
93
+ admin_key: Optional[str] = None,
94
+ default_headers: Optional[Dict[str, str]] = None,
95
+ **kwargs: Any,
96
+ ) -> None:
97
+ kwargs.setdefault("timeout", DEFAULT_TIMEOUT)
98
+ self.openai = AsyncOpenAI(
99
+ api_key=_resolve_key(api_key),
100
+ base_url=base_url or INTERFAZE_BASE_URL,
101
+ default_headers=_build_headers(
102
+ default_headers, show_additional_info, bypass_moe, bypass_cache, admin_key
103
+ ),
104
+ **kwargs,
105
+ )
106
+ self.chat = AsyncChat(self.openai)
107
+ self.models = self.openai.models
108
+ self.tasks = AsyncTasks(self.chat.completions)
@@ -0,0 +1,48 @@
1
+ from __future__ import annotations
2
+
3
+ INTERFAZE_BASE_URL = "https://api.interfaze.ai/v1"
4
+ INTERFAZE_MODEL = "interfaze-beta"
5
+ DEFAULT_TIMEOUT = 900.0
6
+
7
+ # Task names accepted in a <task>...</task> tag.
8
+ TASK_NAMES = (
9
+ "ocr",
10
+ "object_detection",
11
+ "gui_detection",
12
+ "web_search",
13
+ "scraper",
14
+ "translate",
15
+ "speech_to_text",
16
+ "forecast",
17
+ )
18
+
19
+ # Guardrail categories (ALL enables everything).
20
+ GUARD_CODES = (
21
+ "S1",
22
+ "S2",
23
+ "S3",
24
+ "S4",
25
+ "S5",
26
+ "S6",
27
+ "S7",
28
+ "S8",
29
+ "S9",
30
+ "S10",
31
+ "S11",
32
+ "S12",
33
+ "S13",
34
+ "S14",
35
+ "S1_IMAGE",
36
+ "S12_IMAGE",
37
+ "S15_IMAGE",
38
+ "ALL",
39
+ )
40
+
41
+ # Formats Interfaze rejects.
42
+ BLACKLISTED_FORMATS = ("image/gif", "image/avif")
43
+
44
+ # Interfaze control-plane headers.
45
+ HEADER_SHOW_ADDITIONAL_INFO = "x-show-additional-info"
46
+ HEADER_BYPASS_MOE = "x-bypass-moe"
47
+ HEADER_BYPASS_CACHE = "x-bypass-cache"
48
+ HEADER_ADMIN_KEY = "x-admin-key"
interfaze/_errors.py ADDED
@@ -0,0 +1,9 @@
1
+ from __future__ import annotations
2
+
3
+
4
+ class InterfazeError(Exception):
5
+ """SDK-level (client-side) error.
6
+
7
+ HTTP errors surface as the openai error classes (openai.BadRequestError, etc.),
8
+ which are re-exported from the package root.
9
+ """
interfaze/_guard.py ADDED
@@ -0,0 +1,21 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import List
4
+
5
+ from ._constants import GUARD_CODES
6
+ from ._errors import InterfazeError
7
+ from ._types import GuardCode
8
+
9
+ _VALID = set(GUARD_CODES)
10
+
11
+
12
+ def guard_tag(codes: List[GuardCode]) -> str:
13
+ """Serialize guardrail categories into the ``<guard>...</guard>`` tag, validating codes."""
14
+ if not codes:
15
+ raise InterfazeError("`guard` must contain at least one code")
16
+ invalid = [c for c in codes if c not in _VALID]
17
+ if invalid:
18
+ raise InterfazeError(
19
+ f"Invalid guard code(s): {', '.join(invalid)}. Valid codes: {', '.join(GUARD_CODES)}"
20
+ )
21
+ return f"<guard>{', '.join(codes)}</guard>"
interfaze/_inputs.py ADDED
@@ -0,0 +1,112 @@
1
+ from __future__ import annotations
2
+
3
+ import base64 as _base64
4
+ from pathlib import Path
5
+ from typing import Any, Dict, Optional, Union
6
+
7
+ from ._constants import BLACKLISTED_FORMATS
8
+ from ._errors import InterfazeError
9
+
10
+ BytesLike = Union[bytes, bytearray]
11
+
12
+ _EXT_MIME = {
13
+ "png": "image/png",
14
+ "jpg": "image/jpeg",
15
+ "jpeg": "image/jpeg",
16
+ "webp": "image/webp",
17
+ "gif": "image/gif",
18
+ "bmp": "image/bmp",
19
+ "heic": "image/heic",
20
+ "heif": "image/heif",
21
+ "pdf": "application/pdf",
22
+ "csv": "text/csv",
23
+ "tsv": "text/tab-separated-values",
24
+ "xml": "application/xml",
25
+ "json": "application/json",
26
+ "txt": "text/plain",
27
+ "md": "text/markdown",
28
+ "markdown": "text/markdown",
29
+ "yaml": "application/yaml",
30
+ "yml": "application/yaml",
31
+ "wav": "audio/wav",
32
+ "mp3": "audio/mpeg",
33
+ "m4a": "audio/mp4",
34
+ "ogg": "audio/ogg",
35
+ "flac": "audio/flac",
36
+ "mp4": "video/mp4",
37
+ "mov": "video/quicktime",
38
+ "webm": "video/webm",
39
+ "avi": "video/x-msvideo",
40
+ "mkv": "video/x-matroska",
41
+ "3gp": "video/3gpp",
42
+ }
43
+
44
+
45
+ def _mime_from_data_url(s: str) -> Optional[str]:
46
+ if s.startswith("data:"):
47
+ return s[5:].split(";")[0].split(",")[0] or None
48
+ return None
49
+
50
+
51
+ def _ext_of(url_or_name: str) -> Optional[str]:
52
+ base = url_or_name.split("?")[0].split("#")[0]
53
+ return base.rsplit(".", 1)[-1].lower() if "." in base else None
54
+
55
+
56
+ def _assert_allowed(mime: Optional[str]) -> None:
57
+ if mime and mime in BLACKLISTED_FORMATS:
58
+ raise InterfazeError(f'Format "{mime}" is not supported by Interfaze.')
59
+
60
+
61
+ def data_url(data: BytesLike, mime_type: str) -> str:
62
+ """Build a base64 ``data:`` URI from raw bytes."""
63
+ _assert_allowed(mime_type)
64
+ return f"data:{mime_type};base64,{_base64.b64encode(bytes(data)).decode('ascii')}"
65
+
66
+
67
+ def from_path(path: Union[str, Path]) -> str:
68
+ """Read a local file into a ``data:`` URI (mime by extension)."""
69
+ p = Path(path)
70
+ mime = _EXT_MIME.get((p.suffix.lstrip(".")).lower(), "application/octet-stream")
71
+ return data_url(p.read_bytes(), mime)
72
+
73
+
74
+ def image(src: str) -> Dict[str, Any]:
75
+ """Image content part. ``src`` = https URL or ``data:`` URI."""
76
+ _assert_allowed(_mime_from_data_url(src) or _EXT_MIME.get(_ext_of(src) or ""))
77
+ return {"type": "image_url", "image_url": {"url": src}}
78
+
79
+
80
+ def file(src: str, *, filename: Optional[str] = None, format: Optional[str] = None) -> Dict[str, Any]:
81
+ """File content part (pdf/csv/xml/json/text/audio/video/…). ``src`` = https URL or ``data:`` URI."""
82
+ mime = format or _mime_from_data_url(src) or _EXT_MIME.get(_ext_of(filename or src) or "")
83
+ _assert_allowed(mime)
84
+ f: Dict[str, Any] = {"file_data": src}
85
+ if filename:
86
+ f["filename"] = filename
87
+ if mime:
88
+ f["format"] = mime
89
+ return {"type": "file", "file": f}
90
+
91
+
92
+ def audio(src: str, *, format: Optional[str] = None) -> Dict[str, Any]:
93
+ """Audio content part via ``input_audio`` (``audio_url`` is a dead field in Interfaze)."""
94
+ mime = _mime_from_data_url(src)
95
+ _assert_allowed(mime or _EXT_MIME.get(_ext_of(src) or ""))
96
+ fmt = format or (mime.split("/", 1)[-1] if mime else _ext_of(src)) or "wav"
97
+ return {"type": "input_audio", "input_audio": {"data": src, "format": fmt}}
98
+
99
+
100
+ def video(src: str, *, filename: Optional[str] = None) -> Dict[str, Any]:
101
+ """Video content part — rides on the ``file`` part (there is no video content part)."""
102
+ return file(src, filename=filename)
103
+
104
+
105
+ def auto_part(src: str, *, filename: Optional[str] = None, format: Optional[str] = None) -> Dict[str, Any]:
106
+ """Pick a content part by media type: image → image_url, audio → input_audio, else file."""
107
+ mime = format or _mime_from_data_url(src) or _EXT_MIME.get(_ext_of(filename or src) or "")
108
+ if mime and mime.startswith("image/"):
109
+ return image(src)
110
+ if mime and mime.startswith("audio/"):
111
+ return audio(src, format=format or mime.split("/", 1)[-1])
112
+ return file(src, filename=filename, format=format)
interfaze/_schema.py ADDED
@@ -0,0 +1,30 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Dict
4
+
5
+ JSONSchema = Dict[str, Any]
6
+
7
+
8
+ def empty_task_schema(name: str = "empty_schema") -> Dict[str, Any]:
9
+ """Empty ``response_format`` for raw ``<task>`` runs."""
10
+ return {"type": "json_schema", "json_schema": {"name": name, "schema": {}}}
11
+
12
+
13
+ def response_format(schema: JSONSchema, name: str = "response") -> Dict[str, Any]:
14
+ """Build a structured-output ``response_format`` from a JSON Schema.
15
+
16
+ Non-object roots are wrapped in a ``{"result": ...}`` object (structured output requires
17
+ an object root); the wrapped output is then under the ``result`` key.
18
+ """
19
+ return {"type": "json_schema", "json_schema": {"name": name, "schema": _ensure_object_root(schema)}}
20
+
21
+
22
+ def _ensure_object_root(schema: JSONSchema) -> JSONSchema:
23
+ if isinstance(schema, dict) and schema.get("type") == "object":
24
+ return schema
25
+ return {
26
+ "type": "object",
27
+ "properties": {"result": schema},
28
+ "required": ["result"],
29
+ "additionalProperties": False,
30
+ }