oransim-causal-marketing-twin

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Oransim — Causal Digital Twin for Marketing

Oransim — 营销领域的因果数字孪生工具

Skill by ara.so — Daily 2026 Skills collection.
Oransim is an open-source causal simulation engine for marketing teams. It lets you predict campaign ROI, run counterfactual "what if" scenarios (swap KOLs, reallocate budget, change platforms), and audit every prediction through a transparent 64-node causal graph — before spending a dollar.
Core capabilities:
  • Pre-launch ROI ranking across creative × KOL × budget combinations
  • Mid-campaign
    do()
    -operator rollouts
    (e.g. swap KOL on day 3, see 14-day path diff)
  • Post-mortem counterfactuals (what if we'd spent on 小红书 instead of 抖音?)
  • LLM-backed "soul personas" for 1M+ virtual consumer agents
  • Causal Neural Hawkes Process for temporal cascade simulation
  • Per-arm counterfactual heads (TARNet / Dragonnet architecture)

来自ara.so的技能工具——2026每日技能合集。
Oransim是面向营销团队的开源因果模拟引擎。它能让你在投入任何资金前,预测营销活动ROI、运行「假设场景」反事实模拟(替换KOL、重新分配预算、更换平台),并通过透明的64节点因果图审核每一项预测结果。
核心功能:
  • 创意×KOL×预算组合的活动启动前ROI排名
  • 活动进行中的
    do()
    算子部署(例如:第3天替换KOL,查看14天路径差异)
  • 活动复盘反事实分析(如果我们把预算投给小红书而不是抖音会怎样?)
  • 基于LLM的「灵魂人设」,支持超100万虚拟消费者Agent
  • 用于时间级联模拟的Causal Neural Hawkes Process
  • 基于TARNet/Dragonnet架构的单臂反事实头部模型

Installation

安装

bash
git clone https://github.com/OranAi-Ltd/oransim.git
cd oransim
pip install -e '.[dev]'
bash
git clone https://github.com/OranAi-Ltd/oransim.git
cd oransim
pip install -e '.[dev]'

Backend (mock mode — no API key needed)

后端(模拟模式——无需API密钥)

bash
LLM_MODE=mock python -m uvicorn oransim.api:app --port 8001
bash
LLM_MODE=mock python -m uvicorn oransim.api:app --port 8001

Backend (real LLM pipeline)

后端(真实LLM流水线)

bash
LLM_MODE=api \
LLM_API_KEY=$YOUR_LLM_API_KEY \
LLM_MODEL=gpt-4o \
python -m uvicorn oransim.api:app --port 8001
bash
LLM_MODE=api \
LLM_API_KEY=$YOUR_LLM_API_KEY \
LLM_MODEL=gpt-4o \
python -m uvicorn oransim.api:app --port 8001

Frontend

前端

bash
python -m http.server 8090 --directory frontend
bash
python -m http.server 8090 --directory frontend

---

---

Configuration

配置

All config via environment variables (see
.env.example
):
bash
undefined
所有配置通过环境变量实现(参考
.env.example
):
bash
undefined

LLM mode: "mock" (deterministic stubs) or "api" (real LLM)

LLM模式:"mock"(确定性桩代码)或 "api"(真实LLM)

LLM_MODE=api
LLM_MODE=api

Provider: openai (default), anthropic, gemini, qwen

服务商:openai(默认)、anthropic、gemini、qwen

LLM_PROVIDER=openai
LLM_PROVIDER=openai

API key (also accepts provider-specific: OPENAI_API_KEY, ANTHROPIC_API_KEY, etc.)

API密钥(也支持服务商专属密钥:OPENAI_API_KEY、ANTHROPIC_API_KEY等)

LLM_API_KEY=sk-...
LLM_API_KEY=sk-...

Model

模型

LLM_MODEL=gpt-4o
LLM_MODEL=gpt-4o

Custom base URL (DeepSeek, vLLM, etc.)

自定义基础URL(DeepSeek、vLLM等)

undefined
undefined

Provider quick-reference

服务商速查表

Provider
LLM_PROVIDER
LLM_BASE_URL
Example model
OpenAI
openai
https://api.openai.com/v1
gpt-4o
DeepSeek
openai
https://api.deepseek.com/v1
deepseek-chat
vLLM (local)
openai
http://localhost:8000/v1
any
Anthropic
anthropic
(default)
claude-sonnet-4-6
Gemini
gemini
(default)
gemini-2.5-flash
Qwen
qwen
(default)
qwen-plus

服务商
LLM_PROVIDER
LLM_BASE_URL
示例模型
OpenAI
openai
https://api.openai.com/v1
gpt-4o
DeepSeek
openai
https://api.deepseek.com/v1
deepseek-chat
vLLM(本地)
openai
http://localhost:8000/v1
任意模型
Anthropic
anthropic
(默认)
claude-sonnet-4-6
Gemini
gemini
(默认)
gemini-2.5-flash
Qwen
qwen
(默认)
qwen-plus

Key API Endpoints

核心API端点

All endpoints served at
http://localhost:8001
.
所有端点均部署在
http://localhost:8001

POST
/api/predict
— Run a campaign simulation

POST
/api/predict
— 运行营销活动模拟

python
import httpx

payload = {
    "campaign": {
        "name": "Summer Beauty Launch",
        "platform": "xhs",           # xhs | douyin | tiktok
        "budget": 500000,            # CNY
        "duration_days": 14,
        "creatives": [
            {"id": "vid_A", "type": "video", "duration_sec": 30},
            {"id": "vid_B", "type": "video", "duration_sec": 60},
        ],
        "kols": [
            {"id": "kol_001", "tier": "mid", "vertical": "beauty", "fans": 250000},
            {"id": "kol_002", "tier": "koc", "vertical": "skincare", "fans": 45000},
        ],
        "budget_split": {"xhs": 0.6, "douyin": 0.4},
    },
    "mode": "fast",   # "fast" (quantile baseline) | "full" (LLM agent simulation)
    "n_simulations": 100,
}

response = httpx.post("http://localhost:8001/api/predict", json=payload, timeout=120)
result = response.json()

print(result["roi"]["p50"])    # median ROI
print(result["roi"]["p35"])    # lower confidence band
print(result["roi"]["p65"])    # upper confidence band
print(result["causal_path"])   # which nodes drove the prediction
python
import httpx

payload = {
    "campaign": {
        "name": "Summer Beauty Launch",
        "platform": "xhs",           # xhs | douyin | tiktok
        "budget": 500000,            # 人民币
        "duration_days": 14,
        "creatives": [
            {"id": "vid_A", "type": "video", "duration_sec": 30},
            {"id": "vid_B", "type": "video", "duration_sec": 60},
        ],
        "kols": [
            {"id": "kol_001", "tier": "mid", "vertical": "beauty", "fans": 250000},
            {"id": "kol_002", "tier": "koc", "vertical": "skincare", "fans": 45000},
        ],
        "budget_split": {"xhs": 0.6, "douyin": 0.4},
    },
    "mode": "fast",   # "fast"(分位数基线)| "full"(LLM Agent模拟)
    "n_simulations": 100,
}

response = httpx.post("http://localhost:8001/api/predict", json=payload, timeout=120)
result = response.json()

print(result["roi"]["p50"])    # 中位数ROI
print(result["roi"]["p35"])    # 置信区间下限
print(result["roi"]["p65"])    # 置信区间上限
print(result["causal_path"])   # 驱动预测结果的节点路径

GET
/api/graph/inspect
— Audit the causal graph

GET
/api/graph/inspect
— 审核因果图

python
import httpx, json

graph = httpx.get("http://localhost:8001/api/graph/inspect").json()
print(f"Nodes: {len(graph['nodes'])}")   # 64 nodes
print(f"Edges: {len(graph['edges'])}")   # 117 edges
python
import httpx, json

graph = httpx.get("http://localhost:8001/api/graph/inspect").json()
print(f"节点数: {len(graph['nodes'])}")   # 64个节点
print(f"边数: {len(graph['edges'])}")   # 117条边

Find all paths from budget allocation to purchase intent

查找从预算分配到购买意愿的所有路径

for edge in graph["edges"]: if edge["source"] == "budget_allocation": print(edge)
undefined
for edge in graph["edges"]: if edge["source"] == "budget_allocation": print(edge)
undefined

POST
/api/sandbox/counterfactual
— Mid-campaign KOL swap

POST
/api/sandbox/counterfactual
— 活动进行中替换KOL

python
import httpx
python
import httpx

Scenario: campaign running, day 3, swap KOL

场景:活动已进行3天,替换KOL

counterfactual = httpx.post( "http://localhost:8001/api/sandbox/counterfactual", json={ "base_campaign_id": "campaign_abc123", "intervention": { "do": { "kol": {"remove": ["kol_001"], "add": ["kol_003"]}, "day": 3, "budget_realloc": {"kol_001_budget": "kol_003"}, } }, "rollout_days": 14, }, timeout=120, ).json()
print(counterfactual["roi_diff"]) # ROI change from intervention print(counterfactual["trajectory_diff"]) # day-by-day path difference print(counterfactual["attribution"]) # which causal nodes shifted
undefined
counterfactual = httpx.post( "http://localhost:8001/api/sandbox/counterfactual", json={ "base_campaign_id": "campaign_abc123", "intervention": { "do": { "kol": {"remove": ["kol_001"], "add": ["kol_003"]}, "day": 3, "budget_realloc": {"kol_001_budget": "kol_003"}, } }, "rollout_days": 14, }, timeout=120, ).json()
print(counterfactual["roi_diff"]) # 干预带来的ROI变化 print(counterfactual["trajectory_diff"]) # 每日路径差异 print(counterfactual["attribution"]) # 发生变化的因果节点
undefined

POST
/api/sandbox/postmortem
— Platform counterfactual

POST
/api/sandbox/postmortem
— 平台选择反事实分析

python
import httpx

postmortem = httpx.post(
    "http://localhost:8001/api/sandbox/postmortem",
    json={
        "actuals": {
            "campaign_id": "q2_campaign",
            "spend": {"xhs": 200000, "douyin": 300000},
            "observed_roi": 1.4,
        },
        "counterfactual_alloc": {"xhs": 1.0, "douyin": 0.0},  # what if all on XHS?
    },
    timeout=120,
).json()

print(postmortem["counterfactual_roi"])   # what ROI would have been
print(postmortem["delta"])                # difference from actuals
python
import httpx

postmortem = httpx.post(
    "http://localhost:8001/api/sandbox/postmortem",
    json={
        "actuals": {
            "campaign_id": "q2_campaign",
            "spend": {"xhs": 200000, "douyin": 300000},
            "observed_roi": 1.4,
        },
        "counterfactual_alloc": {"xhs": 1.0, "douyin": 0.0},  # 如果全部预算投给小红书会怎样?
    },
    timeout=120,
).json()

print(postmortem["counterfactual_roi"])   # 预期ROI
print(postmortem["delta"])                # 与实际结果的差异

GET
/api/adapters
— List available platform adapters

GET
/api/adapters
— 查看可用平台适配器

python
import httpx
adapters = httpx.get("http://localhost:8001/api/adapters").json()
python
import httpx
adapters = httpx.get("http://localhost:8001/api/adapters").json()

Returns: ["xhs_v1", "tiktok_agent", "douyin", ...]

返回结果: ["xhs_v1", "tiktok_agent", "douyin", ...]


---

---

Python SDK Usage (Direct Engine)

Python SDK使用(直接调用引擎)

For programmatic use without the HTTP layer:
python
from oransim.world_model import AgentSociety
from oransim.causal import CausalGraph, do_operator
from oransim.diffusion import HawkesRollout
无需HTTP层的程序化调用方式:
python
from oransim.world_model import AgentSociety
from oransim.causal import CausalGraph, do_operator
from oransim.diffusion import HawkesRollout

1. Build the causal graph

1. 构建因果图

graph = CausalGraph.from_config("configs/default_graph.yaml")
graph = CausalGraph.from_config("configs/default_graph.yaml")

2. Initialize virtual consumer society

2. 初始化虚拟消费者群体

society = AgentSociety( n_agents=10_000, # scale down from 1M for local dev vertical="beauty", platform="xhs", llm_mode="mock", # "mock" | "api" )
society = AgentSociety( n_agents=10_000, # 本地开发可从100万规模缩小 vertical="beauty", platform="xhs", llm_mode="mock", # "mock" | "api" )

3. Define campaign

3. 定义营销活动

campaign = { "budget": 200_000, "kols": [{"id": "kol_001", "tier": "mid", "fans": 150_000}], "creative_ids": ["vid_A"], "duration_days": 14, }
campaign = { "budget": 200_000, "kols": [{"id": "kol_001", "tier": "mid", "fans": 150_000}], "creative_ids": ["vid_A"], "duration_days": 14, }

4. Run baseline simulation

4. 运行基线模拟

baseline = HawkesRollout(graph=graph, society=society) result = baseline.run(campaign, n_simulations=50) print(f"P50 ROI: {result.roi.p50:.2f}")
baseline = HawkesRollout(graph=graph, society=society) result = baseline.run(campaign, n_simulations=50) print(f"P50 ROI: {result.roi.p50:.2f}")

5. Apply do()-operator intervention

5. 应用do()算子干预

with do_operator(graph) as intervened_graph: intervened_graph.set("kol_assignment", "kol_002") intervened_graph.set("intervention_day", 3)
counterfactual = HawkesRollout(graph=intervened_graph, society=society)
cf_result = counterfactual.run(campaign, n_simulations=50)
print(f"Counterfactual P50 ROI: {cf_result.roi.p50:.2f}") print(f"Delta: {cf_result.roi.p50 - result.roi.p50:.2f}")

---
with do_operator(graph) as intervened_graph: intervened_graph.set("kol_assignment", "kol_002") intervened_graph.set("intervention_day", 3)
counterfactual = HawkesRollout(graph=intervened_graph, society=society)
cf_result = counterfactual.run(campaign, n_simulations=50)
print(f"反事实P50 ROI: {cf_result.roi.p50:.2f}") print(f"差异值: {cf_result.roi.p50 - result.roi.p50:.2f}")

---

Pre-launch ROI Ranking (All Combinations)

活动启动前ROI排名(全组合)

python
from itertools import product
from oransim.world_model import AgentSociety
from oransim.causal import CausalGraph
from oransim.diffusion import HawkesRollout
import pandas as pd

graph = CausalGraph.from_config("configs/default_graph.yaml")
society = AgentSociety(n_agents=5_000, vertical="beauty", platform="xhs", llm_mode="mock")

creatives = ["vid_A", "vid_B", "vid_C", "vid_D"]
kol_lists = [["kol_001"], ["kol_002"], ["kol_003"]]
budgets = [200_000, 500_000]

results = []
for creative, kols, budget in product(creatives, kol_lists, budgets):
    campaign = {"budget": budget, "kols": kols, "creative_ids": [creative], "duration_days": 14}
    rollout = HawkesRollout(graph=graph, society=society)
    r = rollout.run(campaign, n_simulations=30)
    results.append({
        "creative": creative,
        "kol": kols[0],
        "budget": budget,
        "roi_p35": r.roi.p35,
        "roi_p50": r.roi.p50,
        "roi_p65": r.roi.p65,
    })

df = pd.DataFrame(results).sort_values("roi_p50", ascending=False)
print(df.head(5).to_string())   # top 5 combinations

python
from itertools import product
from oransim.world_model import AgentSociety
from oransim.causal import CausalGraph
from oransim.diffusion import HawkesRollout
import pandas as pd

graph = CausalGraph.from_config("configs/default_graph.yaml")
society = AgentSociety(n_agents=5_000, vertical="beauty", platform="xhs", llm_mode="mock")

creatives = ["vid_A", "vid_B", "vid_C", "vid_D"]
kol_lists = [["kol_001"], ["kol_002"], ["kol_003"]]
budgets = [200_000, 500_000]

results = []
for creative, kols, budget in product(creatives, kol_lists, budgets):
    campaign = {"budget": budget, "kols": kols, "creative_ids": [creative], "duration_days": 14}
    rollout = HawkesRollout(graph=graph, society=society)
    r = rollout.run(campaign, n_simulations=30)
    results.append({
        "creative": creative,
        "kol": kols[0],
        "budget": budget,
        "roi_p35": r.roi.p35,
        "roi_p50": r.roi.p50,
        "roi_p65": r.roi.p65,
    })

df = pd.DataFrame(results).sort_values("roi_p50", ascending=False)
print(df.head(5).to_string())   # 排名前5的组合

Common Patterns

常见使用模式

Pattern 1: Mock mode for CI / testing

模式1:用于CI/测试的模拟模式

python
import os
os.environ["LLM_MODE"] = "mock"

from oransim.world_model import AgentSociety
society = AgentSociety(n_agents=100, vertical="beauty", platform="xhs", llm_mode="mock")
python
import os
os.environ["LLM_MODE"] = "mock"

from oransim.world_model import AgentSociety
society = AgentSociety(n_agents=100, vertical="beauty", platform="xhs", llm_mode="mock")

All LLM calls return deterministic stubs — fast, free, reproducible

所有LLM调用返回确定性桩代码——快速、免费、可复现

undefined
undefined

Pattern 2: Check if backend is in mock mode

模式2:检查后端是否处于模拟模式

python
import httpx
health = httpx.get("http://localhost:8001/health").json()
if health.get("llm_mode") == "mock":
    print("WARNING: Running in mock mode — LLM features are stubs")
python
import httpx
health = httpx.get("http://localhost:8001/health").json()
if health.get("llm_mode") == "mock":
    print("警告:当前运行在模拟模式下——LLM功能为桩代码")

Pattern 3: Inspect a prediction's causal path

模式3:查看预测结果的因果路径

python
result = httpx.post("http://localhost:8001/api/predict", json=payload).json()
python
result = httpx.post("http://localhost:8001/api/predict", json=payload).json()

Every prediction includes which causal nodes fired

每一项预测都会包含触发的因果节点

for node in result["causal_path"]: print(f"{node['id']:30s} weight={node['weight']:.3f} layer={node['layer']}")
undefined
for node in result["causal_path"]: print(f"{node['id']:30s} 权重={node['weight']:.3f} 层级={node['layer']}")
undefined

Pattern 4: Load the LightGBM quantile baseline (fast mode)

模式4:加载LightGBM分位数基线(快速模式)

python
import pickle, numpy as np

with open("models/lgbm_quantile_baseline.pkl", "rb") as f:
    model = pickle.load(f)
python
import pickle, numpy as np

with open("models/lgbm_quantile_baseline.pkl", "rb") as f:
    model = pickle.load(f)

Feature vector: [budget, n_kols, avg_fans, duration_days, platform_enc]

特征向量: [预算, KOL数量, 平均粉丝数, 活动天数, 平台编码]

X = np.array([[500_000, 2, 150_000, 14, 0]]) # 0=xhs, 1=douyin p35, p50, p65 = model.predict(X) print(f"ROI P35={p35[0]:.2f} P50={p50[0]:.2f} P65={p65[0]:.2f}")

---
X = np.array([[500_000, 2, 150_000, 14, 0]]) # 0=小红书, 1=抖音 p35, p50, p65 = model.predict(X) print(f"ROI P35={p35[0]:.2f} P50={p50[0]:.2f} P65={p65[0]:.2f}")

---

Project Structure

项目结构

oransim/
├── oransim/
│   ├── api.py                  # FastAPI app + god-file (being refactored to api_routers/)
│   ├── api_routers/            # Split routers: predict, sandbox, graph, adapters
│   ├── causal/                 # SCM, do()-operator, 64-node graph, Pearl 3-step
│   ├── world_model/            # AgentSociety, IPF population synthesis, soul personas
│   ├── diffusion/              # Causal Neural Hawkes Process rollout (14-day)
│   └── adapters/               # Platform adapters: xhs_v1, tiktok_agent, douyin, ...
├── frontend/
│   ├── index.html
│   └── js/                     # Modular JS: hero, tabs, cascade animation
├── configs/
│   └── default_graph.yaml      # 64 nodes / 117 edges causal graph definition
├── models/
│   └── lgbm_quantile_baseline.pkl
├── data/                       # 21k-note OSS demo corpus
├── docs/
│   └── en/quickstart.md
└── .env.example

oransim/
├── oransim/
│   ├── api.py                  # FastAPI应用(正在重构为api_routers/)
│   ├── api_routers/            # 拆分的路由模块:predict、sandbox、graph、adapters
│   ├── causal/                 # SCM、do()算子、64节点图、Pearl三步法
│   ├── world_model/            # AgentSociety、IPF人群合成、灵魂人设
│   ├── diffusion/              # Causal Neural Hawkes Process部署(14天周期)
│   └── adapters/               # 平台适配器:xhs_v1、tiktok_agent、douyin等
├── frontend/
│   ├── index.html
│   └── js/                     # 模块化JS:hero、tabs、级联动画
├── configs/
│   └── default_graph.yaml      # 64节点/117条边的因果图定义
├── models/
│   └── lgbm_quantile_baseline.pkl
├── data/                       # 2.1万条笔记的开源演示语料库
├── docs/
│   └── en/quickstart.md
└── .env.example

Troubleshooting

故障排查

Backend returns mock data even with API key set

已设置API密钥,但后端仍返回模拟数据

Check the yellow banner in the frontend. Verify env vars are exported:
bash
echo $LLM_MODE    # should be "api"
echo $LLM_API_KEY # should be non-empty
查看前端的黄色提示栏。确认环境变量已正确导出:
bash
echo $LLM_MODE    # 应为"api"
echo $LLM_API_KEY # 不应为空

Restart the server after setting env vars — uvicorn reads them at startup

设置环境变量后重启服务器——uvicorn在启动时读取变量

undefined
undefined

ModuleNotFoundError: oransim

ModuleNotFoundError: oransim

Install in editable mode from the repo root:
bash
pip install -e '.[dev]'
在仓库根目录以可编辑模式安装:
bash
pip install -e '.[dev]'

Simulation times out (> 120s)

模拟超时(超过120秒)

Reduce agent count or use fast mode:
python
undefined
减少Agent数量或使用快速模式:
python
undefined

In payload:

在请求体中设置:

{"mode": "fast", "n_simulations": 30}
{"mode": "fast", "n_simulations": 30}

Or reduce society size in direct SDK usage:

或在直接使用SDK时缩小群体规模:

AgentSociety(n_agents=1_000, ...)
undefined
AgentSociety(n_agents=1_000, ...)
undefined

CORS errors when calling API from browser

从浏览器调用API时出现CORS错误

The FastAPI app includes CORS middleware for
localhost:8090
. If using a different port:
python
undefined
FastAPI应用已包含针对
localhost:8090
的CORS中间件。如果使用其他端口:
python
undefined

In oransim/api.py, update the origins list or set:

在oransim/api.py中更新origins列表,或设置:

CORS_ORIGINS=http://localhost:YOUR_PORT uvicorn oransim.api:app --port 8001
undefined
CORS_ORIGINS=http://localhost:YOUR_PORT uvicorn oransim.api:app --port 8001
undefined

Counterfactual returns identical result to baseline

反事实结果与基线结果完全一致

In mock mode this is expected — stubs are deterministic. Switch to
LLM_MODE=api
for real divergence between baseline and intervention arms.
在模拟模式下这是预期行为——桩代码是确定性的。切换到
LLM_MODE=api
以获得基线与干预组之间的真实差异。

Enterprise data access (4.3M+ 小红书 notes, 2.1M+ creators)

企业级数据访问(430万+小红书笔记、210万+创作者)

The OSS corpus is 21k notes. For production-scale data:

开源语料库包含2.1万条笔记。如需生产级数据:

Key Concepts

核心概念

TermMeaning
do()
operator
Pearl's intervention operator — sets a variable to a value, cuts its incoming causal edges
Soul personaLLM-backed agent personality that reads actual creatives and decides engagement
Hawkes rolloutSelf-exciting point process simulating cascade of social shares over 14 days
P35/P50/P65Confidence bands on ROI — not point estimates, always a distribution
KOL tierTop (>1M fans), Mid (50k–1M), KOC (1k–50k), long-tail (<1k)
Fast modeLightGBM quantile baseline — seconds, no LLM calls
Full modeComplete agent simulation with LLM soul personas — minutes, requires
LLM_MODE=api
术语含义
do()
operator
Pearl干预算子——将变量设置为指定值,切断其传入因果边
Soul persona基于LLM的Agent人设,可读取真实创意内容并决定互动行为
Hawkes rollout自激点过程,模拟14天内的社交分享级联效应
P35/P50/P65ROI置信区间——非点估计值,始终为分布值
KOL tier头部(粉丝>100万)、中部(5万-100万)、KOC(1千-5万)、长尾(<1千)
Fast modeLightGBM分位数基线——耗时数秒,无需LLM调用
Full mode完整Agent模拟+LLM灵魂人设——耗时数分钟,需设置
LLM_MODE=api