Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 34 additions & 1 deletion docs/guides/configure-llms.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,9 @@ Marvin supports any model provider that is compatible with Pydantic AI. Common p
- Anthropic
- Azure OpenAI
- Google
- MiniMax

Each provider may require its own API key and configuration. Refer to the provider's [documentation](https://ai.pydantic.dev/models/) for specific setup instructions.
Each provider may require its own API key and configuration. Refer to the provider's [documentation](https://ai.pydantic.dev/models/) for specific setup instructions.

### Passing Configuration to Models
For more control, you may pass `pydantic-ai` models when creating an agent.
Expand Down Expand Up @@ -119,3 +120,35 @@ writer = marvin.Agent(
result = marvin.run("how to use pydantic? write haiku to docs.md", agents=[writer])
print(result)
```

### Using MiniMax Models

[MiniMax](https://www.minimax.io) provides OpenAI-compatible models with 204K context windows. Available models include `MiniMax-M2.7` (peak performance) and `MiniMax-M2.7-highspeed` (faster, more agile).

```bash
export MINIMAX_API_KEY="your-api-key"
```

```python
import os

from pydantic_ai.models.openai import OpenAIModel
from pydantic_ai.providers.openai import OpenAIProvider
import marvin

provider = OpenAIProvider(
api_key=os.environ["MINIMAX_API_KEY"],
base_url="https://api.minimax.io/v1",
)

agent = marvin.Agent(
model=OpenAIModel("MiniMax-M2.7", provider=provider),
name="MiniMax Agent",
instructions="You are a helpful assistant",
)

result = marvin.run("Write a haiku about AI", agents=[agent])
print(result)
```

See the `examples/provider_specific/minimax/` directory for more examples including structured output and tool usage.
49 changes: 49 additions & 0 deletions examples/provider_specific/minimax/run_agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"""
» MINIMAX_API_KEY=your-api-key \
uv run examples/provider_specific/minimax/run_agent.py
"""

from __future__ import annotations

import os
from pathlib import Path

from pydantic_ai.models.openai import OpenAIModel
from pydantic_ai.providers.openai import OpenAIProvider

import marvin

MINIMAX_API_URL = "https://api.minimax.io/v1"


def get_provider() -> OpenAIProvider:
api_key = os.getenv("MINIMAX_API_KEY")
if not api_key:
raise RuntimeError(
"Set MINIMAX_API_KEY environment variable to your MiniMax API key."
)
return OpenAIProvider(api_key=api_key, base_url=MINIMAX_API_URL)


def write_file(path: str, content: str) -> None:
"""Write content to a file."""
Path(path).write_text(content)


def main() -> None:
writer = marvin.Agent(
model=OpenAIModel("MiniMax-M2.7", provider=get_provider()),
name="MiniMax Writer",
instructions="Write concise, engaging content for developers",
tools=[write_file],
)

result = marvin.run(
"how to use pydantic? write haiku to docs.md",
agents=[writer],
)
print(result)


if __name__ == "__main__":
main()
54 changes: 54 additions & 0 deletions examples/provider_specific/minimax/structured_output.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"""
» MINIMAX_API_KEY=your-api-key \
uv run examples/provider_specific/minimax/structured_output.py
"""

from __future__ import annotations

import os

from pydantic_ai.models.openai import OpenAIModel
from pydantic_ai.providers.openai import OpenAIProvider
from typing_extensions import TypedDict

import marvin

MINIMAX_API_URL = "https://api.minimax.io/v1"


class LearningResource(TypedDict):
title: str
url: str
summary: str


def get_provider() -> OpenAIProvider:
api_key = os.getenv("MINIMAX_API_KEY")
if not api_key:
raise RuntimeError(
"Set MINIMAX_API_KEY environment variable to your MiniMax API key."
)
return OpenAIProvider(api_key=api_key, base_url=MINIMAX_API_URL)


def main() -> None:
researcher = marvin.Agent(
model=OpenAIModel("MiniMax-M2.7", provider=get_provider()),
name="Resource Researcher",
instructions=(
"Return structured JSON describing useful developer resources."
),
)

resources = marvin.run(
"share three quickstart resources for building AI applications with Python",
result_type=list[LearningResource],
agents=[researcher],
)

for resource in resources:
print(f"- {resource['title']}\n {resource['url']}\n {resource['summary']}\n")


if __name__ == "__main__":
main()
58 changes: 58 additions & 0 deletions examples/provider_specific/minimax/tools_agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
"""
» MINIMAX_API_KEY=your-api-key \
uv run examples/provider_specific/minimax/tools_agent.py
"""

from __future__ import annotations

import os
from datetime import date, timedelta

from pydantic_ai.models.openai import OpenAIModel
from pydantic_ai.providers.openai import OpenAIProvider

import marvin

MINIMAX_API_URL = "https://api.minimax.io/v1"


def get_provider() -> OpenAIProvider:
api_key = os.getenv("MINIMAX_API_KEY")
if not api_key:
raise RuntimeError(
"Set MINIMAX_API_KEY environment variable to your MiniMax API key."
)
return OpenAIProvider(api_key=api_key, base_url=MINIMAX_API_URL)


def get_event_date(offset_days: int = 0) -> str:
"""Return an ISO formatted date offset from today."""
today = date.today()
return (today + timedelta(days=offset_days)).isoformat()


def mock_weather_lookup(city: str) -> str:
"""Pretend to look up weather for a city."""
return f"The forecast in {city} calls for mild temperatures and light winds."


def main() -> None:
planner = marvin.Agent(
model=OpenAIModel("MiniMax-M2.7", provider=get_provider()),
name="MiniMax Event Planner",
instructions=(
"Plan concise community events for AI enthusiasts."
" Use the available tools for dates and weather when helpful."
),
tools=[get_event_date, mock_weather_lookup],
)

plan = marvin.run(
"Design a Saturday workshop introducing AI development in Berlin",
agents=[planner],
)
print(plan)


if __name__ == "__main__":
main()
147 changes: 147 additions & 0 deletions tests/basic/actors/test_minimax_provider.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
"""Tests for MiniMax provider integration."""

import os
from unittest.mock import patch

import pytest
from pydantic_ai.models.openai import OpenAIModel
from pydantic_ai.providers.openai import OpenAIProvider

from marvin.agents.agent import Agent

MINIMAX_API_URL = "https://api.minimax.io/v1"


def _make_minimax_model(
model_name: str = "MiniMax-M2.7",
api_key: str = "test-key",
) -> OpenAIModel:
"""Create a MiniMax model via OpenAI-compatible provider."""
return OpenAIModel(
model_name,
provider=OpenAIProvider(api_key=api_key, base_url=MINIMAX_API_URL),
)


class TestMiniMaxModelConfiguration:
"""Unit tests for MiniMax model configuration (no API calls)."""

async def test_agent_with_minimax_model(self):
"""Test creating an agent with MiniMax model."""
model = _make_minimax_model()
agent = Agent(name="MiniMax Agent", model=model)
assert agent.model is model
assert agent.get_model() is model

async def test_agent_with_minimax_m27_highspeed(self):
"""Test creating an agent with MiniMax-M2.7-highspeed model."""
model = _make_minimax_model("MiniMax-M2.7-highspeed")
agent = Agent(name="MiniMax Fast Agent", model=model)
assert agent.model is model

async def test_minimax_model_with_custom_temperature(self):
"""Test MiniMax model with custom temperature setting."""
model = _make_minimax_model()
agent = Agent(
name="MiniMax Temp Agent",
model=model,
model_settings={"temperature": 0.7},
)
settings = agent.get_model_settings()
assert settings["temperature"] == 0.7

async def test_minimax_model_preserves_tools(self):
"""Test MiniMax agent preserves tool configuration."""

def my_tool() -> str:
"""A test tool."""
return "result"

model = _make_minimax_model()
agent = Agent(
name="MiniMax Tool Agent",
model=model,
tools=[my_tool],
)
assert len(agent.get_tools()) == 1
assert agent.get_tools()[0] is my_tool

async def test_minimax_provider_base_url(self):
"""Test MiniMax provider uses correct base URL."""
provider = OpenAIProvider(api_key="test-key", base_url=MINIMAX_API_URL)
assert provider.base_url.rstrip("/") == MINIMAX_API_URL

async def test_minimax_model_default_temperature(self):
"""Test MiniMax agent with default temperature (no override)."""
model = _make_minimax_model()
agent = Agent(name="MiniMax Default Agent", model=model)
settings = agent.get_model_settings()
assert "temperature" not in settings or settings.get("temperature") is None


class TestMiniMaxIntegration:
"""Integration tests for MiniMax API (requires MINIMAX_API_KEY)."""

@pytest.fixture
def minimax_api_key(self):
key = os.getenv("MINIMAX_API_KEY")
if not key:
pytest.skip("MINIMAX_API_KEY not set")
return key

@pytest.fixture
def minimax_model(self, minimax_api_key):
return OpenAIModel(
"MiniMax-M2.7",
provider=OpenAIProvider(
api_key=minimax_api_key,
base_url=MINIMAX_API_URL,
),
)

@pytest.fixture
def minimax_highspeed_model(self, minimax_api_key):
return OpenAIModel(
"MiniMax-M2.7-highspeed",
provider=OpenAIProvider(
api_key=minimax_api_key,
base_url=MINIMAX_API_URL,
),
)

async def test_minimax_simple_run(self, minimax_model):
"""Test basic MiniMax API call via marvin.run."""
import marvin

agent = Agent(name="MiniMax Test", model=minimax_model)
result = marvin.run(
"Reply with exactly: hello",
agents=[agent],
)
assert result is not None
assert isinstance(result, str)
assert len(result) > 0

async def test_minimax_structured_output(self, minimax_model):
"""Test MiniMax structured output extraction."""
import marvin

agent = Agent(name="MiniMax Extractor", model=minimax_model)
result = marvin.run(
"What is 2 + 2?",
result_type=int,
agents=[agent],
)
assert result == 4

async def test_minimax_highspeed_model(self, minimax_highspeed_model):
"""Test MiniMax-M2.7-highspeed model works correctly."""
import marvin

agent = Agent(name="MiniMax Highspeed", model=minimax_highspeed_model)
result = marvin.run(
"Reply with exactly: fast",
agents=[agent],
)
assert result is not None
assert isinstance(result, str)
Loading