项目文件夹

文件
wehub-resource-sync aa1a5d5763
CI / Release hygiene (push) Has been cancelled
CI / Python targeted tests (push) Has been cancelled
CI / Frontend build (push) Has been cancelled
Pages / Build and deploy public pages (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:18:17 +08:00

155 行
6.1 KiB
Python

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
"""Deterministic pixel-town agent for replayable multi-step demos."""
from __future__ import annotations
from datetime import datetime
import re
from typing import Any
from agentsociety2.agent.base import AgentBase
class ScriptedPixelTownAgent(AgentBase):
"""Agent that executes a configured step script against PixelTownSocialEnv."""
def __init__(
self,
id: int,
profile: Any,
name: str | None = None,
script: list[dict[str, Any]] | None = None,
) -> None:
super().__init__(id=id, profile=profile, name=name)
self._script = script or []
self._step_index = 0
self._last_result: dict[str, Any] = {}
self._queued_interventions: list[dict[str, Any]] = []
@classmethod
def mcp_description(cls) -> str:
return """ScriptedPixelTownAgent: deterministic replay demo agent.
Provide a `script` list. Each entry can set location/action/status/emotion,
phase, direct_messages, and group_messages. It is useful for fast multi-step
frontend replay validation without spending LLM calls.
"""
async def ask(self, message: str, readonly: bool = True) -> str:
if any(keyword in message for keyword in ("今天", "干了什么", "做了什么", "what did")):
completed = self._script[: min(self._step_index, len(self._script))]
if completed:
summaries = []
for index, action in enumerate(completed, start=1):
phase = action.get("phase") or "阶段"
location = action.get("location") or "未知地点"
activity = action.get("action") or action.get("status") or "等待"
summaries.append(f"{index}. {phase}:在{location}{activity}")
return (
f"{self.name} 今天已经执行到第 {self._step_index} 步。"
"主要做了:\n" + "\n".join(summaries)
)
return f"{self.name} 今天还没有执行新的行动。"
if any(keyword in message for keyword in ("听到", "收到", "实时提问", "身份回答")):
role = self.get_profile().get("role", "居民")
return (
f"我是 {self.name},身份是{role}。我收到了你的实时提问。"
f"当前我执行到第 {self._step_index} 步,最近状态是:{self._last_result or '暂无'}。"
)
return (
f"{self.name} is a scripted pixel-town agent. "
f"Current script step: {self._step_index}. Last result: {self._last_result}"
)
async def step(self, tick: int, t: datetime) -> str:
if self._queued_interventions:
action = self._queued_interventions.pop(0)
else:
action = (
self._script[self._step_index]
if self._step_index < len(self._script)
else {}
)
env = self._find_pixel_env()
if env is None:
result = {"error": "PixelTownSocialEnv not found"}
else:
result = await env.apply_scripted_action(self.id, action)
self._last_result = result
self._step_index += 1
return str(result)
def queue_intervention(self, instruction: str) -> str:
action = self._build_intervention_action(instruction)
self._queued_interventions.append(action)
return "已排队到下一次 step"
def _build_intervention_action(self, instruction: str) -> dict[str, Any]:
clean_instruction = " ".join(str(instruction).split())
location = self._match_first(
clean_instruction,
[
r"(?:前往|移动到|去到|去|到)([^,。,;;、]+)",
r"(?:location|地点|位置)[:= ]+([^,。,;;]+)",
],
)
status = self._match_first(
clean_instruction,
[
r"(?:状态设为|状态设置为|将状态设为|status[:= ]+)([^,。,;;]+)",
r"(?:设为|设置为)([^,。,;;]+)",
],
)
return {
"phase": "实时干预",
"location": location,
"action": f"执行实时干预:{clean_instruction}",
"status": status or "intervened",
"emotion": "focused",
"event": f"{self.name} 接收到实时干预:{clean_instruction}",
}
@staticmethod
def _match_first(text: str, patterns: list[str]) -> str | None:
for pattern in patterns:
match = re.search(pattern, text, flags=re.IGNORECASE)
if match:
value = match.group(1).strip()
return value or None
return None
async def dump(self) -> dict[str, Any]:
return {
"id": self.id,
"name": self.name,
"profile": self.get_profile(),
"script": self._script,
"step_index": self._step_index,
"last_result": self._last_result,
"queued_interventions": self._queued_interventions,
}
async def load(self, dump_data: dict[str, Any]) -> None:
self._id = int(dump_data.get("id", self._id))
self._name = str(dump_data.get("name", self._name))
self._profile = dump_data.get("profile", self._profile)
script = dump_data.get("script")
if isinstance(script, list):
self._script = script
self._step_index = int(dump_data.get("step_index", self._step_index))
last_result = dump_data.get("last_result")
if isinstance(last_result, dict):
self._last_result = last_result
queued_interventions = dump_data.get("queued_interventions")
if isinstance(queued_interventions, list):
self._queued_interventions = [
item for item in queued_interventions if isinstance(item, dict)
]
def _find_pixel_env(self) -> Any:
if self._env is None:
return None
for module in getattr(self._env, "env_modules", []):
if hasattr(module, "apply_scripted_action"):
return module
return None