Notícias
Notícias
5 min de leitura
26 de setembro de 2026

Seu SaaS suporta múltiplos LLMs? Use wrapper, não código.

Wrapper pattern (Jev-like) simplifica suporte a múltiplos LLMs + vision. Seu SaaS tem código repetido? Abstração = menos boilerplate, mais reutilização.

Equipe OpenClaw

Equipe OpenClaw · Time de Engenharia & Produto

A Equipe OpenClaw é formada por engenheiros, designers e especialistas em IA dedicados a construir a melhor plataforma de agentes conversacionais para negócios brasileiros. Combinamos expertise…


Seu SaaS suporta múltiplos LLMs? Use wrapper, não código.

Você é founder de SaaS.

Você construiu AI agent (atendimento, recomendações, automação).

Agent suporta 1 LLM (OpenAI GPT-4).

Code é simples (diretamente OpenAI API).

Then requirements change:

Customer requests: ├─ Support Claude (Anthropic) ├─ Support Mistral (cheaper alternative) ├─ Support vision models (understand images) ├─ Support local models (privacy-sensitive) │ Your code: ├─ Add OpenAI code ├─ Add Anthropic code (different API format) ├─ Add Mistral code (yet another format) ├─ Add vision support (different request format) ├─ Result: 3x boilerplate code, hard to maintain │

Then you read news (setembro 2026):

Headline: "A single function Jev-like wrapper for LLMs, including vision models" │ What's happening: ├─ New pattern published (Jev-like wrapper) ├─ Concept: Single abstraction (all LLMs use same interface) ├─ Benefit: Write code once, use any LLM (no duplication) ├─ Includes: Vision model support (built-in) ├─ Result: Cleaner architecture (less boilerplate) │ Your thought: ├─ "Wait, I could eliminate all this repetition?" ├─ "Single wrapper handles OpenAI, Claude, Mistral, vision?" ├─ "My code would be 70% smaller?" ├─ "I could add new LLMs in minutes (not hours)?" │

The problem: You're maintaining separate code for each LLM (OpenAI API different from Anthropic different from Mistral). Vision models add another layer (different request/response format). Result: Code duplication, hard to maintain, slow to add new models. Jev-like wrapper solves this (single abstraction, handles all models, vision included). This changes how you architect SaaS (simpler, more flexible, easier to maintain). Understanding wrapper pattern = competitive advantage (you iterate faster than competitors still managing multiple integrations).


O problema real (why separate LLM integrations are painful)

Dilema 1: Each LLM has different API (boilerplate nightmare)

=== API DIFFERENCES === │ OpenAI API: ├─ { │ "model": "gpt-4", │ "messages": [{"role": "user", "content": "..."} │ "temperature": 0.7 ├─ } │ Anthropic API: ├─ { │ "model": "claude-opus", │ "messages": [{"role": "user", "content": "..."} │ "temperature": 0.7 ├─ } │ Mistral API: ├─ { │ "model": "mistral-large", │ "messages": [{"role": "user", "content": "..."} │ "temperature": 0.7 ├─ } │ Problem: ├─ All use similar format (but slightly different) ├─ Different response formats (OpenAI vs Anthropic vs Mistral) ├─ Different error handling (each provider handles errors differently) ├─ Your code: Full of if/else statements (which provider? format accordingly) │ Example: ├─ if provider == "openai": │ response = client.ChatCompletion.create(...) ├─ elif provider == "anthropic": │ response = client.messages.create(...) ├─ elif provider == "mistral": │ response = client.chat.complete(...) │ Result: ├─ 3x code for same functionality (boilerplate) ├─ Hard to maintain (change logic = change all 3 paths) ├─ Slow to add new provider (add new if/elif, repeat code) │

Dilema 2: Vision models complicate things further (different format)

=== VISION MODEL COMPLEXITY === │ Text-only request (simple): ├─ "What is this product?" │ Vision request (complex): ├─ "What is this product?" ├─ + Image URL or Base64 encoded image ├─ + Different API format (vision endpoint) │ OpenAI vision API: ├─ { │ "model": "gpt-4-vision", │ "messages": [{ │ "role": "user", │ "content": [ │ {"type": "text", "text": "..."}, │ {"type": "image_url", "image_url": {"url": "..."}} │ ] │ }] ├─ } │ Claude vision API (different): ├─ { │ "model": "claude-opus", │ "messages": [{ │ "role": "user", │ "content": [ │ {"type": "text", "text": "..."}, │ {"type": "image", "source": {"type": "base64", "media_type": "...", "data": "..."}} │ ] │ }] ├─ } │ Your code gets worse: ├─ if provider == "openai": │ if has_vision: │ format as image_url │ else: │ format as text ├─ elif provider == "anthropic": │ if has_vision: │ format as base64 │ else: │ format as text │ Result: ├─ Combinatorial explosion (text × openai, text × anthropic, vision × openai, vision × anthropic...) ├─ Code becomes unreadable (nested if/else) ├─ Maintenance nightmare (change one format = ripple effects) │

Dilema 3: Adding new provider means rewriting same code (inefficient)

=== NEW PROVIDER COST === │ Currently you support: ├─ OpenAI (text) ├─ OpenAI (vision) ├─ Anthropic (text) ├─ Anthropic (vision) │ Customer requests: "Add Mistral support" │ Your work: ├─ Add Mistral API integration (2 hours) ├─ Add Mistral vision support (1 hour) ├─ Add error handling (1 hour) ├─ Test all combinations (2 hours) ├─ Total: 6 hours (per new provider) │ With 10 providers: ├─ 10 × 6 hours = 60 hours (1.5 weeks of work) │ Problem: ├─ This is repetitive work (same logic, different API format) ├─ Could be automated (if you had abstraction) ├─ Currently: Must be done manually (no abstraction) │

Dilema 4: Testing becomes exponential (text vs vision, each provider)

=== TESTING COMPLEXITY === │ Providers: 4 (OpenAI, Anthropic, Mistral, Grok) Modalities: 2 (text, vision) Scenarios: 10 (success, error, timeout, etc) │ Total test cases: ├─ 4 × 2 × 10 = 80 test cases (must pass all) │ With new provider (5th): ├─ 5 × 2 × 10 = 100 test cases (20 more) │ With new modality (3D): ├─ 4 × 3 × 10 = 120 test cases (40 more) │ Problem: ├─ Testing time scales exponentially (number of providers × modalities) ├─ Hard to maintain (change logic = re-run all 100 tests) ├─ Easy to miss edge cases (forgot to test vision + Anthropic + error scenario) │


Solution: Jev-like wrapper (single abstraction for all LLMs)

What is Jev-like wrapper?

=== CONCEPT === │ Instead of: ├─ if provider == "openai": do_openai_thing() ├─ elif provider == "anthropic": do_anthropic_thing() ├─ elif provider == "mistral": do_mistral_thing() │ Use: ├─ llm_call(provider, messages, vision=False) → response │ Wrapper internals: ├─ Takes any provider (openai, anthropic, mistral, etc) ├─ Takes any modality (text, vision) ├─ Handles formatting (converts to provider-specific format) ├─ Handles errors (normalizes error handling) ├─ Returns unified response (same format regardless of provider) │ Result: ├─ Your code: No if/else provider logic ├─ Your code: Doesn't care which provider (abstraction handles it) ├─ Your code: Same for text and vision │

Example implementation (pseudocode)

=== PSEUDOCODE === │

Define wrapper (once)

function llm_call(provider, messages, vision=false, model="auto"): ├─ if provider == "openai": │ ├─ if vision: │ │ ├─ format_messages_openai_vision(messages) │ │ ├─ call OpenAI vision API │ │ ├─ return response │ ├─ else: │ │ ├─ format_messages_openai_text(messages) │ │ ├─ call OpenAI text API │ │ ├─ return response ├─ elif provider == "anthropic": │ ├─ if vision: │ │ ├─ format_messages_anthropic_vision(messages) │ │ ├─ call Anthropic vision API │ │ ├─ return response │ ├─ else: │ │ ├─ format_messages_anthropic_text(messages) │ │ ├─ call Anthropic text API │ │ ├─ return response ├─ (... similar for other providers ...) │

Use wrapper (in your agent code)

response = llm_call("openai", messages, vision=false) response = llm_call("anthropic", messages, vision=true) response = llm_call("mistral", messages, vision=false) │

Change provider (one line)

response = llm_call("grok", messages, vision=true) # New provider, no code change needed │ Benefit: ├─ All provider logic is in wrapper (not scattered in your code) ├─ Your code: Clean, simple, no provider-specific logic ├─ Adding new provider: Update wrapper only (not your code) │

Benefits of wrapper pattern

=== ADVANTAGES === │

  1. Code reusability: ├─ Write LLM logic once (in wrapper) ├─ Use from anywhere (agent, API, background job) ├─ Benefit: DRY principle (don't repeat yourself)

  2. Easy to add providers: ├─ Add new if/elif in wrapper (5 min) ├─ Your code: No changes needed ├─ Benefit: Rapid iteration (add new model in minutes)

  3. Consistent interface: ├─ All providers return same format ├─ Your code: Doesn't care which provider ├─ Benefit: Simpler code (no conditional logic)

  4. Easy to test: ├─ Test wrapper once (handles all providers) ├─ Your code: Simple to unit test (no provider logic) ├─ Benefit: Fewer test cases (not exponential)

  5. Easy to switch providers: ├─ Change config (which provider to use) ├─ Your code: No changes ├─ Benefit: A/B testing (test GPT-4 vs Claude easily)

  6. Vision model support: ├─ Wrapper handles vision formatting ├─ Your code: vision=true parameter ├─ Benefit: Vision works automatically (no code duplication)

  7. Cost optimization: ├─ Wrapper can route (easy requests → cheap model, hard → expensive) ├─ Your code: No changes (routing is in wrapper) ├─ Benefit: Automatic cost optimization │


How to implement wrapper in your SaaS

Step 1: Design abstraction (1-2 hours)

=== DESIGN === │ Define wrapper interface: ├─ Input: (provider, messages, vision=false, temperature=0.7, max_tokens=2000) ├─ Output: {"text": "...", "usage": {"tokens": ...}, "provider": "..."} │ Define message format (unified): ├─ [{"role": "user", "content": "..."}] ├─ [{"role": "user", "content": [{"type": "text", "text": "..."}, {"type": "image", "url": "..."}]}] │ Define error handling (unified): ├─ RateLimitError ├─ APIKeyError ├─ TimeoutError ├─ ValidationError │ Define provider config: ├─ providers = {"openai": {"api_key": "...", "models": ["gpt-4", "gpt-4-vision"]}, ...} │

Step 2: Implement wrapper (2-4 hours)

=== IMPLEMENTATION === │ Create wrapper function: ├─ llm_call(provider, messages, vision=false, ...) ├─ Inside: Provider-specific formatting ├─ Inside: Error handling ├─ Inside: Response normalization │ Create provider adapters (one per provider): ├─ adapter_openai.py (OpenAI API logic) ├─ adapter_anthropic.py (Anthropic API logic) ├─ adapter_mistral.py (Mistral API logic) │ Create message formatter (one per provider): ├─ format_openai_text(messages) ├─ format_openai_vision(messages) ├─ format_anthropic_text(messages) ├─ format_anthropic_vision(messages) ├─ (... etc) │ Create error handler (unified): ├─ normalize_error(error, provider) → {"type": "...", "message": "..."} │

Step 3: Migrate your code (2-4 hours)

=== MIGRATION === │ Old code (scattered provider logic): ├─ if self.provider == "openai": │ response = openai_client.chat.completions.create(...) ├─ elif self.provider == "anthropic": │ response = anthropic_client.messages.create(...) │ New code (using wrapper): ├─ response = llm_call(self.provider, messages) │ Benefit: ├─ 70% less code ├─ Much easier to read ├─ Much easier to maintain │

Step 4: Test wrapper (1-2 hours)

=== TESTING === │ Test wrapper (single test suite): ├─ test_llm_call_openai_text() ├─ test_llm_call_openai_vision() ├─ test_llm_call_anthropic_text() ├─ test_llm_call_anthropic_vision() ├─ test_llm_call_error_handling() │ Mock providers (don't need real API keys for testing): ├─ Mock OpenAI responses ├─ Mock Anthropic responses ├─ Mock error scenarios │ Benefit: ├─ Smaller test suite (not exponential) ├─ Easier to maintain (test wrapper once) │

Step 5: Add new provider (30 minutes)

=== ADD NEW PROVIDER (GROK) === │

  1. Create adapter: ├─ adapter_grok.py (Grok API logic)

  2. Add to wrapper: ├─ elif provider == "grok": │ ├─ return adapter_grok.call(messages, vision, ...)

  3. Test: ├─ test_llm_call_grok_text() ├─ test_llm_call_grok_vision()

  4. Deploy: ├─ Your code: No changes needed ├─ Already works with Grok (wrapper abstraction) │ Compare (without wrapper): ├─ Would need to update 20+ places in code ├─ With wrapper: Update 1 place (wrapper) │


Practical roadmap (this month)

Week 1: Design + assessment (4-6 hours)

  1. Audit current code (1-2 hours): ├─ How many places use LLM calls? ├─ How much code is provider-specific? ├─ How many providers do you support? (OpenAI, Claude, others?) ├─ Do you support vision? (if not, will you need to?)

  2. Design wrapper (2-4 hours): ├─ Define interface (inputs, outputs) ├─ List all providers (what APIs do you need to support?) ├─ Design error handling (how to normalize errors?) ├─ Design message format (how to handle text + vision?)

Week 2: Implementation (4-6 hours)

  1. Implement wrapper (2-3 hours): ├─ Create wrapper function ├─ Create provider adapters (at least 2-3) ├─ Create message formatters ├─ Create error handler

  2. Write tests (1-2 hours): ├─ Test wrapper (each provider + modality) ├─ Test error scenarios ├─ Test message formatting

  3. Documentation (30 min - 1 hour): ├─ How to use wrapper ├─ How to add new provider ├─ Examples (text, vision)

Week 3: Migration (2-4 hours)

  1. Find and replace (1-2 hours): ├─ Replace provider-specific calls with wrapper calls ├─ Test each replacement (incremental)

  2. Validation (1-2 hours): ├─ Verify wrapper works (staging environment) ├─ Run full test suite ├─ A/B test (old code vs new wrapper)

Week 4+: Maintenance + expansion (ongoing)

  1. Add new providers (as needed): ├─ 30 min per provider (using wrapper pattern) ├─ No changes to your code

  2. Optimize routing (if using multi-model): ├─ Easy requests → Cheap model ├─ Hard requests → Expensive model ├─ Implemented in wrapper (your code unchanged)

  3. Monitor costs: ├─ Which providers are used most? ├─ Which are most expensive? ├─ Opportunity to switch providers (easy, with wrapper)


Conclusão

Simple verdade:

Wrapper pattern (Jev-like) simplifies multi-model + vision support (single abstraction, all providers). Without wrapper: You maintain separate code for each provider (OpenAI, Anthropic, Mistral, etc). With wrapper: You maintain provider logic in one place (wrapper). Benefit: Faster iteration, easier maintenance, easier testing. Implement this month (4-6 hours total effort). ROI: 10+ hours saved per new provider added. This is table-stakes architecture for modern SaaS using multiple LLMs.

3 facts:

  1. Provider-specific code is boilerplate (OpenAI API ≠ Anthropic API ≠ Mistral API). Why? Each provider has slightly different format. Vision adds another layer (different again). Result: You write same logic 3+ times (one per provider). Solution: Wrapper abstracts provider differences. Benefit: Write once, use everywhere.

  2. Vision models need abstraction too (OpenAI vision format ≠ Claude vision format). Why? Image encoding, message structure, response format all different. Result: Vision implementation is duplicated (same as text problem). Solution: Wrapper includes vision formatting. Benefit: Vision works automatically (no special handling needed in your code).

  3. Adding new provider should take 30 min, not 6 hours (with wrapper, it does). Why? Without wrapper: Rewrite provider logic in multiple places. With wrapper: Add provider adapter (1 place). Benefit: Rapid iteration (test new LLM in minutes, not days). This is competitive advantage (you experiment with models, competitors maintain legacy code).

3 action items (this week):

  1. Audit your LLM code (1 hour, today). How many places call LLM API? Is code duplicated per provider? How much provider-specific logic? If answer is "a lot": You need wrapper. If answer is "we use single provider": Still implement wrapper (future-proof).**

  2. Design wrapper interface (1-2 hours, today). What should llm_call() take as input? What should it return? How should errors be handled? Document this (share with team). This is your abstraction contract.**

  3. Start implementation this week (4-6 hours, week 1-2). Pick one provider (start with OpenAI). Implement wrapper for that. Then add second provider (Anthropic or Mistral). Test both work. Celebrate (you now have abstraction).**


Próximos passos

Na OpenClaw, ajudamos SaaS builders implement wrapper pattern (standardize LLM integration, multi-model support, vision included):

  • LLM Wrapper Architecture Design: Como estruturar wrapper (interfaces, adapters, error handling)?
  • Multi-Provider Implementation: Como suportar OpenAI, Anthropic, Mistral, etc (single codebase)?
  • Vision Model Integration: Como adicionar vision models (consistent abstraction)?
  • Provider Router: Como rotear requests (easy → cheap, hard → expensive model)?
  • Error Normalization: Como lidar com provider-specific errors (unified handling)?
  • Message Formatting Standardization: Como abstrair provider message formats?
  • Cost Monitoring: Como rastrear custo por provider (use wrapper metrics)?
  • A/B Testing Infrastructure: Como testar providers (via wrapper abstraction)?
  • Testing Strategy for Wrapper: Como testar wrapper (less boilerplate, more coverage)?
  • Migration Plan: Como migrar existing code (old provider-specific → new wrapper)?
  • Performance Optimization: Como cache responses (in wrapper layer)?
  • Documentation for Team: Como educar time (wrapper usage, adding providers)?

LLM Wrapper Pattern | Multi-Provider Architecture | Vision Model Abstraction | Code Reusability | SaaS LLM Integration →


Publicado em 26 de setembro de 2026

Leia também