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
123 changes: 123 additions & 0 deletions examples/sugarscrap_g1mt/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# Sugarscape Constant Growback Model with Traders

## Summary

This model is based on Epstein & Axtell's classic "Sugarscape" simulation from Growing Artificial Societies (1996), specifically the G1MT (Growback 1, Metabolism, Trade) variation. Trader agents wander a grid populated with two unevenly distributed resources: Sugar and Spice. Agents are endowed with individual metabolic rates for each resource and a vision range; there are also Resource agents, which represent the landscape and regenerate food over time.

The model generates emergent economic dynamics through decentralized interactions. Traders must constantly harvest resources to satisfy their metabolic needs; if they run out of either sugar or spice, they starve. Crucially, agents can trade with neighbors. Decisions are governed by the Marginal Rate of Substitution (MRS); agents rich in sugar but poor in spice will trade sugar to acquire spice, and vice versa. Over time, this decentralized trading allows for the emergence of a price equilibrium and wealth distribution patterns.

This model is implemented using Mesa-LLM, unlike the original deterministic versions. All Trader agents use Large Language Models to "think" about their survival. They observe their internal inventory and MRS, then autonomously decide to use tools to move to high-value resource tiles or propose trades to neighbors to ensure their continued existence.

## Technical Details

### Agents

- `Trader (LLMAgent):` The primary actor equipped with STLTMemory and ReActReasoning.

Internal State: Dynamically updates a context string with current inventory (Sugar, Spice) and hunger warnings to guide the LLM.

Metabolism: Consumes a fixed amount of resources per step. Zero inventory results in agent removal (death).

MRS Calculation: Computes the Marginal Rate of Substitution (MRS) using the Cobb-Douglas formula to value Sugar vs. Spice relative to biological needs.

- `Resource (CellAgent):` A passive environmental agent that acts as a container for resources. It regenerates its current_amount by 1 unit per step up to a max_capacity.

### Tools

- `move_to_best_resource:`

Function: Scans the local grid within the agent's vision radius.

Action: Identifies the cell with the highest current_amount of resources, moves the agent there, and automatically harvests the full amount into the agent's inventory.

- `propose_trade:`

Function: Targets a specific neighbor by unique_id.

Logic: Executes a trade only if the partner's MRS is higher than the proposer's (indicating the partner values Sugar more highly). This ensures trades are mathematically rational and mutually beneficial.


### Movement Rule (Rule M)

A Trader agent moves to a new location and harvests resources if the following logic, executed by the *move_to_best_resource tool*, is satisfied:

Scan: The agent inspects all cells within its *vision range* (von Neumann neighborhood).

Identify: It identifies the cell containing a *Resource* agent with the highest *current_amount* of sugar/spice.

Harvest: The agent moves to that cell, sets the resource's amount to 0, and adds the harvested amount to its own inventory.

### Trade Rule (Rule T)

Agents determine whether to trade based on their Marginal Rate of Substitution (MRS). A trade is proposed via the propose_trade tool and accepted if it is mutually beneficial:

```
Trade occurs if: Partner_MRS > Agent_MRS
```

Where the MRS is calculated using the agent's inventory and metabolism:

```
MRS = (spice_inventory / spice_metabolism) / (sugar_inventory / sugar_metabolism)
```

In this implementation:

- `Agent (Proposer):` Gives Sugar, Receives Spice.

- `Partner (Receiver):` Receives Sugar, Gives Spice.

- This flow ensures resources move from agents who value them less to agents who value them more.

### Resource Behavior

Resource Agents represent the landscape. They are passive agents that regenerate wealth over time:

- `Growback:` At every step, a Resource agent increases its current_amount by growback (default: 1).

- `Capacity:` This growth is capped at the agent's max_capacity.

### LLM-Powered Agents

Both Traders and the simulation logic are driven by LLM-powered agents, meaning:

- Their actions (e.g., `move_to_best_resource`, `propose_trade`) are determined by a ReAct reasoning module.

- This module takes as input:

The agent’s internal state (current inventory, metabolic warnings, and calculated MRS).

Local observations of the grid.

- A set of available tools defined in `tools.py`.



## How to Run

If you have cloned the repo into your local machine, ensure you run the following command from the root of the library: ``pip install -e . ``. Then, you will need an api key of an LLM-provider of your choice. (This model in particular makes a large amount of calls per minute and we therefore recommend getting a paid version of an api-key that can offer high rate-limits). Once you have obtained the api-key follow the below steps to set it up for this model.
1) Ensure the dotenv package is installed. If not, run ``pip install python-dotenv``.
2) In the root folder of the project, create a file named .env.
3) If you are using openAI's api key, add the following command in the .env file: ``OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx``. If you have the paid version of Gemini, use this line instead: ``GEMINI_API_KEY=your-gemini-api-key-here``(the free ones tend to not work with this model).
4) Change the ``api_key`` specification in app.py according to the provider you have chosen.
5) Similarly change the ``llm_model`` attribute as well in app.py to the name of a model you have access to. Ensure it is in the form of {provider}/{model_name}. For e.g. ``openai/gpt-4o-mini``.

Once you have set up the api-key in your system, run the following command from this directory:

```
$ solara run app.py
```


## Files

* ``model.py``: Core model code.
* ``agents.py``: Agent classes.
* ``app.py``: Sets up the interactive visualization.
* ``tools.py``: Tools for the llm-agents to use.


## Further Reading

[Growing Artificial Societies](https://mitpress.mit.edu/9780262550253/growing-artificial-societies/)
[Complexity Explorer Sugarscape with Traders Tutorial](https://www.complexityexplorer.org/courses/172-agent-based-models-with-python-an-introduction-to-mesa#gsc.tab=0)
1 change: 1 addition & 0 deletions examples/sugarscrap_g1mt/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import examples.sugarscrap_g1mt.tools # noqa: F401, to register tools
162 changes: 162 additions & 0 deletions examples/sugarscrap_g1mt/agents.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
from enum import Enum

import mesa

from mesa_llm.llm_agent import LLMAgent
from mesa_llm.memory.st_lt_memory import STLTMemory
from mesa_llm.tools.tool_manager import ToolManager

trader_tool_manager = ToolManager()
resource_tool_manager = ToolManager()


class TraderState(Enum):
Total_Spice = 4
Total_Sugar = 6
Total_Count = 10


class Trader(LLMAgent, mesa.discrete_space.CellAgent):
def __init__(
self,
model,
reasoning,
llm_model,
system_prompt,
vision,
internal_state,
step_prompt,
sugar=0,
spice=0,
metabolism_sugar=1,
metabolism_spice=1,
):
super().__init__(
model=model,
reasoning=reasoning,
llm_model=llm_model,
system_prompt=system_prompt,
vision=vision,
internal_state=internal_state,
step_prompt=step_prompt,
)
self.sugar = sugar
self.spice = spice
self.metabolism_sugar = metabolism_sugar
self.metabolism_spice = metabolism_spice

self.memory = STLTMemory(
agent=self,
display=True,
llm_model="openai/gpt-4o-mini",
)

self.tool_manager = trader_tool_manager

self.system_prompt = (
"You are a Trader agent in a Sugarscape simulation. "
"You need Sugar and Spice to survive. "
"If your MRS (Marginal Rate of Substitution) is high, you desperately need Sugar. "
"If MRS is low, you need Spice. "
"You can move to harvest resources or trade with neighbors."
)

self.update_internal_metrics()

def calculate_mrs(self):
if self.sugar == 0:
return 100.0

if self.metabolism_sugar == 0:
return 100.0

if self.metabolism_spice == 0:
return 0.0

return (self.spice / self.metabolism_spice) / (
self.sugar / self.metabolism_sugar
)

def update_internal_metrics(self):
mrs = self.calculate_mrs()

self.internal_state = [
s
for s in self.internal_state
if not any(x in s for x in ["Sugar", "Spice", "MRS", "WARNING_"])
]

self.internal_state.append(f"My Sugar inventory is: {self.sugar}")
self.internal_state.append(f"My Spice inventory is: {self.spice}")
self.internal_state.append(
f"My Marginal Rate of Substitution (MRS) is {mrs:.2f}"
)

if self.sugar < self.metabolism_sugar * 2:
self.internal_state.append(
"WARNING: I am in danger of starvation from lack of sugar"
)
if self.spice < self.metabolism_spice * 2:
self.internal_state.append(
"WARNING: I am in danger of starvation from lack of spice"
)

def step(self):
self.sugar -= self.metabolism_sugar
self.spice -= self.metabolism_spice

if self.sugar <= 0 or self.spice <= 0:
self.model.grid.remove_agent(self)
self.remove()
return

self.update_internal_metrics()

observation = self.generate_obs()

plan = self.reasoning.plan(
obs=observation,
selected_tools=["move_to_best_resource", "propose_trade"],
)

self.apply_plan(plan)

async def astep(self):
self.sugar -= self.metabolism_sugar
self.spice -= self.metabolism_spice

if self.sugar <= 0 or self.spice <= 0:
self.model.grid.remove_agent(self)
self.remove()
return

self.update_internal_metrics()
observation = self.generate_obs()

plan = await self.reasoning.aplan(
obs=observation,
selected_tools=["move_to_best_resource", "propose_trade"],
)
self.apply_plan(plan)


class Resource(mesa.discrete_space.CellAgent):
def __init__(self, model, max_capacity=10, current_amount=10, growback=1):
super().__init__(model=model)

self.max_capacity = max_capacity
self.current_amount = current_amount
self.growback = growback

self.internal_state = []

self.tool_manager = resource_tool_manager
Comment on lines +143 to +153
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am assuming you want to represent sugar and spice as Resource agents, in which case you should consider adding an attribute here that makes it possible to classify the instances of resource.


def step(self):
if self.current_amount < self.max_capacity:
self.current_amount += self.growback
if self.current_amount > self.max_capacity:
self.current_amount = self.max_capacity

async def astep(self):
self.step()
Loading
Loading