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 +69 -0
- interfaze/_chat.py +284 -0
- interfaze/_client.py +108 -0
- interfaze/_constants.py +48 -0
- interfaze/_errors.py +9 -0
- interfaze/_guard.py +21 -0
- interfaze/_inputs.py +112 -0
- interfaze/_schema.py +30 -0
- interfaze/_stream.py +323 -0
- interfaze/_tasks.py +110 -0
- interfaze/_types.py +57 -0
- interfaze/langchain.py +220 -0
- interfaze/py.typed +0 -0
- interfaze-1.0.1.dist-info/METADATA +168 -0
- interfaze-1.0.1.dist-info/RECORD +17 -0
- interfaze-1.0.1.dist-info/WHEEL +4 -0
- interfaze-1.0.1.dist-info/licenses/LICENSE +21 -0
interfaze/_stream.py
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import re
|
|
5
|
+
from typing import Any, AsyncIterator, Dict, Iterator, List, Optional, Tuple
|
|
6
|
+
|
|
7
|
+
from openai import AsyncOpenAI, OpenAI
|
|
8
|
+
from openai.lib.streaming.chat import ChatCompletionStreamEvent, ChatCompletionStreamState
|
|
9
|
+
from openai.types.chat import ChatCompletionChunk
|
|
10
|
+
|
|
11
|
+
from ._errors import InterfazeError
|
|
12
|
+
from ._types import InterfazeChatCompletion
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _tag(tag: str) -> "re.Pattern[str]":
|
|
16
|
+
return re.compile(rf"<{tag}>([\s\S]*?)</{tag}>")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def strip_side_channels(content: str) -> Tuple[str, Optional[str], Optional[List[Dict[str, Any]]]]:
|
|
20
|
+
"""Pull ``<think>``/``<precontext>`` blocks out of streamed content; return the rest as text."""
|
|
21
|
+
think_re, pre_re = _tag("think"), _tag("precontext")
|
|
22
|
+
thinks = [m.strip() for m in think_re.findall(content)]
|
|
23
|
+
text = pre_re.sub("", think_re.sub("", content))
|
|
24
|
+
pre: List[Dict[str, Any]] = []
|
|
25
|
+
for block in pre_re.findall(content):
|
|
26
|
+
try:
|
|
27
|
+
parsed = json.loads(block.strip())
|
|
28
|
+
pre.extend(parsed if isinstance(parsed, list) else [parsed])
|
|
29
|
+
except (ValueError, TypeError):
|
|
30
|
+
pass
|
|
31
|
+
return text.strip(), ("\n".join(thinks) if thinks else None), (pre or None)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
_FENCE = re.compile(r"^```(?:json)?\s*|\s*```$", re.IGNORECASE)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def strip_json_fence(content: str) -> str:
|
|
38
|
+
"""Interfaze wraps ``json_object`` content in a ```json fence; unwrap it."""
|
|
39
|
+
t = content.strip()
|
|
40
|
+
if not t.startswith("```"):
|
|
41
|
+
return content
|
|
42
|
+
return _FENCE.sub("", t).strip()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
_SIDE_OPEN = ("<think>", "<precontext>")
|
|
46
|
+
_SIDE_CLOSE = {"<think>": "</think>", "<precontext>": "</precontext>"}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _suffix_prefix_len(s: str, tag: str) -> int:
|
|
50
|
+
for k in range(min(len(s), len(tag) - 1), 0, -1):
|
|
51
|
+
if s[-k:] == tag[:k]:
|
|
52
|
+
return k
|
|
53
|
+
return 0
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class _SideChannelFilter:
|
|
57
|
+
"""Incrementally strips ``<think>``/``<precontext>`` blocks, buffering tags split across chunks."""
|
|
58
|
+
|
|
59
|
+
def __init__(self) -> None:
|
|
60
|
+
self._buf = ""
|
|
61
|
+
self._close: Optional[str] = None
|
|
62
|
+
|
|
63
|
+
def feed(self, text: str) -> str:
|
|
64
|
+
self._buf += text
|
|
65
|
+
out: List[str] = []
|
|
66
|
+
while self._buf:
|
|
67
|
+
if self._close is None:
|
|
68
|
+
lt = self._buf.find("<")
|
|
69
|
+
if lt == -1:
|
|
70
|
+
out.append(self._buf)
|
|
71
|
+
self._buf = ""
|
|
72
|
+
break
|
|
73
|
+
if lt:
|
|
74
|
+
out.append(self._buf[:lt])
|
|
75
|
+
self._buf = self._buf[lt:]
|
|
76
|
+
opened = next((t for t in _SIDE_OPEN if self._buf.startswith(t)), None)
|
|
77
|
+
if opened:
|
|
78
|
+
self._close = _SIDE_CLOSE[opened]
|
|
79
|
+
self._buf = self._buf[len(opened) :]
|
|
80
|
+
continue
|
|
81
|
+
if any(t.startswith(self._buf) for t in _SIDE_OPEN):
|
|
82
|
+
break
|
|
83
|
+
out.append("<")
|
|
84
|
+
self._buf = self._buf[1:]
|
|
85
|
+
else:
|
|
86
|
+
end = self._buf.find(self._close)
|
|
87
|
+
if end == -1:
|
|
88
|
+
keep = _suffix_prefix_len(self._buf, self._close)
|
|
89
|
+
self._buf = self._buf[len(self._buf) - keep :] if keep else ""
|
|
90
|
+
break
|
|
91
|
+
self._buf = self._buf[end + len(self._close) :]
|
|
92
|
+
self._close = None
|
|
93
|
+
return "".join(out)
|
|
94
|
+
|
|
95
|
+
def flush(self) -> str:
|
|
96
|
+
if self._close is not None:
|
|
97
|
+
self._buf = ""
|
|
98
|
+
return ""
|
|
99
|
+
rest, self._buf = self._buf, ""
|
|
100
|
+
return rest
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _clean_for_events(
|
|
104
|
+
chunk: ChatCompletionChunk, filt: _SideChannelFilter, role_injected: bool
|
|
105
|
+
) -> "Tuple[ChatCompletionChunk, bool]":
|
|
106
|
+
"""Clean a chunk for openai's event state: strip side channels from content, inject a role."""
|
|
107
|
+
cleaned = chunk.model_copy(deep=True)
|
|
108
|
+
if not cleaned.choices:
|
|
109
|
+
return cleaned, role_injected
|
|
110
|
+
delta = cleaned.choices[0].delta
|
|
111
|
+
if not role_injected:
|
|
112
|
+
role_injected = True
|
|
113
|
+
if not delta.role:
|
|
114
|
+
delta.role = "assistant"
|
|
115
|
+
piece = filt.feed(delta.content) if isinstance(delta.content, str) else ""
|
|
116
|
+
if cleaned.choices[0].finish_reason:
|
|
117
|
+
piece += filt.flush()
|
|
118
|
+
if isinstance(delta.content, str) or piece:
|
|
119
|
+
delta.content = piece
|
|
120
|
+
return cleaned, role_injected
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class _State:
|
|
124
|
+
def __init__(self) -> None:
|
|
125
|
+
self.content = ""
|
|
126
|
+
self.role: Optional[str] = None
|
|
127
|
+
self.finish: Optional[str] = None
|
|
128
|
+
self.id = ""
|
|
129
|
+
self.model = ""
|
|
130
|
+
self.created = 0
|
|
131
|
+
self.tool_calls: Dict[int, Dict[str, str]] = {}
|
|
132
|
+
self.usage: Any = None
|
|
133
|
+
self.system_fingerprint: Optional[str] = None
|
|
134
|
+
|
|
135
|
+
def accumulate(self, chunk: ChatCompletionChunk) -> None:
|
|
136
|
+
if not self.id and chunk.id:
|
|
137
|
+
self.id = chunk.id
|
|
138
|
+
if not self.model and chunk.model:
|
|
139
|
+
self.model = chunk.model
|
|
140
|
+
if not self.created and chunk.created:
|
|
141
|
+
self.created = chunk.created
|
|
142
|
+
if chunk.usage:
|
|
143
|
+
self.usage = chunk.usage
|
|
144
|
+
if chunk.system_fingerprint:
|
|
145
|
+
self.system_fingerprint = chunk.system_fingerprint
|
|
146
|
+
if not chunk.choices:
|
|
147
|
+
return
|
|
148
|
+
choice = chunk.choices[0]
|
|
149
|
+
delta = choice.delta
|
|
150
|
+
if delta and delta.role:
|
|
151
|
+
self.role = delta.role
|
|
152
|
+
if delta and isinstance(delta.content, str):
|
|
153
|
+
self.content += delta.content
|
|
154
|
+
if choice.finish_reason:
|
|
155
|
+
self.finish = choice.finish_reason
|
|
156
|
+
for tc in (delta.tool_calls or []) if delta else []:
|
|
157
|
+
acc = self.tool_calls.setdefault(tc.index, {"id": "", "name": "", "arguments": ""})
|
|
158
|
+
if tc.id:
|
|
159
|
+
acc["id"] = tc.id
|
|
160
|
+
if tc.function and tc.function.name:
|
|
161
|
+
acc["name"] = tc.function.name
|
|
162
|
+
if tc.function and tc.function.arguments:
|
|
163
|
+
acc["arguments"] += tc.function.arguments
|
|
164
|
+
|
|
165
|
+
def build(self, strip_fence: bool = False) -> InterfazeChatCompletion:
|
|
166
|
+
text, reasoning, precontext = strip_side_channels(self.content)
|
|
167
|
+
if strip_fence:
|
|
168
|
+
text = strip_json_fence(text)
|
|
169
|
+
tool_calls = [
|
|
170
|
+
{"id": t["id"], "type": "function", "function": {"name": t["name"], "arguments": t["arguments"]}}
|
|
171
|
+
for t in self.tool_calls.values()
|
|
172
|
+
]
|
|
173
|
+
message: Dict[str, Any] = {"role": self.role or "assistant", "content": None if tool_calls else text}
|
|
174
|
+
if tool_calls:
|
|
175
|
+
message["tool_calls"] = tool_calls
|
|
176
|
+
data: Dict[str, Any] = {
|
|
177
|
+
"id": self.id or "",
|
|
178
|
+
"object": "chat.completion",
|
|
179
|
+
"created": self.created or 0,
|
|
180
|
+
"model": self.model or "interfaze-beta",
|
|
181
|
+
"choices": [
|
|
182
|
+
{"index": 0, "message": message, "finish_reason": self.finish or "stop", "logprobs": None}
|
|
183
|
+
],
|
|
184
|
+
"vcache": False,
|
|
185
|
+
}
|
|
186
|
+
if self.usage is not None:
|
|
187
|
+
data["usage"] = self.usage.model_dump()
|
|
188
|
+
if self.system_fingerprint:
|
|
189
|
+
data["system_fingerprint"] = self.system_fingerprint
|
|
190
|
+
if reasoning:
|
|
191
|
+
data["reasoning"] = reasoning
|
|
192
|
+
if precontext:
|
|
193
|
+
data["precontext"] = precontext
|
|
194
|
+
return InterfazeChatCompletion.model_validate(data)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class InterfazeStream:
|
|
198
|
+
"""Sync streaming helper — iterate OpenAI-style events, then ``get_final_completion()``."""
|
|
199
|
+
|
|
200
|
+
def __init__(self, client: OpenAI, kwargs: Dict[str, Any], strip_fence: bool = False) -> None:
|
|
201
|
+
self._client = client
|
|
202
|
+
self._kwargs = kwargs
|
|
203
|
+
self._strip_fence = strip_fence
|
|
204
|
+
self._state = _State()
|
|
205
|
+
self._openai_state: ChatCompletionStreamState[None] = ChatCompletionStreamState()
|
|
206
|
+
self._filter = _SideChannelFilter()
|
|
207
|
+
self._role_injected = False
|
|
208
|
+
self._started = False
|
|
209
|
+
self._done = False
|
|
210
|
+
|
|
211
|
+
def __enter__(self) -> "InterfazeStream":
|
|
212
|
+
return self
|
|
213
|
+
|
|
214
|
+
def __exit__(self, *exc: Any) -> None:
|
|
215
|
+
return None
|
|
216
|
+
|
|
217
|
+
def __iter__(self) -> "Iterator[ChatCompletionStreamEvent[None]]":
|
|
218
|
+
if self._started:
|
|
219
|
+
raise InterfazeError("This stream has already been consumed.")
|
|
220
|
+
self._started = True
|
|
221
|
+
for chunk in self._client.chat.completions.create(stream=True, **self._kwargs):
|
|
222
|
+
self._state.accumulate(chunk)
|
|
223
|
+
cleaned, self._role_injected = _clean_for_events(chunk, self._filter, self._role_injected)
|
|
224
|
+
yield from self._openai_state.handle_chunk(cleaned)
|
|
225
|
+
self._done = True
|
|
226
|
+
|
|
227
|
+
def text_deltas(self) -> "Iterator[str]":
|
|
228
|
+
"""Iterate only the visible text (no side channels, no events) — for plain-token consumers."""
|
|
229
|
+
if self._started:
|
|
230
|
+
raise InterfazeError("This stream has already been consumed.")
|
|
231
|
+
self._started = True
|
|
232
|
+
for chunk in self._client.chat.completions.create(stream=True, **self._kwargs):
|
|
233
|
+
self._state.accumulate(chunk)
|
|
234
|
+
if not chunk.choices:
|
|
235
|
+
continue
|
|
236
|
+
delta = chunk.choices[0].delta
|
|
237
|
+
piece = self._filter.feed(delta.content) if isinstance(delta.content, str) else ""
|
|
238
|
+
if chunk.choices[0].finish_reason:
|
|
239
|
+
piece += self._filter.flush()
|
|
240
|
+
if piece:
|
|
241
|
+
yield piece
|
|
242
|
+
self._done = True
|
|
243
|
+
|
|
244
|
+
@property
|
|
245
|
+
def text(self) -> str:
|
|
246
|
+
text = strip_side_channels(self._state.content)[0]
|
|
247
|
+
return strip_json_fence(text) if self._strip_fence else text
|
|
248
|
+
|
|
249
|
+
def get_final_completion(self) -> InterfazeChatCompletion:
|
|
250
|
+
if not self._started:
|
|
251
|
+
self._started = True
|
|
252
|
+
for chunk in self._client.chat.completions.create(stream=True, **self._kwargs):
|
|
253
|
+
self._state.accumulate(chunk)
|
|
254
|
+
self._done = True
|
|
255
|
+
elif not self._done:
|
|
256
|
+
raise InterfazeError(
|
|
257
|
+
"Call get_final_completion() after fully iterating, or instead of iterating."
|
|
258
|
+
)
|
|
259
|
+
return self._state.build(self._strip_fence)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
class AsyncInterfazeStream:
|
|
263
|
+
"""Async streaming helper — ``async for`` OpenAI-style events, then ``await get_final_completion()``."""
|
|
264
|
+
|
|
265
|
+
def __init__(self, client: AsyncOpenAI, kwargs: Dict[str, Any], strip_fence: bool = False) -> None:
|
|
266
|
+
self._client = client
|
|
267
|
+
self._kwargs = kwargs
|
|
268
|
+
self._strip_fence = strip_fence
|
|
269
|
+
self._state = _State()
|
|
270
|
+
self._openai_state: ChatCompletionStreamState[None] = ChatCompletionStreamState()
|
|
271
|
+
self._filter = _SideChannelFilter()
|
|
272
|
+
self._role_injected = False
|
|
273
|
+
self._started = False
|
|
274
|
+
self._done = False
|
|
275
|
+
|
|
276
|
+
async def __aenter__(self) -> "AsyncInterfazeStream":
|
|
277
|
+
return self
|
|
278
|
+
|
|
279
|
+
async def __aexit__(self, *exc: Any) -> None:
|
|
280
|
+
return None
|
|
281
|
+
|
|
282
|
+
async def __aiter__(self) -> "AsyncIterator[ChatCompletionStreamEvent[None]]":
|
|
283
|
+
if self._started:
|
|
284
|
+
raise InterfazeError("This stream has already been consumed.")
|
|
285
|
+
self._started = True
|
|
286
|
+
stream = await self._client.chat.completions.create(stream=True, **self._kwargs)
|
|
287
|
+
async for chunk in stream:
|
|
288
|
+
self._state.accumulate(chunk)
|
|
289
|
+
cleaned, self._role_injected = _clean_for_events(chunk, self._filter, self._role_injected)
|
|
290
|
+
for event in self._openai_state.handle_chunk(cleaned):
|
|
291
|
+
yield event
|
|
292
|
+
self._done = True
|
|
293
|
+
|
|
294
|
+
async def text_deltas(self) -> "AsyncIterator[str]":
|
|
295
|
+
"""Iterate only the visible text (no side channels, no events) — for plain-token consumers."""
|
|
296
|
+
if self._started:
|
|
297
|
+
raise InterfazeError("This stream has already been consumed.")
|
|
298
|
+
self._started = True
|
|
299
|
+
stream = await self._client.chat.completions.create(stream=True, **self._kwargs)
|
|
300
|
+
async for chunk in stream:
|
|
301
|
+
self._state.accumulate(chunk)
|
|
302
|
+
if not chunk.choices:
|
|
303
|
+
continue
|
|
304
|
+
delta = chunk.choices[0].delta
|
|
305
|
+
piece = self._filter.feed(delta.content) if isinstance(delta.content, str) else ""
|
|
306
|
+
if chunk.choices[0].finish_reason:
|
|
307
|
+
piece += self._filter.flush()
|
|
308
|
+
if piece:
|
|
309
|
+
yield piece
|
|
310
|
+
self._done = True
|
|
311
|
+
|
|
312
|
+
async def get_final_completion(self) -> InterfazeChatCompletion:
|
|
313
|
+
if not self._started:
|
|
314
|
+
self._started = True
|
|
315
|
+
stream = await self._client.chat.completions.create(stream=True, **self._kwargs)
|
|
316
|
+
async for chunk in stream:
|
|
317
|
+
self._state.accumulate(chunk)
|
|
318
|
+
self._done = True
|
|
319
|
+
elif not self._done:
|
|
320
|
+
raise InterfazeError(
|
|
321
|
+
"Call get_final_completion() after fully iterating, or instead of iterating."
|
|
322
|
+
)
|
|
323
|
+
return self._state.build(self._strip_fence)
|
interfaze/_tasks.py
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Any, Dict, List, Optional, Union
|
|
5
|
+
|
|
6
|
+
from ._chat import AsyncCompletions, Completions
|
|
7
|
+
from ._inputs import auto_part
|
|
8
|
+
from ._types import TaskName
|
|
9
|
+
|
|
10
|
+
Content = Union[str, List[Dict[str, Any]]]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _text(t: str) -> Dict[str, Any]:
|
|
14
|
+
return {"type": "text", "text": t}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _extract(content: Optional[str]) -> Any:
|
|
18
|
+
if not content:
|
|
19
|
+
return None
|
|
20
|
+
try:
|
|
21
|
+
parsed = json.loads(content)
|
|
22
|
+
return parsed.get("result", parsed) if isinstance(parsed, dict) else parsed
|
|
23
|
+
except (ValueError, TypeError):
|
|
24
|
+
return content
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _forecast_prompt(csv_source: str, periods: int, unit: str) -> str:
|
|
28
|
+
return f"Forecast the next {periods} {unit} of this: {csv_source}"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Tasks:
|
|
32
|
+
"""High-level task helpers (sync). Each returns the task's raw ``result``."""
|
|
33
|
+
|
|
34
|
+
def __init__(self, completions: Completions) -> None:
|
|
35
|
+
self._c = completions
|
|
36
|
+
|
|
37
|
+
def _run(self, task: TaskName, content: Content) -> Any:
|
|
38
|
+
r = self._c.create(task=task, messages=[{"role": "user", "content": content}])
|
|
39
|
+
return _extract(r.choices[0].message.content)
|
|
40
|
+
|
|
41
|
+
def ocr(self, source: str, *, prompt: str = "Extract all text and data.") -> Any:
|
|
42
|
+
return self._run("ocr", [_text(prompt), auto_part(source)])
|
|
43
|
+
|
|
44
|
+
def object_detection(self, source: str, *, prompt: str = "Detect all objects.") -> Any:
|
|
45
|
+
return self._run("object_detection", [_text(prompt), auto_part(source)])
|
|
46
|
+
|
|
47
|
+
def gui_detection(self, source: str, *, prompt: str = "Detect all GUI elements.") -> Any:
|
|
48
|
+
return self._run("gui_detection", [_text(prompt), auto_part(source)])
|
|
49
|
+
|
|
50
|
+
def transcribe(self, source: str, *, prompt: str = "Transcribe this audio.") -> Any:
|
|
51
|
+
return self._run("speech_to_text", [_text(prompt), auto_part(source)])
|
|
52
|
+
|
|
53
|
+
def web_search(self, query: str) -> Any:
|
|
54
|
+
return self._run("web_search", query)
|
|
55
|
+
|
|
56
|
+
def scrape(self, url: str, *, prompt: str = "Scrape this page") -> Any:
|
|
57
|
+
return self._run("scraper", f"{prompt}: {url}")
|
|
58
|
+
|
|
59
|
+
def translate(self, text: str, *, to: str) -> Any:
|
|
60
|
+
return self._run("translate", f"Translate the following into {to}:\n\n{text}")
|
|
61
|
+
|
|
62
|
+
def forecast(self, csv_source: str, *, periods: int = 10, unit: str = "days") -> Any:
|
|
63
|
+
r = self._c.create(
|
|
64
|
+
messages=[{"role": "user", "content": _forecast_prompt(csv_source, periods, unit)}]
|
|
65
|
+
)
|
|
66
|
+
for p in r.precontext or []:
|
|
67
|
+
if p.name == "forecast":
|
|
68
|
+
return p.result
|
|
69
|
+
return r.choices[0].message.content
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class AsyncTasks:
|
|
73
|
+
"""High-level task helpers (async)."""
|
|
74
|
+
|
|
75
|
+
def __init__(self, completions: AsyncCompletions) -> None:
|
|
76
|
+
self._c = completions
|
|
77
|
+
|
|
78
|
+
async def _run(self, task: TaskName, content: Content) -> Any:
|
|
79
|
+
r = await self._c.create(task=task, messages=[{"role": "user", "content": content}])
|
|
80
|
+
return _extract(r.choices[0].message.content)
|
|
81
|
+
|
|
82
|
+
async def ocr(self, source: str, *, prompt: str = "Extract all text and data.") -> Any:
|
|
83
|
+
return await self._run("ocr", [_text(prompt), auto_part(source)])
|
|
84
|
+
|
|
85
|
+
async def object_detection(self, source: str, *, prompt: str = "Detect all objects.") -> Any:
|
|
86
|
+
return await self._run("object_detection", [_text(prompt), auto_part(source)])
|
|
87
|
+
|
|
88
|
+
async def gui_detection(self, source: str, *, prompt: str = "Detect all GUI elements.") -> Any:
|
|
89
|
+
return await self._run("gui_detection", [_text(prompt), auto_part(source)])
|
|
90
|
+
|
|
91
|
+
async def transcribe(self, source: str, *, prompt: str = "Transcribe this audio.") -> Any:
|
|
92
|
+
return await self._run("speech_to_text", [_text(prompt), auto_part(source)])
|
|
93
|
+
|
|
94
|
+
async def web_search(self, query: str) -> Any:
|
|
95
|
+
return await self._run("web_search", query)
|
|
96
|
+
|
|
97
|
+
async def scrape(self, url: str, *, prompt: str = "Scrape this page") -> Any:
|
|
98
|
+
return await self._run("scraper", f"{prompt}: {url}")
|
|
99
|
+
|
|
100
|
+
async def translate(self, text: str, *, to: str) -> Any:
|
|
101
|
+
return await self._run("translate", f"Translate the following into {to}:\n\n{text}")
|
|
102
|
+
|
|
103
|
+
async def forecast(self, csv_source: str, *, periods: int = 10, unit: str = "days") -> Any:
|
|
104
|
+
r = await self._c.create(
|
|
105
|
+
messages=[{"role": "user", "content": _forecast_prompt(csv_source, periods, unit)}]
|
|
106
|
+
)
|
|
107
|
+
for p in r.precontext or []:
|
|
108
|
+
if p.name == "forecast":
|
|
109
|
+
return p.result
|
|
110
|
+
return r.choices[0].message.content
|
interfaze/_types.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, List, Literal, Optional
|
|
4
|
+
|
|
5
|
+
from openai.types.chat import ChatCompletion
|
|
6
|
+
from pydantic import BaseModel, ConfigDict
|
|
7
|
+
|
|
8
|
+
TaskName = Literal[
|
|
9
|
+
"ocr",
|
|
10
|
+
"object_detection",
|
|
11
|
+
"gui_detection",
|
|
12
|
+
"web_search",
|
|
13
|
+
"scraper",
|
|
14
|
+
"translate",
|
|
15
|
+
"speech_to_text",
|
|
16
|
+
"forecast",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
GuardCode = Literal[
|
|
20
|
+
"S1",
|
|
21
|
+
"S2",
|
|
22
|
+
"S3",
|
|
23
|
+
"S4",
|
|
24
|
+
"S5",
|
|
25
|
+
"S6",
|
|
26
|
+
"S7",
|
|
27
|
+
"S8",
|
|
28
|
+
"S9",
|
|
29
|
+
"S10",
|
|
30
|
+
"S11",
|
|
31
|
+
"S12",
|
|
32
|
+
"S13",
|
|
33
|
+
"S14",
|
|
34
|
+
"S1_IMAGE",
|
|
35
|
+
"S12_IMAGE",
|
|
36
|
+
"S15_IMAGE",
|
|
37
|
+
"ALL",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
ReasoningEffort = Literal["minimal", "low", "medium", "high", "on", "off", "auto"]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class Precontext(BaseModel):
|
|
44
|
+
"""One internal task's output; lenient since raw tool-call entries omit name/result."""
|
|
45
|
+
|
|
46
|
+
model_config = ConfigDict(extra="allow")
|
|
47
|
+
name: Optional[str] = None
|
|
48
|
+
result: Any = None
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class InterfazeChatCompletion(ChatCompletion):
|
|
52
|
+
"""ChatCompletion plus Interfaze extras: precontext, reasoning, vcache, debug."""
|
|
53
|
+
|
|
54
|
+
precontext: Optional[List[Precontext]] = None
|
|
55
|
+
reasoning: Optional[str] = None
|
|
56
|
+
vcache: bool = False
|
|
57
|
+
debug: Optional[Any] = None
|