Skip to content

Commit cb7389d

Browse files
KavyaSree2610Kavya Sree Kaitepalli
andauthored
Adds support for Anthropic models (#72)
* Adds support for Anthropic's Claude models * update anthropic env var in test.yml * remove condition since model is validated already * move comment to end * fix Anthropic API tests to use 'thoughts' field instead of 'result' * add integration tests * restore test files * add test for anthropic in microbot test --------- Co-authored-by: Kavya Sree Kaitepalli <kkaitepalli@microsoft.com>
1 parent 8f923ff commit cb7389d

8 files changed

Lines changed: 645 additions & 1 deletion

File tree

.github/workflows/test.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,10 @@ jobs:
111111
AZURE_OPENAI_ENDPOINT: ${{ secrets.AZURE_OPENAI_ENDPOINT }}
112112
BROWSER_USE_LLM_MODEL: "gpt-5"
113113
BROWSER_USE_LLM_TEMPERATURE: 1
114+
#Anthrpic API Configuration
115+
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
116+
ANTHROPIC_DEPLYMENT_NAME: ${{ secrets.ANTHROPIC_DEPLOYMENT_NAME }}
117+
ANTHROPIC_END_POINT: ${{ secrets.ANTHROPIC_END_POINT }}
114118
run: |
115119
python -m pytest ${{ matrix.pytest-args }} \
116120
-n auto \

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
openai==1.107.3
2+
anthropic==0.75.0
23
python-dotenv==1.1.1
34
docker==7.1.0
45
fastapi==0.116.1

src/microbots/MicroBot.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from microbots.environment.local_docker.LocalDockerEnvironment import (
1111
LocalDockerEnvironment,
1212
)
13+
from microbots.llm.anthropic_api import AnthropicApi
1314
from microbots.llm.openai_api import OpenAIApi
1415
from microbots.llm.ollama_local import OllamaLocal
1516
from microbots.llm.llm import llm_output_format_str
@@ -279,6 +280,10 @@ def _create_llm(self):
279280
self.llm = OllamaLocal(
280281
system_prompt=self.system_prompt, model_name=self.deployment_name
281282
)
283+
elif self.model_provider == ModelProvider.ANTHROPIC:
284+
self.llm = AnthropicApi(
285+
system_prompt=self.system_prompt, deployment_name=self.deployment_name
286+
)
282287
# No Else case required as model provider is already validated using _validate_model_and_provider
283288

284289
def _validate_model_and_provider(self, model):

src/microbots/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
class ModelProvider(StrEnum):
66
OPENAI = "azure-openai"
77
OLLAMA_LOCAL = "ollama-local"
8+
ANTHROPIC = "anthropic"
89

910

1011
class ModelEnum(StrEnum):

src/microbots/llm/anthropic_api.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import json
2+
import os
3+
from dataclasses import asdict
4+
from logging import getLogger
5+
6+
from dotenv import load_dotenv
7+
from anthropic import Anthropic
8+
from microbots.llm.llm import LLMAskResponse, LLMInterface
9+
10+
logger = getLogger(__name__)
11+
12+
load_dotenv()
13+
14+
endpoint = os.getenv("ANTHROPIC_END_POINT")
15+
deployment_name = os.getenv("ANTHROPIC_DEPLOYMENT_NAME")
16+
api_key = os.getenv("ANTHROPIC_API_KEY")
17+
18+
19+
class AnthropicApi(LLMInterface):
20+
21+
def __init__(self, system_prompt, deployment_name=deployment_name, max_retries=3):
22+
self.ai_client = Anthropic(
23+
api_key=api_key,
24+
base_url=endpoint
25+
)
26+
self.deployment_name = deployment_name
27+
self.system_prompt = system_prompt
28+
self.messages = []
29+
30+
# Set these values here. This logic will be handled in the parent class.
31+
self.max_retries = max_retries
32+
self.retries = 0
33+
34+
def ask(self, message) -> LLMAskResponse:
35+
self.retries = 0 # reset retries for each ask. Handled in parent class.
36+
37+
self.messages.append({"role": "user", "content": message})
38+
39+
valid = False
40+
while not valid:
41+
response = self.ai_client.messages.create(
42+
model=self.deployment_name,
43+
system=self.system_prompt,
44+
messages=self.messages,
45+
max_tokens=4096,
46+
)
47+
48+
# Extract text content from response
49+
response_text = response.content[0].text if response.content else ""
50+
logger.debug("Raw Anthropic response (first 500 chars): %s", response_text[:500])
51+
52+
# Try to extract JSON if wrapped in markdown code blocks
53+
import re
54+
json_match = re.search(r'```(?:json)?\s*(\{.*?\})\s*```', response_text, re.DOTALL)
55+
if json_match:
56+
response_text = json_match.group(1)
57+
58+
valid, askResponse = self._validate_llm_response(response=response_text)
59+
60+
self.messages.append({"role": "assistant", "content": json.dumps(asdict(askResponse))})
61+
62+
return askResponse
63+
64+
def clear_history(self):
65+
self.messages = []
66+
return True

test/bot/test_microbot.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from pathlib import Path
99
import subprocess
1010
import sys
11+
from unittest.mock import patch, Mock
1112

1213
import pytest
1314
# Add src directory to path to import from local source
@@ -79,6 +80,19 @@ def no_mount_microBot(self):
7980
yield bot
8081
del bot
8182

83+
@pytest.fixture(scope="function")
84+
def anthropic_microBot(self):
85+
with patch('microbots.llm.anthropic_api.endpoint', 'https://api.anthropic.com'), \
86+
patch('microbots.llm.anthropic_api.deployment_name', 'claude-sonnet-4'), \
87+
patch('microbots.llm.anthropic_api.api_key', 'test-api-key'), \
88+
patch('microbots.llm.anthropic_api.Anthropic'):
89+
bot = MicroBot(
90+
model="anthropic/claude-sonnet-4",
91+
system_prompt=SYSTEM_PROMPT,
92+
)
93+
yield bot
94+
del bot
95+
8296
def test_microbot_ro_mount(self, ro_microBot, test_repo: Path):
8397
assert test_repo is not None
8498

@@ -100,6 +114,15 @@ def test_microbot_overlay_teardown(self, ro_microBot, caplog):
100114

101115
assert "Failed to remove working directory" not in caplog.text
102116

117+
def test_microbot_anthropic_initialization(self, anthropic_microBot):
118+
"""Test that MicroBot correctly initializes with Anthropic model provider."""
119+
assert anthropic_microBot is not None
120+
assert anthropic_microBot.model_provider == "anthropic"
121+
assert anthropic_microBot.deployment_name == "claude-sonnet-4"
122+
assert anthropic_microBot.llm is not None
123+
from microbots.llm.anthropic_api import AnthropicApi
124+
assert isinstance(anthropic_microBot.llm, AnthropicApi)
125+
103126
def test_microbot_2bot_combo(self, log_file_path, test_repo, issue_1):
104127
assert test_repo is not None
105128
assert log_file_path is not None
@@ -229,6 +252,28 @@ def test_incorrect_model_format(self, test_repo):
229252
folder_to_mount=test_repo_mount_ro,
230253
)
231254

255+
def test_microbot_anthropic_with_mount(self, test_repo):
256+
"""Test that MicroBot with Anthropic provider works with mounted folders."""
257+
assert test_repo is not None
258+
259+
test_repo_mount_ro = Mount(
260+
str(test_repo), f"{DOCKER_WORKING_DIR}/{test_repo.name}", PermissionLabels.READ_ONLY
261+
)
262+
with patch('microbots.llm.anthropic_api.endpoint', 'https://api.anthropic.com'), \
263+
patch('microbots.llm.anthropic_api.deployment_name', 'claude-sonnet-4'), \
264+
patch('microbots.llm.anthropic_api.api_key', 'test-api-key'), \
265+
patch('microbots.llm.anthropic_api.Anthropic'):
266+
bot = MicroBot(
267+
model="anthropic/claude-sonnet-4",
268+
system_prompt=SYSTEM_PROMPT,
269+
folder_to_mount=test_repo_mount_ro,
270+
)
271+
assert bot is not None
272+
assert bot.model_provider == "anthropic"
273+
from microbots.llm.anthropic_api import AnthropicApi
274+
assert isinstance(bot.llm, AnthropicApi)
275+
del bot
276+
232277
def test_max_iterations_exceeded(self, no_mount_microBot, monkeypatch):
233278
assert no_mount_microBot is not None
234279

test/llm/conftest.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -336,9 +336,13 @@ def test_ollama_response_parsing(mock_ollama_response):
336336
}
337337

338338

339-
# Marker for tests that require Ollama Local
339+
# Marker for tests that require Ollama Local and anthropic model
340340
def pytest_configure(config):
341341
config.addinivalue_line(
342342
"markers",
343343
"ollama_local: mark test as requiring Ollama Local setup (deselect with '-m \"not ollama_local\"')"
344344
)
345+
config.addinivalue_line(
346+
"markers",
347+
"anthropic_integration: mark test as requiring Anthropic API (deselect with '-m \"not anthropic_integration\"')"
348+
)

0 commit comments

Comments
 (0)