Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
0619e1d
test(translation): cover normalized Responses image encoding
ayushag-nv Sep 19, 2026
8e92e94
feat(classifier): control images shown to the judge
ayushag-nv Sep 19, 2026
ff33386
docs(vision): demonstrate Cosmos judge routing to vision models
ayushag-nv Sep 19, 2026
56cd9c8
fix(examples): add license header to vision judge demo
ayushag-nv Sep 19, 2026
bf5c5c1
perf(classifier): skip copying omitted judge images
ayushag-nv Sep 19, 2026
bff3610
chore(vision): remove judge demo artifacts
ayushag-nv Oct 9, 2026
1a5e740
test(classifier): simplify judge image limit coverage
ayushag-nv Oct 9, 2026
335fb86
fix(classifier): enforce judge image caps without sampling
ayushag-nv Oct 12, 2026
6902672
test(translation): cover normalized Responses image encoding
ayushag-nv Sep 19, 2026
1f08fab
feat(classifier): control images shown to the judge
ayushag-nv Sep 19, 2026
4d53754
docs(vision): demonstrate Cosmos judge routing to vision models
ayushag-nv Sep 19, 2026
91d620b
fix(examples): add license header to vision judge demo
ayushag-nv Sep 19, 2026
deec427
perf(classifier): skip copying omitted judge images
ayushag-nv Sep 19, 2026
9137f29
feat(translation): preserve video inputs across provider formats
ayushag-nv Sep 21, 2026
efefe07
feat(media): prepare endpoint media in the LLM client
ayushag-nv Sep 21, 2026
91b9e3c
feat(runner): configure media policies per target
ayushag-nv Sep 21, 2026
f66ebf2
build(media): include FFmpeg runtime and release dependencies
ayushag-nv Sep 21, 2026
e9c2ae8
docs(media): add a reproducible image and video routing example
ayushag-nv Sep 21, 2026
e562287
chore(media): merge updated judge image controls
ayushag-nv Oct 12, 2026
272c931
refactor(media): simplify video dispatch and routing example
ayushag-nv Oct 12, 2026
702997b
refactor(media): flatten video modes and trim example configuration
ayushag-nv Oct 12, 2026
0e7f139
fix(media): enforce image caps without selecting frames
ayushag-nv Oct 12, 2026
ca9657a
refactor(client): replace switchyard-media with Dynamo multimodal pro…
ayushag-nv Oct 14, 2026
c782948
fix(media): reconcile tests and removed demos after rebase
ayushag-nv Oct 14, 2026
82929f9
refactor(media): simplify preparation tests and documentation
ayushag-nv Oct 14, 2026
6d51328
chore(media): use published dynamo-multimodal 0.2.0
ayushag-nv Oct 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
260 changes: 250 additions & 10 deletions Cargo.lock

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
ARG RUST_VERSION=1.96.1
FROM rust:${RUST_VERSION}-bookworm AS builder

RUN apt-get update \
&& apt-get install --no-install-recommends -y cmake \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /opt/switchyard
COPY Cargo.toml Cargo.lock rust-toolchain.toml ./
# .cargo/config.toml carries the workspace rustflags (target-cpu, force-frame-pointers).
Expand All @@ -17,7 +21,7 @@ RUN cargo build --locked --release -p switchyard-server
FROM debian:bookworm-slim

RUN apt-get update \
&& apt-get install --no-install-recommends -y ca-certificates \
&& apt-get install --no-install-recommends -y ca-certificates libvpx7 \
&& rm -rf /var/lib/apt/lists/*

COPY --from=builder \
Expand Down
6 changes: 5 additions & 1 deletion crates/libsy-llm-client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ keywords = ["llm", "client", "openai", "anthropic"]
publish = ["crates-io"]

[dependencies]
base64.workspace = true
dynamo-multimodal = { version = "0.2.0", default-features = false, features = ["fetch", "video-decode"] }
image = { version = "0.25", default-features = false, features = ["jpeg", "png", "webp"] }
thiserror.workspace = true
url = "2"
switchyard-libsy.workspace = true
switchyard-protocol.workspace = true
switchyard-translation.workspace = true
Expand All @@ -38,6 +43,5 @@ tracing-opentelemetry.workspace = true
tokio = { workspace = true, features = ["test-util"] }
# SDK + in-memory exporter to assert what the observability layer records.
opentelemetry_sdk = { version = "0.32", features = ["metrics", "testing", "trace"] }
thiserror.workspace = true
tracing-subscriber.workspace = true
wiremock = "0.6"
100 changes: 100 additions & 0 deletions crates/libsy-llm-client/MEDIA.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Media settings

Configure `[targets.<name>.media]` to prepare images and videos for an endpoint.
The LLM client prepares a separate outgoing body for each judge, answer, or
fallback call. The original request stays available to the other targets.
Omit the media table to keep existing behavior.

## Configure judge and answer inputs

This example gives the judge one small video frame and the answer up to eight.
Still images sent to the judge are resized to fit 384 pixels. The judge rejects
requests with more than one image or video; choose the input before routing.

```toml
[targets.judge.media]
image_max_edge = 384
max_images = 1
video = "frames"
video_max_frames = 1
frame_max_edge = 384

[targets.answer.media]
video = "frames"
video_max_frames = 8
frame_max_edge = 768
```

For a text-only target, set `max_images = 0` and `video = "omit"`.
The route's `judge_max_images` setting affects existing image blocks only;
video processing belongs in the target's media settings.

| Setting | Default | Behavior |
|---|---|---|
| `image_max_edge` | unset | Resize still images to fit this edge (1–4096 pixels); preserve aspect ratio and never upscale. |
| `max_images` | unset | Maximum images and requested video frames per call (0–128). Unset allows all; zero omits them; a positive limit rejects excess input. |
| `video` | `passthrough` | Video handling mode; see below. |
| `video_max_frames` | 4 | Maximum samples per video (1–64). |
| `frame_max_edge` | 640 | Maximum frame edge (1–4096 pixels); preserve aspect ratio and never upscale. |
| `max_input_bytes` | 33554432 | Maximum downloaded or decoded inline bytes per locally processed source (1 byte–256 MiB). |
| `max_output_bytes` | 33554432 | Maximum combined prepared image bytes before base64 encoding (1 byte–256 MiB). |
| `timeout_ms` | 30000 | Preparation deadline, including downloads, queueing, and decoding (1–300000 ms). |

Image limits cover all selected messages and nested tool results. A positive
`max_images` reserves `video_max_frames` images per video before downloading it,
even if the clip later yields fewer frames. Excess input returns HTTP 400,
including on a fail-open judge. The limit never selects or reorders images.

## Video modes

| Mode | Outgoing content |
|---|---|
| `passthrough` | Keep the video block produced by translation. |
| `frames` | Replace each video with timestamped JPEG images. |
| `video_url` | Chat Completions `video_url` block. Requires an `openai_chat` target. |
| `file` | Gemini-on-Inference-Hub video `file` block. Requires an `openai_chat` target. |
| `omit` | Replace videos with text markers without fetching them. |

Native modes forward the existing URL or inline data without fetching or
transcoding it. They do not count toward `max_images`. The endpoint must support
the selected format. Responses `input_video` and Anthropic video source blocks
are intermediate formats; use `frames` for endpoints that accept images only.

Frame sampling uses uniformly spaced indices on the video track, including its
endpoints. One sample uses the first frame; a short clip returns at most its
available frames. Timestamps are presentation times, and longer audio does not
extend sampling. Uniform sampling can miss brief events.

## Sources and limits

Local processing accepts public HTTP(S) URLs and base64 data URIs. Use inline
data for local files. Local paths, private network URLs, and provider-managed
file IDs cannot be fetched. Downloads validate DNS and redirects and receive no
inference credentials or environment proxy settings.

Still images support PNG, JPEG, and WebP. Resizing applies EXIF orientation and
outputs JPEG, or PNG for transparency. Images already within the requested size
stay encoded as supplied when no orientation change is needed. Other provider
fields, including image detail and cache hints, are retained.

Each target reuses its own fetcher. At most 64 sources are processed per call,
and two decoding jobs run at once per client. Decoded image allocations and each
video frame's RGB buffer are limited to 128 MiB; selected video frames together
Comment on lines +80 to +82

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is architecture decision. Should not be mentioned here. also all these source and limit points should be at max 3-4 small bullet points. No need to consume so much words for this.

are also limited to 128 MiB after resizing. A timeout stops the caller's wait.
An active native decoder keeps its worker slot until it finishes.

`max_input_bytes` and `max_output_bytes` apply to locally processed media.
The server separately limits the entire incoming JSON body to 32 MiB, including
base64 overhead. Raise it with `--max-request-body-bytes`; see the
[CLI reference](../../docs/cli_reference.md).

## Runtime requirements

Media processing uses the published `dynamo-multimodal` 0.2 release with the
`fetch` and `video-decode` features.

Building from source requires CMake and a C/C++ toolchain.
Video decoding requires 64-bit Linux. H.264, HEVC, VP9, and AV1 use NVDEC when
supported by the GPU and matching NVIDIA driver libraries. VP8 and VP9 also have
a CPU path through system libvpx, which is included in the server image.
Unsupported formats or missing decoders return an error. FFmpeg is not required.
3 changes: 3 additions & 0 deletions crates/libsy-llm-client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ retries plus ordered candidate fallback.
It depends on `switchyard-libsy`, `switchyard-protocol`, and
`switchyard-translation`; no server, no provider SDK.

Per-target [media settings](MEDIA.md) use `dynamo-multimodal` to fetch media,
resize images, and decode video frames. The client formats the result for each provider.

## Concepts

- **Model configs.** A client is built from [`ModelConfig`] values, each keyed by a
Expand Down
Loading
Loading