fleetmesh-node 0.2.0__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.
- fleetmesh_node-0.2.0.dist-info/METADATA +57 -0
- fleetmesh_node-0.2.0.dist-info/RECORD +338 -0
- fleetmesh_node-0.2.0.dist-info/WHEEL +5 -0
- fleetmesh_node-0.2.0.dist-info/entry_points.txt +2 -0
- fleetmesh_node-0.2.0.dist-info/licenses/LICENSE +121 -0
- fleetmesh_node-0.2.0.dist-info/top_level.txt +2 -0
- node/RELEASE +1 -0
- node/__init__.py +35 -0
- node/app/__init__.py +49 -0
- node/app/commands.py +265 -0
- node/app/config.py +131 -0
- node/app/control.py +670 -0
- node/app/core.py +867 -0
- node/app/plugins.py +76 -0
- node/app/router.py +83 -0
- node/app/socket.py +431 -0
- node/app/systemd.py +36 -0
- node/app/views.py +204 -0
- node/channels/__init__.py +378 -0
- node/channels/bus.py +331 -0
- node/channels/journal.py +46 -0
- node/commands/__init__.py +140 -0
- node/commands/adapter.py +210 -0
- node/commands/base.py +278 -0
- node/commands/cluster_cmds.py +183 -0
- node/commands/compute_cmds.py +401 -0
- node/commands/core.py +474 -0
- node/commands/gossip_cmds.py +106 -0
- node/commands/nsite_cmds.py +141 -0
- node/commands/peering.py +402 -0
- node/commands/plugin_cmds.py +135 -0
- node/commands/sidecar_cmds.py +120 -0
- node/commands/sync.py +388 -0
- node/commands/transport_cmds.py +451 -0
- node/commands/video_cmds.py +99 -0
- node/compartment/__init__.py +124 -0
- node/compartment/base.py +868 -0
- node/compartment/command.py +458 -0
- node/compartment/context.py +163 -0
- node/compartment/every.py +108 -0
- node/compartment/handler.py +131 -0
- node/compartment/lifecycle.py +460 -0
- node/compartment/lint.py +388 -0
- node/compartment/need.py +158 -0
- node/compartment/problem.py +521 -0
- node/compartment/refusals.py +120 -0
- node/compartment/registry.py +790 -0
- node/compartment/schema.py +290 -0
- node/compartment/sidecar.py +499 -0
- node/compartment/stores.py +662 -0
- node/compartment/wire.py +225 -0
- node/compartment/worker.py +61 -0
- node/config/__init__.py +43 -0
- node/config/base.py +116 -0
- node/config/change.py +441 -0
- node/config/changestore.py +201 -0
- node/config/compartments.py +1144 -0
- node/config/node.py +318 -0
- node/config/parameter.py +563 -0
- node/config/protocols.py +274 -0
- node/config/records.py +171 -0
- node/examples/__init__.py +1 -0
- node/examples/demo.py +226 -0
- node/examples/follower.py +46 -0
- node/interfaces.py +235 -0
- node/keystore/__init__.py +27 -0
- node/keystore/core.py +103 -0
- node/keystore/enclave.py +688 -0
- node/meshnode.py +111 -0
- node/metrics/__init__.py +396 -0
- node/metrics/adapters.py +102 -0
- node/metrics/face.py +110 -0
- node/planes/__init__.py +21 -0
- node/planes/identity/__init__.py +20 -0
- node/planes/identity/announce/__init__.py +9 -0
- node/planes/identity/announce/compartment.py +447 -0
- node/planes/identity/announce/types.py +26 -0
- node/planes/identity/discovery/__init__.py +9 -0
- node/planes/identity/discovery/compartment.py +522 -0
- node/planes/identity/discovery/types.py +13 -0
- node/planes/identity/linked/__init__.py +1 -0
- node/planes/identity/linked/compartment.py +270 -0
- node/planes/identity/linked/types.py +25 -0
- node/planes/identity/presence/__init__.py +7 -0
- node/planes/identity/presence/compartment.py +409 -0
- node/planes/identity/presence/types.py +14 -0
- node/planes/operations/__init__.py +13 -0
- node/planes/operations/scheduler/__init__.py +9 -0
- node/planes/operations/scheduler/compartment.py +353 -0
- node/planes/operations/scheduler/types.py +21 -0
- node/planes/operations/updates/__init__.py +17 -0
- node/planes/operations/updates/core.py +310 -0
- node/planes/operations/updates/store.py +79 -0
- node/planes/operations/updates/types.py +20 -0
- node/planes/peering/__init__.py +28 -0
- node/planes/peering/budget/__init__.py +43 -0
- node/planes/peering/budget/chain.py +43 -0
- node/planes/peering/budget/core.py +537 -0
- node/planes/peering/budget/evaluation.py +80 -0
- node/planes/peering/budget/meter.py +100 -0
- node/planes/peering/budget/receipts.py +439 -0
- node/planes/peering/budget/shape.py +101 -0
- node/planes/peering/budget/tick.py +110 -0
- node/planes/peering/budget/types.py +67 -0
- node/planes/peering/budget/windows.py +346 -0
- node/planes/peering/capacity/__init__.py +7 -0
- node/planes/peering/capacity/compartment.py +342 -0
- node/planes/peering/capacity/types.py +24 -0
- node/planes/peering/peering/__init__.py +25 -0
- node/planes/peering/peering/core.py +464 -0
- node/planes/peering/peering/envelope.py +142 -0
- node/planes/peering/peering/handshake.py +198 -0
- node/planes/peering/peering/shape.py +80 -0
- node/planes/peering/peering/types.py +39 -0
- node/planes/peering/reporter/__init__.py +42 -0
- node/planes/peering/reporter/constants.py +29 -0
- node/planes/peering/reporter/core.py +299 -0
- node/planes/peering/reporter/finding.py +74 -0
- node/planes/peering/reporter/findings.py +171 -0
- node/planes/peering/reporter/publish.py +285 -0
- node/planes/peering/reporter/reading.py +193 -0
- node/planes/peering/reporter/shape.py +86 -0
- node/planes/peering/reporter/types.py +50 -0
- node/planes/peering/strain/__init__.py +9 -0
- node/planes/peering/strain/compartment.py +204 -0
- node/planes/peering/strain/types.py +21 -0
- node/planes/peering/trust/__init__.py +9 -0
- node/planes/peering/trust/compartment.py +239 -0
- node/planes/peering/trust/types.py +30 -0
- node/planes/services/__init__.py +33 -0
- node/planes/services/compute/__init__.py +160 -0
- node/planes/services/compute/common.py +134 -0
- node/planes/services/compute/compartment.py +681 -0
- node/planes/services/compute/executors/__init__.py +121 -0
- node/planes/services/compute/executors/agent.py +840 -0
- node/planes/services/compute/executors/bacalhau.py +605 -0
- node/planes/services/compute/executors/base.py +127 -0
- node/planes/services/compute/executors/harness.py +439 -0
- node/planes/services/compute/executors/mcp.py +454 -0
- node/planes/services/compute/executors/media.py +898 -0
- node/planes/services/compute/executors/nondet.py +283 -0
- node/planes/services/compute/executors/providers.py +499 -0
- node/planes/services/compute/executors/registry.py +140 -0
- node/planes/services/compute/executors/wasm.py +244 -0
- node/planes/services/compute/executors/zk.py +604 -0
- node/planes/services/compute/jobs.py +227 -0
- node/planes/services/compute/load.py +180 -0
- node/planes/services/compute/market.py +433 -0
- node/planes/services/compute/sandbox.py +282 -0
- node/planes/services/compute/types.py +84 -0
- node/planes/services/compute_wire/__init__.py +58 -0
- node/planes/services/compute_wire/compartment.py +256 -0
- node/planes/services/compute_wire/messages.py +320 -0
- node/planes/services/compute_wire/sealing.py +147 -0
- node/planes/services/compute_wire/session.py +572 -0
- node/planes/services/compute_wire/types.py +49 -0
- node/planes/services/compute_wire/verify.py +331 -0
- node/planes/services/dvm/__init__.py +24 -0
- node/planes/services/dvm/gateway.py +526 -0
- node/planes/services/dvm/protocol.py +35 -0
- node/planes/services/dvm/translate.py +15 -0
- node/planes/services/dvm/types.py +25 -0
- node/planes/services/gateway/__init__.py +40 -0
- node/planes/services/gateway/settle.py +279 -0
- node/planes/services/gateway/translate.py +644 -0
- node/planes/services/gossip/__init__.py +22 -0
- node/planes/services/gossip/core.py +929 -0
- node/planes/services/gossip/envelope.py +154 -0
- node/planes/services/gossip/mesh.py +127 -0
- node/planes/services/gossip/types.py +50 -0
- node/planes/services/nsite/__init__.py +7 -0
- node/planes/services/nsite/client.py +62 -0
- node/planes/services/nsite/compartment.py +752 -0
- node/planes/services/nsite/types.py +44 -0
- node/planes/services/paidfetch/__init__.py +7 -0
- node/planes/services/paidfetch/compartment.py +862 -0
- node/planes/services/paidfetch/types.py +99 -0
- node/planes/services/settle/__init__.py +27 -0
- node/planes/services/settle/compartment.py +384 -0
- node/planes/services/settle/engine.py +456 -0
- node/planes/services/settle/gateways.py +541 -0
- node/planes/services/settle/payment.py +512 -0
- node/planes/services/settle/store.py +74 -0
- node/planes/services/settle/types.py +58 -0
- node/planes/services/sidecars/__init__.py +8 -0
- node/planes/services/sidecars/compartment.py +461 -0
- node/planes/services/sidecars/manifest.py +304 -0
- node/planes/services/sidecars/manifests/10-grasp.json +37 -0
- node/planes/services/sidecars/manifests/20-ncontainer.json +31 -0
- node/planes/services/sidecars/manifests/30-magpie.json +28 -0
- node/planes/services/sidecars/types.py +43 -0
- node/planes/services/video/__init__.py +106 -0
- node/planes/services/video/barter.py +407 -0
- node/planes/services/video/cli.py +158 -0
- node/planes/services/video/compartment.py +932 -0
- node/planes/services/video/ingest.py +524 -0
- node/planes/services/video/ledger.py +705 -0
- node/planes/services/video/packager.py +847 -0
- node/planes/services/video/player.py +456 -0
- node/planes/services/video/timed_segments.py +124 -0
- node/planes/services/video/transit.py +478 -0
- node/planes/store/__init__.py +24 -0
- node/planes/store/blob_replication/__init__.py +7 -0
- node/planes/store/blob_replication/compartment.py +496 -0
- node/planes/store/blob_replication/types.py +35 -0
- node/planes/store/blob_wire/__init__.py +48 -0
- node/planes/store/blob_wire/client.py +57 -0
- node/planes/store/blob_wire/core.py +359 -0
- node/planes/store/blob_wire/intake.py +74 -0
- node/planes/store/blob_wire/messages.py +85 -0
- node/planes/store/projected/__init__.py +7 -0
- node/planes/store/projected/compartment.py +258 -0
- node/planes/store/projected/types.py +29 -0
- node/planes/store/retention/__init__.py +7 -0
- node/planes/store/retention/compartment.py +214 -0
- node/planes/store/retention/types.py +12 -0
- node/planes/store/sync/__init__.py +35 -0
- node/planes/store/sync/core.py +311 -0
- node/planes/store/sync/envelope.py +106 -0
- node/planes/store/sync/handover.py +206 -0
- node/planes/store/sync/initiator.py +128 -0
- node/planes/store/sync/responder.py +114 -0
- node/planes/store/sync/rules.py +179 -0
- node/planes/store/sync/shape.py +80 -0
- node/planes/store/sync/types.py +51 -0
- node/planes/transport/__init__.py +23 -0
- node/planes/transport/bridge/__init__.py +13 -0
- node/planes/transport/bridge/compartment.py +785 -0
- node/planes/transport/bridge/ratelimit.py +44 -0
- node/planes/transport/bridge/types.py +43 -0
- node/planes/transport/lan/__init__.py +9 -0
- node/planes/transport/lan/compartment.py +446 -0
- node/planes/transport/lan/types.py +25 -0
- node/planes/transport/link/__init__.py +7 -0
- node/planes/transport/link/compartment.py +257 -0
- node/planes/transport/link/types.py +27 -0
- node/planes/transport/mediation/__init__.py +45 -0
- node/planes/transport/mediation/compartment.py +238 -0
- node/planes/transport/mediation/leaf.py +107 -0
- node/planes/transport/mediation/messages.py +302 -0
- node/planes/transport/mediation/service.py +233 -0
- node/planes/transport/mediation/store.py +37 -0
- node/planes/transport/mediation/types.py +20 -0
- node/planes/transport/p2p/__init__.py +7 -0
- node/planes/transport/p2p/compartment.py +349 -0
- node/planes/transport/p2p/types.py +22 -0
- node/protocols/__init__.py +277 -0
- node/protocols/activitypub/__init__.py +56 -0
- node/protocols/activitypub/actor.py +230 -0
- node/protocols/activitypub/federation.py +490 -0
- node/protocols/activitypub/media.py +145 -0
- node/protocols/activitypub/models.py +286 -0
- node/protocols/activitypub/package.py +271 -0
- node/protocols/activitypub/store.py +564 -0
- node/protocols/arcade/__init__.py +26 -0
- node/protocols/atproto/__init__.py +13 -0
- node/protocols/atproto/firehose.py +126 -0
- node/protocols/atproto/metering.py +165 -0
- node/protocols/atproto/package.py +155 -0
- node/protocols/atproto/repo.py +250 -0
- node/protocols/atproto/xrpc.py +285 -0
- node/protocols/base.py +109 -0
- node/protocols/circles/__init__.py +29 -0
- node/protocols/dm/__init__.py +50 -0
- node/protocols/grasp/__init__.py +22 -0
- node/protocols/isotime.py +15 -0
- node/protocols/nostr/__init__.py +51 -0
- node/protocols/nostr/blobstore.py +383 -0
- node/protocols/nostr/blossom.py +627 -0
- node/protocols/nostr/client.py +377 -0
- node/protocols/nostr/descriptor.py +211 -0
- node/protocols/nostr/eventstore.py +304 -0
- node/protocols/nostr/package.py +182 -0
- node/protocols/nostr/peers.py +109 -0
- node/protocols/nostr/relayserver.py +656 -0
- node/protocols/p2panda/__init__.py +22 -0
- node/protocols/p2panda/identity.py +19 -0
- node/protocols/p2panda/package.py +236 -0
- node/protocols/p2panda/rung.py +152 -0
- node/protocols/p2panda/store.py +273 -0
- node/protocols/rhio/__init__.py +33 -0
- node/protocols/rhio/package.py +275 -0
- node/protocols/rhio/rung.py +136 -0
- node/protocols/rhio/store.py +1423 -0
- node/protocols/ssb/muxrpc.py +256 -0
- node/protocols/ssb/package.py +267 -0
- node/protocols/textile/__init__.py +111 -0
- node/protocols/textile/transducers.py +259 -0
- node/release.py +119 -0
- node/storage/__init__.py +88 -0
- node/storage/accounts.py +305 -0
- node/storage/adapter.py +161 -0
- node/storage/base.py +385 -0
- node/storage/dialect.py +209 -0
- node/storage/grants.py +317 -0
- node/storage/locator.py +150 -0
- node/storage/postgres.py +294 -0
- node/storage/projected.py +953 -0
- node/storage/sharding.py +448 -0
- node/storage/standing.py +568 -0
- node/testnet/__init__.py +9 -0
- node/testnet/cluster.py +731 -0
- node/testnet/fips_daemon.py +230 -0
- node/transport/__init__.py +94 -0
- node/transport/const.py +110 -0
- node/transport/didcomm_inbound.py +251 -0
- node/transport/dnstxt.py +169 -0
- node/transport/fips_rung/__init__.py +67 -0
- node/transport/fips_rung/core.py +666 -0
- node/transport/fips_rung/listener.py +96 -0
- node/transport/fips_rung/types.py +12 -0
- node/transport/host.py +334 -0
- node/transport/iroh_rung/__init__.py +29 -0
- node/transport/iroh_rung/core.py +477 -0
- node/transport/iroh_rung/quic.py +942 -0
- node/transport/iroh_rung/types.py +22 -0
- node/transport/ladder.py +145 -0
- node/transport/lineproto.py +49 -0
- node/transport/mdns.py +303 -0
- node/transport/netfetch.py +283 -0
- node/transport/relay_rung/__init__.py +23 -0
- node/transport/relay_rung/cooldown.py +87 -0
- node/transport/relay_rung/core.py +138 -0
- node/transport/relay_rung/inbound.py +161 -0
- node/transport/relay_rung/ledger.py +104 -0
- node/transport/relay_rung/listen.py +121 -0
- node/transport/relay_rung/outbound.py +181 -0
- node/transport/relay_rung/shape.py +71 -0
- node/transport/relay_rung/types.py +58 -0
- node/transport/relay_rung/wire.py +63 -0
- node/transport/router.py +761 -0
- node/transport/rung.py +276 -0
- node/transport/ssb_rung.py +166 -0
- node/transport/ws.py +502 -0
- sidecar/fleetmesh_announce.py +338 -0
- sidecar/fleetmesh_p2p.py +373 -0
- sidecar/fleetmesh_rhio.py +352 -0
- sidecar/fleetmesh_ssb.py +603 -0
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
"""Operator-linked AI/LLM inference provider adapters for FleetMesh non-deterministic compute.
|
|
2
|
+
|
|
3
|
+
Supports linking a MeshNode to external and local inference providers:
|
|
4
|
+
- OpenRouter (`openrouter`)
|
|
5
|
+
- Anthropic Claude (`claude` / `anthropic`)
|
|
6
|
+
- OpenAI (`openai`)
|
|
7
|
+
- Google Gemini (`gemini`)
|
|
8
|
+
- GitHub Copilot (`copilot`)
|
|
9
|
+
- Routstr eCash Proxy (`routstr`)
|
|
10
|
+
- PayPerQuery / PPQ (`payperquery` / `ppq`)
|
|
11
|
+
- Local vLLM / Ollama (`vllm`, `ollama`, `custom`)
|
|
12
|
+
|
|
13
|
+
Security Invariant:
|
|
14
|
+
All upstream URLs and credentials (`api_key`, `cashu_token`) are strictly
|
|
15
|
+
operator-configured on the host node. Remote compute jobs may select an
|
|
16
|
+
advertised `provider` alias or `model` ID, never an arbitrary URL.
|
|
17
|
+
"""
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import dataclasses
|
|
21
|
+
import os
|
|
22
|
+
from typing import Any, Mapping, Optional, Sequence
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclasses.dataclass(frozen=True)
|
|
26
|
+
class ProviderPreset:
|
|
27
|
+
provider_id: str
|
|
28
|
+
label: str
|
|
29
|
+
endpoint: str
|
|
30
|
+
default_model: str
|
|
31
|
+
models: tuple[str, ...]
|
|
32
|
+
auth_scheme: str # 'none' | 'bearer' | 'anthropic' | 'cashu' | 'cashu_402'
|
|
33
|
+
wire_format: str # 'openai' | 'anthropic_messages'
|
|
34
|
+
env_key: str
|
|
35
|
+
rate_msat_per_token: int
|
|
36
|
+
extra_headers: Mapping[str, str] = dataclasses.field(default_factory=dict)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _https(host: str, path: str = '') -> str:
|
|
40
|
+
return f'https://{host}{path}'
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
PROVIDER_PRESETS: dict[str, ProviderPreset] = {
|
|
44
|
+
'openrouter': ProviderPreset(
|
|
45
|
+
provider_id='openrouter',
|
|
46
|
+
label='OpenRouter (Multi-Model Aggregator)',
|
|
47
|
+
endpoint=_https('openrouter.ai', '/api/v1'),
|
|
48
|
+
default_model='anthropic/claude-3.5-sonnet',
|
|
49
|
+
models=(
|
|
50
|
+
'anthropic/claude-3.5-sonnet',
|
|
51
|
+
'openai/gpt-4o-mini',
|
|
52
|
+
'google/gemini-2.5-flash',
|
|
53
|
+
'meta-llama/llama-3.3-70b-instruct',
|
|
54
|
+
'deepseek/deepseek-r1',
|
|
55
|
+
),
|
|
56
|
+
auth_scheme='bearer',
|
|
57
|
+
wire_format='openai',
|
|
58
|
+
env_key='OPENROUTER_API_KEY',
|
|
59
|
+
rate_msat_per_token=15,
|
|
60
|
+
extra_headers={
|
|
61
|
+
'HTTP-Referer': _https('fleetmesh.org'),
|
|
62
|
+
'X-Title': 'FleetMesh NonDet Compute',
|
|
63
|
+
},
|
|
64
|
+
),
|
|
65
|
+
'claude': ProviderPreset(
|
|
66
|
+
provider_id='claude',
|
|
67
|
+
label='Anthropic Claude',
|
|
68
|
+
endpoint=_https('api.anthropic.com', '/v1'),
|
|
69
|
+
default_model='claude-3-7-sonnet-latest',
|
|
70
|
+
models=(
|
|
71
|
+
'claude-3-7-sonnet-latest',
|
|
72
|
+
'claude-3-5-sonnet-latest',
|
|
73
|
+
'claude-3-5-haiku-latest',
|
|
74
|
+
),
|
|
75
|
+
auth_scheme='anthropic',
|
|
76
|
+
wire_format='anthropic_messages',
|
|
77
|
+
env_key='ANTHROPIC_API_KEY',
|
|
78
|
+
rate_msat_per_token=18,
|
|
79
|
+
),
|
|
80
|
+
'anthropic': ProviderPreset(
|
|
81
|
+
provider_id='anthropic',
|
|
82
|
+
label='Anthropic Claude',
|
|
83
|
+
endpoint=_https('api.anthropic.com', '/v1'),
|
|
84
|
+
default_model='claude-3-7-sonnet-latest',
|
|
85
|
+
models=(
|
|
86
|
+
'claude-3-7-sonnet-latest',
|
|
87
|
+
'claude-3-5-sonnet-latest',
|
|
88
|
+
'claude-3-5-haiku-latest',
|
|
89
|
+
),
|
|
90
|
+
auth_scheme='anthropic',
|
|
91
|
+
wire_format='anthropic_messages',
|
|
92
|
+
env_key='ANTHROPIC_API_KEY',
|
|
93
|
+
rate_msat_per_token=18,
|
|
94
|
+
),
|
|
95
|
+
'openai': ProviderPreset(
|
|
96
|
+
provider_id='openai',
|
|
97
|
+
label='OpenAI',
|
|
98
|
+
endpoint=_https('api.openai.com', '/v1'),
|
|
99
|
+
default_model='gpt-4o-mini',
|
|
100
|
+
models=('gpt-4o-mini', 'gpt-4o', 'o3-mini'),
|
|
101
|
+
auth_scheme='bearer',
|
|
102
|
+
wire_format='openai',
|
|
103
|
+
env_key='OPENAI_API_KEY',
|
|
104
|
+
rate_msat_per_token=12,
|
|
105
|
+
),
|
|
106
|
+
'gemini': ProviderPreset(
|
|
107
|
+
provider_id='gemini',
|
|
108
|
+
label='Google Gemini',
|
|
109
|
+
endpoint=_https('generativelanguage.googleapis.com', '/v1beta/openai'),
|
|
110
|
+
default_model='gemini-2.5-flash',
|
|
111
|
+
models=('gemini-2.5-flash', 'gemini-2.5-pro', 'gemini-2.0-flash'),
|
|
112
|
+
auth_scheme='bearer',
|
|
113
|
+
wire_format='openai',
|
|
114
|
+
env_key='GEMINI_API_KEY',
|
|
115
|
+
rate_msat_per_token=10,
|
|
116
|
+
),
|
|
117
|
+
'copilot': ProviderPreset(
|
|
118
|
+
provider_id='copilot',
|
|
119
|
+
label='GitHub Copilot / GitHub Models',
|
|
120
|
+
endpoint=_https('models.inference.ai.azure.com'),
|
|
121
|
+
default_model='gpt-4o',
|
|
122
|
+
models=('gpt-4o', 'gpt-4o-mini', 'Llama-3.3-70B-Instruct'),
|
|
123
|
+
auth_scheme='bearer',
|
|
124
|
+
wire_format='openai',
|
|
125
|
+
env_key='GITHUB_TOKEN',
|
|
126
|
+
rate_msat_per_token=12,
|
|
127
|
+
),
|
|
128
|
+
'routstr': ProviderPreset(
|
|
129
|
+
provider_id='routstr',
|
|
130
|
+
label='Routstr (Decentralized Cashu LLM Router)',
|
|
131
|
+
endpoint=_https('api.routstr.com', '/v1'),
|
|
132
|
+
default_model='meta-llama/Llama-3.3-70B-Instruct',
|
|
133
|
+
models=(
|
|
134
|
+
'meta-llama/Llama-3.3-70B-Instruct',
|
|
135
|
+
'deepseek-ai/DeepSeek-R1',
|
|
136
|
+
'anthropic/claude-3.5-sonnet',
|
|
137
|
+
),
|
|
138
|
+
auth_scheme='cashu',
|
|
139
|
+
wire_format='openai',
|
|
140
|
+
env_key='ROUTSTR_CASHU_TOKEN',
|
|
141
|
+
rate_msat_per_token=12,
|
|
142
|
+
),
|
|
143
|
+
'payperquery': ProviderPreset(
|
|
144
|
+
provider_id='payperquery',
|
|
145
|
+
label='PayPerQuery / PPQ (HTTP 402 eCash Inference)',
|
|
146
|
+
endpoint=_https('api.ppq.ai', '/v1'),
|
|
147
|
+
default_model='meta-llama/Llama-3.3-70B-Instruct',
|
|
148
|
+
models=(
|
|
149
|
+
'meta-llama/Llama-3.3-70B-Instruct',
|
|
150
|
+
'gpt-4o-mini',
|
|
151
|
+
'claude-3-5-sonnet',
|
|
152
|
+
),
|
|
153
|
+
auth_scheme='cashu_402',
|
|
154
|
+
wire_format='openai',
|
|
155
|
+
env_key='PPQ_CASHU_TOKEN',
|
|
156
|
+
rate_msat_per_token=12,
|
|
157
|
+
),
|
|
158
|
+
'ppq': ProviderPreset(
|
|
159
|
+
provider_id='ppq',
|
|
160
|
+
label='PayPerQuery / PPQ (HTTP 402 eCash Inference)',
|
|
161
|
+
endpoint=_https('api.ppq.ai', '/v1'),
|
|
162
|
+
default_model='meta-llama/Llama-3.3-70B-Instruct',
|
|
163
|
+
models=(
|
|
164
|
+
'meta-llama/Llama-3.3-70B-Instruct',
|
|
165
|
+
'gpt-4o-mini',
|
|
166
|
+
'claude-3-5-sonnet',
|
|
167
|
+
),
|
|
168
|
+
auth_scheme='cashu_402',
|
|
169
|
+
wire_format='openai',
|
|
170
|
+
env_key='PPQ_CASHU_TOKEN',
|
|
171
|
+
rate_msat_per_token=12,
|
|
172
|
+
),
|
|
173
|
+
'ollama': ProviderPreset(
|
|
174
|
+
provider_id='ollama',
|
|
175
|
+
label='Ollama (Local Node)',
|
|
176
|
+
endpoint='http://127.0.0.1:11434/v1',
|
|
177
|
+
default_model='llama3.2',
|
|
178
|
+
models=('llama3.2', 'qwen2.5:14b', 'deepseek-r1:14b', 'mistral'),
|
|
179
|
+
auth_scheme='none',
|
|
180
|
+
wire_format='openai',
|
|
181
|
+
env_key='OLLAMA_API_KEY',
|
|
182
|
+
rate_msat_per_token=5,
|
|
183
|
+
),
|
|
184
|
+
'vllm': ProviderPreset(
|
|
185
|
+
provider_id='vllm',
|
|
186
|
+
label='vLLM (Local / Cluster GPU)',
|
|
187
|
+
endpoint='http://127.0.0.1:8000/v1',
|
|
188
|
+
default_model='meta-llama/Llama-3.3-70B-Instruct',
|
|
189
|
+
models=('meta-llama/Llama-3.3-70B-Instruct', 'deepseek-ai/DeepSeek-R1'),
|
|
190
|
+
auth_scheme='none',
|
|
191
|
+
wire_format='openai',
|
|
192
|
+
env_key='VLLM_API_KEY',
|
|
193
|
+
rate_msat_per_token=12,
|
|
194
|
+
),
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
@dataclasses.dataclass
|
|
199
|
+
class LinkedProviderConfig:
|
|
200
|
+
"""Operator-linked configuration for a single inference provider."""
|
|
201
|
+
|
|
202
|
+
provider_id: str
|
|
203
|
+
endpoint: str
|
|
204
|
+
default_model: str
|
|
205
|
+
models: tuple[str, ...] = ()
|
|
206
|
+
api_key: str = ''
|
|
207
|
+
cashu_token: str = ''
|
|
208
|
+
cashu_mint: str = ''
|
|
209
|
+
auth_scheme: str = 'bearer'
|
|
210
|
+
wire_format: str = 'openai'
|
|
211
|
+
rate_msat_per_token: int = 12
|
|
212
|
+
enabled: bool = True
|
|
213
|
+
label: str = ''
|
|
214
|
+
extra_headers: dict[str, str] = dataclasses.field(default_factory=dict)
|
|
215
|
+
|
|
216
|
+
@classmethod
|
|
217
|
+
def from_preset(
|
|
218
|
+
cls,
|
|
219
|
+
provider_id: str,
|
|
220
|
+
*,
|
|
221
|
+
endpoint: Optional[str] = None,
|
|
222
|
+
default_model: Optional[str] = None,
|
|
223
|
+
models: Optional[Sequence[str]] = None,
|
|
224
|
+
api_key: Optional[str] = None,
|
|
225
|
+
cashu_token: Optional[str] = None,
|
|
226
|
+
cashu_mint: Optional[str] = None,
|
|
227
|
+
rate_msat_per_token: Optional[int] = None,
|
|
228
|
+
enabled: bool = True,
|
|
229
|
+
extra_headers: Optional[Mapping[str, str]] = None,
|
|
230
|
+
) -> 'LinkedProviderConfig':
|
|
231
|
+
pid = provider_id.strip().lower()
|
|
232
|
+
preset = PROVIDER_PRESETS.get(pid)
|
|
233
|
+
if preset is None:
|
|
234
|
+
resolved_ep = (endpoint or 'http://127.0.0.1:8000/v1').rstrip('/')
|
|
235
|
+
resolved_model = default_model or 'meta-llama/Llama-3.3-70B-Instruct'
|
|
236
|
+
resolved_models = tuple(models) if models else (resolved_model,)
|
|
237
|
+
return cls(
|
|
238
|
+
provider_id=pid,
|
|
239
|
+
label=pid.upper(),
|
|
240
|
+
endpoint=resolved_ep,
|
|
241
|
+
default_model=resolved_model,
|
|
242
|
+
models=resolved_models,
|
|
243
|
+
api_key=api_key or '',
|
|
244
|
+
cashu_token=cashu_token or '',
|
|
245
|
+
cashu_mint=cashu_mint or '',
|
|
246
|
+
auth_scheme='bearer' if api_key else ('cashu' if cashu_token else 'none'),
|
|
247
|
+
wire_format='openai',
|
|
248
|
+
rate_msat_per_token=int(rate_msat_per_token if rate_msat_per_token is not None else 12),
|
|
249
|
+
enabled=enabled,
|
|
250
|
+
extra_headers=dict(extra_headers or {}),
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
env_secret = os.environ.get(preset.env_key, '') if preset.env_key else ''
|
|
254
|
+
resolved_key = api_key if api_key is not None else (env_secret if preset.auth_scheme in ('bearer', 'anthropic') else '')
|
|
255
|
+
resolved_cashu = cashu_token if cashu_token is not None else (env_secret if preset.auth_scheme in ('cashu', 'cashu_402') else '')
|
|
256
|
+
resolved_ep = (endpoint or preset.endpoint).rstrip('/')
|
|
257
|
+
resolved_model = default_model or preset.default_model
|
|
258
|
+
resolved_models = tuple(models) if models else preset.models
|
|
259
|
+
if resolved_model not in resolved_models:
|
|
260
|
+
resolved_models = (resolved_model, *resolved_models)
|
|
261
|
+
merged_headers = dict(preset.extra_headers)
|
|
262
|
+
if extra_headers:
|
|
263
|
+
merged_headers.update(extra_headers)
|
|
264
|
+
return cls(
|
|
265
|
+
provider_id=preset.provider_id,
|
|
266
|
+
label=preset.label,
|
|
267
|
+
endpoint=resolved_ep,
|
|
268
|
+
default_model=resolved_model,
|
|
269
|
+
models=resolved_models,
|
|
270
|
+
api_key=resolved_key,
|
|
271
|
+
cashu_token=resolved_cashu,
|
|
272
|
+
cashu_mint=cashu_mint or '',
|
|
273
|
+
auth_scheme=preset.auth_scheme,
|
|
274
|
+
wire_format=preset.wire_format,
|
|
275
|
+
rate_msat_per_token=int(rate_msat_per_token if rate_msat_per_token is not None else preset.rate_msat_per_token),
|
|
276
|
+
enabled=enabled,
|
|
277
|
+
extra_headers=merged_headers,
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
def has_credentials(self) -> bool:
|
|
281
|
+
if self.auth_scheme == 'none':
|
|
282
|
+
return True
|
|
283
|
+
if self.auth_scheme in ('cashu', 'cashu_402'):
|
|
284
|
+
return bool(self.cashu_token or self.api_key or self.cashu_mint)
|
|
285
|
+
return bool(self.api_key)
|
|
286
|
+
|
|
287
|
+
def is_runnable(self) -> bool:
|
|
288
|
+
return bool(self.enabled and self.endpoint.startswith(('http://', 'https://')) and self.has_credentials())
|
|
289
|
+
|
|
290
|
+
def build_headers(self, per_query_cashu_token: Optional[str] = None) -> dict[str, str]:
|
|
291
|
+
headers: dict[str, str] = {
|
|
292
|
+
'Content-Type': 'application/json',
|
|
293
|
+
'User-Agent': 'fleetmesh-llm/1.1',
|
|
294
|
+
}
|
|
295
|
+
headers.update(self.extra_headers)
|
|
296
|
+
active_cashu = per_query_cashu_token or self.cashu_token
|
|
297
|
+
if self.auth_scheme == 'anthropic':
|
|
298
|
+
if self.api_key:
|
|
299
|
+
headers['x-api-key'] = self.api_key
|
|
300
|
+
headers.setdefault('anthropic-version', '2023-06-01')
|
|
301
|
+
elif self.auth_scheme in ('cashu', 'cashu_402'):
|
|
302
|
+
if active_cashu:
|
|
303
|
+
headers['X-Cashu'] = active_cashu
|
|
304
|
+
headers['Authorization'] = f'Bearer {active_cashu}'
|
|
305
|
+
elif self.api_key:
|
|
306
|
+
headers['Authorization'] = f'Bearer {self.api_key}'
|
|
307
|
+
elif self.auth_scheme == 'bearer' or self.api_key:
|
|
308
|
+
if self.api_key:
|
|
309
|
+
headers['Authorization'] = f'Bearer {self.api_key}'
|
|
310
|
+
return headers
|
|
311
|
+
|
|
312
|
+
def format_request(
|
|
313
|
+
self,
|
|
314
|
+
*,
|
|
315
|
+
messages: Sequence[Mapping[str, Any]],
|
|
316
|
+
model: Optional[str] = None,
|
|
317
|
+
max_tokens: int = 512,
|
|
318
|
+
temperature: float = 0.7,
|
|
319
|
+
top_p: float = 1.0,
|
|
320
|
+
seed: Optional[int] = None,
|
|
321
|
+
grammar: Optional[Mapping[str, Any]] = None,
|
|
322
|
+
stream: bool = False,
|
|
323
|
+
) -> tuple[str, dict[str, Any]]:
|
|
324
|
+
target_model = model or self.default_model
|
|
325
|
+
if self.wire_format == 'anthropic_messages':
|
|
326
|
+
url = f"{self.endpoint}/messages" if not self.endpoint.endswith('/messages') else self.endpoint
|
|
327
|
+
system_parts: list[str] = []
|
|
328
|
+
chat_msgs: list[dict[str, str]] = []
|
|
329
|
+
for m in messages:
|
|
330
|
+
role = str(m.get('role', 'user'))
|
|
331
|
+
content = str(m.get('content', ''))
|
|
332
|
+
if role == 'system':
|
|
333
|
+
if content:
|
|
334
|
+
system_parts.append(content)
|
|
335
|
+
else:
|
|
336
|
+
chat_msgs.append({
|
|
337
|
+
'role': 'assistant' if role == 'assistant' else 'user',
|
|
338
|
+
'content': content or ' ',
|
|
339
|
+
})
|
|
340
|
+
if not chat_msgs:
|
|
341
|
+
chat_msgs = [{'role': 'user', 'content': 'Hello'}]
|
|
342
|
+
body: dict[str, Any] = {
|
|
343
|
+
'model': target_model,
|
|
344
|
+
'messages': chat_msgs,
|
|
345
|
+
'max_tokens': max_tokens,
|
|
346
|
+
'temperature': temperature,
|
|
347
|
+
'stream': stream,
|
|
348
|
+
}
|
|
349
|
+
if system_parts:
|
|
350
|
+
body['system'] = '\n\n'.join(system_parts)
|
|
351
|
+
return url, body
|
|
352
|
+
|
|
353
|
+
url = f"{self.endpoint}/chat/completions" if not self.endpoint.endswith('/chat/completions') else self.endpoint
|
|
354
|
+
req_body: dict[str, Any] = {
|
|
355
|
+
'model': target_model,
|
|
356
|
+
'messages': [dict(m) for m in messages],
|
|
357
|
+
'max_tokens': max_tokens,
|
|
358
|
+
'temperature': temperature,
|
|
359
|
+
'top_p': top_p,
|
|
360
|
+
'stream': stream,
|
|
361
|
+
}
|
|
362
|
+
if seed is not None:
|
|
363
|
+
req_body['seed'] = int(seed)
|
|
364
|
+
if grammar:
|
|
365
|
+
g_type = grammar.get('type')
|
|
366
|
+
if g_type == 'json_schema' or 'json_schema' in grammar or grammar.get('response_format') == 'json_object':
|
|
367
|
+
req_body['response_format'] = {
|
|
368
|
+
'type': 'json_object',
|
|
369
|
+
'schema': grammar.get('definition') or grammar.get('json_schema') or grammar.get('schema_cid'),
|
|
370
|
+
}
|
|
371
|
+
elif g_type == 'regex' or 'guided_regex' in grammar:
|
|
372
|
+
req_body['guided_regex'] = grammar.get('definition') or grammar.get('guided_regex')
|
|
373
|
+
return url, req_body
|
|
374
|
+
|
|
375
|
+
def parse_response(
|
|
376
|
+
self,
|
|
377
|
+
resp_data: Mapping[str, Any],
|
|
378
|
+
default_prompt_tokens: int = 1,
|
|
379
|
+
) -> tuple[str, int, int]:
|
|
380
|
+
if self.wire_format == 'anthropic_messages' or ('content' in resp_data and isinstance(resp_data.get('content'), list) and 'choices' not in resp_data):
|
|
381
|
+
blocks = resp_data.get('content') or []
|
|
382
|
+
text_parts: list[str] = []
|
|
383
|
+
for blk in blocks:
|
|
384
|
+
if isinstance(blk, Mapping) and blk.get('type') in ('text', None):
|
|
385
|
+
text_parts.append(str(blk.get('text') or ''))
|
|
386
|
+
raw_text = ''.join(text_parts)
|
|
387
|
+
usage = resp_data.get('usage') or {}
|
|
388
|
+
prompt_toks = int(usage.get('input_tokens', usage.get('prompt_tokens', default_prompt_tokens)))
|
|
389
|
+
completion_toks = int(usage.get('output_tokens', usage.get('completion_tokens', max(1, len(raw_text) // 4))))
|
|
390
|
+
return raw_text, prompt_toks, completion_toks
|
|
391
|
+
|
|
392
|
+
choices = resp_data.get('choices') or []
|
|
393
|
+
if not choices or not isinstance(choices[0], Mapping) or 'message' not in choices[0]:
|
|
394
|
+
raise ValueError("LLM backend returned no completion choices")
|
|
395
|
+
raw_text = str(choices[0]['message'].get('content') or '')
|
|
396
|
+
usage = resp_data.get('usage') or {}
|
|
397
|
+
prompt_toks = int(usage.get('prompt_tokens', default_prompt_tokens))
|
|
398
|
+
completion_toks = int(usage.get('completion_tokens', max(1, len(raw_text) // 4)))
|
|
399
|
+
return raw_text, prompt_toks, completion_toks
|
|
400
|
+
|
|
401
|
+
def to_public_dict(self) -> dict[str, Any]:
|
|
402
|
+
"""Serialize provider metadata without exposing secret API keys or Cashu tokens."""
|
|
403
|
+
return {
|
|
404
|
+
'provider_id': self.provider_id,
|
|
405
|
+
'label': self.label or self.provider_id,
|
|
406
|
+
'endpoint': self.endpoint,
|
|
407
|
+
'default_model': self.default_model,
|
|
408
|
+
'models': list(self.models),
|
|
409
|
+
'auth_scheme': self.auth_scheme,
|
|
410
|
+
'wire_format': self.wire_format,
|
|
411
|
+
'rate_msat_per_token': self.rate_msat_per_token,
|
|
412
|
+
'enabled': self.enabled,
|
|
413
|
+
'has_credentials': self.has_credentials(),
|
|
414
|
+
'is_runnable': self.is_runnable(),
|
|
415
|
+
'cashu_mint': self.cashu_mint,
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
class OperatorProviderRouter:
|
|
420
|
+
"""Registry of operator-linked LLM providers on a FleetMesh node."""
|
|
421
|
+
|
|
422
|
+
def __init__(
|
|
423
|
+
self,
|
|
424
|
+
providers: Optional[Sequence[LinkedProviderConfig]] = None,
|
|
425
|
+
default_provider: Optional[str] = None,
|
|
426
|
+
) -> None:
|
|
427
|
+
self._providers: dict[str, LinkedProviderConfig] = {}
|
|
428
|
+
self.default_provider: Optional[str] = default_provider
|
|
429
|
+
if providers:
|
|
430
|
+
for p in providers:
|
|
431
|
+
self.register_provider(p)
|
|
432
|
+
|
|
433
|
+
def register_provider(self, config: LinkedProviderConfig, set_default: bool = False) -> None:
|
|
434
|
+
pid = config.provider_id.strip().lower()
|
|
435
|
+
self._providers[pid] = config
|
|
436
|
+
if set_default or self.default_provider is None:
|
|
437
|
+
self.default_provider = pid
|
|
438
|
+
|
|
439
|
+
def remove_provider(self, provider_id: str) -> bool:
|
|
440
|
+
pid = provider_id.strip().lower()
|
|
441
|
+
existed = pid in self._providers
|
|
442
|
+
self._providers.pop(pid, None)
|
|
443
|
+
if self.default_provider == pid:
|
|
444
|
+
self.default_provider = next(iter(self._providers.keys()), None)
|
|
445
|
+
return existed
|
|
446
|
+
|
|
447
|
+
def get_provider(self, provider_id: Optional[str] = None) -> Optional[LinkedProviderConfig]:
|
|
448
|
+
if provider_id:
|
|
449
|
+
pid = provider_id.strip().lower()
|
|
450
|
+
if pid in self._providers:
|
|
451
|
+
return self._providers[pid]
|
|
452
|
+
if pid == 'anthropic' and 'claude' in self._providers:
|
|
453
|
+
return self._providers['claude']
|
|
454
|
+
if pid == 'claude' and 'anthropic' in self._providers:
|
|
455
|
+
return self._providers['anthropic']
|
|
456
|
+
if pid == 'ppq' and 'payperquery' in self._providers:
|
|
457
|
+
return self._providers['payperquery']
|
|
458
|
+
if pid == 'payperquery' and 'ppq' in self._providers:
|
|
459
|
+
return self._providers['ppq']
|
|
460
|
+
if self.default_provider and self.default_provider in self._providers:
|
|
461
|
+
return self._providers[self.default_provider]
|
|
462
|
+
for p in self._providers.values():
|
|
463
|
+
if p.is_runnable():
|
|
464
|
+
return p
|
|
465
|
+
return next(iter(self._providers.values()), None)
|
|
466
|
+
|
|
467
|
+
def resolve_for_job(self, job_payload: Mapping[str, Any]) -> Optional[LinkedProviderConfig]:
|
|
468
|
+
req_provider = job_payload.get('provider') or (job_payload.get('input') or {}).get('provider') if isinstance(job_payload.get('input'), Mapping) else job_payload.get('provider')
|
|
469
|
+
if req_provider:
|
|
470
|
+
found = self.get_provider(str(req_provider))
|
|
471
|
+
if found and found.enabled:
|
|
472
|
+
return found
|
|
473
|
+
req_model = str(job_payload.get('model') or '')
|
|
474
|
+
if req_model:
|
|
475
|
+
for p in self._providers.values():
|
|
476
|
+
if p.enabled and req_model in p.models:
|
|
477
|
+
return p
|
|
478
|
+
return self.get_provider()
|
|
479
|
+
|
|
480
|
+
def runnable_providers(self) -> list[LinkedProviderConfig]:
|
|
481
|
+
return [p for p in self._providers.values() if p.is_runnable()]
|
|
482
|
+
|
|
483
|
+
def list_public_providers(self) -> list[dict[str, Any]]:
|
|
484
|
+
return [p.to_public_dict() for p in self._providers.values()]
|
|
485
|
+
|
|
486
|
+
@classmethod
|
|
487
|
+
def from_env(cls) -> 'OperatorProviderRouter':
|
|
488
|
+
"""Auto-discover any operator-configured providers from environment variables."""
|
|
489
|
+
router = cls()
|
|
490
|
+
seen_Canonical: set[str] = set()
|
|
491
|
+
for pid, preset in PROVIDER_PRESETS.items():
|
|
492
|
+
if pid in ('anthropic', 'ppq'):
|
|
493
|
+
continue
|
|
494
|
+
val = os.environ.get(preset.env_key, '').strip()
|
|
495
|
+
if val and pid not in seen_Canonical:
|
|
496
|
+
seen_Canonical.add(pid)
|
|
497
|
+
cfg = LinkedProviderConfig.from_preset(pid)
|
|
498
|
+
router.register_provider(cfg)
|
|
499
|
+
return router
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""ExecutorRegistry: which runtime answers which job kind, and the default set."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import Any, Mapping, Optional
|
|
5
|
+
|
|
6
|
+
from .base import ExecutorProtocol
|
|
7
|
+
from .media import FfmpegTranscodeExecutor
|
|
8
|
+
from .nondet import AgentExecutor, BacalhauExecutor, LlmExecutor, LlmAgentBackend
|
|
9
|
+
from .providers import PROVIDER_PRESETS, LinkedProviderConfig, OperatorProviderRouter
|
|
10
|
+
from .wasm import WasmExecutor
|
|
11
|
+
from .zk import ZkGroth16Executor, ZkProverExecutor, ZkVmExecutor
|
|
12
|
+
|
|
13
|
+
class ExecutorRegistry:
|
|
14
|
+
"""Registry managing available compute runtime execution drivers."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, blob_store: Optional[Any] = None, mock_mode: bool = False,
|
|
17
|
+
media_cache_dir: Optional[str] = None,
|
|
18
|
+
provider_router: Optional[OperatorProviderRouter] = None) -> None:
|
|
19
|
+
# blob_store: where WASM modules/inputs named by hash are looked up
|
|
20
|
+
# when the job itself does not carry one (e.g. a job received over
|
|
21
|
+
# the compute wire, which cannot carry a live store).
|
|
22
|
+
# mock_mode: build the LLM/Bacalhau/agent drivers as deterministic
|
|
23
|
+
# fixtures. Off by default so a node never ships fixture output as a
|
|
24
|
+
# paid result; tests and demos opt in explicitly.
|
|
25
|
+
self.blob_store: Optional[Any] = blob_store
|
|
26
|
+
self.mock_mode: bool = mock_mode
|
|
27
|
+
self.provider_router: OperatorProviderRouter = provider_router or OperatorProviderRouter()
|
|
28
|
+
wasm = WasmExecutor()
|
|
29
|
+
llm = LlmExecutor(mock_mode=mock_mode, provider_router=self.provider_router)
|
|
30
|
+
bacalhau = BacalhauExecutor(mock_mode=mock_mode)
|
|
31
|
+
agent_backend = LlmAgentBackend(provider_router=self.provider_router) if self.provider_router.runnable_providers() else None
|
|
32
|
+
agent = AgentExecutor(mock_mode=mock_mode, backend=agent_backend)
|
|
33
|
+
zkvm = ZkVmExecutor(mock_mode=mock_mode)
|
|
34
|
+
zk_prover = ZkProverExecutor(mock_mode=mock_mode)
|
|
35
|
+
ffmpeg = FfmpegTranscodeExecutor(mock_mode=mock_mode, cache_dir=media_cache_dir)
|
|
36
|
+
self.executors: dict[str, ExecutorProtocol] = {
|
|
37
|
+
'wasmtime': wasm,
|
|
38
|
+
'wasm': wasm,
|
|
39
|
+
'wasmer': wasm,
|
|
40
|
+
'zk-verify-groth16': ZkGroth16Executor(),
|
|
41
|
+
'zkvm': zkvm,
|
|
42
|
+
'zkvm-risc0': zkvm,
|
|
43
|
+
'zkvm-sp1': zkvm,
|
|
44
|
+
'zk-prove-groth16': zk_prover,
|
|
45
|
+
'zk-prove-risc0': zk_prover,
|
|
46
|
+
'llm': llm,
|
|
47
|
+
'vllm': llm,
|
|
48
|
+
'ollama': llm,
|
|
49
|
+
'bacalhau': bacalhau,
|
|
50
|
+
'container': bacalhau,
|
|
51
|
+
'docker': bacalhau,
|
|
52
|
+
'agent': agent,
|
|
53
|
+
'react': agent,
|
|
54
|
+
'fleetmesh-react-v1': agent,
|
|
55
|
+
'ffmpeg': ffmpeg,
|
|
56
|
+
'media-transcode': ffmpeg,
|
|
57
|
+
'video-transcode': ffmpeg,
|
|
58
|
+
'transcode': ffmpeg,
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
def configure_llm_provider(self, config: LinkedProviderConfig, set_default: bool = True) -> None:
|
|
62
|
+
"""Link or update an external/local LLM provider for both LLM and Agent executors."""
|
|
63
|
+
self.provider_router.register_provider(config, set_default=set_default)
|
|
64
|
+
llm_ex = self.executors.get('llm')
|
|
65
|
+
if isinstance(llm_ex, LlmExecutor):
|
|
66
|
+
llm_ex.provider_router = self.provider_router
|
|
67
|
+
agent_ex = self.executors.get('agent')
|
|
68
|
+
if isinstance(agent_ex, AgentExecutor) and not self.mock_mode:
|
|
69
|
+
if self.provider_router.runnable_providers():
|
|
70
|
+
agent_ex.backend = LlmAgentBackend(
|
|
71
|
+
endpoint=config.endpoint,
|
|
72
|
+
model=config.default_model,
|
|
73
|
+
api_key=config.api_key,
|
|
74
|
+
cashu_token=config.cashu_token,
|
|
75
|
+
provider_id=config.provider_id,
|
|
76
|
+
provider_router=self.provider_router,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
def register(self, executor: ExecutorProtocol, *aliases: str) -> None:
|
|
80
|
+
for name in (executor.engine_id, *aliases):
|
|
81
|
+
self.executors[name] = executor
|
|
82
|
+
|
|
83
|
+
def get(self, engine_id: str) -> Optional[ExecutorProtocol]:
|
|
84
|
+
return self.executors.get(engine_id)
|
|
85
|
+
|
|
86
|
+
def runnable_engines(self) -> list[str]:
|
|
87
|
+
"""Engine ids that can actually complete a job on this node.
|
|
88
|
+
|
|
89
|
+
Queries is_live_runnable() polymorphically across all registered
|
|
90
|
+
executors. Executors reporting False (e.g. unconfigured backends outside
|
|
91
|
+
mock_mode, or WasmExecutor when wasmtime is not installed) are excluded
|
|
92
|
+
so Kind 11803 compute offers only advertise truly runnable capabilities.
|
|
93
|
+
"""
|
|
94
|
+
out: list[str] = []
|
|
95
|
+
for eid, ex in self.executors.items():
|
|
96
|
+
if hasattr(ex, 'is_live_runnable'):
|
|
97
|
+
if not ex.is_live_runnable():
|
|
98
|
+
continue
|
|
99
|
+
out.append(eid)
|
|
100
|
+
return out
|
|
101
|
+
|
|
102
|
+
def select_for_job(self, job_payload: Mapping[str, Any]) -> ExecutorProtocol:
|
|
103
|
+
job_exec = job_payload.get('executor')
|
|
104
|
+
if job_exec and job_exec in self.executors:
|
|
105
|
+
return self.executors[job_exec]
|
|
106
|
+
if job_exec and str(job_exec).lower() in PROVIDER_PRESETS:
|
|
107
|
+
return self.executors['llm']
|
|
108
|
+
|
|
109
|
+
mod = job_payload.get('module') or {}
|
|
110
|
+
mod_type = mod.get('type') or mod.get('runtime')
|
|
111
|
+
if mod_type and mod_type in self.executors:
|
|
112
|
+
return self.executors[mod_type]
|
|
113
|
+
|
|
114
|
+
if job_payload.get('engine') in ('ffmpeg', 'media-transcode', 'video-transcode', 'transcode'):
|
|
115
|
+
return self.executors['ffmpeg']
|
|
116
|
+
|
|
117
|
+
if 'messages' in job_payload or 'model' in job_payload or 'prompt' in job_payload:
|
|
118
|
+
return self.executors['llm']
|
|
119
|
+
|
|
120
|
+
if 'storage_mounts' in job_payload or job_payload.get('engine') in ('docker', 'bacalhau'):
|
|
121
|
+
return self.executors['bacalhau']
|
|
122
|
+
|
|
123
|
+
if 'max_steps' in job_payload or 'tools' in job_payload or 'agent' in job_payload or 'task' in job_payload:
|
|
124
|
+
return self.executors['agent']
|
|
125
|
+
|
|
126
|
+
inp = job_payload.get('input', {})
|
|
127
|
+
if isinstance(inp, dict):
|
|
128
|
+
if 'source_url' in inp or 'source_path' in inp or ('profile' in inp and ('vod_id' in inp or 'duration_sec' in inp)):
|
|
129
|
+
return self.executors['ffmpeg']
|
|
130
|
+
if inp.get('action') == 'prove' or 'witness' in inp or 'private_inputs' in inp:
|
|
131
|
+
return self.executors.get('zk-prove-groth16', self.executors['zk-verify-groth16'])
|
|
132
|
+
if 'image_id' in inp or 'journal' in inp or str(job_payload.get('proof_system', '')).startswith(('risc0', 'sp1', 'zkvm')):
|
|
133
|
+
return self.executors.get('zkvm-risc0', self.executors['zk-verify-groth16'])
|
|
134
|
+
if 'proof' in inp or 'vk_sha256' in inp:
|
|
135
|
+
return self.executors['zk-verify-groth16']
|
|
136
|
+
|
|
137
|
+
return self.executors['wasmtime']
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
DEFAULT_REGISTRY: ExecutorRegistry = ExecutorRegistry()
|