Skip to content
Merged
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
13 changes: 5 additions & 8 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -124,20 +124,17 @@ jobs:

- name: Run ${{ matrix.test-type }} tests
env:
# OpenAI API Configuration
Comment thread
KavyaSree2610 marked this conversation as resolved.
OPEN_AI_KEY: ${{ secrets.OPEN_AI_KEY }}
OPEN_AI_DEPLOYMENT_NAME: ${{ secrets.OPEN_AI_DEPLOYMENT_NAME }}
OPEN_AI_END_POINT: ${{ secrets.OPEN_AI_END_POINT }}
# Azure OpenAI API Configuration
AZURE_OPENAI_API_VERSION: ${{ secrets.AZURE_OPENAI_API_VERSION }}
AZURE_OPENAI_API_KEY: ${{ secrets.AZURE_OPENAI_API_KEY }}
AZURE_OPENAI_ENDPOINT: ${{ secrets.AZURE_OPENAI_ENDPOINT }}
AZURE_OPENAI_DEPLOYMENT_NAME: ${{ vars.AZURE_OPENAI_DEPLOYMENT_NAME }}
AZURE_OPENAI_ENDPOINT: ${{ vars.AZURE_OPENAI_ENDPOINT }}
AZURE_OPENAI_API_VERSION: ${{ vars.AZURE_OPENAI_API_VERSION }}
BROWSER_USE_LLM_MODEL: "gpt-5"
BROWSER_USE_LLM_TEMPERATURE: 1
#Anthrpic API Configuration
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
ANTHROPIC_DEPLOYMENT_NAME: ${{ secrets.ANTHROPIC_DEPLOYMENT_NAME }}
ANTHROPIC_END_POINT: ${{ secrets.ANTHROPIC_END_POINT }}
ANTHROPIC_DEPLOYMENT_NAME: ${{ vars.ANTHROPIC_DEPLOYMENT_NAME }}
ANTHROPIC_END_POINT: ${{ vars.ANTHROPIC_END_POINT }}
#Local Model Configuration
LOCAL_MODEL_NAME: "qwen2.5-coder:latest"
LOCAL_MODEL_PORT: 11434
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,9 @@ pip install microbots
Azure OpenAI Models - Add the below environment variables in a `.env` file in the root of your application

```env
OPEN_AI_END_POINT=XXXXXXXXXXXXXXXXXXXXXXXXXX
Comment thread
KavyaSree2610 marked this conversation as resolved.
OPEN_AI_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
AZURE_OPENAI_ENDPOINT=XXXXXXXXXXXXXXXXXXXXXXXXXX
AZURE_OPENAI_API_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
AZURE_OPENAI_API_VERSION=2025-03-01-preview
```

## 🤖 Bots & Usage Examples
Expand Down
125 changes: 89 additions & 36 deletions docs/authentication.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,69 @@
# Authentication

Microbots supports two authentication methods for LLM providers:
Microbots supports multiple LLM providers, each with their own authentication method.

## 1. API Key Authentication (Default)
## Providers Overview

Set the API key as an environment variable. This is the default and requires no additional setup.
| Provider string | Description | SDK | Authentication |
|---|---|---|---|
| `openai` | OpenAI or Azure OpenAI via OpenAI SDK | OpenAI SDK | API key |
| `azure-openai` | Azure OpenAI via Azure SDK | AzureOpenAI SDK | API key or Azure AD token |
| `anthropic` | Anthropic models | Anthropic SDK | API key or Azure AD token (Foundry) |
| `ollama-local` | Local models via Ollama | — | None (local) |

---

## 1. OpenAI (Direct)

Uses the **OpenAI SDK** with an API key. This works for both:
- **OpenAI directly** (api.openai.com)
- **Azure OpenAI via OpenAI SDK compatibility** (Azure endpoint + API key)

```bash
# For Azure OpenAI
export OPEN_AI_KEY="your-api-key"
export OPEN_AI_END_POINT="https://your-endpoint.openai.azure.com"
export OPEN_AI_API_VERSION="2024-02-01"
export OPEN_AI_DEPLOYMENT_NAME="your-deployment"
export OPENAI_API_KEY="your-api-key"
export OPENAI_ENDPOINT="https://api.openai.com/v1" # or your Azure endpoint
```

# For Anthropic
export ANTHROPIC_API_KEY="your-api-key"
export ANTHROPIC_END_POINT="https://your-endpoint"
export ANTHROPIC_DEPLOYMENT_NAME="your-deployment"
For Azure-hosted models using the OpenAI SDK (as shown in Azure Foundry portal):
```bash
export OPENAI_API_KEY="your-azure-api-key"
export OPENAI_ENDPOINT="https://your-resource.openai.azure.com/openai/v1/"
```

Usage:
```python
from microbots.MicroBot import MicroBot

bot = MicroBot(model="openai/gpt-5")
```

> **When to use this:** Use the `openai` provider when you have an API key and want to use the OpenAI SDK — whether pointing at OpenAI directly or at an Azure OpenAI endpoint that supports the OpenAI SDK.

---

## 2. Azure OpenAI (Azure SDK)

Uses the **AzureOpenAI SDK**. Use this provider when you need **Azure AD token authentication** or prefer the Azure-specific SDK.

### API Key Authentication (Default)

```bash
export AZURE_OPENAI_API_KEY="your-api-key"
export AZURE_OPENAI_ENDPOINT="https://your-endpoint.openai.azure.com"
export AZURE_OPENAI_API_VERSION="2025-03-01-preview"
export AZURE_OPENAI_DEPLOYMENT_NAME="your-deployment"
```

> **Note:** The Responses API requires `api-version` `2025-03-01-preview` or later. Earlier versions will return a `400 BadRequest` error.

Usage:
```python
from microbots.MicroBot import MicroBot

bot = MicroBot(model="azure-openai/your-deployment")
```

## 2. Azure AD Token Authentication
### Azure AD Token Authentication

For environments that require Azure AD authentication (no static API keys), Microbots can automatically obtain and refresh tokens using `azure-identity`.

Expand All @@ -29,7 +73,7 @@ For environments that require Azure AD authentication (no static API keys), Micr
pip install microbots[azure_ad]
```

### Option A: Environment Variable Opt-In
#### Option A: Environment Variable Opt-In

Set `AZURE_AUTH_METHOD=azure_ad` and configure your credentials. Microbots will use `DefaultAzureCredential`, which automatically tries the following sources in order: environment variables, workload identity, managed identity, Azure CLI, and more.

Expand All @@ -56,27 +100,14 @@ export AZURE_AUTH_METHOD=azure_ad
Also set the relevant LLM endpoint env vars (no API key required):

```bash
# Azure OpenAI
export OPEN_AI_END_POINT="https://your-endpoint.openai.azure.com"
export OPEN_AI_API_VERSION="2024-02-01"
export OPEN_AI_DEPLOYMENT_NAME="your-deployment"

# Anthropic Foundry
export ANTHROPIC_END_POINT="https://your-foundry-endpoint"
export ANTHROPIC_DEPLOYMENT_NAME="your-deployment"
export AZURE_OPENAI_ENDPOINT="https://your-endpoint.openai.azure.com"
export AZURE_OPENAI_API_VERSION="2024-02-01"
export AZURE_OPENAI_DEPLOYMENT_NAME="your-deployment"
```

> **Note:** `AZURE_AUTH_METHOD=azure_ad` only auto-creates a token provider for the `azure-openai` provider (using the `https://cognitiveservices.azure.com/.default` scope). For `anthropic` (Azure AI Foundry), the required scope is different and cannot be inferred automatically. You must pass `token_provider` explicitly — see **Option B** below.

### Option B: Pass a Token Provider Programmatically

First install the optional dependency:

```bash
pip install microbots[azure_ad]
Comment thread
KavyaSree2610 marked this conversation as resolved.
```

Then pass any `Callable[[], str]` as `token_provider`.
#### Option B: Pass a Token Provider Programmatically

```python
from azure.identity import DefaultAzureCredential, get_bearer_token_provider
Expand Down Expand Up @@ -113,25 +144,47 @@ bot = MicroBot(
)
```

### How Token Refresh Works
---

## 3. Anthropic

```bash
export ANTHROPIC_API_KEY="your-api-key"
export ANTHROPIC_END_POINT="https://your-endpoint"
export ANTHROPIC_DEPLOYMENT_NAME="your-deployment"
```

Usage:
```python
from microbots.MicroBot import MicroBot

bot = MicroBot(model="anthropic/your-deployment")
```

For Anthropic on Azure AI Foundry, pass a `token_provider` explicitly (see Option B above with the appropriate Foundry scope).

---

## How Token Refresh Works

- `get_bearer_token_provider` returns a `Callable[[], str]` backed by `BearerTokenCredentialPolicy`.
- The token is cached and **proactively refreshed** before expiry — no manual refresh needed.
- Both `AzureOpenAI` and `AnthropicFoundry` SDKs call the provider **before every request**, so the token is always fresh.
- Tasks are **never interrupted** by token expiration.

### How the Provider Is Selected
## How the Provider Is Selected

| `token_provider` present | LLM provider | SDK client used |
|---|---|---|
| Yes | `azure-openai` | `AzureOpenAI(azure_ad_token_provider=...)` |
| No | `azure-openai` | `OpenAI(api_key=...)` |
| No | `azure-openai` | `AzureOpenAI(api_key=...)` |
| — | `openai` | `OpenAI(base_url=..., api_key=...)` |
| Yes | `anthropic` | `AnthropicFoundry(azure_ad_token_provider=...)` |
| No | `anthropic` | `Anthropic(api_key=...)` |

`OllamaLocal` (local models) does not use token authentication.
`ollama-local` does not use token authentication.

### Notes
## Notes

- A `ValueError` is raised at bot creation time if neither an API key nor a token provider is configured. This surfaces misconfigurations early rather than failing on the first API call.
- The browser tool runs inside Docker. When `AZURE_AUTH_METHOD=azure_ad` is set (or a `token_provider` is passed to `BrowsingBot`), `BrowsingBot.run()` calls the token provider, gets a fresh token, and injects it as `AZURE_OPENAI_AD_TOKEN` into the container. `browser.py` inside Docker reads this env var and passes it as `azure_ad_token` to `ChatAzureOpenAI`. The token is valid for ~1 hour, which is sufficient for typical browser tasks. `AZURE_OPENAI_API_KEY` is not required when using Azure AD auth.
5 changes: 3 additions & 2 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,9 @@ MicroBots creates a containerized environment and mounts the specified directory
Azure OpenAI Models — add environment variables in a `.env` file:

```env
OPEN_AI_END_POINT=your-endpoint-url
Comment thread
KavyaSree2610 marked this conversation as resolved.
OPEN_AI_KEY=your-api-key
AZURE_OPENAI_ENDPOINT=your-endpoint-url
AZURE_OPENAI_API_KEY=your-api-key
AZURE_OPENAI_API_VERSION=2025-03-01-preview
```

## 📚 Links
Expand Down
11 changes: 8 additions & 3 deletions src/microbots/MicroBot.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
LocalDockerEnvironment,
)
from microbots.llm.anthropic_api import AnthropicApi
from microbots.llm.azure_openai_api import AzureOpenAIApi
from microbots.llm.openai_api import OpenAIApi
from microbots.llm.ollama_local import OllamaLocal
from microbots.llm.llm import llm_output_format_str
Expand Down Expand Up @@ -170,7 +171,7 @@ def __init__(
self.token_provider = token_provider
elif (
os.getenv("AZURE_AUTH_METHOD", "").strip().lower() == "azure_ad"
and self.model_provider == ModelProvider.OPENAI
and self.model_provider == ModelProvider.AZURE_OPENAI
):
try:
from azure.identity import DefaultAzureCredential, get_bearer_token_provider
Expand Down Expand Up @@ -348,11 +349,15 @@ def _create_llm(self):
if tool.usage_instructions_to_llm:
system_prompt_with_tools += f"\n\n{tool.usage_instructions_to_llm}"

if self.model_provider == ModelProvider.OPENAI:
self.llm = OpenAIApi(
if self.model_provider == ModelProvider.AZURE_OPENAI:
self.llm = AzureOpenAIApi(
system_prompt=system_prompt_with_tools, deployment_name=self.deployment_name,
token_provider=self.token_provider,
)
elif self.model_provider == ModelProvider.OPENAI:
self.llm = OpenAIApi(
system_prompt=system_prompt_with_tools, deployment_name=self.deployment_name,
)
elif self.model_provider == ModelProvider.OLLAMA_LOCAL:
self.llm = OllamaLocal(
system_prompt=system_prompt_with_tools, model_name=self.deployment_name
Expand Down
3 changes: 2 additions & 1 deletion src/microbots/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@


class ModelProvider(StrEnum):
OPENAI = "azure-openai"
AZURE_OPENAI = "azure-openai"
OPENAI = "openai"
OLLAMA_LOCAL = "ollama-local"
ANTHROPIC = "anthropic"

Expand Down
99 changes: 99 additions & 0 deletions src/microbots/llm/azure_openai_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import json
import os
from collections.abc import Callable
from dataclasses import asdict

from dotenv import load_dotenv
from openai import AzureOpenAI
from microbots.llm.llm import LLMAskResponse, LLMInterface

load_dotenv()

endpoint = os.getenv("AZURE_OPENAI_ENDPOINT")
api_version = os.getenv("AZURE_OPENAI_API_VERSION")
deployment_name = os.getenv("AZURE_OPENAI_DEPLOYMENT_NAME")
api_key = os.getenv("AZURE_OPENAI_API_KEY")


class AzureOpenAIApi(LLMInterface):

def __init__(self, system_prompt, deployment_name=deployment_name, max_retries=3,
token_provider: Callable[[], str] | None = None):
self.token_provider = token_provider

if not endpoint:
raise ValueError(
"AZURE_OPENAI_ENDPOINT environment variable is required when using Azure OpenAI. "
"Set it to your Azure OpenAI resource endpoint (e.g. 'https://<resource>.openai.azure.com/')."
)

if not api_version:
raise ValueError(
"AZURE_OPENAI_API_VERSION environment variable is required when using Azure OpenAI. "
"Set it to a valid API version (e.g. '2024-12-01-preview')."
)

if not token_provider and not api_key:
raise ValueError(
"No authentication configured for Azure OpenAI. Either set the AZURE_OPENAI_API_KEY "
"environment variable or provide a token_provider (e.g. AzureTokenProvider)."
)

if token_provider:
if not callable(token_provider):
raise ValueError("token_provider must be a callable that returns a string token.")
try:
token = token_provider()
except Exception as e:
raise ValueError(f"token_provider failed during validation: {e}") from e
if not isinstance(token, str) or not token:
raise ValueError("token_provider must return a non-empty string token.")
self.ai_client = AzureOpenAI(
azure_endpoint=endpoint,
azure_ad_token_provider=token_provider,
api_version=api_version,
)
else:
# Azure OpenAI with API key
self.ai_client = AzureOpenAI(
azure_endpoint=endpoint,
api_key=api_key,
api_version=api_version,
)
self.deployment_name = deployment_name
self.system_prompt = system_prompt
self.messages = [{"role": "system", "content": system_prompt}]

# Set these values here. This logic will be handled in the parent class.
self.max_retries = max_retries
self.retries = 0

def ask(self, message) -> LLMAskResponse:
self.retries = 0 # reset retries for each ask. Handled in parent class.

self.messages.append({"role": "user", "content": message})

valid = False
while not valid:
response = self.ai_client.responses.create(
model=self.deployment_name,
input=self.messages,
)
self.messages.append({"role": "assistant", "content": response.output_text})
valid, askResponse = self._validate_llm_response(response=response.output_text)

# Remove last assistant message and replace with structured response
self.messages.pop()
self.messages.append({"role": "assistant", "content": json.dumps(asdict(askResponse))})

return askResponse

def clear_history(self):
self.messages = [
{
"role": "system",
"content": self.system_prompt,
}
]
return True

Loading
Loading