Skip to content

refactor(text-generation): migrate Anthropic to mixin pattern#75

Merged
Kamilbenkirane merged 2 commits into
mainfrom
feat/anthropic-mixin-migration
Dec 15, 2025
Merged

refactor(text-generation): migrate Anthropic to mixin pattern#75
Kamilbenkirane merged 2 commits into
mainfrom
feat/anthropic-mixin-migration

Conversation

@Kamilbenkirane

Copy link
Copy Markdown
Member

Summary

Migrates text-generation Anthropic provider to use the celeste-anthropic Messages API mixin pattern, achieving 319 lines reduction while improving code maintainability and enabling horizontal reuse across future capabilities.

Changes

Client Refactoring (-104 lines)

  • Inherit from AnthropicMessagesClient mixin
  • Delegate HTTP/SSE logic to mixin via super() calls
  • Remove _make_request() and _make_stream_request() implementations
  • Simplify to only wrap responses in TextGenerationUsage/TextGenerationFinishReason

Before:

async def _make_request(self, request_body, **parameters):
    request_body["model"] = self.model.id
    request_body["max_tokens"] = parameters.get("max_tokens") or 1024
    headers = {...}
    return await self.http_client.post(...)

After:

def _parse_usage(self, response_data):
    usage = super()._parse_usage(response_data)  # Mixin handles extraction
    return TextGenerationUsage(**usage)

Streaming Refactoring (-49 lines, 34% reduction)

  • Inherit from AnthropicMessagesStream mixin
  • Remove manual SSE event parsing (content_block_delta, message_delta, message_stop)
  • Remove _parse_usage_from_event() duplication (reuse mixin's usage mapping)

Mixin benefit: When Anthropic adds image/video generation, they get SSE parsing for free.

Parameters Refactoring (-166 lines)

  • Import base mappers from celeste-anthropic provider package
  • Wrap with TextGenerationParameter names for capability-specific API
  • ThinkingBudgetMapper translates unified values (-1"auto") to provider-native format

Before: 248 lines of duplicated mapper implementations
After: 68 lines wrapping provider mappers

Core Type Improvements

  • FinishReason standardization:

    • TextGenerationFinishReason.reason: strstr | None (align with base)
    • AnthropicMessagesClient._parse_finish_reason: Always return FinishReason (not None)
    • Remove redundant None checks in capability code
  • Model constraints:

    • Add Parameter.MAX_TOKENS to all Anthropic models (was missing, causing UnsupportedParameterError)
    • Claude 4.5 models: max=64,000 tokens
    • Claude Opus 4.1/4: max=32,000 tokens

Dependencies

  • Add celeste-anthropic workspace dependency to text-generation/pyproject.toml

Architecture Benefits

1. Horizontal Reuse

The mixin enables zero-cost addition of Anthropic support to other capabilities:

# Future: Anthropic Image Generation
class AnthropicImageGenerationClient(AnthropicMessagesClient, ImageGenerationClient):
    pass  # Gets HTTP, usage parsing, beta headers for free

2. Single Source of Truth

  • Usage mapping: AnthropicMessagesClient.map_usage_fields() used by both client and streaming
  • SSE parsing: One implementation in AnthropicMessagesStream for all capabilities

3. Maintainability

  • If Anthropic changes SSE format: Update in 1 place (mixin), not 4+ capabilities
  • If Anthropic adds new beta headers: Update in 1 place (_build_headers)

Metrics

Component Before After Reduction
Client 159 lines 69 lines -90 lines
Streaming 145 lines 96 lines -49 lines
Parameters 248 lines 68 lines -180 lines
Total 552 lines 233 lines -319 lines (58%)

Test Plan

  • All unit tests pass (293 tests, 81% coverage)
  • Pre-commit hooks pass (ruff, mypy, bandit)
  • CI pipeline passes
  • Integration tests pass (Anthropic tests now work with max_tokens)
  • Manual testing with Anthropic API

Migration Validation

Verified the mixin pattern maintains identical behavior:

  • ✅ HTTP request structure unchanged
  • ✅ SSE event parsing produces same chunks
  • ✅ Usage mapping uses same field names (UsageField enum)
  • ✅ Beta header injection works (extended-thinking, structured-outputs)

Migrate text-generation Anthropic provider to use celeste-anthropic Messages API mixin pattern, reducing code duplication and improving maintainability.

**Client (client.py):**
- Inherit from AnthropicMessagesClient mixin
- Delegate HTTP requests to mixin via super()
- Remove _make_request() and _make_stream_request() implementations (104 lines → mixin)
- Simplify to only wrap responses in TextGenerationUsage/FinishReason

**Streaming (streaming.py):**
- Inherit from AnthropicMessagesStream mixin
- Remove manual SSE event parsing (content_block_delta, message_delta, message_stop)
- Remove _parse_usage_from_event() duplication (reuse mixin's usage mapping)
- Reduce from 145 lines to 96 lines (34% reduction)

**Parameters (parameters.py):**
- Import base mappers from celeste-anthropic (TemperatureMapper, MaxTokensMapper, ThinkingMapper, OutputSchemaMapper)
- Wrap with TextGenerationParameter names
- ThinkingBudgetMapper translates unified values (-1 → "auto") to provider-native format
- Remove 248 lines of duplicated mapper implementations

**Core Types:**
- TextGenerationFinishReason.reason: str → str | None (align with base FinishReason)
- AnthropicMessagesClient._parse_finish_reason: Always return FinishReason (not None)
- Remove redundant None checks in capability code

**Models:**
- Add Claude Opus 4.5 model
- Remove config.py (moved to provider package)

**Dependencies:**
- Add celeste-anthropic workspace dependency to text-generation pyproject.toml

**Net change:** -319 lines across 8 files

Stats:
- Client: 104 lines removed (HTTP/request logic → mixin)
- Streaming: 49 lines removed (SSE parsing → mixin)
- Parameters: 166 lines removed (mapper implementations → provider)
All Anthropic models now include Parameter.MAX_TOKENS in parameter_constraints:
- Claude 4.5 models (Sonnet, Haiku, Opus): max 64,000 tokens
- Claude Sonnet 4 (20250514): max 64,000 tokens
- Claude Opus 4.1 & Opus 4 (20250514): max 32,000 tokens

This fixes UnsupportedParameterError when using max_tokens parameter with Anthropic models.
@claude

claude Bot commented Dec 15, 2025

Copy link
Copy Markdown

Pull Request Review: Migrate Anthropic to Mixin Pattern

Summary

This PR successfully refactors the Anthropic text-generation provider to use a shared mixin pattern, achieving significant code reduction (-319 lines, 58%) while maintaining functionality. The architectural improvements enable horizontal reuse across future capabilities.

✅ Strengths

Architecture & Design

  1. Excellent use of mixins - The AnthropicMessagesClient and AnthropicMessagesStream mixins provide clean separation of concerns
  2. Single source of truth - Usage mapping, SSE parsing, and header building are now centralized
  3. Clear inheritance hierarchy - Multiple inheritance is used correctly with proper super() delegation
  4. Future-proof design - The pattern makes it trivial to add new Anthropic-powered capabilities

Code Quality

  1. Significant code reduction - 319 lines removed (58% reduction) without losing functionality
  2. Clean delegation pattern - Capability-specific code properly delegates to mixin
  3. Type safety maintained - Proper type hints throughout
  4. Consistent parameter mapping - Wrapper classes cleanly adapt provider mappers

🔍 Issues & Concerns

1. Missing Validation: Thinking Budget Minimum (Severity: Medium)

Location: packages/capabilities/text-generation/src/celeste_text_generation/providers/anthropic/parameters.py:32-54

The old implementation validated that thinking budget must be >= 1024 when not using auto. This validation is now missing. The new code translates -1 to auto but does not validate the minimum of 1024 for fixed budgets.

Recommendation: Add validation to ThinkingBudgetMapper.map() or document why it's no longer needed.

2. Type Safety in _parse_finish_reason (Severity: High - Documentation)

Location: packages/providers/anthropic/src/celeste_anthropic/messages/client.py:136-142

The method always returns a FinishReason object, but stop_reason can be None. While FinishReason.reason is typed as str | None, the behavior should be clearly documented.

3. Content Parsing Behavior (Severity: Low)

Location: packages/capabilities/text-generation/src/celeste_text_generation/providers/anthropic/client.py:37-51

The logic finds the first text block and breaks. If there are no text blocks, it returns empty string. Consider documenting this behavior or adding error handling.

4. Beta Header Validation (Severity: Low)

Location: packages/providers/anthropic/src/celeste_anthropic/messages/client.py:51-68

The _build_headers method will raise AttributeError if an invalid beta feature name is provided. Consider adding validation or try/except with helpful error message.

🧪 Testing Concerns

The PR description notes integration tests and manual API testing are not yet complete. Before merging:

  1. Run integration tests against real Anthropic API
  2. Test streaming and non-streaming modes
  3. Test structured outputs with beta header mechanism
  4. Test thinking budget with both -1 (auto) and fixed values
  5. Verify mixin pattern works with multiple inheritance

🔒 Security Review

✅ No security issues found

  • No injection vulnerabilities
  • Proper header handling maintained
  • No hardcoded secrets

📊 Performance

✅ Performance improvements expected

  • Code reduction leads to faster import times
  • Shared mixin reduces memory footprint
  • No performance regressions

🎯 Recommendations Before Merge

Must Fix

  1. Verify thinking budget validation (add 1024 minimum check or document why removed)
  2. Run integration tests and manual API tests
  3. Verify Claude Opus 4.5 model ID is correct

Should Fix

  1. Add error handling/validation for beta feature names
  2. Document _parse_finish_reason behavior when reason=None
  3. Clarify content parsing behavior

📈 Overall Assessment

Rating: 8.5/10

Well-architected refactor with excellent code reduction and maintainability improvements. Main concerns are missing validation and need for integration testing confirmation.

Recommendation: Approve with minor changes requested

Great work on this refactor! 🚀

@Kamilbenkirane Kamilbenkirane merged commit 332c85c into main Dec 15, 2025
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant