Skip to content

feat(providers): add Mistral Chat API package and migrate capability to mixins#83

Merged
Kamilbenkirane merged 4 commits intomainfrom
api/mistral
Dec 15, 2025
Merged

feat(providers): add Mistral Chat API package and migrate capability to mixins#83
Kamilbenkirane merged 4 commits intomainfrom
api/mistral

Conversation

@Kamilbenkirane
Copy link
Copy Markdown
Member

Add standalone provider package for Mistral Chat API with mixin pattern for capability-agnostic reuse. Migrate text generation capability client to use the new provider mixins, eliminating code duplication and centralizing API-specific logic.

Provider Package (celeste-mistral)

Chat API (MistralChatClient)

  • HTTP POST/streaming to /v1/chat/completions endpoint
  • Usage parsing: prompt_tokens, completion_tokens, total_tokens
  • Content extraction from choices[0].message.content
  • Finish reason mapping (stop, length, etc.)
  • Structured output support via response_format
  • Supports reasoning models with prompt_mode parameter

Parameters

  • TemperatureMapper: temperature float [0.0-2.0]
  • MaxTokensMapper: max_tokens integer
  • OutputSchemaMapper: JSON schema via response_format
    • Supports single BaseModel and list[BaseModel]
    • Uses StrictRefResolvingJsonSchemaGenerator for schema generation
    • Handles schema reference resolution and strict mode

Streaming (MistralChatStream)

  • SSE event parsing for streaming chat completions
  • Text delta extraction from choices[0].delta.content
  • Finish reason and usage tracking in final events
  • Handles reasoning models with list-based content blocks

Config

  • API base URL: https://api.mistral.ai
  • Endpoint: /v1/chat/completions
  • Comprehensive endpoint enum for future API expansion

All clients follow the mixin pattern for reuse across capabilities.

Capability Migration

Text Generation

  • Mistral client inherits from MistralChatClient mixin
  • Parameter mappers inherit from provider package mappers (TemperatureMapper, MaxTokensMapper, OutputSchemaMapper)
  • Streaming class inherits from MistralChatStream mixin
  • Remove unused config.py file (config now in provider package)
  • Code reduction: ~261 lines removed

Other Changes

Fixes

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

Code Statistics

  • Files changed: 13
  • Lines added: 425
  • Lines removed: 319
  • Net addition: 106 lines (new provider package)
  • Provider package: 7 Python files (client, parameters, streaming, config, init files)

Breaking Changes

None. All changes are internal refactorings that maintain the same public API.

Add standalone provider package for Mistral Chat API with mixin pattern
for capability-agnostic reuse.

## Client (MistralChatClient mixin)
- HTTP POST/streaming to /v1/chat/completions endpoint
- Usage parsing: prompt_tokens, completion_tokens, total_tokens
- Content extraction from choices[0].message.content
- Finish reason mapping (stop, length, etc.)
- Structured output support via response_format

## Parameters
- TemperatureMapper: temperature float [0.0-2.0]
- MaxTokensMapper: max_tokens integer
- OutputSchemaMapper: JSON schema via response_format
  - Supports single BaseModel and list[BaseModel]
  - Uses StrictRefResolvingJsonSchemaGenerator for schema generation

## Streaming (MistralChatStream mixin)
- SSE event parsing for streaming chat completions
- Text delta extraction from choices[0].delta.content
- Finish reason and usage tracking in final events

## Config
- API base URL: https://api.mistral.ai
- Endpoint: /v1/chat/completions

All clients follow the mixin pattern for reuse across capabilities.
Migrate Mistral text generation capability client to use provider package mixins,
eliminating code duplication and centralizing API-specific logic.

## Changes
- Mistral client now inherits from MistralChatClient mixin
- Parameter mappers inherit from provider package mappers (TemperatureMapper, MaxTokensMapper, OutputSchemaMapper)
- Streaming class inherits from MistralChatStream mixin
- Remove duplicated HTTP request logic (_make_request, _make_stream_request)
- Remove duplicated parameter mapping logic (~125 lines)
- Remove duplicated streaming parsing logic (~60 lines)
- Simplify _parse_usage to use mixin's implementation
- Simplify _parse_content to use mixin's output parsing
- Simplify _parse_finish_reason to use mixin's implementation

## Code Reduction
- ~261 lines removed across client, parameters, and streaming files
- Significant deduplication of HTTP request, parameter mapping, and streaming logic
Remove unused config.py file from Mistral capability provider.
Configuration is now centralized in the provider package (celeste_mistral.chat.config)
following the established pattern from OpenAI and other providers.
Add celeste-mistral to [tool.uv.sources] in text-generation capability package
that now imports from the Mistral provider package after the mixin migration.

This ensures workspace dependencies are properly declared for the
refactored capability client that uses Mistral provider mixins.
@claude
Copy link
Copy Markdown

claude Bot commented Dec 15, 2025

PR Review: Mistral Chat API Provider Package & Mixin Migration

Summary

This PR introduces a well-architected provider package for Mistral Chat API and successfully migrates the text generation capability to use mixins. The refactoring eliminates ~261 lines of duplicated code while establishing a reusable pattern for future capabilities. Overall, this is a solid implementation that follows the established OpenAI provider pattern.

✅ Strengths

Architecture & Design

  • Excellent mixin pattern: The separation between MistralChatClient (HTTP/API logic) and MistralChatStream (SSE parsing) is clean and follows the OpenAI provider precedent
  • Good abstraction: map_usage_fields() as a static method is a smart way to share logic between client and streaming
  • Clear separation of concerns: Provider package handles API-specific logic while capability package handles capability-specific transformations
  • DRY principle: Significant code deduplication (~261 lines removed)

Code Quality

  • Type safety: Good use of type hints throughout, proper use of Any with # type: ignore[attr-defined] for mixin attributes
  • Error handling: Appropriate validation (e.g., checking for empty choices list)
  • Documentation: Comprehensive docstrings explaining mixin usage patterns
  • Consistency: Follows established patterns from OpenAI provider package

Migration Quality

  • Safe refactoring: Maintains the same public API (no breaking changes)
  • Progressive inheritance: Capability classes properly inherit from both provider mixins and capability base classes
  • Proper cleanup: Removed unused config.py file in capability package

🔍 Issues & Suggestions

1. Missing Tests (High Priority)

Issue: No test files are included for the new celeste-mistral provider package.

Impact:

  • No verification that the provider package works in isolation
  • Risk of regressions when modifying shared logic
  • Lower confidence in the mixin pattern implementation

Recommendation: Add unit tests for:

  • MistralChatClient._make_request() and _make_stream_request()
  • MistralChatClient.map_usage_fields()
  • MistralChatStream._parse_chunk() with various event types
  • Parameter mappers in parameters.py

Example test structure:

# packages/providers/mistral/tests/chat/test_client.py
async def test_make_request_sets_model_id():
    """Verify model ID is added to request body."""
    ...

2. Config Enum Over-Engineering (Medium Priority)

Location: packages/providers/mistral/src/celeste_mistral/chat/config.py:6-36

Issue: The MistralChatEndpoint enum defines 26 endpoints, but only CREATE_CHAT_COMPLETION is currently used.

Code:

class MistralChatEndpoint(StrEnum):
    """Endpoints for Chat API."""
    CREATE_CHAT_COMPLETION = "/v1/chat/completions"
    CREATE_FIM_COMPLETION = "/v1/fim/completions"
    CREATE_AGENT_COMPLETION = "/v1/agents/completions"
    # ... 23 more unused endpoints

Impact:

  • YAGNI violation (You Aren't Gonna Need It)
  • Maintenance burden for unused code
  • Potential confusion about which endpoints are supported

Recommendation:

  • Keep only CREATE_CHAT_COMPLETION for now
  • Add other endpoints incrementally when capabilities actually need them
  • If keeping for future-proofing, add a comment explaining the intention

3. Unused Config Constants (Medium Priority)

Location: packages/providers/mistral/src/celeste_mistral/chat/config.py:42-52

Issue: Several config constants are defined but never used:

CODESTRAL_HOST = "https://codestral.mistral.ai"
DEFAULT_CONTENT_TYPE = "application/json"
ACCEPT_HEADER = "application/json"
FILE_UPLOAD_CONTENT_TYPE = "multipart/form-data"
STREAMING_DELIMITER = "data: [DONE]"

Recommendation: Remove unused constants or add a comment explaining they're reserved for future use.

4. Inconsistent Type Annotations (Low Priority)

Location: packages/providers/mistral/src/celeste_mistral/chat/client.py:88

Issue: _parse_content() returns Any but could be more specific:

def _parse_content(self, response_data: dict[str, Any]) -> Any:
    """Return choices from response."""
    choices = response_data.get("choices", [])
    if not choices:
        msg = "No choices in response"
        raise ValueError(msg)
    return choices

Recommendation: Use list[dict[str, Any]] as the return type for better type safety.

5. Magistral Models Hard-Coded Logic (Medium Priority)

Location: packages/providers/mistral/src/celeste_mistral/chat/streaming.py:41-47

Issue: Hard-coded handling for "magistral thinking models" that return list content:

# Handle magistral thinking models that may return list content
if isinstance(content_delta, list):
    text_parts = []
    for block in content_delta:
        if isinstance(block, dict) and block.get("type") == "text":
            text_parts.append(block.get("text", ""))
    content_delta = "".join(text_parts) if text_parts else None

Same issue in: packages/capabilities/text-generation/src/celeste_text_generation/providers/mistral/client.py:56-64

Concerns:

  • Model-specific logic in generic provider code
  • No validation that model is actually a "magistral" model
  • Comment references "magistral" but code applies to all models

Recommendation:

  • Either add model validation (e.g., if model.id.startswith("magistral"))
  • Or document that this is a fallback for all models that might return list content
  • Consider extracting this logic to a helper method _extract_text_from_content()

6. ThinkingBudgetMapper Validation Concern (Medium Priority)

Location: packages/capabilities/text-generation/src/celeste_text_generation/providers/mistral/parameters.py:41-58

Issue: The mapper allows positive values (>0) but Mistral doesn't support thinking budget control:

def map(self, request: dict[str, Any], value: object, model: Model) -> dict[str, Any]:
    """Transform thinking_budget into provider request."""
    validated_value = self._validate_value(value, model)
    if validated_value is None:
        return request

    # Map unified values to Mistral's prompt_mode
    if validated_value == -1:
        request["prompt_mode"] = "reasoning"
    elif validated_value == 0:
        request["prompt_mode"] = None
    else:  # >0
        request["prompt_mode"] = "reasoning"

    return request

Concerns:

  • User might expect thinking_budget=1000 to work differently than thinking_budget=-1
  • Silent coercion might lead to confusion
  • No warning that budget control isn't supported

Recommendation:

elif validated_value == 0:
    request["prompt_mode"] = None
elif validated_value > 0:
    # Mistral doesn't support budget control, so we enable reasoning mode
    # and ignore the specific budget value
    request["prompt_mode"] = "reasoning"
else:
    raise ValueError(f"Invalid thinking_budget value: {validated_value}")

7. Missing Validation (Low Priority)

Location: packages/providers/mistral/src/celeste_mistral/chat/parameters.py:94-111

Issue: OutputSchemaMapper.parse_output() doesn't validate that content is valid JSON before parsing:

if isinstance(content, str):
    parsed = json.loads(content)  # Could raise JSONDecodeError
else:
    parsed = content
return TypeAdapter(value).validate_python(parsed)

Recommendation: Add try/except with a more helpful error message:

if isinstance(content, str):
    try:
        parsed = json.loads(content)
    except json.JSONDecodeError as e:
        raise ValueError(f"Failed to parse structured output as JSON: {e}") from e
else:
    parsed = content

8. Potential Issue with Empty Response (Medium Priority)

Location: packages/capabilities/text-generation/src/celeste_text_generation/providers/mistral/client.py:38-41

Issue: Usage parsing wraps parent usage in TextGenerationUsage(**usage):

def _parse_usage(self, response_data: dict[str, Any]) -> TextGenerationUsage:
    """Parse usage from response."""
    usage = super()._parse_usage(response_data)
    return TextGenerationUsage(**usage)

Concerns:

  • If parent returns {"input_tokens": None, "output_tokens": None, "total_tokens": None}, this works
  • But the pattern assumes parent always returns a dict with the right keys
  • Could be more defensive

Recommendation: This is likely fine given the current implementation, but consider documenting the assumption.

🔒 Security

No significant security concerns identified.

Minor notes:

  • ✅ No hardcoded secrets or API keys
  • ✅ Proper use of authentication headers via self.auth.get_headers()
  • ✅ No SQL injection risks (no database queries)
  • ✅ JSON parsing uses standard library (safe)

📊 Performance

No performance concerns identified.

  • ✅ Async/await used properly
  • ✅ Streaming implemented efficiently
  • ✅ No unnecessary copying or allocations
  • ✅ Good use of early returns

🧪 Test Coverage

Major Gap: No tests for the new celeste-mistral provider package.

Existing tests: The PR relies on existing text-generation capability tests, but these:

  • Don't test the provider package in isolation
  • Don't test the mixin pattern edge cases
  • Don't verify the provider package works for future capabilities

Recommendation: Add minimal test coverage:

  1. Unit tests for provider mixins
  2. Integration test that text-generation still works with Mistral
  3. Mock-based tests for HTTP request formatting

📝 Documentation

Missing Documentation:

  1. Provider package README: packages/providers/mistral/README.md should explain:

    • How to use the provider package
    • Mixin pattern and inheritance structure
    • Examples of extending for new capabilities
  2. Migration guide: Consider documenting the mixin pattern for other contributors

  3. Changelog entry: Should be added to track this refactoring

🎯 Recommendations Priority

Must Fix (Block Merge):

  1. Add basic tests for the provider package

Should Fix (High Priority):

  1. Remove unused config constants or document future intent
  2. Address ThinkingBudgetMapper validation concerns
  3. Add model validation for magistral list-content handling

Nice to Have (Medium Priority):

  1. Improve type annotation for _parse_content() return type
  2. Extract magistral content parsing to helper method
  3. Add error handling for JSON parsing in OutputSchemaMapper

Optional (Low Priority):

  1. Add provider package README
  2. Simplify MistralChatEndpoint enum

✨ Conclusion

This is a well-executed refactoring that successfully establishes the provider package pattern for Mistral. The code quality is good, the architecture is sound, and it eliminates significant duplication.

The main blocker is the lack of tests for the new provider package. Once tests are added, this PR will be ready to merge.

Score: 7.5/10 (would be 9/10 with tests)

Great work on this refactoring! The mixin pattern is much cleaner than the previous approach.

@Kamilbenkirane Kamilbenkirane merged commit 6da3983 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