dsh-plugin-image-workbench 1.1.5 → 1.1.6
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.
- package/LICENSE +21 -21
- package/README.en.md +5 -3
- package/README.md +7 -3
- package/client.js +241 -241
- package/cordis.patch.yml +14 -14
- package/host.js +51 -0
- package/install.mjs +333 -387
- package/package.json +3 -2
- package/scripts/fetch-style-images.mjs +104 -0
- package/scripts/vendor-snapshot.py +154 -154
- package/server/access.py +26 -26
- package/server/app.py +1329 -1329
- package/server/catalog.py +147 -147
- package/server/composer.py +391 -391
- package/server/drafts.py +158 -158
- package/server/history.py +575 -575
- package/server/imaging.py +172 -172
- package/server/pool.py +1335 -1335
- package/server/pool_admin.py +921 -921
- package/server/pool_service.py +1073 -1073
- package/server/requirements.txt +6 -6
- package/server/smoke_check.py +402 -402
- package/server/static/access.html +202 -202
- package/vendor/chatgpt2api/source/.env.example +82 -82
- package/vendor/chatgpt2api/source/.python-version +1 -1
- package/vendor/chatgpt2api/source/LICENSE +21 -21
- package/vendor/chatgpt2api/source/README.md +391 -391
- package/vendor/chatgpt2api/source/VERSION +1 -1
- package/vendor/chatgpt2api/source/api/__init__.py +2 -2
- package/vendor/chatgpt2api/source/api/accounts.py +537 -537
- package/vendor/chatgpt2api/source/api/ai.py +217 -217
- package/vendor/chatgpt2api/source/api/app.py +62 -62
- package/vendor/chatgpt2api/source/api/errors.py +46 -46
- package/vendor/chatgpt2api/source/api/image_inputs.py +309 -309
- package/vendor/chatgpt2api/source/api/image_tasks.py +120 -120
- package/vendor/chatgpt2api/source/api/support.py +134 -134
- package/vendor/chatgpt2api/source/api/system.py +365 -365
- package/vendor/chatgpt2api/source/assets/kunrouai.svg +50 -50
- package/vendor/chatgpt2api/source/config.default.json +108 -108
- package/vendor/chatgpt2api/source/main.py +9 -9
- package/vendor/chatgpt2api/source/pyproject.toml +27 -27
- package/vendor/chatgpt2api/source/scripts/init_proxy_config.py +186 -186
- package/vendor/chatgpt2api/source/scripts/migrate_storage.py +163 -163
- package/vendor/chatgpt2api/source/scripts/privoxy-warp.conf +13 -13
- package/vendor/chatgpt2api/source/scripts/test_outlook_token_mailbox.py +385 -385
- package/vendor/chatgpt2api/source/scripts/test_storage.py +129 -129
- package/vendor/chatgpt2api/source/scripts/verify_oauth_refresh.py +92 -92
- package/vendor/chatgpt2api/source/services/account_service.py +1757 -1757
- package/vendor/chatgpt2api/source/services/auth_service.py +253 -253
- package/vendor/chatgpt2api/source/services/backup_service.py +671 -671
- package/vendor/chatgpt2api/source/services/config.py +667 -667
- package/vendor/chatgpt2api/source/services/content_filter.py +242 -242
- package/vendor/chatgpt2api/source/services/cpa_service.py +315 -315
- package/vendor/chatgpt2api/source/services/editable_file_task_service.py +260 -260
- package/vendor/chatgpt2api/source/services/image_service.py +376 -376
- package/vendor/chatgpt2api/source/services/image_storage_service.py +405 -405
- package/vendor/chatgpt2api/source/services/image_tags_service.py +76 -76
- package/vendor/chatgpt2api/source/services/image_task_service.py +550 -550
- package/vendor/chatgpt2api/source/services/log_service.py +337 -337
- package/vendor/chatgpt2api/source/services/model_service.py +182 -182
- package/vendor/chatgpt2api/source/services/oauth_login_service.py +267 -267
- package/vendor/chatgpt2api/source/services/openai_backend_api.py +2763 -2763
- package/vendor/chatgpt2api/source/services/openai_oauth.py +32 -32
- package/vendor/chatgpt2api/source/services/protocol/__init__.py +2 -2
- package/vendor/chatgpt2api/source/services/protocol/anthropic_v1_messages.py +307 -307
- package/vendor/chatgpt2api/source/services/protocol/chat_completion_cache.py +238 -238
- package/vendor/chatgpt2api/source/services/protocol/conversation.py +1644 -1644
- package/vendor/chatgpt2api/source/services/protocol/error_response.py +124 -124
- package/vendor/chatgpt2api/source/services/protocol/openai_search.py +19 -19
- package/vendor/chatgpt2api/source/services/protocol/openai_v1_chat_complete.py +316 -316
- package/vendor/chatgpt2api/source/services/protocol/openai_v1_image_edit.py +86 -86
- package/vendor/chatgpt2api/source/services/protocol/openai_v1_image_generations.py +42 -42
- package/vendor/chatgpt2api/source/services/protocol/openai_v1_models.py +53 -53
- package/vendor/chatgpt2api/source/services/protocol/openai_v1_response.py +453 -453
- package/vendor/chatgpt2api/source/services/protocol/web_search_tool.py +160 -160
- package/vendor/chatgpt2api/source/services/proxy_service.py +648 -648
- package/vendor/chatgpt2api/source/services/storage/__init__.py +5 -5
- package/vendor/chatgpt2api/source/services/storage/base.py +38 -38
- package/vendor/chatgpt2api/source/services/storage/database_storage.py +194 -194
- package/vendor/chatgpt2api/source/services/storage/factory.py +104 -104
- package/vendor/chatgpt2api/source/services/storage/git_storage.py +183 -183
- package/vendor/chatgpt2api/source/services/storage/json_storage.py +95 -95
- package/vendor/chatgpt2api/source/services/sub2api_service.py +531 -531
- package/vendor/chatgpt2api/source/utils/helper.py +469 -469
- package/vendor/chatgpt2api/source/utils/image_tokens.py +346 -346
- package/vendor/chatgpt2api/source/utils/log.py +110 -110
- package/vendor/chatgpt2api/source/utils/pkce.py +19 -19
- package/vendor/chatgpt2api/source/utils/pow.py +207 -207
- package/vendor/chatgpt2api/source/utils/sentinel.py +159 -159
- package/vendor/chatgpt2api/source/utils/turnstile.py +184 -184
- package/vendor/chatgpt2api/source/uv.lock +904 -904
- package/vendor/chatgpt2api/source/web/bun.lock +1501 -1501
- package/vendor/chatgpt2api/source/web/components.json +24 -24
- package/vendor/chatgpt2api/source/web/eslint.config.mjs +17 -17
- package/vendor/chatgpt2api/source/web/next.config.ts +40 -40
- package/vendor/chatgpt2api/source/web/package.json +58 -58
- package/vendor/chatgpt2api/source/web/postcss.config.mjs +5 -5
- package/vendor/chatgpt2api/source/web/src/app/accounts/components/account-import-dialog.tsx +925 -925
- package/vendor/chatgpt2api/source/web/src/app/accounts/page.tsx +1307 -1307
- package/vendor/chatgpt2api/source/web/src/app/debug/components/chat-panel.tsx +224 -224
- package/vendor/chatgpt2api/source/web/src/app/debug/components/editable-file-panel.tsx +444 -444
- package/vendor/chatgpt2api/source/web/src/app/debug/components/ppt-panel.tsx +9 -9
- package/vendor/chatgpt2api/source/web/src/app/debug/components/psd-panel.tsx +9 -9
- package/vendor/chatgpt2api/source/web/src/app/debug/components/search-panel.tsx +172 -172
- package/vendor/chatgpt2api/source/web/src/app/debug/components/skill-panel.tsx +169 -169
- package/vendor/chatgpt2api/source/web/src/app/debug/components/types.ts +39 -39
- package/vendor/chatgpt2api/source/web/src/app/debug/page.tsx +59 -59
- package/vendor/chatgpt2api/source/web/src/app/globals.css +230 -230
- package/vendor/chatgpt2api/source/web/src/app/image/components/image-composer.tsx +542 -542
- package/vendor/chatgpt2api/source/web/src/app/image/components/image-results.tsx +554 -554
- package/vendor/chatgpt2api/source/web/src/app/image/components/image-sidebar.tsx +176 -176
- package/vendor/chatgpt2api/source/web/src/app/image/page.tsx +1799 -1799
- package/vendor/chatgpt2api/source/web/src/app/image-manager/page.tsx +732 -732
- package/vendor/chatgpt2api/source/web/src/app/layout.tsx +50 -50
- package/vendor/chatgpt2api/source/web/src/app/login/page.tsx +101 -101
- package/vendor/chatgpt2api/source/web/src/app/logs/page.tsx +356 -356
- package/vendor/chatgpt2api/source/web/src/app/page.tsx +30 -30
- package/vendor/chatgpt2api/source/web/src/app/settings/components/api-docs-card.tsx +341 -341
- package/vendor/chatgpt2api/source/web/src/app/settings/components/backup-settings-card.tsx +447 -447
- package/vendor/chatgpt2api/source/web/src/app/settings/components/base-url-card.tsx +71 -71
- package/vendor/chatgpt2api/source/web/src/app/settings/components/config-card.tsx +491 -491
- package/vendor/chatgpt2api/source/web/src/app/settings/components/cpa-pool-dialog.tsx +108 -108
- package/vendor/chatgpt2api/source/web/src/app/settings/components/cpa-pools-card.tsx +169 -169
- package/vendor/chatgpt2api/source/web/src/app/settings/components/import-browser-dialog.tsx +185 -185
- package/vendor/chatgpt2api/source/web/src/app/settings/components/proxy-runtime-card.tsx +379 -379
- package/vendor/chatgpt2api/source/web/src/app/settings/components/proxy-settings-card.tsx +127 -127
- package/vendor/chatgpt2api/source/web/src/app/settings/components/proxy-settings.tsx +200 -200
- package/vendor/chatgpt2api/source/web/src/app/settings/components/settings-header.tsx +12 -12
- package/vendor/chatgpt2api/source/web/src/app/settings/components/sub2api-connections.tsx +886 -886
- package/vendor/chatgpt2api/source/web/src/app/settings/components/third-party-apps-card.tsx +81 -81
- package/vendor/chatgpt2api/source/web/src/app/settings/components/user-keys-card.tsx +422 -422
- package/vendor/chatgpt2api/source/web/src/app/settings/page.tsx +135 -135
- package/vendor/chatgpt2api/source/web/src/app/settings/store.ts +1069 -1069
- package/vendor/chatgpt2api/source/web/src/components/date-range-filter.tsx +49 -49
- package/vendor/chatgpt2api/source/web/src/components/header-actions.tsx +24 -24
- package/vendor/chatgpt2api/source/web/src/components/image-lightbox.tsx +434 -434
- package/vendor/chatgpt2api/source/web/src/components/image-thumbnail.tsx +46 -46
- package/vendor/chatgpt2api/source/web/src/components/theme-script.tsx +18 -18
- package/vendor/chatgpt2api/source/web/src/components/theme-toggle.tsx +14 -14
- package/vendor/chatgpt2api/source/web/src/components/top-nav.tsx +263 -263
- package/vendor/chatgpt2api/source/web/src/components/ui/animated-theme-toggler.tsx +256 -256
- package/vendor/chatgpt2api/source/web/src/components/ui/badge.tsx +41 -41
- package/vendor/chatgpt2api/source/web/src/components/ui/button.tsx +59 -59
- package/vendor/chatgpt2api/source/web/src/components/ui/calendar.tsx +46 -46
- package/vendor/chatgpt2api/source/web/src/components/ui/card.tsx +61 -61
- package/vendor/chatgpt2api/source/web/src/components/ui/checkbox.tsx +27 -27
- package/vendor/chatgpt2api/source/web/src/components/ui/dia-text-reveal.tsx +267 -267
- package/vendor/chatgpt2api/source/web/src/components/ui/dialog.tsx +132 -132
- package/vendor/chatgpt2api/source/web/src/components/ui/field.tsx +13 -13
- package/vendor/chatgpt2api/source/web/src/components/ui/input.tsx +19 -19
- package/vendor/chatgpt2api/source/web/src/components/ui/label.tsx +24 -24
- package/vendor/chatgpt2api/source/web/src/components/ui/morphing-text.tsx +149 -149
- package/vendor/chatgpt2api/source/web/src/components/ui/popover.tsx +38 -38
- package/vendor/chatgpt2api/source/web/src/components/ui/select.tsx +175 -175
- package/vendor/chatgpt2api/source/web/src/components/ui/sheet.tsx +143 -143
- package/vendor/chatgpt2api/source/web/src/components/ui/table.tsx +29 -29
- package/vendor/chatgpt2api/source/web/src/components/ui/tabs.tsx +91 -91
- package/vendor/chatgpt2api/source/web/src/components/ui/text-3d-flip.tsx +269 -269
- package/vendor/chatgpt2api/source/web/src/components/ui/textarea.tsx +18 -18
- package/vendor/chatgpt2api/source/web/src/components/version-release-dialog.tsx +127 -127
- package/vendor/chatgpt2api/source/web/src/constants/common-env.ts +6 -6
- package/vendor/chatgpt2api/source/web/src/hooks/use-version-check.ts +85 -85
- package/vendor/chatgpt2api/source/web/src/lib/api.ts +926 -926
- package/vendor/chatgpt2api/source/web/src/lib/auth-session.ts +26 -26
- package/vendor/chatgpt2api/source/web/src/lib/release.ts +25 -25
- package/vendor/chatgpt2api/source/web/src/lib/request.ts +93 -93
- package/vendor/chatgpt2api/source/web/src/lib/use-auth-guard.ts +89 -89
- package/vendor/chatgpt2api/source/web/src/lib/utils.ts +6 -6
- package/vendor/chatgpt2api/source/web/src/store/auth.ts +112 -112
- package/vendor/chatgpt2api/source/web/src/store/editable-file-history.ts +33 -33
- package/vendor/chatgpt2api/source/web/src/store/image-conversations.ts +318 -318
- package/vendor/chatgpt2api/source/web/tsconfig.json +42 -42
|
@@ -1,316 +1,316 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import time
|
|
4
|
-
import uuid
|
|
5
|
-
from typing import Any, Iterable, Iterator
|
|
6
|
-
|
|
7
|
-
from fastapi import HTTPException
|
|
8
|
-
|
|
9
|
-
from services.protocol.chat_completion_cache import cache_key, chat_completion_cache, normalize_text_messages
|
|
10
|
-
from services.protocol.conversation import (
|
|
11
|
-
ConversationRequest,
|
|
12
|
-
ImageOutput,
|
|
13
|
-
collect_image_outputs,
|
|
14
|
-
collect_text,
|
|
15
|
-
count_message_image_tokens,
|
|
16
|
-
count_message_text_tokens,
|
|
17
|
-
count_text_tokens,
|
|
18
|
-
encode_images,
|
|
19
|
-
normalize_messages,
|
|
20
|
-
stream_image_outputs_with_pool,
|
|
21
|
-
stream_text_deltas,
|
|
22
|
-
text_backend,
|
|
23
|
-
)
|
|
24
|
-
from services.protocol.web_search_tool import (
|
|
25
|
-
WEB_SEARCH_TOOL_TYPES,
|
|
26
|
-
has_unsupported_tools,
|
|
27
|
-
is_web_search_chat_request,
|
|
28
|
-
run_web_search,
|
|
29
|
-
search_query_from_messages,
|
|
30
|
-
text_with_url_citations,
|
|
31
|
-
)
|
|
32
|
-
from utils.helper import build_chat_image_markdown_content, extract_chat_image, extract_chat_prompt, is_image_chat_request, parse_image_count
|
|
33
|
-
from utils.image_tokens import (
|
|
34
|
-
chat_usage_from_image_usage,
|
|
35
|
-
count_image_inputs_tokens,
|
|
36
|
-
count_image_output_items_tokens,
|
|
37
|
-
image_usage,
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
TOOL_UNAVAILABLE_SYSTEM_MESSAGE = (
|
|
41
|
-
"This compatibility backend cannot execute local tools, shell commands, non-search tools, "
|
|
42
|
-
"or file operations. Do not claim to have run tools or inspected external resources. "
|
|
43
|
-
"If a user asks you to use a tool, say that tool execution is unavailable through this backend."
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def normalize_thinking_effort(value: object) -> str:
|
|
48
|
-
normalized = str(value or "").strip().lower()
|
|
49
|
-
if normalized in {"", "none"}:
|
|
50
|
-
return ""
|
|
51
|
-
if normalized in {"low", "medium", "high"}:
|
|
52
|
-
return normalized
|
|
53
|
-
if normalized in {"xhigh", "extended"}:
|
|
54
|
-
return "extended"
|
|
55
|
-
return ""
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
def thinking_effort_from_body(body: dict[str, Any]) -> str:
|
|
59
|
-
if "thinking_effort" in body:
|
|
60
|
-
return normalize_thinking_effort(body.get("thinking_effort"))
|
|
61
|
-
if "reasoning_effort" in body:
|
|
62
|
-
return normalize_thinking_effort(body.get("reasoning_effort"))
|
|
63
|
-
reasoning = body.get("reasoning")
|
|
64
|
-
if isinstance(reasoning, dict):
|
|
65
|
-
return normalize_thinking_effort(reasoning.get("effort"))
|
|
66
|
-
return ""
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
def completion_chunk(model: str, delta: dict[str, Any], finish_reason: str | None = None, completion_id: str = "", created: int | None = None) -> dict[str, Any]:
|
|
70
|
-
return {
|
|
71
|
-
"id": completion_id or f"chatcmpl-{uuid.uuid4().hex}",
|
|
72
|
-
"object": "chat.completion.chunk",
|
|
73
|
-
"created": created or int(time.time()),
|
|
74
|
-
"model": model,
|
|
75
|
-
"choices": [{"index": 0, "delta": delta, "finish_reason": finish_reason}],
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
def completion_response(
|
|
80
|
-
model: str,
|
|
81
|
-
content: str,
|
|
82
|
-
created: int | None = None,
|
|
83
|
-
messages: list[dict[str, Any]] | None = None,
|
|
84
|
-
annotations: list[dict[str, Any]] | None = None,
|
|
85
|
-
) -> dict[str, Any]:
|
|
86
|
-
prompt_text_tokens = count_message_text_tokens(messages, model) if messages else 0
|
|
87
|
-
prompt_image_tokens = count_message_image_tokens(messages, model) if messages else 0
|
|
88
|
-
prompt_tokens = prompt_text_tokens + prompt_image_tokens
|
|
89
|
-
completion_tokens = count_text_tokens(content, model) if messages else 0
|
|
90
|
-
message = {"role": "assistant", "content": content}
|
|
91
|
-
if annotations:
|
|
92
|
-
message["annotations"] = annotations
|
|
93
|
-
return {
|
|
94
|
-
"id": f"chatcmpl-{uuid.uuid4().hex}",
|
|
95
|
-
"object": "chat.completion",
|
|
96
|
-
"created": created or int(time.time()),
|
|
97
|
-
"model": model,
|
|
98
|
-
"choices": [{
|
|
99
|
-
"index": 0,
|
|
100
|
-
"message": message,
|
|
101
|
-
"finish_reason": "stop",
|
|
102
|
-
}],
|
|
103
|
-
"usage": {
|
|
104
|
-
"prompt_tokens": prompt_tokens,
|
|
105
|
-
"completion_tokens": completion_tokens,
|
|
106
|
-
"total_tokens": prompt_tokens + completion_tokens,
|
|
107
|
-
"prompt_tokens_details": {
|
|
108
|
-
"text_tokens": prompt_text_tokens,
|
|
109
|
-
"image_tokens": prompt_image_tokens,
|
|
110
|
-
"cached_tokens": 0,
|
|
111
|
-
},
|
|
112
|
-
"completion_tokens_details": {
|
|
113
|
-
"text_tokens": completion_tokens,
|
|
114
|
-
"image_tokens": 0,
|
|
115
|
-
"reasoning_tokens": 0,
|
|
116
|
-
},
|
|
117
|
-
},
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
def stream_text_chat_completion(
|
|
122
|
-
backend,
|
|
123
|
-
messages: list[dict[str, Any]],
|
|
124
|
-
model: str,
|
|
125
|
-
thinking_effort: str = "",
|
|
126
|
-
) -> Iterator[dict[str, Any]]:
|
|
127
|
-
completion_id = f"chatcmpl-{uuid.uuid4().hex}"
|
|
128
|
-
created = int(time.time())
|
|
129
|
-
sent_role = False
|
|
130
|
-
request = ConversationRequest(model=model, messages=messages, thinking_effort=thinking_effort)
|
|
131
|
-
for delta_text in stream_text_deltas(backend, request):
|
|
132
|
-
if not sent_role:
|
|
133
|
-
sent_role = True
|
|
134
|
-
yield completion_chunk(model, {"role": "assistant", "content": delta_text}, None, completion_id, created)
|
|
135
|
-
else:
|
|
136
|
-
yield completion_chunk(model, {"content": delta_text}, None, completion_id, created)
|
|
137
|
-
if not sent_role:
|
|
138
|
-
yield completion_chunk(model, {"role": "assistant", "content": ""}, None, completion_id, created)
|
|
139
|
-
yield completion_chunk(model, {}, "stop", completion_id, created)
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
def collect_chat_content(chunks: Iterable[dict[str, Any]]) -> str:
|
|
143
|
-
parts: list[str] = []
|
|
144
|
-
for chunk in chunks:
|
|
145
|
-
choices = chunk.get("choices")
|
|
146
|
-
first = choices[0] if isinstance(choices, list) and choices and isinstance(choices[0], dict) else {}
|
|
147
|
-
delta = first.get("delta") if isinstance(first.get("delta"), dict) else {}
|
|
148
|
-
content = str(delta.get("content") or "")
|
|
149
|
-
if content:
|
|
150
|
-
parts.append(content)
|
|
151
|
-
return "".join(parts)
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
def chat_messages_from_body(body: dict[str, Any]) -> list[dict[str, Any]]:
|
|
155
|
-
messages = body.get("messages")
|
|
156
|
-
if isinstance(messages, list) and messages:
|
|
157
|
-
return [message for message in messages if isinstance(message, dict)]
|
|
158
|
-
prompt = str(body.get("prompt") or "").strip()
|
|
159
|
-
if prompt:
|
|
160
|
-
return [{"role": "user", "content": prompt}]
|
|
161
|
-
raise HTTPException(status_code=400, detail={"error": "messages or prompt is required"})
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
def chat_image_args(body: dict[str, Any]) -> tuple[str, str, int, list[tuple[bytes, str, str]]]:
|
|
165
|
-
model = str(body.get("model") or "gpt-image-2").strip() or "gpt-image-2"
|
|
166
|
-
prompt = extract_chat_prompt(body)
|
|
167
|
-
if not prompt:
|
|
168
|
-
raise HTTPException(status_code=400, detail={"error": "prompt is required"})
|
|
169
|
-
images = [
|
|
170
|
-
(data, f"image_{idx}.png", mime)
|
|
171
|
-
for idx, (data, mime) in enumerate(extract_chat_image(body), start=1)
|
|
172
|
-
]
|
|
173
|
-
return model, prompt, parse_image_count(body.get("n")), images
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
def text_chat_parts(body: dict[str, Any]) -> tuple[str, list[dict[str, Any]]]:
|
|
177
|
-
model = str(body.get("model") or "auto").strip() or "auto"
|
|
178
|
-
messages = normalize_text_messages(normalize_messages(chat_messages_from_body(body)))
|
|
179
|
-
if has_unsupported_tools(body, WEB_SEARCH_TOOL_TYPES):
|
|
180
|
-
messages.insert(0, {"role": "system", "content": TOOL_UNAVAILABLE_SYSTEM_MESSAGE})
|
|
181
|
-
return model, messages
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
def chat_completion_annotations(annotations: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
185
|
-
output = []
|
|
186
|
-
for item in annotations:
|
|
187
|
-
if item.get("type") != "url_citation":
|
|
188
|
-
continue
|
|
189
|
-
output.append({
|
|
190
|
-
"type": "url_citation",
|
|
191
|
-
"url_citation": {
|
|
192
|
-
"start_index": item.get("start_index", 0),
|
|
193
|
-
"end_index": item.get("end_index", 0),
|
|
194
|
-
"url": item.get("url", ""),
|
|
195
|
-
"title": item.get("title", ""),
|
|
196
|
-
},
|
|
197
|
-
})
|
|
198
|
-
return output
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
def web_search_chat_response(messages: list[dict[str, Any]], model: str) -> dict[str, Any]:
|
|
202
|
-
query = search_query_from_messages(messages)
|
|
203
|
-
if not query:
|
|
204
|
-
raise HTTPException(status_code=400, detail={"error": "messages or prompt is required for web search"})
|
|
205
|
-
text, annotations = text_with_url_citations(run_web_search(query))
|
|
206
|
-
return completion_response(
|
|
207
|
-
model,
|
|
208
|
-
text,
|
|
209
|
-
messages=messages,
|
|
210
|
-
annotations=chat_completion_annotations(annotations),
|
|
211
|
-
)
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
def stream_web_search_chat_completion(messages: list[dict[str, Any]], model: str) -> Iterator[dict[str, Any]]:
|
|
215
|
-
query = search_query_from_messages(messages)
|
|
216
|
-
if not query:
|
|
217
|
-
raise HTTPException(status_code=400, detail={"error": "messages or prompt is required for web search"})
|
|
218
|
-
text, _annotations = text_with_url_citations(run_web_search(query))
|
|
219
|
-
completion_id = f"chatcmpl-{uuid.uuid4().hex}"
|
|
220
|
-
created = int(time.time())
|
|
221
|
-
yield completion_chunk(model, {"role": "assistant", "content": text}, None, completion_id, created)
|
|
222
|
-
yield completion_chunk(model, {}, "stop", completion_id, created)
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
def image_result_content(result: dict[str, Any]) -> str:
|
|
226
|
-
data = result.get("data")
|
|
227
|
-
if isinstance(data, list) and data:
|
|
228
|
-
return build_chat_image_markdown_content(result)
|
|
229
|
-
return str(result.get("message") or "Image generation completed.")
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
def image_chat_response(body: dict[str, Any]) -> dict[str, Any]:
|
|
233
|
-
model, prompt, n, images = chat_image_args(body)
|
|
234
|
-
result = collect_image_outputs(stream_image_outputs_with_pool(ConversationRequest(
|
|
235
|
-
prompt=prompt,
|
|
236
|
-
model=model,
|
|
237
|
-
n=n,
|
|
238
|
-
response_format="b64_json",
|
|
239
|
-
images=encode_images(images) or None,
|
|
240
|
-
)))
|
|
241
|
-
response = completion_response(model, image_result_content(result), int(result.get("created") or 0) or None)
|
|
242
|
-
usage = image_usage(
|
|
243
|
-
input_text_tokens=count_text_tokens(prompt, model),
|
|
244
|
-
input_image_tokens=count_image_inputs_tokens(images, model),
|
|
245
|
-
output_tokens=count_image_output_items_tokens(result.get("data")),
|
|
246
|
-
)
|
|
247
|
-
response["usage"] = chat_usage_from_image_usage(usage)
|
|
248
|
-
return response
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
def image_chat_events(body: dict[str, Any]) -> Iterator[dict[str, Any]]:
|
|
252
|
-
model, prompt, n, images = chat_image_args(body)
|
|
253
|
-
image_outputs = stream_image_outputs_with_pool(ConversationRequest(
|
|
254
|
-
prompt=prompt,
|
|
255
|
-
model=model,
|
|
256
|
-
n=n,
|
|
257
|
-
response_format="b64_json",
|
|
258
|
-
images=encode_images(images) or None,
|
|
259
|
-
))
|
|
260
|
-
yield from stream_image_chat_completion(image_outputs, model)
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
def stream_image_chat_completion(image_outputs: Iterable[ImageOutput], model: str) -> Iterator[dict[str, Any]]:
|
|
264
|
-
completion_id = f"chatcmpl-{uuid.uuid4().hex}"
|
|
265
|
-
created = int(time.time())
|
|
266
|
-
sent_role = False
|
|
267
|
-
sent_text = ""
|
|
268
|
-
for output in image_outputs:
|
|
269
|
-
content = ""
|
|
270
|
-
if output.kind == "progress":
|
|
271
|
-
content = output.text
|
|
272
|
-
sent_text += content
|
|
273
|
-
elif output.kind == "result":
|
|
274
|
-
content = build_chat_image_markdown_content({"data": output.data})
|
|
275
|
-
elif output.kind == "message":
|
|
276
|
-
content = output.text[len(sent_text):] if output.text.startswith(sent_text) else output.text
|
|
277
|
-
if not content:
|
|
278
|
-
continue
|
|
279
|
-
if not sent_role:
|
|
280
|
-
sent_role = True
|
|
281
|
-
yield completion_chunk(model, {"role": "assistant", "content": content}, None, completion_id, created)
|
|
282
|
-
else:
|
|
283
|
-
yield completion_chunk(model, {"content": content}, None, completion_id, created)
|
|
284
|
-
if not sent_role:
|
|
285
|
-
yield completion_chunk(model, {"role": "assistant", "content": ""}, None, completion_id, created)
|
|
286
|
-
yield completion_chunk(model, {}, "stop", completion_id, created)
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
def handle(body: dict[str, Any]) -> dict[str, Any] | Iterator[dict[str, Any]]:
|
|
290
|
-
if body.get("stream"):
|
|
291
|
-
if is_image_chat_request(body):
|
|
292
|
-
return image_chat_events(body)
|
|
293
|
-
model, messages = text_chat_parts(body)
|
|
294
|
-
if is_web_search_chat_request(body) and not has_unsupported_tools(body, WEB_SEARCH_TOOL_TYPES):
|
|
295
|
-
return stream_web_search_chat_completion(messages, model)
|
|
296
|
-
thinking_effort = thinking_effort_from_body(body)
|
|
297
|
-
key = cache_key(body, messages, stream=True)
|
|
298
|
-
return chat_completion_cache.get_or_compute_stream(
|
|
299
|
-
key,
|
|
300
|
-
lambda: stream_text_chat_completion(text_backend(model), messages, model, thinking_effort),
|
|
301
|
-
)
|
|
302
|
-
if is_image_chat_request(body):
|
|
303
|
-
return image_chat_response(body)
|
|
304
|
-
model, messages = text_chat_parts(body)
|
|
305
|
-
if is_web_search_chat_request(body) and not has_unsupported_tools(body, WEB_SEARCH_TOOL_TYPES):
|
|
306
|
-
return web_search_chat_response(messages, model)
|
|
307
|
-
thinking_effort = thinking_effort_from_body(body)
|
|
308
|
-
key = cache_key(body, messages, stream=False)
|
|
309
|
-
return chat_completion_cache.get_or_compute_response(
|
|
310
|
-
key,
|
|
311
|
-
lambda: completion_response(
|
|
312
|
-
model,
|
|
313
|
-
collect_text(text_backend(model), ConversationRequest(model=model, messages=messages, thinking_effort=thinking_effort)),
|
|
314
|
-
messages=messages,
|
|
315
|
-
),
|
|
316
|
-
)
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
import uuid
|
|
5
|
+
from typing import Any, Iterable, Iterator
|
|
6
|
+
|
|
7
|
+
from fastapi import HTTPException
|
|
8
|
+
|
|
9
|
+
from services.protocol.chat_completion_cache import cache_key, chat_completion_cache, normalize_text_messages
|
|
10
|
+
from services.protocol.conversation import (
|
|
11
|
+
ConversationRequest,
|
|
12
|
+
ImageOutput,
|
|
13
|
+
collect_image_outputs,
|
|
14
|
+
collect_text,
|
|
15
|
+
count_message_image_tokens,
|
|
16
|
+
count_message_text_tokens,
|
|
17
|
+
count_text_tokens,
|
|
18
|
+
encode_images,
|
|
19
|
+
normalize_messages,
|
|
20
|
+
stream_image_outputs_with_pool,
|
|
21
|
+
stream_text_deltas,
|
|
22
|
+
text_backend,
|
|
23
|
+
)
|
|
24
|
+
from services.protocol.web_search_tool import (
|
|
25
|
+
WEB_SEARCH_TOOL_TYPES,
|
|
26
|
+
has_unsupported_tools,
|
|
27
|
+
is_web_search_chat_request,
|
|
28
|
+
run_web_search,
|
|
29
|
+
search_query_from_messages,
|
|
30
|
+
text_with_url_citations,
|
|
31
|
+
)
|
|
32
|
+
from utils.helper import build_chat_image_markdown_content, extract_chat_image, extract_chat_prompt, is_image_chat_request, parse_image_count
|
|
33
|
+
from utils.image_tokens import (
|
|
34
|
+
chat_usage_from_image_usage,
|
|
35
|
+
count_image_inputs_tokens,
|
|
36
|
+
count_image_output_items_tokens,
|
|
37
|
+
image_usage,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
TOOL_UNAVAILABLE_SYSTEM_MESSAGE = (
|
|
41
|
+
"This compatibility backend cannot execute local tools, shell commands, non-search tools, "
|
|
42
|
+
"or file operations. Do not claim to have run tools or inspected external resources. "
|
|
43
|
+
"If a user asks you to use a tool, say that tool execution is unavailable through this backend."
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def normalize_thinking_effort(value: object) -> str:
|
|
48
|
+
normalized = str(value or "").strip().lower()
|
|
49
|
+
if normalized in {"", "none"}:
|
|
50
|
+
return ""
|
|
51
|
+
if normalized in {"low", "medium", "high"}:
|
|
52
|
+
return normalized
|
|
53
|
+
if normalized in {"xhigh", "extended"}:
|
|
54
|
+
return "extended"
|
|
55
|
+
return ""
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def thinking_effort_from_body(body: dict[str, Any]) -> str:
|
|
59
|
+
if "thinking_effort" in body:
|
|
60
|
+
return normalize_thinking_effort(body.get("thinking_effort"))
|
|
61
|
+
if "reasoning_effort" in body:
|
|
62
|
+
return normalize_thinking_effort(body.get("reasoning_effort"))
|
|
63
|
+
reasoning = body.get("reasoning")
|
|
64
|
+
if isinstance(reasoning, dict):
|
|
65
|
+
return normalize_thinking_effort(reasoning.get("effort"))
|
|
66
|
+
return ""
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def completion_chunk(model: str, delta: dict[str, Any], finish_reason: str | None = None, completion_id: str = "", created: int | None = None) -> dict[str, Any]:
|
|
70
|
+
return {
|
|
71
|
+
"id": completion_id or f"chatcmpl-{uuid.uuid4().hex}",
|
|
72
|
+
"object": "chat.completion.chunk",
|
|
73
|
+
"created": created or int(time.time()),
|
|
74
|
+
"model": model,
|
|
75
|
+
"choices": [{"index": 0, "delta": delta, "finish_reason": finish_reason}],
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def completion_response(
|
|
80
|
+
model: str,
|
|
81
|
+
content: str,
|
|
82
|
+
created: int | None = None,
|
|
83
|
+
messages: list[dict[str, Any]] | None = None,
|
|
84
|
+
annotations: list[dict[str, Any]] | None = None,
|
|
85
|
+
) -> dict[str, Any]:
|
|
86
|
+
prompt_text_tokens = count_message_text_tokens(messages, model) if messages else 0
|
|
87
|
+
prompt_image_tokens = count_message_image_tokens(messages, model) if messages else 0
|
|
88
|
+
prompt_tokens = prompt_text_tokens + prompt_image_tokens
|
|
89
|
+
completion_tokens = count_text_tokens(content, model) if messages else 0
|
|
90
|
+
message = {"role": "assistant", "content": content}
|
|
91
|
+
if annotations:
|
|
92
|
+
message["annotations"] = annotations
|
|
93
|
+
return {
|
|
94
|
+
"id": f"chatcmpl-{uuid.uuid4().hex}",
|
|
95
|
+
"object": "chat.completion",
|
|
96
|
+
"created": created or int(time.time()),
|
|
97
|
+
"model": model,
|
|
98
|
+
"choices": [{
|
|
99
|
+
"index": 0,
|
|
100
|
+
"message": message,
|
|
101
|
+
"finish_reason": "stop",
|
|
102
|
+
}],
|
|
103
|
+
"usage": {
|
|
104
|
+
"prompt_tokens": prompt_tokens,
|
|
105
|
+
"completion_tokens": completion_tokens,
|
|
106
|
+
"total_tokens": prompt_tokens + completion_tokens,
|
|
107
|
+
"prompt_tokens_details": {
|
|
108
|
+
"text_tokens": prompt_text_tokens,
|
|
109
|
+
"image_tokens": prompt_image_tokens,
|
|
110
|
+
"cached_tokens": 0,
|
|
111
|
+
},
|
|
112
|
+
"completion_tokens_details": {
|
|
113
|
+
"text_tokens": completion_tokens,
|
|
114
|
+
"image_tokens": 0,
|
|
115
|
+
"reasoning_tokens": 0,
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def stream_text_chat_completion(
|
|
122
|
+
backend,
|
|
123
|
+
messages: list[dict[str, Any]],
|
|
124
|
+
model: str,
|
|
125
|
+
thinking_effort: str = "",
|
|
126
|
+
) -> Iterator[dict[str, Any]]:
|
|
127
|
+
completion_id = f"chatcmpl-{uuid.uuid4().hex}"
|
|
128
|
+
created = int(time.time())
|
|
129
|
+
sent_role = False
|
|
130
|
+
request = ConversationRequest(model=model, messages=messages, thinking_effort=thinking_effort)
|
|
131
|
+
for delta_text in stream_text_deltas(backend, request):
|
|
132
|
+
if not sent_role:
|
|
133
|
+
sent_role = True
|
|
134
|
+
yield completion_chunk(model, {"role": "assistant", "content": delta_text}, None, completion_id, created)
|
|
135
|
+
else:
|
|
136
|
+
yield completion_chunk(model, {"content": delta_text}, None, completion_id, created)
|
|
137
|
+
if not sent_role:
|
|
138
|
+
yield completion_chunk(model, {"role": "assistant", "content": ""}, None, completion_id, created)
|
|
139
|
+
yield completion_chunk(model, {}, "stop", completion_id, created)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def collect_chat_content(chunks: Iterable[dict[str, Any]]) -> str:
|
|
143
|
+
parts: list[str] = []
|
|
144
|
+
for chunk in chunks:
|
|
145
|
+
choices = chunk.get("choices")
|
|
146
|
+
first = choices[0] if isinstance(choices, list) and choices and isinstance(choices[0], dict) else {}
|
|
147
|
+
delta = first.get("delta") if isinstance(first.get("delta"), dict) else {}
|
|
148
|
+
content = str(delta.get("content") or "")
|
|
149
|
+
if content:
|
|
150
|
+
parts.append(content)
|
|
151
|
+
return "".join(parts)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def chat_messages_from_body(body: dict[str, Any]) -> list[dict[str, Any]]:
|
|
155
|
+
messages = body.get("messages")
|
|
156
|
+
if isinstance(messages, list) and messages:
|
|
157
|
+
return [message for message in messages if isinstance(message, dict)]
|
|
158
|
+
prompt = str(body.get("prompt") or "").strip()
|
|
159
|
+
if prompt:
|
|
160
|
+
return [{"role": "user", "content": prompt}]
|
|
161
|
+
raise HTTPException(status_code=400, detail={"error": "messages or prompt is required"})
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def chat_image_args(body: dict[str, Any]) -> tuple[str, str, int, list[tuple[bytes, str, str]]]:
|
|
165
|
+
model = str(body.get("model") or "gpt-image-2").strip() or "gpt-image-2"
|
|
166
|
+
prompt = extract_chat_prompt(body)
|
|
167
|
+
if not prompt:
|
|
168
|
+
raise HTTPException(status_code=400, detail={"error": "prompt is required"})
|
|
169
|
+
images = [
|
|
170
|
+
(data, f"image_{idx}.png", mime)
|
|
171
|
+
for idx, (data, mime) in enumerate(extract_chat_image(body), start=1)
|
|
172
|
+
]
|
|
173
|
+
return model, prompt, parse_image_count(body.get("n")), images
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def text_chat_parts(body: dict[str, Any]) -> tuple[str, list[dict[str, Any]]]:
|
|
177
|
+
model = str(body.get("model") or "auto").strip() or "auto"
|
|
178
|
+
messages = normalize_text_messages(normalize_messages(chat_messages_from_body(body)))
|
|
179
|
+
if has_unsupported_tools(body, WEB_SEARCH_TOOL_TYPES):
|
|
180
|
+
messages.insert(0, {"role": "system", "content": TOOL_UNAVAILABLE_SYSTEM_MESSAGE})
|
|
181
|
+
return model, messages
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def chat_completion_annotations(annotations: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
185
|
+
output = []
|
|
186
|
+
for item in annotations:
|
|
187
|
+
if item.get("type") != "url_citation":
|
|
188
|
+
continue
|
|
189
|
+
output.append({
|
|
190
|
+
"type": "url_citation",
|
|
191
|
+
"url_citation": {
|
|
192
|
+
"start_index": item.get("start_index", 0),
|
|
193
|
+
"end_index": item.get("end_index", 0),
|
|
194
|
+
"url": item.get("url", ""),
|
|
195
|
+
"title": item.get("title", ""),
|
|
196
|
+
},
|
|
197
|
+
})
|
|
198
|
+
return output
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def web_search_chat_response(messages: list[dict[str, Any]], model: str) -> dict[str, Any]:
|
|
202
|
+
query = search_query_from_messages(messages)
|
|
203
|
+
if not query:
|
|
204
|
+
raise HTTPException(status_code=400, detail={"error": "messages or prompt is required for web search"})
|
|
205
|
+
text, annotations = text_with_url_citations(run_web_search(query))
|
|
206
|
+
return completion_response(
|
|
207
|
+
model,
|
|
208
|
+
text,
|
|
209
|
+
messages=messages,
|
|
210
|
+
annotations=chat_completion_annotations(annotations),
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def stream_web_search_chat_completion(messages: list[dict[str, Any]], model: str) -> Iterator[dict[str, Any]]:
|
|
215
|
+
query = search_query_from_messages(messages)
|
|
216
|
+
if not query:
|
|
217
|
+
raise HTTPException(status_code=400, detail={"error": "messages or prompt is required for web search"})
|
|
218
|
+
text, _annotations = text_with_url_citations(run_web_search(query))
|
|
219
|
+
completion_id = f"chatcmpl-{uuid.uuid4().hex}"
|
|
220
|
+
created = int(time.time())
|
|
221
|
+
yield completion_chunk(model, {"role": "assistant", "content": text}, None, completion_id, created)
|
|
222
|
+
yield completion_chunk(model, {}, "stop", completion_id, created)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def image_result_content(result: dict[str, Any]) -> str:
|
|
226
|
+
data = result.get("data")
|
|
227
|
+
if isinstance(data, list) and data:
|
|
228
|
+
return build_chat_image_markdown_content(result)
|
|
229
|
+
return str(result.get("message") or "Image generation completed.")
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def image_chat_response(body: dict[str, Any]) -> dict[str, Any]:
|
|
233
|
+
model, prompt, n, images = chat_image_args(body)
|
|
234
|
+
result = collect_image_outputs(stream_image_outputs_with_pool(ConversationRequest(
|
|
235
|
+
prompt=prompt,
|
|
236
|
+
model=model,
|
|
237
|
+
n=n,
|
|
238
|
+
response_format="b64_json",
|
|
239
|
+
images=encode_images(images) or None,
|
|
240
|
+
)))
|
|
241
|
+
response = completion_response(model, image_result_content(result), int(result.get("created") or 0) or None)
|
|
242
|
+
usage = image_usage(
|
|
243
|
+
input_text_tokens=count_text_tokens(prompt, model),
|
|
244
|
+
input_image_tokens=count_image_inputs_tokens(images, model),
|
|
245
|
+
output_tokens=count_image_output_items_tokens(result.get("data")),
|
|
246
|
+
)
|
|
247
|
+
response["usage"] = chat_usage_from_image_usage(usage)
|
|
248
|
+
return response
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def image_chat_events(body: dict[str, Any]) -> Iterator[dict[str, Any]]:
|
|
252
|
+
model, prompt, n, images = chat_image_args(body)
|
|
253
|
+
image_outputs = stream_image_outputs_with_pool(ConversationRequest(
|
|
254
|
+
prompt=prompt,
|
|
255
|
+
model=model,
|
|
256
|
+
n=n,
|
|
257
|
+
response_format="b64_json",
|
|
258
|
+
images=encode_images(images) or None,
|
|
259
|
+
))
|
|
260
|
+
yield from stream_image_chat_completion(image_outputs, model)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def stream_image_chat_completion(image_outputs: Iterable[ImageOutput], model: str) -> Iterator[dict[str, Any]]:
|
|
264
|
+
completion_id = f"chatcmpl-{uuid.uuid4().hex}"
|
|
265
|
+
created = int(time.time())
|
|
266
|
+
sent_role = False
|
|
267
|
+
sent_text = ""
|
|
268
|
+
for output in image_outputs:
|
|
269
|
+
content = ""
|
|
270
|
+
if output.kind == "progress":
|
|
271
|
+
content = output.text
|
|
272
|
+
sent_text += content
|
|
273
|
+
elif output.kind == "result":
|
|
274
|
+
content = build_chat_image_markdown_content({"data": output.data})
|
|
275
|
+
elif output.kind == "message":
|
|
276
|
+
content = output.text[len(sent_text):] if output.text.startswith(sent_text) else output.text
|
|
277
|
+
if not content:
|
|
278
|
+
continue
|
|
279
|
+
if not sent_role:
|
|
280
|
+
sent_role = True
|
|
281
|
+
yield completion_chunk(model, {"role": "assistant", "content": content}, None, completion_id, created)
|
|
282
|
+
else:
|
|
283
|
+
yield completion_chunk(model, {"content": content}, None, completion_id, created)
|
|
284
|
+
if not sent_role:
|
|
285
|
+
yield completion_chunk(model, {"role": "assistant", "content": ""}, None, completion_id, created)
|
|
286
|
+
yield completion_chunk(model, {}, "stop", completion_id, created)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def handle(body: dict[str, Any]) -> dict[str, Any] | Iterator[dict[str, Any]]:
|
|
290
|
+
if body.get("stream"):
|
|
291
|
+
if is_image_chat_request(body):
|
|
292
|
+
return image_chat_events(body)
|
|
293
|
+
model, messages = text_chat_parts(body)
|
|
294
|
+
if is_web_search_chat_request(body) and not has_unsupported_tools(body, WEB_SEARCH_TOOL_TYPES):
|
|
295
|
+
return stream_web_search_chat_completion(messages, model)
|
|
296
|
+
thinking_effort = thinking_effort_from_body(body)
|
|
297
|
+
key = cache_key(body, messages, stream=True)
|
|
298
|
+
return chat_completion_cache.get_or_compute_stream(
|
|
299
|
+
key,
|
|
300
|
+
lambda: stream_text_chat_completion(text_backend(model), messages, model, thinking_effort),
|
|
301
|
+
)
|
|
302
|
+
if is_image_chat_request(body):
|
|
303
|
+
return image_chat_response(body)
|
|
304
|
+
model, messages = text_chat_parts(body)
|
|
305
|
+
if is_web_search_chat_request(body) and not has_unsupported_tools(body, WEB_SEARCH_TOOL_TYPES):
|
|
306
|
+
return web_search_chat_response(messages, model)
|
|
307
|
+
thinking_effort = thinking_effort_from_body(body)
|
|
308
|
+
key = cache_key(body, messages, stream=False)
|
|
309
|
+
return chat_completion_cache.get_or_compute_response(
|
|
310
|
+
key,
|
|
311
|
+
lambda: completion_response(
|
|
312
|
+
model,
|
|
313
|
+
collect_text(text_backend(model), ConversationRequest(model=model, messages=messages, thinking_effort=thinking_effort)),
|
|
314
|
+
messages=messages,
|
|
315
|
+
),
|
|
316
|
+
)
|