diff --git a/models/llm_negotiation_market/README.md b/models/llm_negotiation_market/README.md new file mode 100644 index 0000000..d062088 --- /dev/null +++ b/models/llm_negotiation_market/README.md @@ -0,0 +1,38 @@ +## How to run +# Option 1: Google Colab (recommended) +Open model.ipynb in Google Colab +https://colab.research.google.com/drive/1yLOkKcxm4rOBbcFjULn_gVbCnVYwaoBP?usp=sharing + +# Option 2: Local (requires Linux/WSL due to Windows DLL policy) +pip install mesa mesa-llm groq litellm +python model.py + + +# LLM Negotiation Market Model + +A Mesa-LLM model where buyer and seller agents use Chain-of-Thought +reasoning to negotiate prices over multiple steps. + +## What I learned + +- `LLMAgent.step()` is empty by default — you must call + `self.reasoning.plan()` manually to trigger LLM thinking +- `CoTReasoning` makes two LLM calls: one for thinking, one for + tool execution. When no tools are registered, the second call + crashes with a Groq 400 error (`tool_choice="required"` with empty tools) +- Fixed by subclassing `CoTReasoning` into `SimpleCoTReasoning` that + skips the tool execution step — only uses the thinking/planning call +- Agents have real memory across steps — in Step 2, agents referenced + their Step 1 decisions without any extra code +- Three reasoning engines available: `CoTReasoning`, `ReActReasoning`, + `ReWOOReasoning` +- `llm_model` needs `"groq/"` prefix for LiteLLM routing + +## How to run +pip install mesa mesa-llm groq litellm +export GROQ_API_KEY=your_key +python model.py + +## Potential Issues Found in mesa-llm +- `CoTReasoning` crashes when no tools registered (tool_choice="required" bug) + → This could be a good issue to open on mesa/mesa-llm! \ No newline at end of file diff --git a/models/llm_negotiation_market/model.py b/models/llm_negotiation_market/model.py new file mode 100644 index 0000000..ab32167 --- /dev/null +++ b/models/llm_negotiation_market/model.py @@ -0,0 +1,99 @@ +import os +import mesa +from mesa_llm.llm_agent import LLMAgent +from mesa_llm.reasoning.cot import CoTReasoning + +os.environ["GROQ_API_KEY"] = "your_groq_api_key_here" + + +class SimpleCoTReasoning(CoTReasoning): + """ + Subclass of CoTReasoning that skips tool execution. + Only uses the first LLM call (the thinking/planning step). + This avoids the tool_choice='required' error when no tools are registered. + """ + + def plan(self, prompt=None, obs=None, ttl=1, selected_tools=None): + if prompt is None: + if self.agent.step_prompt is not None: + prompt = self.agent.step_prompt + else: + raise ValueError("No prompt provided and agent.step_prompt is None.") + + if obs is None: + obs = self.agent.generate_obs() + + llm = self.agent.llm + obs_str = str(obs) + + self.agent.memory.add_to_memory( + type="Observation", content={"content": obs_str} + ) + + system_prompt = self.get_cot_system_prompt(obs) + llm.system_prompt = system_prompt + + rsp = llm.generate( + prompt=prompt, + tool_schema=None, + tool_choice="none", + ) + + chaining_message = rsp.choices[0].message.content + self.agent.memory.add_to_memory( + type="Plan", content={"content": chaining_message} + ) + if hasattr(self.agent, "_step_display_data"): + self.agent._step_display_data["plan_content"] = chaining_message + + return chaining_message + + +class SellerAgent(LLMAgent): + def __init__(self, model, price): + super().__init__( + model=model, + reasoning=SimpleCoTReasoning, + llm_model="groq/llama-3.3-70b-versatile", + system_prompt="You are a seller in a market. Reply in one sentence.", + step_prompt=f"Your price is {price}. Should you lower it to attract buyers?", + ) + self.price = price + + def step(self): + response = self.reasoning.plan() + print(f"Seller {self.unique_id} (price={self.price}): {response}") + + +class BuyerAgent(LLMAgent): + def __init__(self, model, budget): + super().__init__( + model=model, + reasoning=SimpleCoTReasoning, + llm_model="groq/llama-3.3-70b-versatile", + system_prompt="You are a buyer in a market. Reply in one sentence.", + step_prompt=f"Your budget is {budget}. What price will you offer?", + ) + self.budget = budget + + def step(self): + response = self.reasoning.plan() + print(f"Buyer {self.unique_id} (budget={self.budget}): {response}") + + +class NegotiationMarket(mesa.Model): + def __init__(self, n_buyers=2, n_sellers=2): + super().__init__() + for _ in range(n_sellers): + SellerAgent(self, price=100) + for _ in range(n_buyers): + BuyerAgent(self, budget=150) + + def step(self): + self.agents.shuffle_do("step") + + +model = NegotiationMarket() +for i in range(2): + print(f"\n--- Step {i + 1} ---") + model.step()