feat: 添加观点传播系统

- 新增 Stance 数据结构 (optimism/fear)
- 情绪影响 stance (happy→乐观, anxious→恐惧)
- 实现 apply_social_influence 社交影响函数
- 确定性随机选择接触对象
- 单次变化限制 ±0.1

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
empty
2025-12-30 10:50:46 +08:00
parent af279bedd9
commit 554d37fd4c
6 changed files with 245 additions and 4 deletions

View File

@@ -6,6 +6,7 @@ from .models import (
)
from .global_events import GLOBAL_EVENT_POOL, GlobalEvent
from .opinions import generate_opinions
from .social_influence import apply_social_influence
MAX_EVENTS = 20
MAX_MEMORY_PER_AGENT = 3
@@ -86,6 +87,9 @@ def process_events(state: WorldState, events: List[Event]) -> WorldState:
# 生成角色对事件的观点
generate_opinions(state)
# 应用社交影响
apply_social_influence(state)
return state
@@ -119,6 +123,9 @@ def check_and_trigger_global_event(
# 生成角色对新事件的观点
generate_opinions(state)
# 应用社交影响
apply_social_influence(state)
result = GlobalEventResult(
triggered=True,
event=global_event.to_info()

View File

@@ -38,6 +38,12 @@ class Opinion(BaseModel):
tick: int # 生成时的 tick
class Stance(BaseModel):
"""角色立场"""
optimism: float = Field(default=0.5, ge=0.0, le=1.0)
fear: float = Field(default=0.5, ge=0.0, le=1.0)
class AgentState(BaseModel):
emotion: Emotion = Emotion.CALM
goal: str = ""
@@ -45,6 +51,8 @@ class AgentState(BaseModel):
opinion: Optional[Opinion] = None
# 记录已评论过的事件,防止重复生成
commented_effects: List[str] = Field(default_factory=list)
# 角色立场
stance: Stance = Field(default_factory=Stance)
class WorldState(BaseModel):

View File

@@ -1,6 +1,13 @@
"""角色事件评论系统 - 基于规则生成观点"""
from typing import Dict, List, Optional
from .models import WorldState, AgentState, Opinion, Emotion, WorldEffect
from .models import WorldState, AgentState, Opinion, Emotion, WorldEffect, Stance
# 情绪对 stance 的影响
EMOTION_STANCE_EFFECTS: Dict[str, Dict[str, float]] = {
"happy": {"optimism": 0.1, "fear": -0.05},
"calm": {"optimism": 0.0, "fear": 0.0},
"anxious": {"optimism": -0.05, "fear": 0.1},
}
# 观点模板effect_type -> emotion -> 观点列表
@@ -123,6 +130,20 @@ def get_opinion_text(
return templates[index]
def update_stance_from_emotion(agent: AgentState) -> None:
"""根据情绪更新 stance单次变化不超过 ±0.1"""
emotion_key = agent.emotion.value
effects = EMOTION_STANCE_EFFECTS.get(emotion_key, {})
# 更新 optimism
new_optimism = agent.stance.optimism + effects.get("optimism", 0.0)
agent.stance.optimism = max(0.0, min(1.0, new_optimism))
# 更新 fear
new_fear = agent.stance.fear + effects.get("fear", 0.0)
agent.stance.fear = max(0.0, min(1.0, new_fear))
def generate_opinions(state: WorldState) -> None:
"""为所有 agent 生成对当前活跃事件的观点"""
# 无活跃效果时,清空 opinion
@@ -155,6 +176,9 @@ def generate_opinions(state: WorldState) -> None:
tick=state.tick
)
# 根据情绪更新 stance
update_stance_from_emotion(agent)
# 记录到 memory
memory_entry = f"[{agent_id}{active_effect.name}的看法] {text}"
agent.memory.append(memory_entry)

View File

@@ -0,0 +1,126 @@
"""社交影响系统 - 角色间观点传播"""
from typing import Dict, List, Tuple
from .models import WorldState, AgentState, Stance
# 单次 stance 变化上限
MAX_STANCE_CHANGE = 0.1
# 影响强度系数
INFLUENCE_STRENGTH = 0.05
def get_contact_targets(
agent_id: str,
all_agent_ids: List[str],
tick: int
) -> List[str]:
"""
确定性地选择 1-2 个接触对象
基于 agent_id 和 tick 确保可复现
"""
# 排除自己
others = [aid for aid in all_agent_ids if aid != agent_id]
if not others:
return []
# 基于 hash 确定性选择
seed = hash(agent_id) + tick
# 选择 1 或 2 个目标
num_targets = 1 + (seed % 2)
num_targets = min(num_targets, len(others))
targets = []
for i in range(num_targets):
idx = (seed + i * 7) % len(others)
target = others[idx]
if target not in targets:
targets.append(target)
return targets
def calculate_influence(
agent_stance: Stance,
target_stance: Stance
) -> Tuple[float, float]:
"""
计算社交影响产生的 stance 变化
逻辑:
- 相近立场 → 强化原有立场
- 相反立场 → 产生反向影响
返回: (optimism_delta, fear_delta)
"""
# 计算差异
opt_diff = target_stance.optimism - agent_stance.optimism
fear_diff = target_stance.fear - agent_stance.fear
# 相近度判断(差异小于 0.3 视为相近)
opt_similar = abs(opt_diff) < 0.3
fear_similar = abs(fear_diff) < 0.3
opt_delta = 0.0
fear_delta = 0.0
if opt_similar:
# 相近 → 强化原有立场(向中间靠拢后再强化)
opt_delta = opt_diff * INFLUENCE_STRENGTH
else:
# 相反 → 反向影响(抵抗)
opt_delta = -opt_diff * INFLUENCE_STRENGTH * 0.5
if fear_similar:
fear_delta = fear_diff * INFLUENCE_STRENGTH
else:
fear_delta = -fear_diff * INFLUENCE_STRENGTH * 0.5
# 限制单次变化幅度
opt_delta = max(-MAX_STANCE_CHANGE, min(MAX_STANCE_CHANGE, opt_delta))
fear_delta = max(-MAX_STANCE_CHANGE, min(MAX_STANCE_CHANGE, fear_delta))
return opt_delta, fear_delta
def apply_social_influence(state: WorldState) -> None:
"""
应用社交影响:每个 agent 与 1-2 个其他 agent 接触
所有变化写入 world_state
"""
agent_ids = list(state.agents.keys())
if len(agent_ids) < 2:
return
# 收集所有变化(先计算,后应用,避免顺序影响)
stance_changes: Dict[str, Tuple[float, float]] = {}
for agent_id in agent_ids:
agent = state.agents[agent_id]
targets = get_contact_targets(agent_id, agent_ids, state.tick)
total_opt_delta = 0.0
total_fear_delta = 0.0
for target_id in targets:
target = state.agents[target_id]
opt_delta, fear_delta = calculate_influence(
agent.stance, target.stance
)
total_opt_delta += opt_delta
total_fear_delta += fear_delta
# 限制总变化
total_opt_delta = max(-MAX_STANCE_CHANGE, min(MAX_STANCE_CHANGE, total_opt_delta))
total_fear_delta = max(-MAX_STANCE_CHANGE, min(MAX_STANCE_CHANGE, total_fear_delta))
stance_changes[agent_id] = (total_opt_delta, total_fear_delta)
# 应用所有变化
for agent_id, (opt_delta, fear_delta) in stance_changes.items():
agent = state.agents[agent_id]
new_opt = agent.stance.optimism + opt_delta
new_fear = agent.stance.fear + fear_delta
agent.stance.optimism = max(0.0, min(1.0, new_opt))
agent.stance.fear = max(0.0, min(1.0, new_fear))

View File

@@ -1,6 +1,6 @@
import json
from pathlib import Path
from .models import WorldState, AgentState, GlobalMeter
from .models import WorldState, AgentState, GlobalMeter, Stance
STATE_FILE = Path(__file__).parent.parent / "state.json"
@@ -12,8 +12,18 @@ def get_default_state() -> WorldState:
weather="sunny",
town_mood=0,
agents={
"alice": AgentState(emotion="calm", goal="探索小镇", memory=[]),
"bob": AgentState(emotion="calm", goal="与人交流", memory=[]),
"alice": AgentState(
emotion="calm",
goal="探索小镇",
memory=[],
stance=Stance(optimism=0.6, fear=0.4),
),
"bob": AgentState(
emotion="calm",
goal="与人交流",
memory=[],
stance=Stance(optimism=0.4, fear=0.6),
),
},
events=[],
global_meter=GlobalMeter(value=0, threshold=100, cooldown=0),