Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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
Empty file.
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 149 to 162
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()
118 changes: 118 additions & 0 deletions examples/sugarscrap_g1mt/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# app.py
import logging
import warnings

from dotenv import load_dotenv
from mesa.visualization import (
SolaraViz,
make_plot_component,
make_space_component,
)

from examples.sugarscrap_g1mt.agents import Resource, Trader
from examples.sugarscrap_g1mt.model import SugarScapeModel
from mesa_llm.reasoning.react import ReActReasoning

# Suppress Pydantic serialization warnings
warnings.filterwarnings(
"ignore",
category=UserWarning,
module="pydantic.main",
message=r".*Pydantic serializer warnings.*",
)

# Also suppress through logging
logging.getLogger("pydantic").setLevel(logging.ERROR)

# enable_automatic_parallel_stepping(mode="threading")

load_dotenv()


model_params = {
"seed": {
"type": "InputText",
"value": 42,
"label": "Random Seed",
},
"initial_traders": 2,
"initial_resources": 10,
"width": 10,
"height": 10,
"reasoning": ReActReasoning,
"llm_model": "gemini/gemini-2.5-flash-lite",
"vision": 5,
"parallel_stepping": True,
}

model = SugarScapeModel(
initial_traders=model_params["initial_traders"],
initial_resources=model_params["initial_resources"],
width=model_params["width"],
height=model_params["height"],
reasoning=model_params["reasoning"],
llm_model=model_params["llm_model"],
vision=model_params["vision"],
seed=model_params["seed"]["value"],
parallel_stepping=model_params["parallel_stepping"],
)


def trader_portrayal(agent):
if agent is None:
return

portrayal = {
"shape": "circle",
"Filled": "true",
"r": 0.5,
"Layer": 1,
"text_color": "black",
}

if isinstance(agent, Trader):
portrayal["Color"] = "red"
portrayal["r"] = 0.8
portrayal["text"] = f"S:{agent.sugar} Sp:{agent.spice}"

elif isinstance(agent, Resource):
portrayal["Color"] = "green"
portrayal["r"] = 0.4
portrayal["Layer"] = 0
if agent.current_amount > 0:
portrayal["alpha"] = agent.current_amount / agent.max_capacity
else:
portrayal["Color"] = "white"

return portrayal


def post_process(ax):
ax.set_aspect("equal")
ax.set_xticks([])
ax.set_yticks([])
ax.get_figure().set_size_inches(10, 10)


space_component = make_space_component(
trader_portrayal, post_process=post_process, draw_grid=False
)

chart_component = make_plot_component({"Total_Sugar": "blue", "Total_Spice": "red"})

if __name__ == "__main__":
page = SolaraViz(
model,
components=[
space_component,
chart_component,
],
model_params=model_params,
name="SugarScape G1MT Example",
)

"""
run with
cd examples/sugarscrap_g1mt
conda activate mesa-llm && solara run app.py
"""
Loading
Loading