Skip to main content

llmposter

Mock HTTP server for LLM provider APIs (OpenAI, Anthropic, Gemini, Responses API). Use when writing integration tests that need deterministic, controllable LLM API responses without calling real providers. Supports fixture-based request matching, SSE streaming, failure injection, auth simulation, VCR record/replay modes, and all four provider response formats.

소스 정보

저장소
SkillDoAI/llmposter
최근 소스 활동
2026년 10월 17일 08:53
감지된 SKILL.md 언어
영어
스타
2
포크
1

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
llmposter
description
Mock HTTP server for LLM provider APIs (OpenAI, Anthropic, Gemini, Responses API). Use when writing integration tests that need deterministic, controllable LLM API responses without calling real providers. Supports fixture-based request matching, SSE streaming, failure injection, auth simulation, VCR record/replay modes, and all four provider response formats.
license
AGPL-3.0-or-later
metadata
{"author":"SkillDoAI","version":"0.5.0","ecosystem":"rust","generated-by":"skilldo/claude-haiku-4-5-20251001 + review:claude-haiku-4-5-20251001"}
# llmposter Mock HTTP server for LLM provider APIs. Clients point their base URL at llmposter and interact using real API paths — no code changes beyond the URL swap. Fixtures define request matchers and canned responses for Anthropic (`POST /v1/messages`), OpenAI (`POST /v1/chat/completions`, `POST /v1/completions`), Gemini (non-streaming `POST /v1beta/models/{model}:generateContent`, streaming `POST /v1beta/models/{model}:streamGenerateContent`), and Responses API (`POST /v1/responses`). Response format automatically matches provider (no prefixing). New in 0.5.0: VCR record/replay modes for recording live upstream responses and deterministic replay; new endpoints `GET /health`, `GET /v1/models`, `POST /v1/embeddings`, `POST /v1/moderations`. ## Imports ```rust // Core types (re-exported at crate root) use llmposter::{Fixture, Provider, ServerBuilder, MockServer, CapturedRequest}; // Fixture sub-types use llmposter::fixture::FailureConfig; // VCR types (0.5.0+, gated by "record" feature) use llmposter::record::VcrMode; ``` ```toml [dependencies] llmposter = "0.5.0" tokio = { version = "1", features = ["full"] } reqwest = { version = "0.13", default-features = false, features = ["json"] } serde_json = "1" # Optional: Disable default features to minimize dependencies # llmposter = { version = "0.5.0", default-features = false, features = ["watch"] } # Optional: UI debug interface # llmposter = { version = "0.5.0", features = ["ui"] } ``` **Default features:** `oauth`, `watch`, `jsonpath`, `record` **Optional features:** `ui`, `templating` ## Core Patterns ### Quick Start ```rust mod quick_start { use llmposter::{Fixture, ServerBuilder}; #[tokio::test] async fn test_basic_mock_server() -> Result<(), Box<dyn std::error::Error>> { let server = ServerBuilder::new() .fixture( Fixture::new() .match_user_message("hello") .respond_with_content("Hi from Claude mock!"), ) .build() .await?; let client = reqwest::Client::new(); let resp = client .post(format!("{}/v1/messages", server.url())) .json(&serde_json::json!({ "model": "claude-sonnet-4-6", "max_tokens": 1024, "messages": [{"role": "user", "content": "hello world"}] })) .send() .await?; assert_eq!(resp.status(), 200); let body: serde_json::Value = resp.json().await?; assert_eq!(body["type"], "message"); assert_eq!(body["content"][0]["text"], "Hi from Claude mock!"); assert_eq!(body["stop_reason"], "end_turn"); assert!(body["id"].as_str().unwrap().starts_with("msg-llmposter-")); assert!(body["usage"]["input_tokens"].as_u64().unwrap_or(0) > 0); Ok(()) } } ``` ### Tool Use Response ```rust mod tool_use_example { use llmposter::fixture::ToolCall; use llmposter::{Fixture, ServerBuilder}; #[tokio::test] async fn test_tool_use_response() -> Result<(), Box<dyn std::error::Error>> { let server = ServerBuilder::new() .fixture( Fixture::new() .match_user_message("weather") .respond_with_tool_calls(vec![ToolCall { name: "get_weather".to_string(), arguments: serde_json::json!({ "location": "London", "unit": "celsius" }), }]), ) .build() .await?; let client = reqwest::Client::new(); let resp = client .post(format!("{}/v1/messages", server.url())) .json(&serde_json::json!({ "model": "claude-sonnet-4-6", "max_tokens": 1024, "messages": [{"role": "user", "content": "What's the weather?"}] })) .send() .await?; assert_eq!(resp.status(), 200); let body: serde_json::Value = resp.json().await?; assert_eq!(body["stop_reason"], "tool_use"); assert_eq!(body["content"][0]["type"], "tool_use"); assert_eq!(body["content"][0]["name"], "get_weather"); let tool_id = body["content"][0]["id"].as_str().unwrap(); assert!(tool_id.starts_with("toolu_llmposter_")); assert_eq!(body["content"][0]["input"]["location"], "London"); Ok(()) } } ``` ### SSE Streaming ```rust mod streaming_example { use llmposter::{Fixture, ServerBuilder}; #[tokio::test] async fn test_streaming_response() -> Result<(), Box<dyn std::error::Error>> { let server = ServerBuilder::new() .fixture( Fixture::new() .match_user_message("hello") .respond_with_content("Hello world") .with_streaming(Some(0), Some(5)), // latency=0ms, chunk_size=5 chars ) .build() .await?; let client = reqwest::Client::new(); let resp = client .post(format!("{}/v1/messages", server.url())) .json(&serde_json::json!({ "model": "claude-sonnet-4-6", "max_tokens": 1024, "messages": [{"role": "user", "content": "hello"}], "stream": true })) .send() .await?; assert_eq!(resp.status(), 200); let text = resp.text().await?; assert!(text.contains("event: message_start")); assert!(text.contains("event: content_block_delta")); assert!(text.contains("event: message_stop")); Ok(()) } } ``` ### Error Simulation ```rust mod error_simulation { use llmposter::{Fixture, ServerBuilder}; #[tokio::test] async fn test_rate_limit_error() -> Result<(), Box<dyn std::error::Error>> { let server = ServerBuilder::new() .fixture( Fixture::new() .match_user_message("rate limit") .with_error(429, "Rate limit exceeded"), ) .build() .await?; let client = reqwest::Client::new(); let resp = client .post(format!("{}/v1/messages", server.url())) .json(&serde_json::json!({ "model": "claude-sonnet-4-6", "max_tokens": 1024, "messages": [{"role": "user", "content": "rate limit test"}] })) .send() .await?; assert_eq!(resp.status(), 429); let body: serde_json::Value = resp.json().await?; assert_eq!(body["error"]["message"], "Rate limit exceeded"); Ok(()) } } ``` ### Failure Injection (Latency) ```rust mod latency_injection { use llmposter::fixture::FailureConfig; use llmposter::{Fixture, ServerBuilder}; use std::time::Instant; #[tokio::test] async fn test_latency_injection() -> Result<(), Box<dyn std::error::Error>> { let server = ServerBuilder::new() .fixture( Fixture::new() .respond_with_content("delayed response") .with_failure(FailureConfig { latency_ms: Some(200), ..Default::default() }), ) .build() .await?; let start = Instant::now(); let client = reqwest::Client::new(); let resp = client .post(format!("{}/v1/messages", server.url())) .json(&serde_json::json!({ "model": "claude-sonnet-4-6", "max_tokens": 1024, "messages": [{"role": "user", "content": "test"}] })) .send() .await?; assert_eq!(resp.status(), 200); assert!(start.elapsed().as_millis() >= 180); let body: serde_json::Value = resp.json().await?; assert_eq!(body["content"][0]["text"], "delayed response"); Ok(()) } } ``` ### Corrupt Body (Overloaded Simulation) ```rust mod corrupt_body_example { use llmposter::fixture::FailureConfig; use llmposter::{Fixture, ServerBuilder}; #[tokio::test] async fn test_corrupt_body() -> Result<(), Box<dyn std::error::Error>> { let server = ServerBuilder::new() .fixture( Fixture::new() .respond_with_content("should not appear") .with_failure(FailureConfig { corrupt_body: Some(true), ..Default::default() }), ) .build() .await?; let client = reqwest::Client::new(); let resp = client .post(format!("{}/v1/messages", server.url())) .json(&serde_json::json!({ "model": "claude-sonnet-4-6", "max_tokens": 1024, "messages": [{"role": "user", "content": "test"}] })) .send() .await?; assert_eq!(resp.status(), 200); let text = resp.text().await?; assert_eq!(text, "overloaded"); Ok(()) } } ``` ### Provider Filtering ```rust mod provider_filtering { use llmposter::{Fixture, Provider, ServerBuilder}; #[tokio::test] async fn test_provider_filters() -> Result<(), Box<dyn std::error::Error>> {
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기