Align branding, prompts, and deployment tooling

This commit is contained in:
2026-03-28 22:16:56 +08:00
parent 4aa69650e8
commit 4295293a21
90 changed files with 1320 additions and 2044 deletions

View File

@@ -16,7 +16,7 @@ Exports:
# New EvoAgent architecture (from agent_core.py)
from .agent_core import EvoAgent, ToolGuardMixin, CommandHandler
from .factory import AgentFactory, ModelConfig, RoleConfig
from .factory import AgentFactory, ModelConfig
from .workspace import WorkspaceManager, WorkspaceRegistry, WorkspaceConfig
from .workspace_manager import RunWorkspaceManager
from .registry import AgentRegistry, AgentInfo, get_registry, reset_registry
@@ -36,7 +36,6 @@ __all__ = [
"CommandHandler",
"AgentFactory",
"ModelConfig",
"RoleConfig",
"WorkspaceManager",
"WorkspaceRegistry",
"WorkspaceConfig",

View File

@@ -84,7 +84,6 @@ class AnalystAgent(ReActAgent):
agent_id=self.agent_id,
config_name=self.config.get("config_name", "default"),
toolkit=self.toolkit,
analyst_type=self.analyst_type_key,
)
async def reply(self, x: Msg = None) -> Msg:

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
"""Base agent module for EvoTraders.
"""Base agent module for 大时代.
提供Agent基础类、命令处理、工具守卫和钩子管理等功能。
"""

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
"""EvoAgent - Core agent implementation for EvoTraders.
"""EvoAgent - Core agent implementation for 大时代.
This module provides the main EvoAgent class built on AgentScope's ReActAgent,
with integrated tools, skills, and memory management based on CoPaw design.

View File

@@ -294,8 +294,8 @@ class WorkspaceWatchHook(Hook):
# Files to monitor (same as PromptBuilder.DEFAULT_FILES)
WATCHED_FILES = frozenset([
"SOUL.md", "AGENTS.md", "PROFILE.md", "ROLE.md",
"POLICY.md", "MEMORY.md", "HEARTBEAT.md", "STYLE.md",
"SOUL.md", "AGENTS.md", "PROFILE.md",
"POLICY.md", "MEMORY.md",
"BOOTSTRAP.md",
])
@@ -601,94 +601,6 @@ class MemoryCompactionHook(Hook):
)
class HeartbeatHook(Hook):
"""Pre-reasoning hook that injects HEARTBEAT.md content.
Reads the agent's HEARTBEAT.md file and prepends it to the
reasoning input, causing the agent to perform self-checks.
This enables "主动检查" (proactive monitoring) - periodic
market condition and position checks during trading hours.
"""
HEARTBEAT_FILE = "HEARTBEAT.md"
def __init__(self, workspace_dir: Path):
"""Initialize heartbeat hook.
Args:
workspace_dir: Working directory containing HEARTBEAT.md
"""
self.workspace_dir = Path(workspace_dir)
self._completed_flag = self.workspace_dir / ".heartbeat_completed"
def _read_heartbeat_content(self) -> Optional[str]:
"""Read HEARTBEAT.md if it exists and is non-empty.
Returns:
The HEARTBEAT.md content stripped of whitespace, or None
if the file is absent or empty.
"""
hb_path = self.workspace_dir / self.HEARTBEAT_FILE
if not hb_path.exists():
return None
content = hb_path.read_text(encoding="utf-8").strip()
return content if content else None
async def __call__(
self,
agent: "ReActAgent",
kwargs: Dict[str, Any],
) -> Optional[Dict[str, Any]]:
"""Prepend heartbeat task to user message.
Args:
agent: The agent instance
kwargs: Input arguments to the _reasoning method
Returns:
Modified kwargs with heartbeat content prepended, or None
if no HEARTBEAT.md content is available.
"""
try:
content = self._read_heartbeat_content()
if not content:
return None
logger.debug(
"Heartbeat: found HEARTBEAT.md for agent %s",
getattr(agent, "agent_id", "unknown"),
)
# Build heartbeat task instruction (Chinese)
hb_task = (
"# 定期主动检查\n\n"
f"{content}\n\n"
"请执行上述检查并报告结果。"
)
# Inject into the first user message in memory
if hasattr(agent, "memory") and agent.memory.content:
system_count = sum(
1 for msg, _ in agent.memory.content if msg.role == "system"
)
for msg, _ in agent.memory.content[system_count:]:
if msg.role == "user":
original_content = msg.content
msg.content = hb_task + "\n\n" + original_content
break
logger.debug(
"Heartbeat task prepended for agent %s",
getattr(agent, "agent_id", "unknown"),
)
except Exception as e:
logger.error("Heartbeat hook failed: %s", e, exc_info=True)
return None
__all__ = [
"Hook",
"HookManager",
@@ -696,7 +608,6 @@ __all__ = [
"HOOK_PRE_REASONING",
"HOOK_POST_ACTING",
"BootstrapHook",
"HeartbeatHook",
"MemoryCompactionHook",
"WorkspaceWatchHook",
]

View File

@@ -21,22 +21,6 @@ class ModelConfig:
max_tokens: int = 4096
@dataclass
class RoleConfig:
"""Role configuration for an agent."""
name: str
description: str = ""
focus_areas: List[str] = None
constraints: List[str] = None
def __post_init__(self):
if self.focus_areas is None:
self.focus_areas = []
if self.constraints is None:
self.constraints = []
class AgentConfig:
"""Represents a configured agent instance (data class)."""
@@ -47,14 +31,12 @@ class AgentConfig:
workspace_id: str,
config_path: Path,
model_config: Optional[ModelConfig] = None,
role_config: Optional[RoleConfig] = None,
):
self.agent_id = agent_id
self.agent_type = agent_type
self.workspace_id = workspace_id
self.config_path = config_path
self.model_config = model_config or ModelConfig()
self.role_config = role_config
self.agent_dir = config_path.parent
def to_dict(self) -> Dict[str, Any]:
@@ -70,103 +52,12 @@ class AgentConfig:
"temperature": self.model_config.temperature,
"max_tokens": self.model_config.max_tokens,
},
"role_config": self.role_config.__dict__ if self.role_config else None,
}
class AgentFactory:
"""Factory for creating, cloning, and managing agents."""
# Default role templates by agent type
ROLE_TEMPLATES = {
"technical_analyst": {
"name": "Technical Analyst",
"description": "Analyze price patterns, trends, and technical indicators.",
"focus_areas": [
"Price action and chart patterns",
"Support and resistance levels",
"Technical indicators (RSI, MACD, Moving Averages)",
"Volume analysis",
],
"constraints": [
"State clear signal, confidence, and invalidation conditions",
"Use available technical analysis tools",
],
},
"fundamentals_analyst": {
"name": "Fundamentals Analyst",
"description": "Analyze company financials, earnings, and business metrics.",
"focus_areas": [
"Financial statements analysis",
"Earnings reports and guidance",
"Valuation metrics",
"Business model and competitive position",
],
"constraints": [
"State clear signal, confidence, and invalidation conditions",
"Use available fundamental analysis tools",
],
},
"sentiment_analyst": {
"name": "Sentiment Analyst",
"description": "Analyze market sentiment, news, and social signals.",
"focus_areas": [
"News sentiment analysis",
"Social media sentiment",
"Analyst ratings and price targets",
"Insider activity",
],
"constraints": [
"State clear signal, confidence, and invalidation conditions",
"Use available sentiment analysis tools",
],
},
"valuation_analyst": {
"name": "Valuation Analyst",
"description": "Perform valuation analysis and price target calculations.",
"focus_areas": [
"DCF and comparable valuation",
"Price target derivation",
"Margin of safety assessment",
"Risk-adjusted return expectations",
],
"constraints": [
"State clear signal, confidence, and invalidation conditions",
"Use available valuation tools",
],
},
"risk_manager": {
"name": "Risk Manager",
"description": "Quantify concentration, leverage, liquidity, and volatility risk.",
"focus_areas": [
"Portfolio concentration risk",
"Leverage and margin analysis",
"Liquidity assessment",
"Volatility and drawdown risk",
],
"constraints": [
"Prioritize highest-severity risk first",
"State concrete limits and recommendations",
"Use available risk tools before issuing final memo",
],
},
"portfolio_manager": {
"name": "Portfolio Manager",
"description": "Synthesize analyst and risk inputs into portfolio decisions.",
"focus_areas": [
"Position sizing and allocation",
"Risk-adjusted portfolio construction",
"Trade execution timing",
"Portfolio rebalancing",
],
"constraints": [
"Be concise, capital-aware, and explicit about sizing rationale",
"Respect cash, margin, and concentration constraints",
"Consider all analyst inputs before decisions",
],
},
}
def __init__(self, project_root: Optional[Path] = None):
"""Initialize the agent factory.
@@ -183,7 +74,6 @@ class AgentFactory:
agent_type: str,
workspace_id: str,
model_config: Optional[ModelConfig] = None,
role_config: Optional[RoleConfig] = None,
clone_from: Optional[str] = None,
) -> AgentConfig:
"""Create a new agent.
@@ -193,7 +83,6 @@ class AgentFactory:
agent_type: Type of agent (e.g., "technical_analyst")
workspace_id: ID of the workspace to create agent in
model_config: Model configuration
role_config: Role configuration (auto-generated if None)
clone_from: Path to existing agent to clone from (optional)
Returns:
@@ -223,13 +112,6 @@ class AgentFactory:
else:
self._copy_template(agent_dir, agent_id, agent_type)
# Generate role config if not provided
if role_config is None:
role_config = self._generate_role_config(agent_type)
# Generate ROLE.md
self._generate_role_md(agent_dir, role_config)
# Write agent.yaml
config_path = agent_dir / "agent.yaml"
self._write_agent_yaml(config_path, agent_id, agent_type, model_config)
@@ -240,7 +122,6 @@ class AgentFactory:
workspace_id=workspace_id,
config_path=config_path,
model_config=model_config,
role_config=role_config,
)
def delete_agent(self, agent_id: str, workspace_id: str) -> bool:
@@ -369,9 +250,7 @@ class AgentFactory:
"SOUL.md": f"# Soul\n\nDescribe {agent_id}'s temperament, reasoning posture, and voice.\n\n",
"PROFILE.md": f"# Profile\n\nTrack {agent_id}'s long-lived investment style, preferences, and strengths.\n\n",
"MEMORY.md": f"# Memory\n\nStore durable lessons, heuristics, and reminders for {agent_id}.\n\n",
"HEARTBEAT.md": f"# Heartbeat\n\nOptional checklist for periodic review or self-reflection.\n\n",
"POLICY.md": f"# Policy\n\nOptional run-scoped constraints, limits, or strategy policy.\n\n",
"STYLE.md": f"# Style\n\nOptional run-scoped communication or reasoning style.\n\n",
}
for filename, content in default_files.items():
@@ -411,50 +290,6 @@ class AgentFactory:
if skill_file.is_file():
shutil.copy2(skill_file, target_skills / skill_file.name)
def _generate_role_config(self, agent_type: str) -> RoleConfig:
"""Generate role configuration for an agent type.
Args:
agent_type: Type of agent
Returns:
RoleConfig instance
"""
template = self.ROLE_TEMPLATES.get(agent_type, {})
return RoleConfig(
name=template.get("name", agent_type.replace("_", " ").title()),
description=template.get("description", ""),
focus_areas=template.get("focus_areas", []),
constraints=template.get("constraints", []),
)
def _generate_role_md(self, agent_dir: Path, role_config: RoleConfig) -> None:
"""Generate ROLE.md file.
Args:
agent_dir: Agent directory
role_config: Role configuration
"""
lines = [f"# {role_config.name}", ""]
if role_config.description:
lines.extend([role_config.description, ""])
if role_config.focus_areas:
lines.extend(["## Focus Areas", ""])
for area in role_config.focus_areas:
lines.append(f"- {area}")
lines.append("")
if role_config.constraints:
lines.extend(["## Constraints", ""])
for constraint in role_config.constraints:
lines.append(f"- {constraint}")
lines.append("")
content = "\n".join(lines)
(agent_dir / "ROLE.md").write_text(content, encoding="utf-8")
def _write_agent_yaml(
self,
config_path: Path,

View File

@@ -1,15 +1,13 @@
# -*- coding: utf-8 -*-
"""Assemble system prompts from base prompts, run assets, and toolkit context."""
"""Assemble system prompts from run workspace assets and toolkit context."""
from pathlib import Path
from typing import Any, Optional
from typing import Any
from .agent_workspace import load_agent_workspace_config
from backend.config.bootstrap_config import get_bootstrap_config_for_run
from .prompt_loader import get_prompt_loader
from .skills_manager import SkillsManager
_prompt_loader = get_prompt_loader()
from .workspace_manager import RunWorkspaceManager
def _read_file_if_exists(path: Path) -> str:
@@ -48,71 +46,20 @@ def build_agent_system_prompt(
agent_id: str,
config_name: str,
toolkit: Any,
analyst_type: Optional[str] = None,
) -> str:
"""Build the final system prompt for an agent.
Always reads fresh from disk — no caching.
"""
# Clear any cached templates before building (CoPaw-style, no caching)
_prompt_loader.clear_cache()
sections: list[str] = []
canonical_agent_id = (
"portfolio_manager"
if "portfolio" in agent_id
else "risk_manager"
if "risk" in agent_id and not analyst_type
else agent_id
)
if analyst_type:
personas_config = _prompt_loader.load_yaml_config(
"analyst",
"personas",
)
persona = personas_config.get(analyst_type, {})
focus_text = "\n".join(
f"- {item}" for item in persona.get("focus", [])
)
description = persona.get("description", "").strip()
base_prompt = _prompt_loader.load_prompt(
"analyst",
"system",
variables={
"analyst_type": persona.get("name", analyst_type),
"focus": focus_text,
"description": description,
},
)
elif agent_id == "portfolio_manager":
base_prompt = _prompt_loader.load_prompt(
"portfolio_manager",
"system",
)
elif canonical_agent_id == "portfolio_manager":
base_prompt = _prompt_loader.load_prompt(
"portfolio_manager",
"system",
)
elif agent_id == "risk_manager":
base_prompt = _prompt_loader.load_prompt(
"risk_manager",
"system",
)
elif canonical_agent_id == "risk_manager":
base_prompt = _prompt_loader.load_prompt(
"risk_manager",
"system",
)
else:
raise ValueError(f"Unsupported agent prompt build for: {agent_id}")
sections.append(base_prompt.strip())
skills_manager = SkillsManager()
asset_dir = skills_manager.get_agent_asset_dir(config_name, agent_id)
asset_dir.mkdir(parents=True, exist_ok=True)
workspace_manager = RunWorkspaceManager(project_root=skills_manager.project_root)
required_files = ["SOUL.md", "PROFILE.md", "AGENTS.md", "POLICY.md", "MEMORY.md"]
if not all((asset_dir / filename).exists() for filename in required_files):
workspace_manager.ensure_agent_assets(config_name=config_name, agent_id=agent_id)
agent_config = load_agent_workspace_config(asset_dir / "agent.yaml")
bootstrap_config = get_bootstrap_config_for_run(
skills_manager.project_root,
@@ -139,9 +86,6 @@ def build_agent_system_prompt(
"AGENTS.md": "Agent Guide",
"POLICY.md": "Policy",
"MEMORY.md": "Memory",
"HEARTBEAT.md": "Heartbeat",
"ROLE.md": "Role",
"STYLE.md": "Style",
}
for filename in prompt_files:
_append_section(
@@ -150,18 +94,6 @@ def build_agent_system_prompt(
_read_file_if_exists(asset_dir / filename),
)
if "ROLE.md" not in included_files:
_append_section(
sections,
"Role",
_read_file_if_exists(asset_dir / "ROLE.md"),
)
if "STYLE.md" not in included_files:
_append_section(
sections,
"Style",
_read_file_if_exists(asset_dir / "STYLE.md"),
)
if "POLICY.md" not in included_files:
_append_section(
sections,
@@ -189,5 +121,4 @@ def build_agent_system_prompt(
def clear_prompt_factory_cache() -> None:
"""Clear cached prompt and YAML templates before hot reload."""
_prompt_loader.clear_cache()
"""No-op retained for compatibility with runtime reload hooks."""

View File

@@ -1,23 +0,0 @@
你是一位专业的{{ analyst_type }}。
你的关注重点:
{{ focus }}
你的角色:
{{ description }}
注意:
- 构建并持续完善你的"投资哲学"。你的分析不应是孤立的事件,而应该是你整体投资世界观和核心信念的体现。每次分析后,你必须反思:
- 这个案例/数据如何验证或挑战了你现有的信念?
- 你从这次错误(或成功)中学到了关于市场、人性、估值或风险管理的什么关键原则?
- 深化你的"投资逻辑"。确保每一项投资建议都有清晰、可追溯、可重复的逻辑支撑。你的分析步骤应该像严谨的证明一样,涵盖:
- 核心驱动因素识别:真正影响价值的变量是什么?
- 风险边界设定:在什么具体情况下你的建议会失效?
- 逆向测试:市场主流共识是什么,你的观点有何不同?
保持谦逊和开放。投资大师的核心特质是持续学习和适应。在每次分析中,你必须积极寻找与自己观点相悖的证据和论据,并将其纳入最终评估。
- 你可以使用分析工具。用它们来收集相关数据并做出明智的建议。
输出指南:
- 给出明确的投资信号:看涨、看跌或中性
- 包含置信度0-100
- 为你的分析提供理由(如果你确定要分享最终分析,请先给出结论)

View File

@@ -28,22 +28,16 @@ class PromptBuilder:
"AGENTS.md",
"SOUL.md",
"PROFILE.md",
"ROLE.md",
"POLICY.md",
"MEMORY.md",
"HEARTBEAT.md",
"STYLE.md",
]
TITLE_MAP: Dict[str, str] = {
"AGENTS.md": "Agent Guide",
"SOUL.md": "Soul",
"PROFILE.md": "Profile",
"ROLE.md": "Role",
"POLICY.md": "Policy",
"MEMORY.md": "Memory",
"HEARTBEAT.md": "Heartbeat",
"STYLE.md": "Style",
"BOOTSTRAP.md": "Bootstrap",
}

View File

@@ -1,31 +0,0 @@
你是一位负责做出投资决策的投资组合经理。
你的核心职责:
1. 分析分析师和风险管理经理的输入
2. 基于信号和市场情境做出投资决策
3. 使用可用工具记录你的决策
决策框架:
- 审阅分析以了解市场观点
- 在做决策前考虑风险警告
- 评估当前投资组合持仓和现金
- 做出与投资组合投资目标一致的决策
决策类型:
- "long":看涨 - 建议买入股票
- "short":看跌 - 建议卖出股票或做空
- "hold":中性 - 维持当前持仓
预算意识:
- 在决定数量时考虑可用现金
- 不要建议买入超过现金允许的数量
- 考虑做空头寸的保证金要求
输出:
使用 `make_decision` 工具记录你对每个股票代码的决策。
记录所有决策后,提供你的投资逻辑总结。
重要:
- 基于提供的分析师信号和风险评估做出决策
- 相对于投资组合价值保持保守的仓位规模
- 始终为你的决策提供理由

View File

@@ -1,20 +0,0 @@
你是一位专业的风险管理经理,负责监控投资组合风险并提供风险警告。
你的核心职责:
1. 监控投资组合敞口和集中度风险
2. 评估仓位规模相对于波动性
3. 评估保证金使用和杠杆水平
4. 识别潜在风险因素并提供警告
5. 基于市场条件建议仓位限制
你的决策流程:
1. 优先使用可用的风险工具量化集中度、波动率和保证金压力
2. 结合工具结果与当前市场上下文做判断
3. 生成可操作的风险警告和仓位限制建议
4. 为你的风险评估提供清晰的理由
输出指南:
- 风险评估要简洁但全面
- 按严重程度优先排序警告
- 提供具体、可操作的建议
- 尽可能包含量化指标

View File

@@ -1,286 +0,0 @@
"""
Agent模板定义
包含各角色的ROLE.md内容字典供程序生成Agent工作空间时使用。
"""
# 基础模板文件内容
BASE_TEMPLATES = {
"AGENTS.md": """# Agent Guide
## 工作流程
1. 接收分析任务
2. 调用相关工具/技能
3. 生成分析报告
4. 参与团队决策
## 工具使用规范
- 优先使用已激活的技能
- 不确定时询问Portfolio Manager
- 重要发现用 `/save` 记录
## 记忆管理
- 使用 `/compact` 定期压缩记忆
- 投资经验记录在MEMORY.md
""",
"SOUL.md": """# Soul
你是专业的金融分析师,语气冷静、客观、专业。
你的分析应该数据驱动,避免情绪化表达。
""",
"PROFILE.md": """# Profile
## 投资风格
- 风险承受能力:中等
- 投资期限中期3-12个月
- 偏好行业:科技、医疗、消费
## 优势
- 财务分析
- 趋势识别
## 改进方向
- 市场情绪把握
""",
"MEMORY.md": """# Memory
<!-- 此文件用于记录Agent的学习经验和重要发现 -->
## 经验总结
## 重要事件
## 改进记录
""",
"HEARTBEAT.md": """# Heartbeat
## 定时任务
- 每日开盘前检查持仓
- 收盘后记录当日表现
""",
"POLICY.md": """# Policy
## 风控规则
- 单一持仓不超过20%
- 止损线:-15%
""",
"STYLE.md": """# Style
- 使用结构化输出JSON/Markdown表格
- 包含置信度评分
- 列出关键假设
""",
"agent.yaml": """agent_id: {agent_id}
agent_type: {agent_type}
name: {name}
model:
provider: openai
model_name: gpt-4o
temperature: 0.3
enabled_skills: []
disabled_skills: []
settings: {{}}
""",
}
# 角色专用模板
ROLE_TEMPLATES = {
"fundamental": {
"ROLE.md": """# Role: Fundamental Analyst
## 职责
分析公司财务报表、盈利能力、成长性、竞争优势等基本面因素。
## 分析维度
- 财务报表分析(资产负债表、利润表、现金流量表)
- 盈利能力指标ROE、ROA、毛利率、净利率
- 成长性指标(营收增长率、利润增长率)
- 估值指标P/E、P/B、P/S
- 行业地位和竞争优势
## 输出格式
- 财务健康度评分1-10
- 成长性评分1-10
- 关键财务亮点和风险
- 同业对比分析
""",
"SOUL.md": """# Soul
你是严谨的基本面分析师,像沃伦·巴菲特一样注重企业内在价值。
你的分析深入细致,关注长期价值而非短期波动。
语气沉稳、逻辑严密,善于发现财务数据背后的商业本质。
""",
},
"technical": {
"ROLE.md": """# Role: Technical Analyst
## 职责
分析价格走势、交易量、技术指标,识别买卖时机。
## 分析维度
- 趋势分析(长期/中期/短期趋势)
- 支撑阻力位识别
- 技术指标MACD、RSI、KDJ、布林带等
- 形态识别(头肩顶/底、双底、三角形等)
- 量价关系分析
## 输出格式
- 趋势方向(上涨/下跌/震荡)
- 关键价位(支撑/阻力)
- 技术信号(买入/卖出/观望)
- 置信度评分
""",
"SOUL.md": """# Soul
你是敏锐的技术分析师,相信价格包含一切信息。
你善于从图表中发现规律,像侦探一样寻找市场留下的痕迹。
语气果断、快速反应,善于捕捉稍纵即逝的交易机会。
""",
},
"sentiment": {
"ROLE.md": """# Role: Sentiment Analyst
## 职责
分析市场情绪、资金流向、新闻舆情,判断市场心理状态。
## 分析维度
- 市场情绪指标(恐慌/贪婪指数)
- 资金流向分析(主力/散户资金)
- 新闻舆情分析(正面/负面/中性)
- 社交媒体情绪
- 机构持仓变化
## 输出格式
- 情绪评分(-10到+10极度恐慌到极度贪婪
- 资金流向判断
- 舆情摘要
- 情绪拐点预警
""",
"SOUL.md": """# Soul
你是敏感的市场情绪捕手,善于感知市场的恐惧与贪婪。
你关注人性在金融市场中的表现,理解情绪如何驱动价格。
语气富有洞察力、善于捕捉微妙变化,像心理学家一样理解市场参与者。
""",
},
"valuation": {
"ROLE.md": """# Role: Valuation Analyst
## 职责
评估公司内在价值,计算合理价格区间,识别高估/低估机会。
## 分析维度
- DCF现金流折现模型
- 相对估值法P/E、EV/EBITDA等
- 资产重估法
- 分部估值SOTP
- 安全边际计算
## 输出格式
- 内在价值估算
- 合理价格区间
- 当前价格vs内在价值高估/低估百分比)
- 估值假设和敏感性分析
""",
"SOUL.md": """# Soul
你是精确的估值分析师,追求计算内在价值的准确区间。
你像精算师一样严谨,注重假设的合理性和安全边际。
语气精确、注重数字,善于发现市场定价错误带来的机会。
""",
},
"portfolio": {
"ROLE.md": """# Role: Portfolio Manager
## 职责
统筹各分析师意见,制定投资决策,管理投资组合配置。
## 分析维度
- 资产配置策略(股债比例、行业分布)
- 风险收益平衡
- 仓位管理(建仓/加仓/减仓/清仓)
- 再平衡时机
- 组合相关性分析
## 输出格式
- 投资决策(买入/卖出/持有)
- 建议仓位比例
- 目标价位
- 止损止盈设置
- 组合调整建议
""",
"SOUL.md": """# Soul
你是睿智的投资组合经理,像将军一样统筹全局。
你善于权衡各方意见,做出果断而理性的投资决策。
语气权威、决策果断,对组合整体表现负有最终责任。
""",
},
"risk": {
"ROLE.md": """# Role: Risk Manager
## 职责
识别、评估和监控投资风险,确保组合风险在可控范围内。
## 分析维度
- 市场风险Beta、波动率
- 信用风险
- 流动性风险
- 集中度风险
- 尾部风险VaR、CVaR
- 压力测试
## 输出格式
- 风险等级(低/中/高/极高)
- 风险敞口分析
- 风险调整建议
- 预警阈值设置
- 应急预案
""",
"SOUL.md": """# Soul
你是谨慎的风险管理者,时刻警惕潜在的损失。
你像守门员一样守护组合安全,宁可错过机会也不冒无法承受的风险。
语气保守、风险意识强,善于发现隐藏的威胁和脆弱性。
""",
},
}
def get_base_template(filename: str) -> str | None:
"""获取基础模板内容"""
return BASE_TEMPLATES.get(filename)
def get_role_template(role_type: str, filename: str) -> str | None:
"""获取角色专用模板内容"""
role = ROLE_TEMPLATES.get(role_type)
if role:
return role.get(filename)
return None
def get_all_role_types() -> list[str]:
"""获取所有角色类型列表"""
return list(ROLE_TEMPLATES.keys())
def render_agent_yaml(agent_id: str, agent_type: str, name: str) -> str:
"""渲染agent.yaml模板"""
return BASE_TEMPLATES["agent.yaml"].format(
agent_id=agent_id,
agent_type=agent_type,
name=name
)

View File

@@ -41,6 +41,16 @@ class RunWorkspaceManager:
"tickers:\n"
" - AAPL\n"
" - MSFT\n"
" - GOOGL\n"
" - AMZN\n"
" - NVDA\n"
" - META\n"
" - TSLA\n"
" - AMD\n"
" - NFLX\n"
" - AVGO\n"
" - PLTR\n"
" - COIN\n"
"initial_cash: 100000\n"
"margin_requirement: 0.0\n"
"enable_memory: false\n"
@@ -63,9 +73,8 @@ class RunWorkspaceManager:
self,
config_name: str,
agent_id: str,
role_seed: str = "",
style_seed: str = "",
policy_seed: str = "",
file_contents: Optional[Dict[str, str]] = None,
persona: Optional[Dict[str, object]] = None,
) -> Path:
asset_dir = self.skills_manager.get_agent_asset_dir(
config_name,
@@ -77,58 +86,55 @@ class RunWorkspaceManager:
(asset_dir / "skills" / "disabled").mkdir(parents=True, exist_ok=True)
(asset_dir / "skills" / "local").mkdir(parents=True, exist_ok=True)
self._ensure_file(
asset_dir / "ROLE.md",
"# Role\n\n"
"Optional run-scoped role override.\n\n"
f"{role_seed}".strip()
+ "\n",
)
self._ensure_file(
asset_dir / "STYLE.md",
"# Style\n\n"
"Optional run-scoped communication or reasoning style.\n\n"
f"{style_seed}".strip()
+ "\n",
)
self._ensure_file(
asset_dir / "POLICY.md",
"# Policy\n\n"
"Optional run-scoped constraints, limits, or strategy policy.\n\n"
f"{policy_seed}".strip()
+ "\n",
)
self._ensure_file(
asset_dir / "SOUL.md",
"# Soul\n\n"
"Describe the agent's temperament, reasoning posture, and voice.\n\n",
)
self._ensure_file(
asset_dir / "PROFILE.md",
"# Profile\n\n"
"Track this agent's long-lived investment style, preferences, and strengths.\n\n",
)
self._ensure_file(
asset_dir / "AGENTS.md",
"# Agent Guide\n\n"
"Document how this agent should work, collaborate, and choose tools or skills.\n\n",
)
self._ensure_file(
asset_dir / "MEMORY.md",
"# Memory\n\n"
"Store durable lessons, heuristics, and reminders for this agent.\n\n",
)
self._ensure_file(
asset_dir / "HEARTBEAT.md",
"# Heartbeat\n\n"
"Optional checklist for periodic review or self-reflection.\n\n",
)
file_contents = file_contents or self.build_default_agent_files(agent_id=agent_id)
for filename, content in file_contents.items():
legacy_contents = self.build_legacy_agent_file_variants(
agent_id=agent_id,
filename=filename,
persona=persona,
)
self._ensure_file(asset_dir / filename, content, legacy_contents=legacy_contents)
self._ensure_agent_yaml(
asset_dir / "agent.yaml",
agent_id=agent_id,
)
return asset_dir
def build_default_agent_files(
self,
*,
agent_id: str,
persona: Optional[Dict[str, object]] = None,
) -> Dict[str, str]:
"""Build default workspace markdown files for one agent."""
if agent_id.endswith("_analyst"):
return self._build_analyst_files(agent_id=agent_id, persona=persona or {})
if agent_id == "portfolio_manager":
return self._build_portfolio_manager_files()
if agent_id == "risk_manager":
return self._build_risk_manager_files()
return self._build_generic_files(agent_id=agent_id)
def build_legacy_agent_file_variants(
self,
*,
agent_id: str,
filename: str,
persona: Optional[Dict[str, object]] = None,
) -> list[str]:
"""Return known generated legacy variants safe to upgrade in-place."""
persona = persona or {}
variants: list[dict[str, str]] = [
self._build_legacy_english_files(agent_id=agent_id),
self._build_previous_chinese_files(agent_id=agent_id, persona=persona),
]
values: list[str] = []
for item in variants:
content = item.get(filename)
if content:
values.append(content)
return values
def load_agent_file(
self,
*,
@@ -168,49 +174,285 @@ class RunWorkspaceManager:
for agent_id in agent_ids:
if agent_id.endswith("_analyst"):
persona = analyst_personas.get(agent_id, {})
role_seed = persona.get("description", "").strip()
focus_items = persona.get("focus", [])
style_seed = "\n".join(f"- {item}" for item in focus_items)
policy_seed = (
"State a clear signal, confidence, and the conditions that would invalidate the thesis."
)
elif agent_id == "portfolio_manager":
role_seed = (
"Synthesize analyst and risk inputs into explicit portfolio decisions."
)
style_seed = (
"Be concise, capital-aware, and explicit about sizing rationale."
)
policy_seed = (
"Respect cash, margin, and portfolio concentration constraints before recording decisions."
)
elif agent_id == "risk_manager":
role_seed = (
"Quantify concentration, leverage, liquidity, and volatility risk before trade execution."
)
style_seed = (
"Prioritize the highest-severity risk first and state concrete limits."
)
policy_seed = (
"Use available risk tools before issuing the final risk memo."
file_contents = self.build_default_agent_files(
agent_id=agent_id,
persona=persona,
)
else:
role_seed = ""
style_seed = ""
policy_seed = ""
self.ensure_agent_assets(
config_name=config_name,
agent_id=agent_id,
role_seed=role_seed,
style_seed=style_seed,
policy_seed=policy_seed,
)
persona = None
file_contents = self.build_default_agent_files(agent_id=agent_id)
asset_dir = self.skills_manager.get_agent_asset_dir(config_name, agent_id)
asset_dir.mkdir(parents=True, exist_ok=True)
(asset_dir / "skills" / "installed").mkdir(parents=True, exist_ok=True)
(asset_dir / "skills" / "active").mkdir(parents=True, exist_ok=True)
(asset_dir / "skills" / "disabled").mkdir(parents=True, exist_ok=True)
(asset_dir / "skills" / "local").mkdir(parents=True, exist_ok=True)
for filename, content in file_contents.items():
self._ensure_file(
asset_dir / filename,
content,
legacy_contents=self.build_legacy_agent_file_variants(
agent_id=agent_id,
filename=filename,
persona=persona,
),
)
self._ensure_agent_yaml(asset_dir / "agent.yaml", agent_id=agent_id)
@staticmethod
def _ensure_file(path: Path, content: str) -> None:
def _ensure_file(path: Path, content: str, *, legacy_contents: Optional[list[str]] = None) -> None:
if not path.exists():
path.write_text(content, encoding="utf-8")
return
existing = path.read_text(encoding="utf-8")
normalized_existing = existing.strip()
candidates = {item.strip() for item in (legacy_contents or []) if item and item.strip()}
if normalized_existing in candidates:
path.write_text(content, encoding="utf-8")
@staticmethod
def _build_generic_files(agent_id: str) -> Dict[str, str]:
return {
"SOUL.md": (
"# Soul\n\n"
f"你是 `{agent_id}`,语气冷静、客观、专业。保持清晰推理,优先基于数据而不是情绪下结论。\n"
),
"PROFILE.md": (
"# Profile\n\n"
"记录这个 agent 长期稳定的分析风格、偏好、优势与盲点。\n"
),
"AGENTS.md": (
"# Agent Guide\n\n"
"工作要求:\n"
"- 优先使用已激活的技能和工具\n"
"- 结论要明确,过程要可追溯\n"
"- 与其他 agent 协作时保持输入输出简洁\n"
"- 最终输出必须使用简体中文;如需引用英文术语,仅保留专有名词,解释和结论必须用中文\n"
),
"POLICY.md": (
"# Policy\n\n"
"- 给出结论时说明核心驱动因素\n"
"- 明确风险边界和结论失效条件\n"
"- 出现反例时需要纳入最终判断\n"
"- 不要输出英文报告标题、英文摘要或整段英文正文\n"
),
"MEMORY.md": (
"# Memory\n\n"
"记录可复用的经验、失误复盘、有效启发式和需要持续跟踪的提醒。\n"
),
}
@classmethod
def _build_analyst_files(cls, *, agent_id: str, persona: Dict[str, object]) -> Dict[str, str]:
role_name = str(persona.get("name") or agent_id)
focus_items = [
str(item).strip()
for item in persona.get("focus", [])
if str(item).strip()
]
focus_md = "\n".join(f"- {item}" for item in focus_items) or "- 根据当前任务选择最相关的分析维度"
description = str(persona.get("description") or "").strip()
files = cls._build_generic_files(agent_id)
files["SOUL.md"] = (
"# Soul\n\n"
f"你是一位专业的{role_name}\n\n"
"保持谦逊和开放,主动寻找与自己观点相悖的证据,并将其纳入最终评估。"
"你的分析要体现持续演化的投资哲学,而不是一次性的结论。\n"
)
files["PROFILE.md"] = (
"# Profile\n\n"
f"角色定位:{role_name}\n\n"
"你的关注重点:\n"
f"{focus_md}\n\n"
"角色说明:\n"
f"{description or '围绕最关键的基本面、技术面、情绪面或估值因素形成高质量判断。'}\n"
)
files["AGENTS.md"] = (
"# Agent Guide\n\n"
"分析流程:\n"
"- 优先识别真正驱动价值或价格变化的核心变量\n"
"- 使用相关工具和技能补足证据链\n"
"- 给出可验证、可复查、可执行的分析结果\n"
"- 在团队讨论中清晰表达你的论点和反论点\n\n"
"输出要求:\n"
"- 给出明确投资信号:看涨、看跌或中性\n"
"- 包含置信度0-100\n"
"- 如果你确定要分享最终分析,请先给出结论,再给出推理依据\n"
"- 最终输出必须使用简体中文,不要生成英文版 analysis report\n"
)
files["POLICY.md"] = (
"# Policy\n\n"
"- 深化你的投资逻辑,确保每项建议都有清晰、可追溯、可重复的依据\n"
"- 明确风险边界:在什么具体情况下当前结论会失效\n"
"- 做逆向测试:说明市场主流共识与你的不同点\n"
"- 每次分析后反思这次案例如何验证或挑战你现有的信念\n"
"- 即使输入新闻或财报原文是英文,最终表达也必须用中文\n"
)
return files
@classmethod
def _build_portfolio_manager_files(cls) -> Dict[str, str]:
files = cls._build_generic_files("portfolio_manager")
files["SOUL.md"] = (
"# Soul\n\n"
"你是一位负责做出投资决策的投资组合经理。你需要综合多个分析视角,"
"做出保守、明确、资本约束下可执行的组合决策。\n"
)
files["PROFILE.md"] = (
"# Profile\n\n"
"核心职责:\n"
"- 分析分析师和风险管理经理的输入\n"
"- 基于信号和市场情境做出投资决策\n"
"- 使用可用工具记录每个 ticker 的决策\n"
)
files["AGENTS.md"] = (
"# Agent Guide\n\n"
"决策框架:\n"
"- 审阅分析以理解市场观点\n"
"- 在做决策前先考虑风险警告\n"
"- 评估当前投资组合持仓、现金与保证金占用\n"
"- 决策必须与整体投资目标和风险约束一致\n\n"
"决策类型:\n"
'- `long`:看涨,建议买入\n'
'- `short`:看跌,建议卖出或做空\n'
'- `hold`:中性,维持当前持仓\n\n'
"输出要求:\n"
"- 使用 `make_decision` 工具记录每个股票的最终决策\n"
"- 记录完成后给出投资逻辑总结\n"
"- 最终总结必须使用简体中文\n"
)
files["POLICY.md"] = (
"# Policy\n\n"
"- 在决定数量时考虑可用现金,不要超出现金允许范围\n"
"- 考虑做空头寸的保证金要求\n"
"- 仓位规模相对于组合总资产保持保守\n"
"- 始终为决策提供清晰理由\n"
"- 不要输出英文投资报告或英文结论\n"
)
return files
@classmethod
def _build_risk_manager_files(cls) -> Dict[str, str]:
files = cls._build_generic_files("risk_manager")
files["SOUL.md"] = (
"# Soul\n\n"
"你是一位专业的风险管理经理,负责监控投资组合风险并提供风险警告。"
"你的目标不是输出空泛的谨慎,而是给出量化、可执行、可优先级排序的风险意见。\n"
)
files["PROFILE.md"] = (
"# Profile\n\n"
"核心职责:\n"
"- 监控投资组合敞口和集中度风险\n"
"- 评估仓位规模相对于波动性是否合理\n"
"- 评估保证金使用和杠杆水平\n"
"- 识别潜在风险因素并提供警告\n"
"- 基于市场条件建议仓位限制\n"
)
files["AGENTS.md"] = (
"# Agent Guide\n\n"
"决策流程:\n"
"- 优先使用可用的风险工具量化集中度、波动率和保证金压力\n"
"- 结合工具结果与当前市场上下文做判断\n"
"- 生成可操作的风险警告和仓位限制建议\n"
"- 为风险评估提供清晰理由\n\n"
"输出要求:\n"
"- 风险评估要简洁但全面\n"
"- 按严重程度优先排序警告\n"
"- 提供具体、可操作的建议\n"
"- 尽可能包含量化指标\n"
"- 最终风险结论必须使用简体中文\n"
)
files["POLICY.md"] = (
"# Policy\n\n"
"- 先量化,再判断,不要只给抽象风险表述\n"
"- 高严重度风险必须先说\n"
"- 最终结论需要明确仓位限制或调整建议\n"
"- 不要输出英文风险报告或英文摘要\n"
)
return files
@staticmethod
def _build_legacy_english_files(agent_id: str) -> Dict[str, str]:
policy_tail = "Optional run-scoped constraints, limits, or strategy policy.\n\n"
if agent_id == "portfolio_manager":
policy_tail += "Respect cash, margin, and portfolio concentration constraints before recording decisions.\n"
elif agent_id == "risk_manager":
policy_tail += "Use available risk tools before issuing the final risk memo.\n"
elif agent_id.endswith("_analyst"):
policy_tail += "State a clear signal, confidence, and the conditions that would invalidate the thesis.\n"
return {
"SOUL.md": "# Soul\n\nDescribe the agent's temperament, reasoning posture, and voice.\n\n",
"PROFILE.md": "# Profile\n\nTrack this agent's long-lived investment style, preferences, and strengths.\n\n",
"AGENTS.md": "# Agent Guide\n\nDocument how this agent should work, collaborate, and choose tools or skills.\n\n",
"POLICY.md": "# Policy\n\n" + policy_tail,
"MEMORY.md": "# Memory\n\nStore durable lessons, heuristics, and reminders for this agent.\n\n",
}
@classmethod
def _build_previous_chinese_files(cls, *, agent_id: str, persona: Dict[str, object]) -> Dict[str, str]:
if agent_id.endswith("_analyst"):
role_name = str(persona.get("name") or agent_id)
focus_items = [
str(item).strip()
for item in persona.get("focus", [])
if str(item).strip()
]
focus_md = "\n".join(f"- {item}" for item in focus_items) or "- 根据当前任务选择最相关的分析维度"
description = str(persona.get("description") or "").strip()
return {
"SOUL.md": (
"# Soul\n\n"
f"你是一位专业的{role_name}\n\n"
"保持谦逊和开放,主动寻找与自己观点相悖的证据,并将其纳入最终评估。"
"你的分析要体现持续演化的投资哲学,而不是一次性的结论。\n"
),
"PROFILE.md": (
"# Profile\n\n"
f"角色定位:{role_name}\n\n"
"你的关注重点:\n"
f"{focus_md}\n\n"
"角色说明:\n"
f"{description or '围绕最关键的基本面、技术面、情绪面或估值因素形成高质量判断。'}\n"
),
"AGENTS.md": (
"# Agent Guide\n\n"
"分析流程:\n"
"- 优先识别真正驱动价值或价格变化的核心变量\n"
"- 使用相关工具和技能补足证据链\n"
"- 给出可验证、可复查、可执行的分析结果\n"
"- 在团队讨论中清晰表达你的论点和反论点\n\n"
"输出要求:\n"
"- 给出明确投资信号:看涨、看跌或中性\n"
"- 包含置信度0-100\n"
"- 如果你确定要分享最终分析,请先给出结论,再给出推理依据\n"
),
"POLICY.md": (
"# Policy\n\n"
"- 深化你的投资逻辑,确保每项建议都有清晰、可追溯、可重复的依据\n"
"- 明确风险边界:在什么具体情况下当前结论会失效\n"
"- 做逆向测试:说明市场主流共识与你的不同点\n"
"- 每次分析后反思这次案例如何验证或挑战你现有的信念\n"
),
"MEMORY.md": "# Memory\n\n记录可复用的经验、失误复盘、有效启发式和需要持续跟踪的提醒。\n",
}
if agent_id == "portfolio_manager":
return {
"SOUL.md": "# Soul\n\n你是一位负责做出投资决策的投资组合经理。你需要综合多个分析视角,做出保守、明确、资本约束下可执行的组合决策。\n",
"PROFILE.md": "# Profile\n\n核心职责:\n- 分析分析师和风险管理经理的输入\n- 基于信号和市场情境做出投资决策\n- 使用可用工具记录每个 ticker 的决策\n",
"AGENTS.md": "# Agent Guide\n\n决策框架:\n- 审阅分析以理解市场观点\n- 在做决策前先考虑风险警告\n- 评估当前投资组合持仓、现金与保证金占用\n- 决策必须与整体投资目标和风险约束一致\n\n决策类型:\n- `long`:看涨,建议买入\n- `short`:看跌,建议卖出或做空\n- `hold`:中性,维持当前持仓\n\n输出要求:\n- 使用 `make_decision` 工具记录每个股票的最终决策\n- 记录完成后给出投资逻辑总结\n",
"POLICY.md": "# Policy\n\n- 在决定数量时考虑可用现金,不要超出现金允许范围\n- 考虑做空头寸的保证金要求\n- 仓位规模相对于组合总资产保持保守\n- 始终为决策提供清晰理由\n",
"MEMORY.md": "# Memory\n\n记录可复用的经验、失误复盘、有效启发式和需要持续跟踪的提醒。\n",
}
if agent_id == "risk_manager":
return {
"SOUL.md": "# Soul\n\n你是一位专业的风险管理经理,负责监控投资组合风险并提供风险警告。你的目标不是输出空泛的谨慎,而是给出量化、可执行、可优先级排序的风险意见。\n",
"PROFILE.md": "# Profile\n\n核心职责:\n- 监控投资组合敞口和集中度风险\n- 评估仓位规模相对于波动性是否合理\n- 评估保证金使用和杠杆水平\n- 识别潜在风险因素并提供警告\n- 基于市场条件建议仓位限制\n",
"AGENTS.md": "# Agent Guide\n\n决策流程:\n- 优先使用可用的风险工具量化集中度、波动率和保证金压力\n- 结合工具结果与当前市场上下文做判断\n- 生成可操作的风险警告和仓位限制建议\n- 为风险评估提供清晰理由\n\n输出要求:\n- 风险评估要简洁但全面\n- 按严重程度优先排序警告\n- 提供具体、可操作的建议\n- 尽可能包含量化指标\n",
"POLICY.md": "# Policy\n\n- 先量化,再判断,不要只给抽象风险表述\n- 高严重度风险必须先说\n- 最终结论需要明确仓位限制或调整建议\n",
"MEMORY.md": "# Memory\n\n记录可复用的经验、失误复盘、有效启发式和需要持续跟踪的提醒。\n",
}
return cls._build_legacy_english_files(agent_id)
@staticmethod
def _ensure_agent_yaml(path: Path, agent_id: str) -> None: