{"id":50449474,"url":"https://github.com/aktagon/llmkit-rust","last_synced_at":"2026-05-31T23:32:04.971Z","repository":{"id":356106943,"uuid":"1231022985","full_name":"aktagon/llmkit-rust","owner":"aktagon","description":"Unified LLM client library for Rust - one API, 27 providers (Anthropic, OpenAI, Google Gemini, AWS Bedrock, Mistral, Groq, DeepSeek, +20 more), async via tokio + reqwest.","archived":false,"fork":false,"pushed_at":"2026-05-21T21:23:19.000Z","size":370,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-05-22T06:52:20.068Z","etag":null,"topics":["agents","ai","ai-sdk","anthropic","bedrock","claude","gemini","gpt","groq","llm","llm-client","mistral","openai","rust","streaming","tool-calling"],"latest_commit_sha":null,"homepage":"https://llmkit.aktagon.com","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/aktagon.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-06T14:49:28.000Z","updated_at":"2026-05-21T21:23:29.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/aktagon/llmkit-rust","commit_stats":null,"previous_names":["aktagon/llmkit-rust"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/aktagon/llmkit-rust","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aktagon%2Fllmkit-rust","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aktagon%2Fllmkit-rust/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aktagon%2Fllmkit-rust/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aktagon%2Fllmkit-rust/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/aktagon","download_url":"https://codeload.github.com/aktagon/llmkit-rust/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aktagon%2Fllmkit-rust/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33753923,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-31T02:00:06.040Z","response_time":95,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["agents","ai","ai-sdk","anthropic","bedrock","claude","gemini","gpt","groq","llm","llm-client","mistral","openai","rust","streaming","tool-calling"],"created_at":"2026-05-31T23:32:04.052Z","updated_at":"2026-05-31T23:32:04.962Z","avatar_url":"https://github.com/aktagon.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# llmkit (Rust)\n\nOne Rust API for Anthropic, OpenAI, Google, and 20+ other providers — including local models through Ollama and vLLM. Switch providers without rewriting your request.\n\nAsync, built on `tokio` and `reqwest`.\n\nAlso available for Go, TypeScript, and Python.\n\n## Install\n\n```toml\n[dependencies]\nllmkit = \"1.0\"\ntokio = { version = \"1\", features = [\"macros\", \"rt-multi-thread\"] }\n```\n\n## Quick Start\n\n```rust\nuse llmkit::builders::anthropic;\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c(), Box\u003cdyn std::error::Error\u003e\u003e {\n    let c = anthropic(std::env::var(\"ANTHROPIC_API_KEY\")?);\n    let resp = c.text()\n        .system(\"Be concise.\")\n        .temperature(0.3)\n        .prompt(\"Why is the sky blue?\")\n        .await?;\n\n    println!(\"{}\", resp.text);\n    println!(\"{} input tokens\", resp.usage.input);\n    Ok(())\n}\n```\n\nThe typed builder is the only public surface as of v1.0.0. One mental model — `client.\u003ccapability\u003e().\u003cchain\u003e.\u003cterminal\u003e` — across every capability.\n\nRunnable counterparts to every code block below live in [`examples/`](./examples/) and are exercised by `tests/examples.rs` against a mock HTTP server, so the call shapes shown here are guaranteed to execute against the real builder surface.\n\n## Providers\n\nPer-provider factory functions in `llmkit::builders`:\n\n```\nai21       anthropic  azure      bedrock    cerebras   cohere\ndeepseek   doubao     ernie      fireworks  google     grok\ngroq       jan        llamacpp   lmstudio   minimax    mistral\nmoonshot   ollama     openai     openrouter perplexity qwen\nsambanova  together   vertex     vllm       yi         zhipu\n```\n\nOr use the generic `new_client(ProviderName::OpenAI, key)`. 30 providers, 4 API shapes (OpenAI-compatible, Anthropic Messages, Google Generative AI, AWS Bedrock Converse). Bedrock auth uses SigV4; other providers use API-key auth.\n\n## API\n\n### Text — one-shot prompt\n\n```rust\nlet resp = c.text()\n    .system(\"You are helpful\")\n    .temperature(0.7)\n    .max_tokens(200)\n    .prompt(\"What is 2+2?\")\n    .await?;\n\nprintln!(\"{}\", resp.text);              // \"4\"\nprintln!(\"{}\", resp.usage.input);       // prompt tokens\nprintln!(\"{}\", resp.usage.output);      // completion tokens\nprintln!(\"{}\", resp.usage.cache_read);  // tokens served from cache\nprintln!(\"{}\", resp.usage.cache_write); // tokens written to cache (Anthropic explicit)\nprintln!(\"{}\", resp.usage.reasoning);   // internal reasoning tokens (OpenAI o-series, Gemini 2.5+)\n```\n\nCapability-scoped fields (`cache_read`, `cache_write`, `reasoning`) are zero when the provider doesn't report them separately.\n\n### Stream — callback + trailing handle\n\nRust's stream surface is callback-based. The callback fires for each chunk; the awaited terminal returns the final `Response` with token counts.\n\n```rust\nlet resp = c.text()\n    .system(\"Be brief\")\n    .stream(\"Tell me a joke\", |chunk| print!(\"{}\", chunk))\n    .await?;\nprintln!(\"\\nUsage: {:?}\", resp.usage);\n```\n\nThe callback shape is the trailing-handle pattern from the other SDKs expressed in callback form: callback receives chunks (≡ iterator), the returned `Result\u003cResponse\u003e` is the trailing handle (≡ `stream.response()` in TS/Python). The `impl Stream\u003cItem = ...\u003e` variant from `futures` would mirror the other SDKs visually but pulls in an extra dependency we chose to avoid.\n\n### Agent — tool loop\n\n```rust\nuse llmkit::Tool;\n\nlet add = Tool::new(\n    \"add\",\n    \"Add two numbers\",\n    serde_json::json!({\n        \"type\": \"object\",\n        \"properties\": {\n            \"a\": {\"type\": \"number\"},\n            \"b\": {\"type\": \"number\"},\n        },\n    }),\n    |args| Ok((args[\"a\"].as_f64().unwrap() + args[\"b\"].as_f64().unwrap()).to_string()),\n);\n\nlet mut bot = c.agent()\n    .system(\"You are a calculator.\")\n    .add_tool(add)\n    .max_tool_iterations(5);\n\nlet resp = bot.prompt(\"What is 2+3?\").await?;\nprintln!(\"{}\", resp.text);\n```\n\n`*Agent` is **stateful** — repeated `bot.prompt(...)` calls accumulate history. Chain methods (`.system(...)`, `.add_tool(...)`) consume `self` and produce a fresh-state clone, so a forked builder gets a fresh conversation. `bot.reset()` clears state without dropping chained config.\n\nTool dispatch covers Anthropic `tool_use`, OpenAI `tool_calls`, Google `functionCall`, and Bedrock Converse `toolUse`.\n\n### Image — text-to-image and edit\n\nSupports Google's Nano Banana 2 (`gemini-3.1-flash-image-preview`) and Pro (`gemini-3-pro-image-preview`); OpenAI's `gpt-image-2`, `gpt-image-1.5`, `gpt-image-1`, and `gpt-image-1-mini`; xAI's `grok-imagine-image-quality`; Google Cloud Vertex AI's Imagen 3 / Imagen 4 (`imagen-3.0-generate-002`, `imagen-3.0-fast-generate-001`, `imagen-4.0-generate-preview-06-06`).\n\n```rust\nuse llmkit::builders::google;\n\nlet c = google(std::env::var(\"GOOGLE_API_KEY\")?);\nlet img = c.image()\n    .model(\"gemini-3.1-flash-image-preview\")\n    .aspect_ratio(\"16:9\")\n    .image_size(\"2K\")\n    .generate(\"A nano banana dish, studio lighting\")\n    .await?;\n\nstd::fs::write(\"out.png\", \u0026img.images[0].data)?;\n```\n\nFor compositional editing, chain `.text(...)` and `.image(mime, bytes)` to interleave references with descriptions:\n\n```rust\nc.image()\n    .model(\"gemini-3.1-flash-image-preview\")\n    .text(\"Person:\")\n    .image(\"image/png\", person_bytes)\n    .text(\"Outfit:\")\n    .image(\"image/png\", outfit_bytes)\n    .generate(\"Generate the person wearing the outfit.\")\n    .await?;\n```\n\nAspect ratios and sizes validate against a per-model whitelist before the HTTP request. Empty whitelists mean \"no client-side check; pass through\" — providers like OpenAI accept arbitrary sizes within documented bounds (max edge ≤3840, both edges multiples of 16, ratio ≤3:1, total pixels 655K–8.3M), so the SDK trusts the API boundary instead of carrying a stale list.\n\nFor OpenAI, the chain dispatches automatically — no image parts hits `/v1/images/generations` (JSON), one or more image parts hits `/v1/images/edits` (multipart/form-data with one `image[]` field per reference, in caller order).\n\nProvider knobs are typed chain methods on the `Image` builder:\n\n| Method               | Provider support            | Wire field       |\n| -------------------- | --------------------------- | ---------------- |\n| `.quality(s)`        | OpenAI gpt-image-\\*         | `quality`        |\n| `.output_format(s)`  | OpenAI gpt-image-\\*         | `output_format`  |\n| `.background(s)`     | OpenAI gpt-image-\\*         | `background`     |\n| `.count(n)`          | OpenAI + xAI Grok           | `n`              |\n| `.mask(mime, bytes)` | OpenAI gpt-image-\\* (edits) | multipart `mask` |\n\nThe chain validates per provider — calling `.quality(...)` on a Google or xAI builder returns `Err(Validation { ... })` immediately, no HTTP round-trip. Knobs without typed methods (OpenAI: `output_compression`, `moderation`) remain reachable via `.extra_fields(...)`, which is unvalidated and freeform.\n\n```rust\nuse llmkit::builders::openai;\n\nlet c = openai(std::env::var(\"OPENAI_API_KEY\")?);\nlet resp = c.image()\n    .model(\"gpt-image-2\")\n    .image_size(\"1024x1024\")\n    .quality(\"high\")\n    .count(4)\n    .generate(\"A red circle on a white background\")\n    .await?;\n```\n\nOpenAI gpt-image-\\* models require organization verification — see [platform.openai.com/docs/guides/your-data#organization-verification](https://platform.openai.com/docs/guides/your-data#organization-verification).\n\nUp to 14 reference images per Google request, 16 per OpenAI request.\n\n#### Vertex AI Imagen (Google Cloud)\n\nVertex Imagen uses the `:predict` endpoint family and OAuth bearer auth instead of API keys. The SDK takes a bearer token (string); caller manages OAuth refresh externally (e.g. `gcloud auth print-access-token`, service-account JSON, or workload identity).\n\n```rust\nuse llmkit::builders::vertex;\n\n// Caller substitutes {project_id} and {location} before passing the URL.\nlet base_url = \"https://us-central1-aiplatform.googleapis.com\\\n    /v1/projects/my-gcp-project/locations/us-central1/publishers/google/models\";\n\nlet c = vertex(std::env::var(\"VERTEX_BEARER_TOKEN\")?).with_base_url(base_url);\n\nlet resp = c\n    .image()\n    .model(\"imagen-3.0-generate-002\")\n    .aspect_ratio(\"16:9\")\n    .count(2)\n    .generate(\"A red circle\")\n    .await?;\n```\n\nEdit-mode (single image into `instances[0].image`) and inpainting (`.mask(mime, bytes)` into `instances[0].mask.image`) work the same way. Imagen-specific knobs like `negativePrompt` and `safetySetting` are reachable through `.extra_fields(...)` — they spread into the request's `parameters` block. Vertex's `:predict` response does not carry token counts; `resp.usage` stays zero.\n\n### Safety Settings\n\nControl content filtering for Gemini providers. `safety_settings` applies to text\ngeneration, streaming, agents, and Gemini image generation. `safety_filter` applies\nto Vertex Imagen only.\n\n```rust\nuse llmkit::builders::{google, vertex};\nuse llmkit::types::{\n    SafetySetting,\n    HARM_CATEGORY_DANGEROUS_CONTENT,\n    HARM_CATEGORY_HARASSMENT,\n    HARM_BLOCK_THRESHOLD_NONE,\n    HARM_BLOCK_THRESHOLD_HIGH_ONLY,\n    IMAGE_SAFETY_FILTER_BLOCK_FEW,\n};\n\n// Gemini text or agent\nlet c = google(std::env::var(\"GOOGLE_API_KEY\")?);\nlet resp = c\n    .text()\n    .safety_settings(vec![\n        SafetySetting { category: HARM_CATEGORY_DANGEROUS_CONTENT.into(), threshold: HARM_BLOCK_THRESHOLD_NONE.into() },\n        SafetySetting { category: HARM_CATEGORY_HARASSMENT.into(), threshold: HARM_BLOCK_THRESHOLD_HIGH_ONLY.into() },\n    ])\n    .prompt(\"Write a story\")\n    .await?;\n\n// Vertex Imagen\nlet vc = vertex(std::env::var(\"VERTEX_BEARER_TOKEN\")?);\nlet img = vc\n    .image()\n    .model(\"imagen-3.0-generate-002\")\n    .safety_filter(IMAGE_SAFETY_FILTER_BLOCK_FEW)\n    .generate(\"A landscape\")\n    .await?;\n```\n\n`safety_settings` on Vertex Imagen and `safety_filter` on non-Imagen providers return\n`Err(ValidationError)`. The `HARM_CATEGORY_*`, `HARM_BLOCK_THRESHOLD_*`, and\n`IMAGE_SAFETY_FILTER_*` constants cover all documented values; raw strings also work.\n\n### Upload — Path or Bytes\n\n```rust\nuse llmkit::builders::openai;\n\nlet c = openai(std::env::var(\"OPENAI_API_KEY\")?);\n\n// from a path\nlet file = c.upload().path(\"./data.pdf\").run().await?;\n\n// from bytes (filename required)\nlet file2 = c.upload()\n    .bytes(buf)\n    .filename(\"report.pdf\")\n    .mime_type(\"application/pdf\")\n    .run()\n    .await?;\n```\n\n### Batches\n\n```rust\nuse llmkit::builders::BatchHandleExt;\n\nlet results = c.text()\n    .system(\"Be brief\")\n    .batch(vec![\"Translate hello to French\".into(), \"Translate hello to Spanish\".into()])\n    .await?;\nfor r in \u0026results { println!(\"{}\", r.text); }\n\n// Or split:\nlet handle = c.text().submit_batch(prompts).await?;\nlet results = handle.wait().await?;\n```\n\nBoth inline (Anthropic) and file-reference (OpenAI two-hop) flows are handled internally. Import the `BatchHandleExt` trait to call `.wait()` on the returned handle.\n\n### Caching\n\n```rust\n// Anthropic — explicit cache_control wrap of the system prompt.\nc.text().system(long_sys_prompt).caching().prompt(\"...\").await?;\n\n// OpenAI — automatic server-side caching (caching() is a hint; reads\n// surface in resp.usage.cache_read regardless).\nc.text().system(long_sys_prompt).caching().prompt(\"...\").await?;\n\n// Google — pre-flight POST creates a cachedContents resource, then\n// the main call references it. Google requires ~1k+ tokens of system\n// prompt:\nc.text().system(big_sys_prompt).caching().prompt(\"...\").await?;\n```\n\nThe mode is provider-specific and inferred from the provider config. The default TTL for Google is 3600s.\n\n### Model catalogue\n\n`c.models()` and `c.providers()` cover model discovery in three modes. Runnable counterpart at [`examples/catalogue.rs`](./examples/catalogue.rs).\n\n```rust\nuse llmkit::{Capability, Provider, ProviderName};\n\n// 1. Compiled-in catalogue — synchronous, no HTTP.\nlet all = c.models().list();\nlet info = c.models().get(\"claude-opus-4-7\");         // Option\u003cModelInfo\u003e\nlet chat = c.models().with_capability(Capability::ChatCompletion).list();\n\n// 2. Providers namespace.\nc.providers().list();      // configured (credentials + /v1/models endpoint)\nc.providers().supported(); // every provider the SDK was built with\n\n// 3. Live + scoped HTTP.\nlet live = c.models().live().await;                   // LiveResult — fan-out\nlet p = Provider::new(ProviderName::Anthropic, \"sk-...\");\nlet scoped = c.models().provider(p.clone()).list().await?;\nlet raw = c.models().provider(p).raw().list().await?; // ModelInfo.raw populated\n```\n\n`live().await` calls every configured provider's `/v1/models` in parallel and aggregates results into `LiveResult.models` + a per-provider `LiveResult.errors` map (partial success is the normal case). `provider(p).raw().list()` opts into populating `ModelInfo.raw` with the provider-native record — useful when you need fields the universal `ModelInfo` does not carry (Anthropic's capability matrix, Google's `supportedGenerationMethods`, etc.).\n\n## Options\n\nAcross every `*Text` / `*Agent` builder:\n\n| Concept          | Method                 |\n| ---------------- | ---------------------- |\n| System prompt    | `.system(s)`           |\n| Model override   | `.model(name)`         |\n| Sampling         | `.temperature(t)`      |\n| Token cap        | `.max_tokens(n)`       |\n| Caching          | `.caching()`           |\n| Middleware hooks | `.add_middleware(fns)` |\n| Reasoning effort | `.reasoning_effort(l)` |\n| Thinking budget  | `.thinking_budget(n)`  |\n\n`*Text` adds `.history(msgs)` and `.schema(json)`; `*Agent` adds `.add_tool(t)` and `.max_tool_iterations(n)` and carries conversation history implicitly across `.prompt(...)` calls.\n\nSampling hyperparameters (`.top_p`, `.top_k`, `.seed`, `.frequency_penalty`, `.presence_penalty`, `.stop_sequences`) are validated per provider; unsupported options return `Error::Validation` rather than silently dropping.\n\nThe Image builder has a narrower set: `.model`, `.aspect_ratio`, `.image_size`, `.include_text`, `.text`, `.image`, `.middleware`. Upload: `.path`, `.bytes`, `.filename`, `.mime_type`, `.middleware`.\n\n## Self-hosted endpoints\n\n```rust\nuse llmkit::builders::openai;\n\nlet c = openai(\"anything\").with_base_url(\"http://localhost:8080/v1\");\n```\n\nWorks for any OpenAI-compatible server (vLLM, LM Studio, Ollama, corporate gateways).\n\n## Middleware\n\nRegister pre/post hooks around LLM requests, tool calls, image generation, cache creation, uploads, and batch submits. Pre-phase middleware can veto by returning `Some(error)`; post-phase return values are discarded.\n\n```rust\nuse std::sync::Arc;\nuse llmkit::builders::anthropic;\nuse llmkit::middleware::{Event, MiddlewareFn, MiddlewareOp, MiddlewarePhase};\n\n// Observation: log token usage after every LLM request.\nlet log_usage: MiddlewareFn = Arc::new(|e: \u0026Event| {\n    if e.op == MiddlewareOp::LlmRequest \u0026\u0026 e.phase == MiddlewarePhase::Post {\n        if let Some(u) = \u0026e.usage {\n            let ms = e.duration.map(|d| d.as_millis()).unwrap_or(0);\n            println!(\n                \"{}/{}: {} in, {} out, {ms} ms\",\n                e.provider, e.model, u.input, u.output,\n            );\n        }\n    }\n    None\n});\n\n// Veto: abort if a daily budget is exceeded (pre-phase).\nlet limit = 5.00_f64;\nlet spent = Arc::new(std::sync::Mutex::new(0.0_f64));\nlet spent_for_gate = Arc::clone(\u0026spent);\nlet budget_gate: MiddlewareFn = Arc::new(move |e: \u0026Event| {\n    if e.op == MiddlewareOp::LlmRequest\n        \u0026\u0026 e.phase == MiddlewarePhase::Pre\n        \u0026\u0026 *spent_for_gate.lock().unwrap() \u003e= limit\n    {\n        let msg = format!(\"daily budget ${:.2} exceeded\", limit);\n        return Some(Box::\u003cdyn std::error::Error + Send + Sync\u003e::from(msg));\n    }\n    None\n});\n\nlet c = anthropic(\"…\");\nlet resp = c\n    .text()\n    .add_middleware(vec![budget_gate, log_usage])\n    .prompt(\"…\")\n    .await?;\n```\n\nA pre-phase veto surfaces as `llmkit::Error::MiddlewareVeto(String)` carrying the formatted cause, so callers can discriminate it from transport or provider errors via `match err { Error::MiddlewareVeto(msg) =\u003e … }`. Middlewares fire in registration order; the first `Some(_)` pre-phase return aborts.\n\nWired at six sites: `Text.prompt` / `Agent::chat` LLM call (`op=LlmRequest`), `Agent` tool execution (`op=ToolCall`), `Image.generate` (`op=ImageGeneration`), `Upload.run` (`op=Upload`), `Text.submit_batch` (`op=BatchSubmit`), Google resource caching pre-flight (`op=CacheCreate`).\n\n## Wire-format stability\n\n`*Agent` history persists across process boundaries through two paired\nfunctions:\n\n```rust\nlet data = bot.save()?;                                  // String\n// ...later, fresh process...\nlet bot = c.agent().system(\"...\").tool(t).load(\u0026data)?;\n// returns Err(WireError::UnsupportedVersion { .. }) on mismatch\n```\n\nOr the free-function form for admin tooling:\n\n```rust\nuse llmkit::{save_history, load_history};\n\nlet data = save_history(\u0026msgs)?;\nlet msgs = load_history(\u0026data)?;\n```\n\nThe output is a JSON document with a `_v` integer envelope plus a\n`messages` array. The version is tracked through\n`WIRE_SCHEMA_VERSION`; the in-memory `Message` schema may evolve\nadditively under one version (new optional fields work on older\nreaders), but a renamed, removed, or retyped field requires a `_v`\nbump and a migrator.\n\n`save_history` / `load_history` are the ONLY guaranteed-stable\nserialization path. `Message` does not implement `serde::Serialize`\ntoday, so direct `serde_json::to_string` will not compile on a\n`Message` value; even if that changes, the bytes would still lack the\n`_v` envelope and `load_history` would reject them with\n`WireError::MissingVersion`. Use the contract path for\nanything that crosses a process boundary or a release.\n\n## Mirror\n\nThis repo is a read-only mirror of a private monorepo. File issues here; code patches should target the private source via `christian@aktagon.com`.\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faktagon%2Fllmkit-rust","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faktagon%2Fllmkit-rust","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faktagon%2Fllmkit-rust/lists"}