deepparallel 0.4.2__tar.gz → 0.5.0__tar.gz
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.
- {deepparallel-0.4.2 → deepparallel-0.5.0}/PKG-INFO +1 -1
- {deepparallel-0.4.2 → deepparallel-0.5.0}/deepparallel/__init__.py +1 -1
- {deepparallel-0.4.2 → deepparallel-0.5.0}/deepparallel/backend.py +232 -5
- deepparallel-0.5.0/deepparallel/branding.py +396 -0
- {deepparallel-0.4.2 → deepparallel-0.5.0}/deepparallel/cli.py +19 -2
- {deepparallel-0.4.2 → deepparallel-0.5.0}/deepparallel/config.py +35 -1
- deepparallel-0.5.0/deepparallel/crowe_id.py +75 -0
- deepparallel-0.5.0/deepparallel/dsml.py +129 -0
- {deepparallel-0.4.2 → deepparallel-0.5.0}/deepparallel/licensing.py +1 -1
- {deepparallel-0.4.2 → deepparallel-0.5.0}/deepparallel/renderer.py +28 -6
- deepparallel-0.5.0/deepparallel/research/provider.py +125 -0
- deepparallel-0.5.0/deepparallel/routing.example.json +32 -0
- deepparallel-0.5.0/deepparallel/routing.py +135 -0
- deepparallel-0.5.0/deepparallel/serve.py +354 -0
- deepparallel-0.5.0/deepparallel/system_prompt.txt +7 -0
- {deepparallel-0.4.2 → deepparallel-0.5.0}/deepparallel/tools/__init__.py +1 -0
- deepparallel-0.5.0/deepparallel/tools/mcp.py +280 -0
- deepparallel-0.5.0/deepparallel/userinput.py +135 -0
- {deepparallel-0.4.2 → deepparallel-0.5.0}/deepparallel.egg-info/PKG-INFO +1 -1
- {deepparallel-0.4.2 → deepparallel-0.5.0}/deepparallel.egg-info/SOURCES.txt +21 -1
- {deepparallel-0.4.2 → deepparallel-0.5.0}/pyproject.toml +2 -2
- {deepparallel-0.4.2 → deepparallel-0.5.0}/tests/test_backend.py +60 -0
- deepparallel-0.5.0/tests/test_crowe_backend.py +79 -0
- deepparallel-0.5.0/tests/test_crowe_gateway_backend.py +99 -0
- deepparallel-0.5.0/tests/test_crowe_id_auth.py +112 -0
- deepparallel-0.5.0/tests/test_crowe_payment_required.py +70 -0
- deepparallel-0.5.0/tests/test_dsml.py +104 -0
- {deepparallel-0.4.2 → deepparallel-0.5.0}/tests/test_renderer.py +64 -2
- deepparallel-0.5.0/tests/test_research_provider.py +96 -0
- deepparallel-0.5.0/tests/test_routing.py +169 -0
- deepparallel-0.5.0/tests/test_serve.py +259 -0
- deepparallel-0.5.0/tests/test_spinner_color.py +45 -0
- deepparallel-0.5.0/tests/test_tools_mcp.py +47 -0
- deepparallel-0.5.0/tests/test_userinput.py +40 -0
- deepparallel-0.5.0/tests/test_userinput_paste.py +71 -0
- deepparallel-0.4.2/deepparallel/branding.py +0 -211
- deepparallel-0.4.2/deepparallel/system_prompt.txt +0 -4
- {deepparallel-0.4.2 → deepparallel-0.5.0}/README.md +0 -0
- {deepparallel-0.4.2 → deepparallel-0.5.0}/deepparallel/agent.py +0 -0
- {deepparallel-0.4.2 → deepparallel-0.5.0}/deepparallel/fusion.py +0 -0
- {deepparallel-0.4.2 → deepparallel-0.5.0}/deepparallel/registry.json +0 -0
- {deepparallel-0.4.2 → deepparallel-0.5.0}/deepparallel/research/__init__.py +0 -0
- {deepparallel-0.4.2 → deepparallel-0.5.0}/deepparallel/research/conduit.py +0 -0
- {deepparallel-0.4.2 → deepparallel-0.5.0}/deepparallel/supply_chain.py +0 -0
- {deepparallel-0.4.2 → deepparallel-0.5.0}/deepparallel/tools/codeast.py +0 -0
- {deepparallel-0.4.2 → deepparallel-0.5.0}/deepparallel/tools/edit.py +0 -0
- {deepparallel-0.4.2 → deepparallel-0.5.0}/deepparallel/tools/files.py +0 -0
- {deepparallel-0.4.2 → deepparallel-0.5.0}/deepparallel/tools/registry.py +0 -0
- {deepparallel-0.4.2 → deepparallel-0.5.0}/deepparallel/tools/sandbox.py +0 -0
- {deepparallel-0.4.2 → deepparallel-0.5.0}/deepparallel/tools/search.py +0 -0
- {deepparallel-0.4.2 → deepparallel-0.5.0}/deepparallel/tools/shell.py +0 -0
- {deepparallel-0.4.2 → deepparallel-0.5.0}/deepparallel/tools/vision.py +0 -0
- {deepparallel-0.4.2 → deepparallel-0.5.0}/deepparallel/tools/web.py +0 -0
- {deepparallel-0.4.2 → deepparallel-0.5.0}/deepparallel.egg-info/dependency_links.txt +0 -0
- {deepparallel-0.4.2 → deepparallel-0.5.0}/deepparallel.egg-info/entry_points.txt +0 -0
- {deepparallel-0.4.2 → deepparallel-0.5.0}/deepparallel.egg-info/requires.txt +0 -0
- {deepparallel-0.4.2 → deepparallel-0.5.0}/deepparallel.egg-info/top_level.txt +0 -0
- {deepparallel-0.4.2 → deepparallel-0.5.0}/setup.cfg +0 -0
- {deepparallel-0.4.2 → deepparallel-0.5.0}/tests/test_agent.py +0 -0
- {deepparallel-0.4.2 → deepparallel-0.5.0}/tests/test_backend_chat.py +0 -0
- {deepparallel-0.4.2 → deepparallel-0.5.0}/tests/test_backend_stream.py +0 -0
- {deepparallel-0.4.2 → deepparallel-0.5.0}/tests/test_branding.py +0 -0
- {deepparallel-0.4.2 → deepparallel-0.5.0}/tests/test_cli.py +0 -0
- {deepparallel-0.4.2 → deepparallel-0.5.0}/tests/test_config.py +0 -0
- {deepparallel-0.4.2 → deepparallel-0.5.0}/tests/test_fusion.py +0 -0
- {deepparallel-0.4.2 → deepparallel-0.5.0}/tests/test_issuer_signer.py +0 -0
- {deepparallel-0.4.2 → deepparallel-0.5.0}/tests/test_licensing.py +0 -0
- {deepparallel-0.4.2 → deepparallel-0.5.0}/tests/test_research.py +0 -0
- {deepparallel-0.4.2 → deepparallel-0.5.0}/tests/test_supply_chain.py +0 -0
- {deepparallel-0.4.2 → deepparallel-0.5.0}/tests/test_tool_registry.py +0 -0
- {deepparallel-0.4.2 → deepparallel-0.5.0}/tests/test_tools_codeast.py +0 -0
- {deepparallel-0.4.2 → deepparallel-0.5.0}/tests/test_tools_edit.py +0 -0
- {deepparallel-0.4.2 → deepparallel-0.5.0}/tests/test_tools_files.py +0 -0
- {deepparallel-0.4.2 → deepparallel-0.5.0}/tests/test_tools_sandbox.py +0 -0
- {deepparallel-0.4.2 → deepparallel-0.5.0}/tests/test_tools_search.py +0 -0
- {deepparallel-0.4.2 → deepparallel-0.5.0}/tests/test_tools_shell.py +0 -0
- {deepparallel-0.4.2 → deepparallel-0.5.0}/tests/test_tools_vision.py +0 -0
- {deepparallel-0.4.2 → deepparallel-0.5.0}/tests/test_tools_web.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: deepparallel
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: DeepParallel - a multi-model agentic coding CLI with cross-model Guardian review, served via Crowe Logic.
|
|
5
5
|
Author-email: Michael Crowe <michael@crowelogic.com>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -16,9 +16,14 @@ from urllib.parse import urlparse
|
|
|
16
16
|
|
|
17
17
|
import httpx
|
|
18
18
|
|
|
19
|
+
from . import crowe_id
|
|
20
|
+
|
|
19
21
|
Chunk = tuple[str, str] # (channel, text)
|
|
20
22
|
|
|
21
23
|
_STREAM_TIMEOUT = httpx.Timeout(120.0, connect=10.0)
|
|
24
|
+
# Modal scale-to-zero cold start can take 2-3 min before the first byte, so the
|
|
25
|
+
# read timeout must be generous; connect stays short.
|
|
26
|
+
_MODAL_TIMEOUT = httpx.Timeout(600.0, connect=15.0)
|
|
22
27
|
_CHECK_TIMEOUT = 4.0
|
|
23
28
|
|
|
24
29
|
|
|
@@ -210,17 +215,24 @@ class AzureBackend:
|
|
|
210
215
|
class FoundryBackend:
|
|
211
216
|
label = "Foundry control plane"
|
|
212
217
|
|
|
213
|
-
def __init__(self, base_url: str, api_key: str, model: str):
|
|
218
|
+
def __init__(self, base_url: str, api_key: str, model: str, token_provider=None):
|
|
214
219
|
self._base_url = (base_url or "").rstrip("/")
|
|
215
220
|
self._api_key = api_key or ""
|
|
216
221
|
self._model = model
|
|
222
|
+
# Optional callable returning a fresh bearer (e.g. a Crowe ID
|
|
223
|
+
# client_credentials token). When set it takes precedence over the static
|
|
224
|
+
# api_key, so the gateway sees a sovereign agent identity per request.
|
|
225
|
+
self._token_provider = token_provider
|
|
217
226
|
|
|
218
227
|
@property
|
|
219
228
|
def _url(self) -> str:
|
|
220
229
|
return f"{self._base_url}/v1/chat/completions"
|
|
221
230
|
|
|
231
|
+
def _bearer(self) -> str:
|
|
232
|
+
return self._token_provider() if self._token_provider else self._api_key
|
|
233
|
+
|
|
222
234
|
def check(self) -> tuple[bool, str]:
|
|
223
|
-
if not self._base_url or not self._api_key:
|
|
235
|
+
if not self._base_url or not (self._api_key or self._token_provider):
|
|
224
236
|
return False, "Foundry base URL or API key not configured."
|
|
225
237
|
try:
|
|
226
238
|
httpx.get(_host(self._base_url), timeout=_CHECK_TIMEOUT)
|
|
@@ -237,7 +249,7 @@ class FoundryBackend:
|
|
|
237
249
|
"max_tokens": max_tokens,
|
|
238
250
|
}
|
|
239
251
|
headers = {
|
|
240
|
-
"authorization": f"Bearer {self.
|
|
252
|
+
"authorization": f"Bearer {self._bearer()}",
|
|
241
253
|
"content-type": "application/json",
|
|
242
254
|
}
|
|
243
255
|
with httpx.stream(
|
|
@@ -257,7 +269,7 @@ class FoundryBackend:
|
|
|
257
269
|
if tools:
|
|
258
270
|
payload["tools"] = tools
|
|
259
271
|
headers = {
|
|
260
|
-
"authorization": f"Bearer {self.
|
|
272
|
+
"authorization": f"Bearer {self._bearer()}",
|
|
261
273
|
"content-type": "application/json",
|
|
262
274
|
}
|
|
263
275
|
r = httpx.post(self._url, json=payload, headers=headers, timeout=_STREAM_TIMEOUT)
|
|
@@ -275,7 +287,7 @@ class FoundryBackend:
|
|
|
275
287
|
if tools:
|
|
276
288
|
payload["tools"] = tools
|
|
277
289
|
headers = {
|
|
278
|
-
"authorization": f"Bearer {self.
|
|
290
|
+
"authorization": f"Bearer {self._bearer()}",
|
|
279
291
|
"content-type": "application/json",
|
|
280
292
|
}
|
|
281
293
|
with httpx.stream(
|
|
@@ -285,8 +297,208 @@ class FoundryBackend:
|
|
|
285
297
|
return (yield from parse_sse_stream(r.iter_lines()))
|
|
286
298
|
|
|
287
299
|
|
|
300
|
+
class PaymentRequired(Exception):
|
|
301
|
+
"""The agent's wallet can't cover the call — the x402 rail returned HTTP 402.
|
|
302
|
+
|
|
303
|
+
Carries the parsed x402 envelope so callers can see the price + accepted schemes
|
|
304
|
+
and decide how to fund (top-up) or pay (X-PAYMENT)."""
|
|
305
|
+
|
|
306
|
+
def __init__(self, envelope: dict):
|
|
307
|
+
self.envelope = envelope or {}
|
|
308
|
+
accepts = self.envelope.get("accepts", [])
|
|
309
|
+
price = accepts[0].get("maxAmountRequired", "?") if accepts else "?"
|
|
310
|
+
schemes = ", ".join(a.get("scheme", "") for a in accepts) or "?"
|
|
311
|
+
super().__init__(
|
|
312
|
+
f"payment required: {price} micro-USD via [{schemes}] — fund the agent wallet"
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
class CroweGatewayBackend:
|
|
317
|
+
"""Foundry gateway PAID agent rail (/api/agent/v1/chat by default), Crowe ID auth.
|
|
318
|
+
|
|
319
|
+
Targets the x402 agent endpoint that debits the agent's wallet per call (override
|
|
320
|
+
with CROWE_AGENT_RESOURCE). Native GatewayResponse shape, not the OpenAI-compat
|
|
321
|
+
/v1 path (which 404s there); non-streaming with no server-side tool-calls, so we
|
|
322
|
+
adapt it to DeepParallel's streaming seam by yielding the full completion as a
|
|
323
|
+
single content chunk. The bearer is a Crowe ID client_credentials token from
|
|
324
|
+
``token_provider`` (the agent's sovereign identity).
|
|
325
|
+
"""
|
|
326
|
+
|
|
327
|
+
label = "Crowe ID agent (Foundry gateway)"
|
|
328
|
+
|
|
329
|
+
def __init__(self, base_url: str, model: str, token_provider=None):
|
|
330
|
+
self._base_url = (base_url or "").rstrip("/")
|
|
331
|
+
self._model = model
|
|
332
|
+
self._token_provider = token_provider
|
|
333
|
+
|
|
334
|
+
@property
|
|
335
|
+
def _url(self) -> str:
|
|
336
|
+
# The PAID x402 agent rail (debits the agent's wallet). Overridable via
|
|
337
|
+
# CROWE_AGENT_RESOURCE for the legacy non-paid /api/gateway/chat path.
|
|
338
|
+
import os
|
|
339
|
+
|
|
340
|
+
resource = os.environ.get("CROWE_AGENT_RESOURCE", "/api/agent/v1/chat")
|
|
341
|
+
return f"{self._base_url}{resource}"
|
|
342
|
+
|
|
343
|
+
def _bearer(self) -> str:
|
|
344
|
+
return self._token_provider() if self._token_provider else ""
|
|
345
|
+
|
|
346
|
+
def _headers(self) -> dict:
|
|
347
|
+
return {
|
|
348
|
+
"authorization": f"Bearer {self._bearer()}",
|
|
349
|
+
"content-type": "application/json",
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
def check(self) -> tuple[bool, str]:
|
|
353
|
+
if not self._base_url or not self._token_provider:
|
|
354
|
+
return False, "Crowe gateway URL or Crowe ID credentials not configured."
|
|
355
|
+
try:
|
|
356
|
+
httpx.get(_host(self._base_url), timeout=_CHECK_TIMEOUT)
|
|
357
|
+
except Exception as e: # noqa: BLE001 - reachability probe
|
|
358
|
+
return False, f"Crowe gateway unreachable ({e.__class__.__name__})"
|
|
359
|
+
return True, f"Crowe ID @ {_host(self._base_url)}"
|
|
360
|
+
|
|
361
|
+
def _complete(self, messages, temperature, max_tokens) -> str:
|
|
362
|
+
payload = {
|
|
363
|
+
"model": self._model,
|
|
364
|
+
"messages": messages,
|
|
365
|
+
"temperature": temperature,
|
|
366
|
+
"max_tokens": max_tokens,
|
|
367
|
+
}
|
|
368
|
+
r = httpx.post(self._url, json=payload, headers=self._headers(), timeout=_STREAM_TIMEOUT)
|
|
369
|
+
if r.status_code == 402:
|
|
370
|
+
# x402 payment-required: surface the machine-readable envelope as an
|
|
371
|
+
# actionable error (price + schemes) rather than a raw HTTP error.
|
|
372
|
+
try:
|
|
373
|
+
envelope = r.json()
|
|
374
|
+
except Exception: # noqa: BLE001 - tolerate a non-JSON 402 body
|
|
375
|
+
envelope = {}
|
|
376
|
+
raise PaymentRequired(envelope)
|
|
377
|
+
r.raise_for_status()
|
|
378
|
+
return r.json().get("content", "")
|
|
379
|
+
|
|
380
|
+
def stream_chat(self, messages, temperature, max_tokens):
|
|
381
|
+
yield ("content", self._complete(messages, temperature, max_tokens))
|
|
382
|
+
|
|
383
|
+
def chat(self, messages, tools, temperature, max_tokens) -> dict:
|
|
384
|
+
# Native gateway endpoint has no tool-calling; tools are ignored.
|
|
385
|
+
return {"role": "assistant", "content": self._complete(messages, temperature, max_tokens)}
|
|
386
|
+
|
|
387
|
+
def stream_chat_tools(self, messages, tools, temperature, max_tokens):
|
|
388
|
+
# No server-side tool-calls; yields content and returns the final message
|
|
389
|
+
# (matches the FoundryBackend generator-return contract used by the agent loop).
|
|
390
|
+
content = self._complete(messages, temperature, max_tokens)
|
|
391
|
+
yield ("content", content)
|
|
392
|
+
return {"role": "assistant", "content": content}
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
class ModalBackend:
|
|
396
|
+
"""Gemma 4 Mycelium served on a Modal scale-to-zero GPU (the free base tier).
|
|
397
|
+
|
|
398
|
+
OpenAI-compatible /v1/chat/completions, but the Modal web endpoint requires
|
|
399
|
+
proxy-auth headers (Modal-Key / Modal-Secret) on every request — which is why
|
|
400
|
+
the gateway, not OWUI, must own this connection."""
|
|
401
|
+
|
|
402
|
+
label = "Modal (Mycelium)"
|
|
403
|
+
|
|
404
|
+
def __init__(self, endpoint: str, key: str, secret: str, model: str):
|
|
405
|
+
self._base_url = (endpoint or "").rstrip("/")
|
|
406
|
+
self._key = key or ""
|
|
407
|
+
self._secret = secret or ""
|
|
408
|
+
self._model = model
|
|
409
|
+
|
|
410
|
+
@property
|
|
411
|
+
def _url(self) -> str:
|
|
412
|
+
return f"{self._base_url}/v1/chat/completions"
|
|
413
|
+
|
|
414
|
+
def _headers(self) -> dict:
|
|
415
|
+
return {
|
|
416
|
+
"Modal-Key": self._key,
|
|
417
|
+
"Modal-Secret": self._secret,
|
|
418
|
+
"content-type": "application/json",
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
def check(self) -> tuple[bool, str]:
|
|
422
|
+
if not self._base_url or not self._key or not self._secret:
|
|
423
|
+
return False, "Modal Mycelium endpoint or proxy-auth token not configured."
|
|
424
|
+
try:
|
|
425
|
+
httpx.get(_host(self._base_url), timeout=_CHECK_TIMEOUT)
|
|
426
|
+
except Exception as e: # noqa: BLE001 - reachability probe
|
|
427
|
+
return False, f"Modal endpoint unreachable ({e.__class__.__name__})"
|
|
428
|
+
return True, f"Modal @ {_host(self._base_url)}"
|
|
429
|
+
|
|
430
|
+
def stream_chat(self, messages, temperature, max_tokens):
|
|
431
|
+
payload = {
|
|
432
|
+
"model": self._model,
|
|
433
|
+
"messages": messages,
|
|
434
|
+
"stream": True,
|
|
435
|
+
"temperature": temperature,
|
|
436
|
+
"max_tokens": max_tokens,
|
|
437
|
+
}
|
|
438
|
+
with httpx.stream(
|
|
439
|
+
"POST", self._url, json=payload, headers=self._headers(), timeout=_MODAL_TIMEOUT
|
|
440
|
+
) as r:
|
|
441
|
+
r.raise_for_status()
|
|
442
|
+
yield from parse_sse_lines(r.iter_lines())
|
|
443
|
+
|
|
444
|
+
def chat(self, messages, tools, temperature, max_tokens) -> dict:
|
|
445
|
+
payload = {
|
|
446
|
+
"model": self._model,
|
|
447
|
+
"messages": messages,
|
|
448
|
+
"stream": False,
|
|
449
|
+
"temperature": temperature,
|
|
450
|
+
"max_tokens": max_tokens,
|
|
451
|
+
}
|
|
452
|
+
if tools:
|
|
453
|
+
payload["tools"] = tools
|
|
454
|
+
r = httpx.post(self._url, json=payload, headers=self._headers(), timeout=_MODAL_TIMEOUT)
|
|
455
|
+
r.raise_for_status()
|
|
456
|
+
return _message_from_choice(r.json()["choices"][0])
|
|
457
|
+
|
|
458
|
+
def stream_chat_tools(self, messages, tools, temperature, max_tokens):
|
|
459
|
+
payload = {
|
|
460
|
+
"model": self._model,
|
|
461
|
+
"messages": messages,
|
|
462
|
+
"stream": True,
|
|
463
|
+
"temperature": temperature,
|
|
464
|
+
"max_tokens": max_tokens,
|
|
465
|
+
}
|
|
466
|
+
if tools:
|
|
467
|
+
payload["tools"] = tools
|
|
468
|
+
with httpx.stream(
|
|
469
|
+
"POST", self._url, json=payload, headers=self._headers(), timeout=_MODAL_TIMEOUT
|
|
470
|
+
) as r:
|
|
471
|
+
r.raise_for_status()
|
|
472
|
+
return (yield from parse_sse_stream(r.iter_lines()))
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
_crowe_providers: dict[tuple, crowe_id.CroweIDTokenProvider] = {}
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def _crowe_token_provider(settings) -> crowe_id.CroweIDTokenProvider:
|
|
479
|
+
"""One memoized token provider per (issuer, client_id) so fusion's many
|
|
480
|
+
per-deployment backends share a single cached Crowe ID token."""
|
|
481
|
+
key = (settings.crowe_id_issuer, settings.crowe_id_client_id)
|
|
482
|
+
provider = _crowe_providers.get(key)
|
|
483
|
+
if provider is None:
|
|
484
|
+
provider = crowe_id.CroweIDTokenProvider(
|
|
485
|
+
settings.crowe_id_issuer,
|
|
486
|
+
settings.crowe_id_client_id or "",
|
|
487
|
+
settings.crowe_id_client_secret or "",
|
|
488
|
+
audience=settings.crowe_id_audience,
|
|
489
|
+
)
|
|
490
|
+
_crowe_providers[key] = provider
|
|
491
|
+
return provider
|
|
492
|
+
|
|
493
|
+
|
|
288
494
|
def resolve_backend(settings) -> Backend:
|
|
289
495
|
"""Factory keyed on settings.backend."""
|
|
496
|
+
if settings.backend == "crowe":
|
|
497
|
+
return CroweGatewayBackend(
|
|
498
|
+
settings.gateway_url or "",
|
|
499
|
+
settings.foundry_model,
|
|
500
|
+
token_provider=_crowe_token_provider(settings),
|
|
501
|
+
)
|
|
290
502
|
if settings.backend == "foundry":
|
|
291
503
|
return FoundryBackend(
|
|
292
504
|
settings.foundry_base_url or "",
|
|
@@ -305,7 +517,22 @@ def backend_for_deployment(settings, deployment: str) -> Backend:
|
|
|
305
517
|
"""Build a backend targeting a specific deployment/model (for fusion).
|
|
306
518
|
|
|
307
519
|
Uses the same transport as the active backend, just a different model id.
|
|
520
|
+
The Modal-served Mycelium model is the exception: it routes to its own
|
|
521
|
+
endpoint with proxy-auth headers, regardless of the active backend.
|
|
308
522
|
"""
|
|
523
|
+
if settings.mycelium_endpoint and deployment == settings.mycelium_model:
|
|
524
|
+
return ModalBackend(
|
|
525
|
+
settings.mycelium_endpoint,
|
|
526
|
+
settings.mycelium_key or "",
|
|
527
|
+
settings.mycelium_secret or "",
|
|
528
|
+
deployment,
|
|
529
|
+
)
|
|
530
|
+
if settings.backend == "crowe":
|
|
531
|
+
return CroweGatewayBackend(
|
|
532
|
+
settings.gateway_url or "",
|
|
533
|
+
deployment,
|
|
534
|
+
token_provider=_crowe_token_provider(settings),
|
|
535
|
+
)
|
|
309
536
|
if settings.backend == "foundry":
|
|
310
537
|
return FoundryBackend(
|
|
311
538
|
settings.foundry_base_url or "", settings.foundry_api_key or "", deployment
|