Skip to content

feat: add Anthropic Messages API provider with core structured output infrastructure#74

Merged
Kamilbenkirane merged 3 commits into
mainfrom
api/anthropic_messages_api
Dec 15, 2025
Merged

feat: add Anthropic Messages API provider with core structured output infrastructure#74
Kamilbenkirane merged 3 commits into
mainfrom
api/anthropic_messages_api

Conversation

@Kamilbenkirane

Copy link
Copy Markdown
Member

Summary

This PR introduces the Anthropic Messages API provider package along with foundational structured output infrastructure in the core library, enabling type-safe JSON schema generation reusable across all providers and capabilities.

Anthropic Messages API Provider (packages/providers/anthropic)

A capability-agnostic mixin architecture for the Anthropic Messages API:

Client (AnthropicMessagesClient)

  • HTTP POST to /v1/messages with full response parsing
  • Streaming via AsyncIterator[Chunk] with SSE event parsing
  • Dynamic beta header injection for extended thinking, prompt caching, structured outputs
  • Usage extraction mapping to UsageField standard fields

Parameter Mappers (7 total)

Mapper Description
TemperatureMapper Maps temperature (0.0-2.0)
TopPMapper Maps top_p nucleus sampling
TopKMapper Maps top_k token selection
MaxTokensMapper Maps max_tokens with model-specific defaults
StopSequencesMapper Maps stop_sequences array
ThinkingMapper Extended thinking with thinking_budget → injects extended-thinking beta
OutputSchemaMapper Native structured outputs from Pydantic models → injects output-128k-2025-01-24 beta

Streaming (AnthropicMessagesStream)

  • SSE event parsing for content_block_delta, message_delta, message_stop
  • Incremental text accumulation with finish reason propagation
  • Final usage statistics in last chunk

Configuration

  • API version: 2023-06-01
  • Beta features: prompt-caching-2024-07-31, output-128k-2025-01-24, interleaved-thinking-2025-05-14, extended-thinking-2025-05-14

Core Structured Output Infrastructure (src/celeste/)

JSON Schema Generators - Reusable across all providers:

Generator Use Case Transformation
StrictJsonSchemaGenerator OpenAI, Anthropic, xAI Adds additionalProperties: false recursively
RefResolvingJsonSchemaGenerator Cohere Inlines $ref references (no $defs support)
StrictRefResolvingJsonSchemaGenerator Mistral Both: strict props + ref resolution

Type Definitions (types.py)

type JsonValue = str | int | float | bool | None | dict[str, JsonValue] | list[JsonValue]
type StructuredOutput = str | JsonValue | BaseModel | list[BaseModel]

UsageField Enum - 12 standard field names for cross-provider usage mapping:

  • INPUT_TOKENS, OUTPUT_TOKENS, TOTAL_TOKENS
  • CACHED_TOKENS, REASONING_TOKENS, BILLED_TOKENS
  • CACHE_CREATION_INPUT_TOKENS, CACHE_READ_INPUT_TOKENS
  • NUM_IMAGES, BILLED_UNITS, INPUT_MP, OUTPUT_MP

FinishReason Enhancement - Added reason: str | None field for capability-specific finish reasons

Test Coverage

14 comprehensive tests for structured output generators covering:

  • Simple object schemas with additionalProperties injection
  • Nested model hierarchies with recursive processing
  • Array item schemas
  • $ref resolution and $defs removal
  • Combined transformation pipelines
  • Edge cases (non-dict input, preserved existing properties)

Test Plan

  • All 14 structured output tests pass
  • Pre-commit hooks pass (ruff, mypy, bandit)
  • Coverage threshold met (≥80%)
  • CI pipeline validation

Add standalone provider package for Anthropic Messages API with mixin
pattern for capability-agnostic reuse.

## Client (AnthropicMessagesClient mixin)
- HTTP POST/streaming to /v1/messages endpoint
- Usage parsing: input_tokens, output_tokens, cached_tokens, total_tokens
- Content array extraction with finish reason (stop_reason)
- Beta header injection system for feature flags
- Default max_tokens=1024 (Anthropic requires this field)

## Parameters
- TemperatureMapper: temperature float [0.0-1.0]
- TopPMapper: nucleus sampling top_p
- TopKMapper: top-k sampling
- MaxTokensMapper: max output tokens
- StopSequencesMapper: custom stop sequences
- ThinkingMapper: extended thinking ("auto" or budget_tokens int)
- OutputSchemaMapper: native structured outputs via output_format
  - Supports single BaseModel and list[BaseModel]
  - Uses StrictJsonSchemaGenerator for schema generation
  - Auto-injects structured-outputs beta header

## Streaming (AnthropicMessagesStream mixin)
- SSE event parsing: content_block_delta, message_delta, message_stop
- Text delta extraction from content_block_delta
- Stop reason extraction from message_delta
- Usage tracking in final message events

## Config
- API version: 2023-06-01
- Endpoints: /v1/messages, /v1/messages/count_tokens
- Beta features: prompt-caching, computer-use, pdfs, token-counting,
  max-tokens-sonnet-3.5, structured-outputs
Add core modules required by Anthropic provider package:

## structured_outputs.py
- StrictJsonSchemaGenerator: adds additionalProperties:false (OpenAI, Anthropic, xAI)
- RefResolvingJsonSchemaGenerator: resolves $ref inline (Cohere)
- StrictRefResolvingJsonSchemaGenerator: combines both (Mistral)

## types.py
- JsonValue: recursive JSON type alias
- StructuredOutput: union of str | JsonValue | BaseModel | list[BaseModel]

## core.py
- UsageField: standard field names for usage mapping
  - INPUT_TOKENS, OUTPUT_TOKENS, TOTAL_TOKENS
  - CACHED_TOKENS, REASONING_TOKENS, BILLED_TOKENS
  - NUM_IMAGES, BILLED_UNITS, INPUT_MP, OUTPUT_MP
  - CACHE_CREATION_INPUT_TOKENS, CACHE_READ_INPUT_TOKENS

## io.py
- FinishReason: add reason field (str | None)
Tests cover:
- StrictJsonSchemaGenerator: additionalProperties injection
- RefResolvingJsonSchemaGenerator: $ref inline resolution
- StrictRefResolvingJsonSchemaGenerator: combined transformations

14 test cases covering:
- Simple object schemas
- Nested model hierarchies
- Array item schemas
- Non-dict input handling
- $defs removal after resolution
@claude

claude Bot commented Dec 15, 2025

Copy link
Copy Markdown

Code Review: Anthropic Messages API Provider with Structured Output Infrastructure

Overall Assessment

This is a well-architected PR that introduces foundational infrastructure for Anthropic's Messages API and reusable structured output generators. The code demonstrates strong software engineering practices with excellent documentation, comprehensive test coverage, and thoughtful abstraction design. However, there are a few areas that require attention before merging.


Critical Issues

1. Incorrect Beta Header Version ⚠️

Location: packages/providers/anthropic/src/celeste_anthropic/messages/config.py:29

BETA_STRUCTURED_OUTPUTS = "structured-outputs-2025-11-13"

Issue: The date 2025-11-13 is in the future (we're currently in December 2025). This appears to be an incorrect beta version string that will likely fail API calls.

Recommendation: Verify the correct beta header value from Anthropic's official documentation and update accordingly. The correct version might be from 2024 or earlier 2025.


2. Potential Index Out of Range Error 🐛

Location: packages/providers/anthropic/src/celeste_anthropic/messages/parameters.py:183

if isinstance(content, list) and content and isinstance(content[0], BaseModel):
    return content

Issue: While there's a check for content being truthy (non-empty), immediately accessing content[0] could still fail if the list contains non-indexable items or in edge cases.

Recommendation: This check is actually safe due to the and content short-circuit evaluation. However, for extra clarity and robustness, consider:

if isinstance(content, list) and len(content) > 0 and isinstance(content[0], BaseModel):
    return content

Code Quality Observations

Strengths ✅

  1. Excellent Architecture

    • The mixin pattern for AnthropicMessagesClient and AnthropicMessagesStream is well-designed and promotes code reuse
    • Clear separation of concerns between client, streaming, parameters, and configuration
    • The structured output generators follow the strategy pattern effectively
  2. Comprehensive Documentation

    • Docstrings are clear and include usage examples
    • The PR description provides excellent context with tables and detailed explanations
    • Inline comments explain non-obvious logic
  3. Strong Type Safety

    • Extensive use of type hints throughout
    • Proper use of Pydantic's TypeAdapter for runtime validation
    • Type aliases (JsonValue, StructuredOutput) improve code readability
  4. Robust Test Coverage

    • 14 comprehensive tests covering edge cases
    • Tests for nested models, refs resolution, and combined transformations
    • Good use of descriptive test names and docstrings
  5. Security Awareness

    • # nosec B105 annotation shows awareness of security linting (false positive on token counting constant)

Suggestions for Improvement

3. Error Handling in _build_headers

Location: packages/providers/anthropic/src/celeste_anthropic/messages/client.py:62-65

beta_values = [
    getattr(config, f"BETA_{f.upper().replace('-', '_')}")
    for f in beta_features
]

Concern: If a beta feature string doesn't have a corresponding constant in config, getattr will raise AttributeError.

Recommendation: Add defensive error handling:

beta_values = []
for f in beta_features:
    attr_name = f"BETA_{f.upper().replace('-', '_')}"
    if hasattr(config, attr_name):
        beta_values.append(getattr(config, attr_name))
    else:
        # Log warning or raise more descriptive error
        raise ValueError(f"Unknown beta feature: {f}")

4. Magic String in Request Body

Location: packages/providers/anthropic/src/celeste_anthropic/messages/client.py:53

beta_features: list[str] = request_body.pop("_beta_features", [])

Concern: The _beta_features key is a magic string that could lead to maintainability issues.

Recommendation: Define as a constant:

# In config.py
INTERNAL_BETA_FEATURES_KEY = "_beta_features"

# In client.py
beta_features: list[str] = request_body.pop(config.INTERNAL_BETA_FEATURES_KEY, [])

5. Recursive Reference Resolution Risk

Location: src/celeste/structured_outputs.py:51-66

The resolve() function in RefResolvingJsonSchemaGenerator could theoretically encounter circular references, though this is unlikely with Pydantic-generated schemas.

Recommendation: Consider adding a depth limit or visited set to prevent infinite recursion:

def resolve(obj: Any, visited: set[str] | None = None) -> Any:
    if visited is None:
        visited = set()
    
    if isinstance(obj, dict):
        if "$ref" in obj:
            ref_path = obj["$ref"]
            if ref_path in visited:
                raise ValueError(f"Circular reference detected: {ref_path}")
            visited.add(ref_path)
            # ... rest of logic

6. Test Coverage for Error Paths

The tests are comprehensive for happy paths, but could benefit from additional error case coverage:

  • Test for malformed SSE events in streaming
  • Test for missing or invalid usage data
  • Test for empty content arrays
  • Test for invalid beta feature names

7. Configuration Validation

Location: packages/providers/anthropic/src/celeste_anthropic/messages/config.py

Consider adding runtime validation for critical configuration values:

import re

# Validate API version format
if not re.match(r"\d{4}-\d{2}-\d{2}", ANTHROPIC_VERSION):
    raise ValueError(f"Invalid API version format: {ANTHROPIC_VERSION}")

Performance Considerations

8. Schema Generation Caching

Location: packages/providers/anthropic/src/celeste_anthropic/messages/parameters.py:152-161

The OutputSchemaMapper.map() method generates JSON schemas on every request. For frequently used models, this could be optimized with caching:

from functools import lru_cache

@lru_cache(maxsize=128)
def _generate_schema(model_type: type) -> dict:
    return TypeAdapter(model_type).json_schema(
        schema_generator=StrictJsonSchemaGenerator,
        mode="serialization",
    )

Style & Convention Notes

The code follows the project's conventions well (based on pyproject.toml):

  • ✅ Proper use of type hints (mypy strict mode)
  • ✅ Clean imports with __all__ exports
  • ✅ Ruff formatting appears consistent
  • ✅ No obvious security issues (bandit compliant)

Security Review

9. Header Injection Safety

Location: packages/providers/anthropic/src/celeste_anthropic/messages/client.py:66

The header construction concatenates beta features with commas. While unlikely to be exploitable, validate that beta feature strings don't contain malicious characters:

# Validate beta feature format
if not all(re.match(r"^[a-z0-9-]+$", f) for f in beta_features):
    raise ValueError("Invalid beta feature format")

Missing Elements

10. Package Dependencies

Location: packages/providers/anthropic/pyproject.toml

The pyproject.toml doesn't list any dependencies. The code imports from celeste core, which should be listed:

[project]
dependencies = [
    "celeste-ai>=0.2.14",
    "httpx>=0.27.0",
    "pydantic>=2.0",
]

11. Missing py.typed Marker Content

The py.typed file is empty, which is correct for PEP 561, but consider adding a comment explaining its purpose for maintainers.


Test Plan Completion

The PR description mentions:

  • All 14 structured output tests pass
  • Pre-commit hooks pass
  • Coverage threshold met (≥80%)
  • CI pipeline validation ⬅️ Still pending

Please ensure CI passes before merging.


Final Recommendations

Must Fix Before Merge:

  1. ✅ Correct the BETA_STRUCTURED_OUTPUTS date/version
  2. ✅ Add dependencies to packages/providers/anthropic/pyproject.toml
  3. ✅ Add error handling for unknown beta features

Should Consider:

  1. Add constant for _beta_features magic string
  2. Add circular reference protection to schema resolver
  3. Expand test coverage for error paths
  4. Consider schema generation caching for performance

Nice to Have:

  1. Add header injection validation
  2. Add configuration validation
  3. Add type safety improvements

Conclusion

This is high-quality code that demonstrates excellent engineering practices. The architecture is sound, documentation is thorough, and test coverage is strong. Once the critical issues (particularly the incorrect beta version) are addressed, this PR will be ready to merge. Great work on building a solid foundation for the Anthropic provider and structured outputs! 🚀

@Kamilbenkirane Kamilbenkirane merged commit 1c9c916 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