litellm/litellm-rust/AGENTS.md
yujonglee a1b87bcca8
feat(redis): extend the native Redis foundation for the store families (#50169)
Reworks the redis-client, connect, resolve and testing crates, adds python-compat json loads and
TTL seconds, replaces the redis-store memory tier with a DualCache plus a shared failure log, and
gives the Python side the Redis timeout log throttle and a request OTEL parent span for service events

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 12:30:07 +09:00

3.6 KiB

Rust workspace rules

For keeping secrets out of Debug output, follow .agents/skills/rust-redaction/SKILL.md

For diagnostic tracing changes, follow .agents/skills/rust-tracing/SKILL.md

For string-valued enums and their Serde conversions, follow .agents/skills/rust-string-enums/SKILL.md

For error types, error variants and conversions between crate errors, follow .agents/skills/rust-error/SKILL.md

For where config.yaml values, environment layering and kwargs from Python become typed Rust, follow .agents/skills/rust-python-boundary/SKILL.md

The process-wide rustls CryptoProvider (ring) is installed once by the host. rust-proxy does it at the top of main through litellm_gateway::install_crypto_provider, and python-bridge must do the same in its module init before it builds any rustls config. Library crates never install it. The AWS SDK links aws-lc-rs next to ring, so without an installed default the first rustls config a library builds, such as redis-rs TLS, panics

For fieldless enums, derive strum::VariantArray and use VARIANTS instead of a hand-listed ALL array. Derive Strum string conversions instead of hand-written variant-to-string matches

Use the derived conversions directly (<&'static str>::from(x) / .into(), str::parse) with no as_str/parse wrapper that only delegates, and spell each variant with explicit #[strum(serialize = "...")] instead of serialize_all

Test placement

  • Never create a tests.rs (or test.rs) file under src/, and never #[path = "tests.rs"] mod tests;
  • Tests live inline by default, in a #[cfg(test)] mod tests { ... } at the bottom of the file that owns the code under test, whether or not they touch private items
  • crates/<crate>/tests/ is only for tests that drive several modules together end to end, such as a full request through a route. A test of one type or function never goes there
  • A helper shared by several inline test modules goes in a #[cfg(test)] mod test_support; at the crate root, and only once a second module needs it
  • A test for another crate's item belongs in that crate, not in a downstream one
  • Tests that call a real provider go in crates/<crate>/tests/live/<provider>.rs behind a tests/live/main.rs. Each reads the provider's key from the environment through an Option fixture and returns early when it is unset, never #[ignore], so cargo test stays offline until the key is exported
  • Never set autotests = false or hand-list [[test]] targets. Every file directly under tests/ is discovered by cargo, and a shared helper goes in tests/<name>/mod.rs or tests/<subject>/support.rs so it is not picked up as a test crate of its own

Test fixtures and cases

Use #[rstest] for new and updated tests and #[fixture] for reusable setup, injected through typed test arguments. Express input variations as named #[case::name(...)] cases instead of loops or duplicated tests so each failure identifies its case. Keep behavior assertions in the test body and fixtures focused on setup. Use the workspace rstest dependency

  • Never loop over inputs (for, .iter().for_each, .all) inside a test body. Give each input its own #[case::name(...)], or use #[values(...)] for a cross product
  • Exception: a test pinning a Rust table against a repo-owned data file (for example include_str! of a JSON config) may iterate that file's entries