LLM-augmented Agent-Based Social Simulation for modelling public discourse dynamics following a critical real-world event.
Project description
Discourse Simulator
LLM-augmented Agent-Based Social Simulation for modelling public discourse dynamics following a critical real-world event.
Agents observe live news, reason about it, post to a simulated social network, and update multidimensional beliefs through peer influence and news salience, all powered by a local Ollama LLM.
Installation
1. Install discourse-sim
pip install discourse-sim
2. Install Ollama
discourse_sim runs LLMs locally via Ollama. You need to install it separately.
macOS
brew install ollama
Or download the desktop app from ollama.com/download.
Linux
curl -fsSL https://ollama.com/install.sh | sh
Windows
Download and run the installer from ollama.com/download.
3. Start the Ollama server
ollama serve
On macOS with the desktop app, this starts automatically. On Linux/Windows you may need to run this in a separate terminal or set it up as a service.
4. Pull the default model
ollama pull mistral:7b-instruct-q4_0
This is the default model (~4 GB). You can use any other Ollama model by passing ollama_model="..." to DiscourseSimulation.
Lighter alternatives (if disk space is limited):
ollama pull mistral # ~4 GB - good balance
ollama pull llama3.2 # ~2 GB - faster, slightly less capable
ollama pull phi3 # ~2 GB - Microsoft, very efficient
5. Verify everything works
ollama run mistral:7b-instruct-q4_0 "Hello"
You should see a response. If you do, you are ready to run simulations.
All dependencies: (make sure if everything is installed correctly)
pip install langchain-core langchain-community langchain-ollama \
duckduckgo-search networkx numpy tqdm pandas openpyxl
Quick Start
from discourse_sim import DiscourseSimulation
sim = DiscourseSimulation(
critical_event=(
"In late April 2025, Dublin saw a large anti-immigration march "
"from the Garden of Remembrance to the Custom House, with thousands "
"protesting immigration levels and housing pressure..."
),
event_date="2025-04-26",
topic="immigration in Ireland",
n_agents=100,
n_days=15,
)
sim.run()
df = sim.to_dataframe() # one row per agent per day
df.to_excel("output.xlsx", index=False)
All Parameters
Required
| Parameter | Type | Description |
|---|---|---|
critical_event |
str |
Plain-text description of the event injected into every agent prompt on every day |
event_date |
str |
ISO date "YYYY-MM-DD" - this becomes Day 0 |
topic |
str |
Subject domain, e.g. "immigration in Ireland". Anchors search queries and scoring prompts |
Agent Population
| Parameter | Type | Default | Description |
|---|---|---|---|
n_agents |
int |
100 |
Number of synthetic agents |
n_days |
int |
15 |
Simulation duration in days |
agent_distribution |
dict |
Ireland 2025 empirical split | Proportions of each agent kind - must sum to 1.0 |
kind_priors |
dict |
DEFAULT_KIND_PRIORS |
Prior distributions for belief/attitude initialisation per kind |
Default distribution (Ireland 2025):
{"centrist": 0.45, "far_right": 0.20, "pro_imm": 0.25, "media": 0.10}
Timeline
| Parameter | Type | Default | Description |
|---|---|---|---|
timeline |
dict[int, str] |
None |
Optional explicit daily news entries keyed by 0-based day index. Missing days auto-generated. If None, all days use live search. |
Example:
timeline={
0: "[VERIFIED] The march took place at the Garden of Remembrance...",
4: "[VERIFIED] Government announced a deportation flight...",
}
Any day not in the dict falls back to an auto-generated entry. Agents search live news on every day regardless of timeline.
LLM & Network
| Parameter | Type | Default | Description |
|---|---|---|---|
ollama_model |
str |
"mistral:7b-instruct-q4_0" |
Any locally pulled Ollama model |
temperature |
float |
0.75 |
Post generation temperature |
use_llm_scoring |
bool |
True |
If False, uses keyword heuristic for attitude scoring |
network_k |
int |
6 |
Watts-Strogatz nearest-neighbour count |
network_p |
float |
0.3 |
Watts-Strogatz rewiring probability |
network_seed |
int |
42 |
Reproducibility seed for network and agents |
Belief Update Keywords
| Parameter | Type | Default | Description |
|---|---|---|---|
threat_keywords |
list[str] |
See config.py | Words in daily news that trigger security_threat_belief increase and exposure accumulation |
humanitarian_keywords |
list[str] |
See config.py | Words that trigger humanitarian_belief increase |
Override these when simulating non-immigration topics:
threat_keywords=["eviction", "homeless", "unaffordable", "crisis"],
humanitarian_keywords=["social housing", "rights", "family", "support"],
Outputs
sim.run()
# Single DataFrame - one row per agent per day (recommended for analysis)
df = sim.to_dataframe()
# All three DataFrames
dfs = sim.to_dataframes()
dfs["history"] # one row per timestep - aggregate metrics
dfs["agents"] # one row per agent per timestep - full belief state
dfs["messages"] # one row per agent per timestep - posts + beliefs merged
Column reference - df_messages
| Column | Description |
|---|---|
t / date |
Day index and calendar date |
agent_id / kind / quirk |
Agent identity |
message |
The generated social media post |
interpreted_score |
LLM-scored stance [-1, +1] |
attitude |
Agent attitude at end of day t |
mood |
Agent mood at end of day t |
exposure |
Cumulative threat narrative exposure |
economic_threat_belief |
Economic threat sub-belief |
cultural_threat_belief |
Cultural threat sub-belief |
security_threat_belief |
Security threat sub-belief |
humanitarian_belief |
Humanitarian weight sub-belief |
avg_attitude |
Population mean attitude that day |
polarization |
Mean edge-level attitude divergence |
news_summary |
First 120 chars of daily news entry |
Custom Agent Kinds
Add any agent kind by extending DEFAULT_KIND_PRIORS:
from discourse_sim import DiscourseSimulation
from discourse_sim.config import DEFAULT_KIND_PRIORS
custom_priors = {
**DEFAULT_KIND_PRIORS,
"nationalist": {
"attitude": (0.55, 0.95),
"economic_threat": (0.2, 0.6),
"cultural_threat": (0.7, 1.0),
"humanitarian": (-0.6, 0.0),
"openness": (0.1, 0.35),
"emotional_react": (0.5, 0.85),
},
}
sim = DiscourseSimulation(
...,
agent_distribution={"centrist": 0.35, "nationalist": 0.30, "pro_imm": 0.35},
kind_priors=custom_priors,
)
Architecture
discourse_sim/
├── __init__.py # Public API: DiscourseSimulation
├── core.py # DiscourseSimulation class - orchestrator
├── config.py # SimConfig dataclass - all parameters + validation
├── agents/
│ └── agent.py # Agent dataclass + make_agents() factory
├── tools/
│ └── search_tools.py # make_tools() - search, memory, sentiment
├── timeline/
│ └── timeline.py # Timeline class - user-supplied + auto-generated
├── simulation/
│ └── engine.py # generate_message(), score_message(), update_beliefs(), run_loop()
└── utils/
└── dataframes.py # build_dataframes() - history/agents/messages
Per-day flow for each agent:
OBSERVE → search_event_news(query) # live DuckDuckGo
→ recall_agent_memory(posts_json) # last 5 posts
THINK → build prompt: event + profile + memory + search result + today's news
ACT → LLM call → post text (max 40 words)
SCORE → LLM call (temp=0.0) → float in [-1, +1]
UPDATE → news salience → sub-beliefs
→ peer pull → attitude pressure
→ mood shock → affective state
→ inertia → resistance to change
→ composite → attitude(t)
Examples
| File | Description |
|---|---|
examples/dublin_march.py |
Full reproduction of the original Dublin April 2025 experiment |
examples/minimal_usage.py |
Minimal usage - no timeline, live search only |
examples/custom_agent_kinds.py |
Adding a custom nationalist kind with its own priors |
Citation
If you use this package in research, please cite:
@software{discourse_sim,
title = {discourse\_sim: LLM-Augmented Agent-Based Social Simulation of Discourse Dynamics},
author = {dreji18},
year = {2026},
note = {Python package}
}
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file discourse_sim-0.1.1.tar.gz.
File metadata
- Download URL: discourse_sim-0.1.1.tar.gz
- Upload date:
- Size: 26.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
da51c607483d0178e263400f9fdebcca10cce2f286d303f6d42e0b646f59c74b
|
|
| MD5 |
e54e41ec4d877435cb79d640fa2f2519
|
|
| BLAKE2b-256 |
2511a237d15e44503878c941f7b2bf15cdc34633d2acc5f549a54dc51e25449d
|
File details
Details for the file discourse_sim-0.1.1-py3-none-any.whl.
File metadata
- Download URL: discourse_sim-0.1.1-py3-none-any.whl
- Upload date:
- Size: 24.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
88bb66bd267a530881e9c335c3aadaf4fdaa7b5d1e2526c13e7a3b2a68cd1af1
|
|
| MD5 |
ce2022ab16654b9b9eb1767520cdf4fe
|
|
| BLAKE2b-256 |
d05d05975c9eab98e15e966c86f88d7272c83e201c7c3bc32dc56f5b675ad335
|