litellm/litellm-rust/AGENTS.md
Yujong Lee cfabf44c8e
Some checks failed
Cost map pins recheck / rerun-cost-map-guard (push) Has been cancelled
LiteLLM Rust DB / db-queries (push) Has been cancelled
LiteLLM Rust / rust-lint (push) Has been cancelled
LiteLLM Rust / rust-test (push) Has been cancelled
LiteLLM Rust / rust-wheel (push) Has been cancelled
Terraform Modules / fmt, validate, test (aws) (push) Has been cancelled
Terraform Modules / fmt, validate, test (gcp) (push) Has been cancelled
docs(rust): add the rust-python-boundary skill
Python-shaped input becomes typed Rust only in crates/config, which reads
config.yaml with litellm-python-compat, and crates/python-bridge, which reads
live Python objects through PyO3. A crate that stands in for a Python library,
like redis-resolve for redis-py, keeps that library's semantics because they are
its compatibility contract. The skill names the code that still coerces past
the boundary, so it moves there when touched instead of growing
2026-11-05 13:00:49 +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