|
1 | 1 | import asyncio |
| 2 | +import base64 |
2 | 3 | import sqlite3 |
3 | 4 | from contextlib import contextmanager |
4 | 5 | from datetime import datetime |
|
38 | 39 | FileAttachment, |
39 | 40 | ImageAttachment, |
40 | 41 | InferenceOptions, |
| 42 | + InputTranscribeParams, |
| 43 | + InputTranscribeReq, |
41 | 44 | ItemFeedbackParams, |
42 | 45 | ItemsFeedbackReq, |
43 | 46 | ItemsListParams, |
|
75 | 78 | ThreadUpdatedEvent, |
76 | 79 | ThreadUpdateParams, |
77 | 80 | ToolChoice, |
| 81 | + TranscriptionResult, |
78 | 82 | UserMessageInput, |
79 | 83 | UserMessageItem, |
80 | 84 | UserMessageTextContent, |
@@ -159,6 +163,7 @@ def make_server( |
159 | 163 | ] |
160 | 164 | | None = None, |
161 | 165 | file_store: AttachmentStore | None = None, |
| 166 | + transcribe_callback: Callable[[bytes, str, Any], TranscriptionResult] | None = None, |
162 | 167 | ): |
163 | 168 | global server_id |
164 | 169 | db_path = f"file:{server_id}?mode=memory&cache=shared" |
@@ -206,6 +211,13 @@ async def add_feedback( |
206 | 211 | return |
207 | 212 | handle_feedback(thread_id, item_ids, feedback, context) |
208 | 213 |
|
| 214 | + async def transcribe( |
| 215 | + self, audio_bytes: bytes, mime_type: str, context: Any |
| 216 | + ) -> TranscriptionResult: |
| 217 | + if transcribe_callback is None: |
| 218 | + return await super().transcribe(audio_bytes, mime_type, context) |
| 219 | + return transcribe_callback(audio_bytes, mime_type, context) |
| 220 | + |
209 | 221 | async def process_streaming( |
210 | 222 | self, request_obj, context: Any | None = None |
211 | 223 | ) -> list[ThreadStreamEvent]: |
@@ -1887,6 +1899,36 @@ async def responder( |
1887 | 1899 | assert any(e.type == "thread.item.done" for e in events) |
1888 | 1900 |
|
1889 | 1901 |
|
| 1902 | +async def test_input_transcribe_decodes_base64_and_passes_mime_type(): |
| 1903 | + audio_bytes = b"hello audio" |
| 1904 | + audio_b64 = base64.b64encode(audio_bytes).decode("ascii") |
| 1905 | + seen: dict[str, Any] = {} |
| 1906 | + |
| 1907 | + def transcribe_callback( |
| 1908 | + audio: bytes, mime: str, context: Any |
| 1909 | + ) -> TranscriptionResult: |
| 1910 | + seen["audio"] = audio |
| 1911 | + seen["mime"] = mime |
| 1912 | + seen["context"] = context |
| 1913 | + return TranscriptionResult(text="ok") |
| 1914 | + |
| 1915 | + with make_server(transcribe_callback=transcribe_callback) as server: |
| 1916 | + result = await server.process_non_streaming( |
| 1917 | + InputTranscribeReq( |
| 1918 | + params=InputTranscribeParams( |
| 1919 | + audio_base64=audio_b64, |
| 1920 | + mime_type="audio/wav", |
| 1921 | + ) |
| 1922 | + ) |
| 1923 | + ) |
| 1924 | + parsed = TypeAdapter(TranscriptionResult).validate_json(result.json) |
| 1925 | + assert parsed.text == "ok" |
| 1926 | + |
| 1927 | + assert seen["audio"] == audio_bytes |
| 1928 | + assert seen["mime"] == "audio/wav" |
| 1929 | + assert seen["context"] == DEFAULT_CONTEXT |
| 1930 | + |
| 1931 | + |
1890 | 1932 | async def test_retry_after_item_passes_tools_to_responder(): |
1891 | 1933 | pass |
1892 | 1934 |
|
|
0 commit comments