LLM-based negotiators for the negmas negotiation framework.
negmas-llm integrates Large Language Models with the negmas negotiation framework, allowing you to create negotiators that use LLMs for decision-making. The library supports multiple LLM providers through litellm, including:
- Cloud providers: OpenAI, Anthropic, Google Gemini, Cohere, Mistral, Groq, Together AI, Azure OpenAI, AWS Bedrock, OpenRouter, DeepSeek, Hugging Face
- Local models: Ollama, vLLM, LM Studio, text-generation-webui
pip install negmas-llmOr with uv:
uv add negmas-llmfrom negmas import SAOMechanism
from negmas.preferences import LinearAdditiveUtilityFunction
from negmas.outcomes import make_issue
from negmas_llm import OllamaNegotiator, OpenAINegotiator
# Define negotiation issues
issues = [
make_issue("price", (0, 100)),
make_issue("quantity", (1, 10)),
]
# Create utility functions for each party
seller_ufun = LinearAdditiveUtilityFunction(
weights=[0.6, 0.4],
issues=issues,
)
buyer_ufun = LinearAdditiveUtilityFunction(
weights=[-0.6, 0.4], # Buyer prefers lower prices
issues=issues,
)
# Create LLM-based negotiators
seller = OpenAINegotiator(
model="gpt-4o",
name="seller",
ufun=seller_ufun,
)
buyer = OllamaNegotiator(
model="llama3.2",
name="buyer",
ufun=buyer_ufun,
)
# Run the negotiation
mechanism = SAOMechanism(outcome_space=issues, n_steps=20)
mechanism.add(seller)
mechanism.add(buyer)
result = mechanism.run()
print(f"Agreement: {result.agreement}")| Class | Provider | Default Model |
|---|---|---|
OpenAINegotiator |
OpenAI | gpt-4o |
AnthropicNegotiator |
Anthropic | claude-sonnet-4-20250514 |
GeminiNegotiator |
gemini-2.0-flash | |
CohereNegotiator |
Cohere | command-r-plus |
MistralNegotiator |
Mistral AI | mistral-large-latest |
GroqNegotiator |
Groq | llama-3.3-70b-versatile |
TogetherAINegotiator |
Together AI | Meta-Llama-3.1-70B-Instruct-Turbo |
AzureOpenAINegotiator |
Azure OpenAI | (deployment name) |
AWSBedrockNegotiator |
AWS Bedrock | claude-3-sonnet |
OpenRouterNegotiator |
OpenRouter | openai/gpt-4o |
DeepSeekNegotiator |
DeepSeek | deepseek-chat |
HuggingFaceNegotiator |
Hugging Face | Llama-3.2-3B-Instruct |
OllamaNegotiator |
Ollama (local) | llama3.2 |
VLLMNegotiator |
vLLM (local) | (model name) |
LMStudioNegotiator |
LM Studio (local) | local-model |
TextGenWebUINegotiator |
text-generation-webui | local-model |
You can customize how information is presented to the LLM by subclassing LLMNegotiator and overriding the formatting methods:
from negmas_llm import LLMNegotiator
class CustomNegotiator(LLMNegotiator):
def format_outcome_space(self, state):
# Customize how the outcome space is described
return "Custom outcome space description..."
def format_own_ufun(self, state):
# Customize how your utility function is described
return "Custom utility function description..."
def format_state(self, state, offer=None):
# Customize how the negotiation state is presented
return "Custom state description..."For complete control over the prompts, override build_system_prompt or build_user_message:
from negmas_llm import LLMNegotiator
class CustomPromptNegotiator(LLMNegotiator):
def build_system_prompt(self, state):
# Complete control over the system prompt
# By default, this combines: format_outcome_space(), format_nmi_info(),
# format_own_ufun(), format_partner_ufun(), format_response_instructions()
return f"""You are an aggressive negotiator who never accepts the first offer.
{self.format_outcome_space(state)}
{self.format_own_ufun(state)}
{self.format_response_instructions()}
Always aim for at least 80% of your maximum utility."""
def build_user_message(self, state, offer, source):
# Complete control over the per-round user message
if offer is not None:
return (
f"Round {state.step}: Opponent offered {offer}. Counter aggressively."
)
return f"Round {state.step}: Make an ambitious opening offer."Each negotiator class accepts provider-specific configuration:
from negmas_llm import (
OpenAINegotiator,
AnthropicNegotiator,
OllamaNegotiator,
LLMNegotiator,
)
# OpenAI (uses OPENAI_API_KEY env var by default)
negotiator = OpenAINegotiator(model="gpt-4o", api_key="sk-...")
# Anthropic (uses ANTHROPIC_API_KEY env var by default)
negotiator = AnthropicNegotiator(model="claude-sonnet-4-20250514")
# Local Ollama
negotiator = OllamaNegotiator(model="llama3.2", api_base="http://localhost:11434")
# GitHub Copilot (uses OAuth device flow - no API key needed)
# On first use, you'll be prompted to authenticate via browser
negotiator = LLMNegotiator(
provider="github_copilot",
model="gpt-4o",
)
# GitHub Models (marketplace - uses GITHUB_API_KEY)
negotiator = LLMNegotiator(
provider="github",
model="gpt-4o",
)
# Generic provider via litellm
negotiator = LLMNegotiator(
provider="groq",
model="llama-3.3-70b-versatile",
api_key="gsk_...",
)Provider and model are resolved in one place
(negmas_llm/config.py) so you can steer every
negotiator from the environment without editing code — ideal for running the
same experiment across different models. There are three configuration axes:
| Axis | Environment variable | Scope |
|---|---|---|
| Global | NEGMAS_LLM_<VAR> |
Every negotiator that doesn't set the value explicitly |
| Per negotiator type | NEGMAS_LLM_<ClassName>_<VAR> |
Only that one concrete class |
| Per provider (model) | NEGMAS_LLM_<PROVIDER>_DEFAULT_MODEL |
Any negotiator that ends up on that provider |
<VAR> ∈ PROVIDER, MODEL, EFFORT, TEMPERATURE, MAX_WORDS, MAX_TOKENS, TIMEOUT,
NUM_RETRIES, API_KEY, API_BASE. Precedence (highest first):
explicit constructor arg > NEGMAS_LLM_<ClassName>_<VAR> > NEGMAS_LLM_<VAR> > built-in default
Example — run everyone on OpenAI gpt-4o, but one negotiator type on Anthropic
claude-3-opus (a different model and provider):
export NEGMAS_LLM_PROVIDER=openai
export NEGMAS_LLM_MODEL=gpt-4o
export NEGMAS_LLM_LLMBoulwareTBNegotiator_PROVIDER=anthropic
export NEGMAS_LLM_LLMBoulwareTBNegotiator_MODEL=claude-3-opusfrom negmas_llm import LLMBoulwareTBNegotiator, LLMConcederTBNegotiator
LLMBoulwareTBNegotiator(name="a") # anthropic / claude-3-opus
LLMConcederTBNegotiator(name="b") # openai / gpt-4oTwo coherence rules keep a model from reaching the wrong provider: the
provider-named classes (e.g. OpenAINegotiator) ignore the global
NEGMAS_LLM_PROVIDER, and a global NEGMAS_LLM_MODEL is applied only to
negotiators whose provider matches the global provider.
Model types (tiers). A model type is a named (provider, model, effort)
bundle — e.g. fast, accurate — so different parts of a negotiator can ask
for different models. The (uppercased) type name is appended as a suffix to
every variable, and code requests it with model_type=:
export NEGMAS_LLM_PROVIDER_FAST=groq
export NEGMAS_LLM_MODEL_FAST=llama-3.1-8b-instant
export NEGMAS_LLM_EFFORT_FAST=low
export NEGMAS_LLM_PROVIDER_ACCURATE=openai
export NEGMAS_LLM_MODEL_ACCURATE=o3
export NEGMAS_LLM_EFFORT_ACCURATE=highfrom negmas_llm import resolve_llm_config
resolve_llm_config(
"LLMBoulwareTBNegotiator", model_type="fast"
) # groq / llama-3.1-8b-instant / low
resolve_llm_config(
"LLMBoulwareTBNegotiator", model_type="accurate"
) # openai / o3 / highSee the full reference in docs/guide/environment-variables.md.
Tests can be configured via environment variables to use different LLM providers.
GitHub Copilot uses OAuth device flow authentication - no API key needed!
# Run GitHub Copilot tests - will prompt for OAuth on first use
TEST_LLM_PROVIDER=github_copilot uv run pytest -m slow
# Or run only GitHub Copilot-specific tests
uv run pytest -m github_copilotOn first run, litellm will display a device code and URL. Visit the URL, enter the code, and authenticate with your GitHub account. Credentials are cached locally for future use.
| Variable | Description | Default |
|---|---|---|
TEST_LLM_PROVIDER |
Default provider for tests | ollama |
TEST_LLM_MODEL |
Default model (overridden by provider-specific vars) | Provider default |
TEST_LLM_API_KEY |
Default API key | None |
TEST_LLM_API_BASE |
Default API base URL | Provider default |
Each provider can be configured independently:
| Provider | Model Variable | API Key Variable | Notes |
|---|---|---|---|
| Ollama | OLLAMA_MODEL |
- | Local server |
| OpenAI | OPENAI_MODEL |
OPENAI_API_KEY |
|
| Anthropic | ANTHROPIC_MODEL |
ANTHROPIC_API_KEY |
|
| Gemini | GEMINI_MODEL |
GOOGLE_API_KEY |
|
| GitHub Copilot | GITHUB_COPILOT_MODEL |
- | OAuth device flow |
| GitHub Models | GITHUB_MODEL |
GITHUB_API_KEY |
PAT works |
| Groq | GROQ_MODEL |
GROQ_API_KEY |
|
| Mistral | MISTRAL_MODEL |
MISTRAL_API_KEY |
|
| DeepSeek | DEEPSEEK_MODEL |
DEEPSEEK_API_KEY |
|
| OpenRouter | OPENROUTER_MODEL |
OPENROUTER_API_KEY |
| Provider | Default Model |
|---|---|
ollama |
qwen3:1.7b |
openai |
gpt-4o-mini |
anthropic |
claude-sonnet-4-20250514 |
gemini |
gemini-2.0-flash |
github_copilot |
gpt-4o |
github |
gpt-4o |
groq |
llama-3.3-70b-versatile |
mistral |
mistral-large-latest |
deepseek |
deepseek-chat |
openrouter |
openai/gpt-4o-mini |
# Run tests for a specific provider (skips if not configured)
uv run pytest -m openai
uv run pytest -m anthropic
uv run pytest -m github_copilot
uv run pytest -m ollama
# Run all slow tests (actual LLM calls)
uv run pytest -m slow
# Run fast tests only (mocked/unit tests)
uv run pytest -m "not slow"
# Run tests with a specific provider
TEST_LLM_PROVIDER=openai OPENAI_API_KEY=sk-... uv run pytest
# Run tests with Ollama and a specific model
OLLAMA_MODEL=llama3.2 uv run pytest -m ollamaGitHub Copilot uses OAuth device flow - no Personal Access Token needed.
-
Ensure you have GitHub Copilot access (requires GitHub Copilot subscription)
-
Run the tests:
TEST_LLM_PROVIDER=github_copilot uv run pytest -m slow
-
Follow the OAuth prompts: On first run, you'll see:
Please visit https://github.com/login/device and enter code: XXXX-XXXX -
Authenticate: Visit the URL, enter the code, and authorize the app
-
Credentials are cached: Future runs won't require re-authentication
Full documentation is available at negmas-llm.readthedocs.io.
GNU Affero General Public License v3.0 or later - see LICENSE for details.