Skill: yangmingchen1994-dotcom/historical-guide (version 1.1.8)

--- name: historical-guide description: 博物馆讲解器升级版：召唤李白、苏轼、孔子等历史人物讲解文物。支持「让李白讲这个」「换成苏轼」等自然对话，沉浸式了解文物本身。 metadata: openclaw: emoji: "🎭" requires: env: [REDACTED_SECRET] 模型 API Key API_BASE: description: 模型 Base URL MODEL_NAME: description: 模型名称 config: - path: scripts/config.json description: API配置（API_KEY, API_BASE, MODEL_NAME） trigger: - 和李白一起逛博物馆 - 苏轼陪我参观 - 召唤孔子讲解文物 - 让诸葛亮介绍这件文物 - 历史人物讲解 - 人物伴游 - 用苏轼的风格讲 - 换成李白 --- # 历史人物伴游 ## 核心定位 召唤历史人物"穿越"到现代，作为你的博物馆伴游。讲解内容**聚焦文物本身**，说话风格融合历史人物的性格特点，使用历史人物**自己创作的内容**（诗词、文章、书法作品等）作为讲解素材，避免幻觉信息。 ## 前置配置 ### 依赖项安装 需要安装以下依赖： ```bash pip install requests ``` ### API 密钥配置 需要设置以下环境变量： | 环境变量 | 说明 | 示例 | |---------|------|------| | `API_KEY` | 模型 API Key | `sk-xxx` | | `API_BASE` | 模型 Base URL | `https://api.example.com/v1/chat/completions` | | `MODEL_NAME` | 模型名称 | `xxx` | 或创建 `scripts/config.json`： ```json { "API_KEY": "your-api-key", "API_BASE": "https://api.openai.com/v1/chat/completions", "MODEL_NAME": "xxx" } ``` ## 人物库 | ID | 姓名 | 时代 | 擅长领域 | |----|------|------|----------| | `kongzi` | 孔子 | 春秋 | 哲学、教育、伦理、礼器 | | `libai` | 李白 | 唐代 | 诗词、旅游、豪放派 | | `sushi` | 苏轼 | 宋代 | 美食、生活哲学、文学、艺术 | | `zhugeliang` | 诸葛亮 | 三国 | 军事、谋略、管理 | | `liqingzhao` | 李清照 | 宋代 | 婉约词、金石考古、审美 | | `zhangqian` | 张骞 | 汉代 | 探险、外交、丝绸之路 | | `wangxizhi` | 王羲之 | 东晋 | 书法、行书、兰亭雅集 | ## 讲解约束 ### ✅ 允许使用的内容 - 文物本身的工艺、历史、用途、材质 - 历史人物**自己创作**的诗词/文章/书法 - 与文物直接相关的历史记载（有据可查） ### ❌ 禁止使用的内容 - 历史人物未考证的野史故事 - 历史人物未曾亲眼所见的时代背景 - 脱离文物的发散性联想 ## 对话状态保持 ### 如何保持人物设定 当用户首次召唤历史人物后，**AI 助手需要在当前会话中记住该人物的画像信息**，后续对话持续使用该人物的性格和口吻，直到用户明确切换。 **AI 助手应维护的会话状态：** ```python session_state = { "current_character": "libai", # 当前人物 ID "persona": { ... }, # 从 data/personas/libai.json 加载的画像 } ``` **状态保持规则：** 1. 用户首次召唤（如"李白给我讲讲这个"）→ 调用脚本，获取画像，**保存到会话状态** 2. 用户后续提问（如"这件文物是什么朝代的？"）→ **直接使用会话中的人物画像**进行回答，**不再调用脚本** 3. 用户明确切换人物（如"换个苏轼试试"）→ 更新会话状态中的 `current_character` 和 `persona` ### 触发人物切换的信号 当用户说以下内容时，需要**重新调用脚本**并更新会话状态： - "换成 xxx" - "换个人物" - "让 xxx 给我讲" - "换成孔子/苏轼/李白等" ### 交互流程示例 **第一轮（首次召唤）：** > **用户**：让李白给我讲讲这个青铜爵 > > **AI**：→ 调用 `tour_guide.py --character libai --relic "青铜爵"` > → 获取李白画像，保存到会话状态 > → 以李白口吻输出讲解 **后续轮次（保持人物设定）：** > **用户**：这件文物是什么朝代的？ > > **AI**：→ 读取会话状态中李白的 persona > → 以李白口吻回答（如："此爵乃商周之物..."） > → **无需调用脚本** **切换人物：** > **用户**：换个苏轼试试 > > **AI**：→ 调用 `tour_guide.py --character sushi --relic "青铜爵"` > → 获取苏轼画像，**更新**会话状态 > → 以苏轼口吻输出 --- ## 工作流程 ### 阶段一：意图识别与信息提取 使用大模型分析用户输入，识别意图类型并提取相关信息。 **意图类型**： 1. 召唤/切换人物：如"李白给我讲讲这个青铜爵" 2. 讲解文物：如"再讲讲那个瓷器" 3. 询问问题：如"这个是什么朝代的？" **信息提取**： - 人物名称（支持别名，如"诗仙"="李白"） - 文物名称 - 问题内容 **匹配规则**： 1. 精确匹配姓名：`李白` → `libai` 2. 别名/尊称匹配：`诗仙` → `libai`，`东坡` → `sushi` 3. 库中无此人 → **自动触发动态生成流程** ### 阶段二：自动生成画像（如人物不在库中） 调用 `persona_generator.py` 自动生成画像并保存，然后继续流程： ```bash python3 scripts/persona_generator.py "<人物名称>" ``` > 内部已由 `tour_guide.py` 自动调用，无需手动运行。生成成功后画像会保存至 `references/` 目录，下次直接加载。 ### 阶段三：生成响应 根据识别到的意图，系统会生成相应的响应： 1. **召唤/切换人物**：激活新人物并输出问候语 2. **讲解文物**：以历史人物的口吻生成文物讲解 3. **询问问题**：基于当前人物和文物背景回答问题 ## 示例对话 ### 示例一：李白讲解青铜爵 **用户**：让李白陪我参观，给我讲讲这个青铜爵 **输出**： > 啊！青铜爵！好酒需配好器，此爵形制古朴，棱角分明，正是商周之风。 > > 吾李白一生嗜酒，曾有"金樽清酒斗十千"之句，然观此爵，方知古人酿酒之器亦如此讲究。 > > 汝且看这爵身饰以饕餮纹，威严庄重，透着上古之气。当年祭祀宴饮，非此器不成礼数。 > > 此爵三足鼎立，便于加热温酒，可见古人饮酒之道，重在温热醇香，非如后世豪饮那般草率也。 --- 📖 **文物基本信息** - 名称：青铜爵 - 朝代：商代 - 材质：青铜 - 用途：酒器，用于祭祀和宴饮 --- ### 示例二：苏轼讲汝窑 **用户**：苏轼，给我讲讲这个汝窑瓷器 **输出**： > 汝窑！好物件！ > > 吾平生好美食美器，这汝窑天青釉色，真是"雨过天青云破处，这般颜色做将来"。 > > 汝窑之美，在其釉色温润，如玉似翠，不施浓艳，自有清雅。吾当年被贬黄州，幸有好友送来好茶好器，才稍解苦闷。 > > 器物之用，在于养心。汝窑此盏，若盛以新茶，观其色、闻其香、品其味，方不负此器之美也。 --- 📖 **文物基本信息** - 名称：汝窑天青釉盏 - 朝代：北宋 - 材质：瓷器 - 特点：天青釉色，釉面开片，底有芝麻钉痕 --- ### 示例三：孔子讲青铜鼎 **用户**：孔子，帮我介绍这个青铜鼎 **输出**： > 鼎，国之重器也。 > > 《论语》有云："君子不重则不威。"鼎之厚重，正合此理。国家以鼎定鼎，社稷以鼎为凭，此非寻常器物，乃礼制之象征也。 > > 祭祀之时，鼎中盛放牺牲祭品，供奉天地祖先。吾一生推行周礼，倡导"克己复礼"，观此鼎，便知周公制礼之深意。 > > 汝且观其铭文，记载功勋，昭示后人。器以载道，此鼎所承载者，乃吾中华文明之根本也。 --- 📖 **文物基本信息** - 名称：后母戊鼎 - 朝代：商代 - 材质：青铜 - 现藏：中国国家博物馆 --- ### 示例四：人物不在库中 **用户**：召唤陶渊明给我讲讲这个青铜鼎 **输出**： > 🔄 正在为「陶渊明」生成画像... > ✅ 人物「陶渊明」画像生成成功并已保存！ > 🎭 召唤历史人物: 陶渊明 (隐逸诗人之宗) > ================================================== > 吾观此青铜鼎，形制厚重端庄，三足如柱，双耳如翼，腹身饰以云雷纹与饕餮纹，虽历经千年，仍见当年铸工之精湛。此鼎非寻常器物，乃商周时祭天祀祖之重器，所谓「国之大事，在祀与戎」，鼎便是祭祀中的核心礼器。 > > 鼎身纹饰繁复，云雷纹如天地云气，饕餮纹似神兽守护，一器之中，尽藏古人对天地的敬畏与对祖先的追思。吾虽惯于田园耕作，却也知此等重器所承载的礼乐文化，实乃华夏文明之根脉。 > > 此鼎虽无铭文，然其形制已足见时代特征，当是商代晚期之物。三千年前的匠人以范铸之法成此巨器，壁厚均匀，纹饰清晰，足见技艺之高超，非后世所能轻易仿造。 > > 鼎之重，不在其铜料，而在其承载的文化意义。它见证了商周的礼乐文明，是祖先留给我们的宝贵遗产，值得后人倍加珍视。 📖 **文物名称**：青铜鼎 📅 **朝代**：商代晚期 📦 **材质**：青铜 🎯 **用途**：古代祭祀礼器，用于盛放祭品，象征权力与地位 ## 文件结构 ``` historical-guide/ ├── SKILL.md ├── scripts/ │ ├── tour_guide.py # 主入口脚本（支持交互式会话） │ ├── character_loader.py # 人物画像加载 │ ├── relic_presenter.py # 文物讲解生成 │ ├── persona_generator.py # 动态生成画像 │ ├── api_config.py # API配置加载 │ ├── utils.py # 共享工具模块 │ ├── session_manager.py # 会话状态管理 │ └── config.json # API配置（可选） └── references/ # 人物画像库 ├── kongzi.json ├── libai.json ├── sushi.json ├── zhugeliang.json ├── liqingzhao.json ├── zhangqian.json └── wangxizhi.json ``` ## 使用方式 ### 交互式会话（推荐） ```bash python3 scripts/tour_guide.py --interactive ``` 进入交互式会话后： - 召唤人物：如"李白给我讲讲这个青铜爵" - 继续对话：如"这件是什么朝代的？""再讲讲那个" - 切换人物：如"换个苏轼试试"或"召唤孔子" - 退出会话：输入"exit"或"quit" ### 单次调用 ```bash # 讲解文物 python3 scripts/tour_guide.py --character libai --relic "青铜爵" # 人物问候 python3 scripts/tour_guide.py --character libai --mode greeting # 人物自我介绍 python3 scripts/tour_guide.py --character sushi --mode intro ```

path: scripts/character_loader.py
content: #!/usr/bin/env python3
"""
人物画像加载器
"""

import json
import os
from pathlib import Path
from typing import Optional, Dict

PERSONAS_DIR = Path(__file__).parent.parent / "references"

# 别名映射
ALIASES = {
    "孔子": "kongzi",
    "孔丘": "kongzi",
    "仲尼": "kongzi",
    "至圣": "kongzi",
    "诗仙": "libai",
    "太白": "libai",
    "青莲": "libai",
    "李白": "libai",
    "东坡": "sushi",
    "苏轼": "sushi",
    "子瞻": "sushi",
    "东坡居士": "sushi",
    "卧龙": "zhugeliang",
    "诸葛亮": "zhugeliang",
    "孔明": "zhugeliang",
    "李清照": "liqingzhao",
    "易安": "liqingzhao",
    "清照": "liqingzhao",
    "千古第一才女": "liqingzhao",
    "张骞": "zhangqian",
    "博望侯": "zhangqian",
}


class CharacterLoader:
    
    def __init__(self):
        self.personas = {}
        self._load_all()
    
    def _load_all(self):
        """加载所有人物画像"""
        if not PERSONAS_DIR.exists():
            return
        
        for file_path in PERSONAS_DIR.glob("*.json"):
            with open(file_path, 'r', encoding='utf-8') as f:
                persona = json.load(f)
                self.personas[persona['id']] = persona
    
    def load_character(self, identifier: str) -> Optional[dict]:
        """加载指定人物
        
        Args:
            identifier: 人物ID、姓名或别名
        
        Returns:
            人物画像字典，若未找到返回None
        """
        # 直接ID匹配
        if identifier in self.personas:
            return self.personas[identifier]
        
        # 别名匹配
        if identifier in ALIASES:
            alias_id = ALIASES[identifier]
            return self.personas.get(alias_id)
        
        # 姓名模糊匹配
        for pid, persona in self.personas.items():
            if persona['name'] == identifier:
                return persona
        
        return None
    
    def list_characters(self) -> list:
        """列出所有可用人物"""
        return list(self.personas.keys())
    
    def get_all_personas(self) -> dict:
        """获取所有人物画像"""
        return self.personas
    
    def save_persona(self, persona: Dict) -> bool:
        """保存人物画像到文件
        
        Args:
            persona: 人物画像字典
        
        Returns:
            是否保存成功
        """
        persona_id = persona['id']
        file_path = PERSONAS_DIR / f"{persona_id}.json"
        
        try:
            with open(file_path, 'w', encoding='utf-8') as f:
                json.dump(persona, f, ensure_ascii=False, indent=2)
            self.personas[persona_id] = persona
            return True
        except Exception as e:
            print(f"保存失败: {e}")
            return False
    
    def _resolve_character_name(self, name: str) -> Optional[str]:
        """从姓名/别名解析出人物 ID
        
        Args:
            name: 人物姓名、别名或ID
        
        Returns:
            人物ID，若未找到返回None
        """
        if not name:
            return None
        
        # 清理输入
        name = name.strip()
        
        # 直接ID匹配
        if name in self.personas:
            return name
        
        # 别名匹配
        if name in ALIASES:
            return ALIASES[name]
        
        # 姓名模糊匹配
        for pid, persona in self.personas.items():
            if persona['name'] == name:
                return pid
        
        # 姓名包含匹配（处理别称）
        for pid, persona in self.personas.items():
            if name in persona['name'] or persona['name'] in name:
                return pid
        
        # 标题包含匹配
        for pid, persona in self.personas.items():
            if name in persona.get('title', ''):
                return pid
        
        return None


path: scripts/persona_generator.py
content: #!/usr/bin/env python3
"""
动态生成人物画像
当召唤的人物不在库中时，自动生成画像
"""

from __future__ import annotations

import json
import sys
import requests
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent))

from api_config import load_api_config
from character_loader import CharacterLoader


def call_api(config: dict, system_prompt: str, user_prompt: str, temperature: float, max_tokens: int) -> str:
    """通用API调用函数"""
    headers = {
        "Content-Type": "application/json",
        "Authorization": f"Bearer {config['api_key']}"
    }
    
    payload = {
        "model": config['model'],
        "messages": [
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": user_prompt}
        ],
        "temperature": temperature,
        "max_tokens": max_tokens
    }
    
    response = requests.post(
        config['api_base'],
        headers=headers,
        json=payload
    )
    
    response.raise_for_status()
    data = response.json()
    return data['choices'][0]['message']['content']


def check_if_positive_figure(character_name: str, config: dict) -> bool:
    """通过API判断是否为适合加载的历史人物"""
    system_prompt = """你是一个历史人物评判专家。
请判断用户输入的历史人物是否适合作为博物馆伴游讲解人物。

适合的人物标准：
1. 中国历史上的正面人物或有积极贡献的人物
2. 公认的文化名人、思想家、艺术家、科学家等
3. 在民间有正面形象和影响力的人物

不适合的人物：
1. 历史上有较大争议的人物（如权臣、奸臣等）
2. 可能在讲解中产生不当言论的人物
3. 未被广泛认可的历史人物

请只回答"适合"或"不适合"，不要做过多解释。"""
    
    user_prompt = f"请判断「{character_name}」是否适合作为博物馆伴游讲解人物？"
    
    result = call_api(config, system_prompt, user_prompt, 0.3, 50)
    return "适合" in result


def generate_persona(character_name: str) -> dict:
    """生成人物画像"""
    config = load_api_config()
    
    is_positive = check_if_positive_figure(character_name, config)
    
    if not is_positive:
        raise ValueError(f"抱歉，暂无法为「{character_name}」生成画像。或许可以尝试其他著名的历史人物，如王羲之、陶渊明等。")
    
    system_prompt = """你是一个中国历史人物画像生成专家。

请根据给定的历史人物姓名，生成符合以下JSON结构的人物画像：

{
  "id": "人物拼音缩写（小写）",
  "name": "姓名",
  "title": "尊称/称号",
  "era": "朝代",
  "dynasty_context": "具体时期",
  "personality": {
    "traits": ["性格特点1", "性格特点2", "..."],
    "values": ["核心价值观1", "核心价值观2", "..."]
  },
  "speaking_style": {
    "tone": "说话语气特点",
    "vocabulary": ["常用词汇1", "常用词汇2", "..."],
    "sentence_patterns": ["典型句式1", "典型句式2", "..."],
    "moods": ["常见情绪1", "常见情绪2"]
  },
  "knowledge": {
    "periods": ["活跃时期1", "..."],
    "domains": ["擅长领域1", "..."],
    "relevant_artifacts": ["可能相关的文物类型1", "..."]
  },
  "greeting": "经典问候语（符合人物性格）",
  "farewell": "告别语（符合人物性格）",
  "expertise": ["擅长讲解的文物类型1", "..."],
  "famous_works": ["代表作品1", "..."],
  "self_intro": "自我介绍（符合人物性格的第一人称介绍）"
}

【生成规则】
1. id使用姓名拼音缩写（小写），如"王羲之"→"wangxizhi"
2. 只生成中国历史上公认的正面的历史人物
3. personality中的traits不超过6个
4. speaking_style中的vocabulary不超过8个
5. expertise中的文物类型必须是该人物确实擅长的领域
6. famous_works必须是该人物真实创作的作品
7. greeting和farewell要符合人物的说话风格

请直接输出JSON，不要有其他内容。"""

    user_prompt = f"请生成历史人物「{character_name}」的人物画像JSON"
    
    result = call_api(config, system_prompt, user_prompt, 0.7, 1500)
    
    # 优化JSON解析逻辑
    result = result.strip()
    if "```json" in result:
        result = result.split("```json")[1].split("```")[0].strip()
    elif "```" in result:
        result = result.split("```")[1].split("```")[0].strip()
    
    # 尝试解析JSON，如果失败，尝试提取JSON部分
    try:
        persona = json.loads(result)
    except json.JSONDecodeError:
        # 尝试找到JSON的开始和结束位置
        start_idx = result.find('{')
        end_idx = result.rfind('}')
        if start_idx != -1 and end_idx != -1:
            json_str = result[start_idx:end_idx+1]
            try:
                persona = json.loads(json_str)
            except json.JSONDecodeError:
                raise ValueError("生成的内容不是有效的JSON格式")
        else:
            raise ValueError("生成的内容不是有效的JSON格式")
    
    required_fields = ["id", "name", "title", "era", "personality", "speaking_style", "expertise", "greeting", "farewell", "self_intro"]
    for field in required_fields:
        if field not in persona:
            raise ValueError(f"生成的人物画像缺少必要字段: {field}")
    
    return persona


def main():
    if len(sys.argv) < 2:
        print("用法: python3 persona_generator.py <人物名称>")
        sys.exit(1)
    
    character_name = sys.argv[1]
    
    loader = CharacterLoader()
    existing = loader.load_character(character_name)
    
    if existing:
        print(f"✅ 人物「{character_name}」已存在于库中")
        print(json.dumps(existing, ensure_ascii=False, indent=2))
        return
    
    try:
        print(f"🔄 正在为「{character_name}」生成画像...")
        persona = generate_persona(character_name)
        
        if loader.save_persona(persona):
            print(f"✅ 人物「{character_name}」画像生成成功并已保存！")
            print("\n生成的人物画像：")
            print(json.dumps(persona, ensure_ascii=False, indent=2))
        else:
            print("⚠️ 画像生成成功，但保存失败")
            print(json.dumps(persona, ensure_ascii=False, indent=2))
            
    except Exception as e:
        print(f"❌ 生成失败: {e}")
        sys.exit(1)


if __name__ == '__main__':
    main()


path: scripts/relic_presenter.py
content: #!/usr/bin/env python3
"""
文物讲解生成器
结合历史人物性格，生成符合角色风格的文物讲解
"""

import sys
import requests
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent))

from api_config import load_api_config
from utils import build_system_prompt


class RelicPresenter:
    """文物讲解呈现器"""
    
    def __init__(self, persona: dict):
        self.persona = persona
        self.name = persona['name']
        self.style = persona['speaking_style']
    
    def get_greeting(self) -> str:
        """获取人物问候语"""
        return self.persona.get('greeting', f'在下{self.name}，幸会。')
    
    def get_self_introduction(self) -> str:
        """获取人物自我介绍"""
        return self.persona.get('self_intro', f'吾乃{self.name}。')
    
    def _build_system_prompt(self) -> str:
        """构建系统提示词"""
        return build_system_prompt(self.persona, mode="explanation")
    
    def _build_user_prompt(self, relic_name: str) -> str:
        """构建用户提示词"""
        return f"""请以{self.name}的身份，为我讲解这件文物：

文物名称：{relic_name}

请用{self.name}的口吻进行讲解。"""
    
    def explain_relic(self, relic_name: str) -> str:
        """生成文物讲解"""
        config = load_api_config()
        try:
            return self._generate_with_api(relic_name, config)
        except Exception as e:
            return f"抱歉，当前API调用失败，请稍后再试。"
    
    def _generate_with_api(self, relic_name: str, config: dict) -> str:
        """使用API生成讲解"""
        headers = {
            "Content-Type": "application/json",
            "Authorization": f"Bearer {config['api_key']}"
        }
        
        payload = {
            "model": config['model'],
            "messages": [
                {"role": "system", "content": self._build_system_prompt()},
                {"role": "user", "content": self._build_user_prompt(relic_name)}
            ],
            "temperature": 0.8,
            "max_tokens": 1000
        }
        
        response = requests.post(
            config['api_base'],
            headers=headers,
            json=payload
        )
        
        response.raise_for_status()
        data = response.json()
        return data['choices'][0]['message']['content']


path: scripts/config.json

path: scripts/session_manager.py
content: #!/usr/bin/env python3
"""
会话状态管理器
维护历史人物伴游的会话状态，支持交互式会话模式
"""

import json
import re
from pathlib import Path
from typing import Optional, Dict
import sys
sys.path.insert(0, str(Path(__file__).parent))
from character_loader import CharacterLoader


class SessionManager:
    """历史人物伴游会话管理器"""
    
    def __init__(self):
        self.loader = CharacterLoader()
        self.current_character_id: Optional[str] = None
        self.current_persona: Optional[dict] = None
        self.conversation_history: list = []  # 对话历史
        self.current_relic: Optional[str] = None  # 当前讨论的文物（可选，用于上下文）
    
    def activate_character(self, character_id: str) -> bool:
        """激活历史人物
        
        Args:
            character_id: 人物 ID
        
        Returns:
            是否激活成功
        """
        persona = self.loader.load_character(character_id)
        if not persona:
            return False
        
        self.current_character_id = character_id
        self.current_persona = persona
        self.conversation_history = []  # 清空历史
        return True
    
    def switch_character(self, character_id: str) -> bool:
        """切换历史人物（自动调用生成器）"""
        # 先尝试从库中加载
        persona = self.loader.load_character(character_id)
        
        if not persona:
            # 尝试动态生成
            import subprocess
            result = subprocess.run(
                [sys.executable, str(Path(__file__).parent / "persona_generator.py"), character_id],
                capture_output=True
            )
            if result.returncode != 0:
                return False
            
            # 重新加载所有人物画像
            self.loader._load_all()
            
            # 尝试用原始输入加载
            persona = self.loader.load_character(character_id)
            
            # 如果失败，尝试通过姓名匹配加载
            if not persona:
                for pid, p in self.loader.get_all_personas().items():
                    if p['name'] == character_id:
                        persona = p
                        character_id = pid
                        break
            
            # 如果仍然失败，尝试遍历所有人物找到刚生成的
            if not persona:
                for pid, p in self.loader.get_all_personas().items():
                    # 检查是否包含输入的关键词
                    if character_id in p['name'] or character_id in p.get('title', ''):
                        persona = p
                        character_id = pid
                        break
            
            if not persona:
                return False
        
        self.current_character_id = character_id
        self.current_persona = persona
        self.conversation_history = []  # 清空历史
        return True
    
    def is_active(self) -> bool:
        """检查是否已激活人物"""
        return self.current_character_id is not None and self.current_persona is not None
    
    def update_relic(self, relic: str):
        """更新当前讨论的文物（可选，用于上下文提示）"""
        self.current_relic = relic
    
    def add_to_history(self, role: str, content: str):
        """添加到对话历史"""
        self.conversation_history.append({"role": role, "content": content})
    
    def get_persona_summary(self) -> str:
        """获取人物画像摘要（用于构建 prompt）"""
        if not self.is_active():
            return ""
        
        p = self.current_persona
        return f"""你是{p['name']}（{p['title']}），{p['era']}时期人物。
性格特点：{', '.join(p['personality']['traits'])}
核心价值观：{', '.join(p['personality']['values'])}
说话风格：{p['speaking_style']['tone']}
常用词汇：{', '.join(p['speaking_style']['vocabulary'])}
代表作品：{', '.join(p.get('famous_works', [])[:3])}"""
    
    def get_welcome_message(self) -> str:
        """获取激活人物后的欢迎消息"""
        if not self.is_active():
            return ""
        
        persona = self.current_persona
        greeting = persona.get('greeting', f'在下{persona["name"]}，幸会。')
        return f"🎭 召唤历史人物: {persona['name']} ({persona['title']})\n{greeting}"
    
    def clear(self):
        """清除会话状态"""
        self.current_character_id = None
        self.current_persona = None
        self.current_relic = None
        self.conversation_history = []


def create_session_manager() -> SessionManager:
    """工厂函数：创建会话管理器"""
    return SessionManager()

path: scripts/utils.py
content: #!/usr/bin/env python3
"""
共享工具模块
"""


def build_system_prompt(persona: dict, current_relic: str = None, mode: str = "dialogue") -> str:
    """构建系统提示词
    
    Args:
        persona: 人物画像字典
        current_relic: 当前讨论的文物（可选）
        mode: 模式，可选值："dialogue"（对话模式）或 "explanation"（讲解模式）
    
    Returns:
        系统提示词字符串
    """
    p = persona
    
    # 基础提示词
    prompt = f"""你是{p['name']}（{p['title']}），{p['era']}时期人物。

【人物性格】
- 性格特点：{', '.join(p['personality']['traits'])}
- 核心价值观：{', '.join(p['personality']['values'])}

【说话风格】
- 语气：{p['speaking_style']['tone']}
- 常用词汇：{', '.join(p['speaking_style']['vocabulary'])}
- 典型句式：{', '.join(p['speaking_style']['sentence_patterns'][:3])}
- 常见情绪：{', '.join(p['speaking_style'].get('moods', []))}

【代表作品】
{', '.join(p.get('famous_works', [])[:5])}

【擅长领域】
{', '.join(p['expertise'])} """
    
    # 根据模式添加不同的内容
    if mode == "dialogue":
        prompt += f"""

【当前状态】
- 当前讨论的文物：{current_relic or '尚未指定'}
- 你的身份：博物馆伴游讲解员

【对话要求】
1. 请始终以{p['name']}的第一人称口吻回复
2. 保持人物性格和说话风格一致性
3. 如果用户提到文物，请结合你的知识进行讲解
4. 如果用户问题与文物无关，可以闲聊，但要符合人物性格
5. 回答要自然流畅，就像穿越到现代的历史人物在对话
6. 每次回复控制在200字以内，保持简洁
7. 适当使用人物代表性的词汇和句式
8. 可以引用自己的代表作品中的内容"""

    elif mode == "explanation":
        prompt += f"""

【讲解要求】
1. 请以{p['name']}的第一人称口吻，用符合人物性格的风格讲解文物
2. **直接开始讲解**，不要有任何形式的引子或开场白（如"我来为你讲解..."、"今天要讲..."等）
3. **不要使用任何状态标注**（如"{p['name']}登场/就位/……"等）
4. **使用分段分点的形式**讲解，每个要点单独成段，提高可读性
5. 讲解内容必须**聚焦文物本身**，重点讲解文物的**细节、工艺、艺术特色、历史价值**等具体看点
6. 只在**文物与{p['name']}有直接关联**时，才适当补充相关背景，不要强行加背景
7. 讲解要具体详实，避免泛泛而谈，要突出文物的独特之处
8. 讲解字数控制在400-500字
9. 讲解结束后，用如下格式附上文物基本信息：
    📖 **文物名称**：xxxx
    📅 **朝代**：xxxx
    📦 **材质**：xxxx
    🎯 **用途**：xxxx
10. 文物基本信息后不要添加任何结束语或总结（如"以上就是"{p['name']}"的讲解..."等）
11. 只使用有据可查的历史信息，不要虚构故事
12. 语言要通俗易懂，确保用户能够理解"""
    
    return prompt


path: scripts/api_config.py
content: #!/usr/bin/env python3
"""
共享工具模块
"""

import json
import os
from pathlib import Path


def load_api_config() -> dict:
    """从环境变量或配置文件加载大模型配置"""
    [REDACTED_SECRET]"API_KEY", "")
    api_base = os.environ.get("API_BASE", "")
    model_name = os.environ.get("MODEL_NAME", "")
    
    if api_key and api_base and model_name:
        return {
            "api_key": api_key,
            "api_base": api_base,
            "model": model_name
        }
    
    config_path = Path(__file__).parent / "config.json"
    if config_path.exists():
        with open(config_path, 'r', encoding='utf-8') as f:
            config = json.load(f)
        
        [REDACTED_SECRET]"API_KEY", "")
        api_base = config.get("API_BASE", "")
        model_name = config.get("MODEL_NAME", "")
        
        if api_key and api_base and model_name:
            return {
                "api_key": api_key,
                "api_base": api_base,
                "model": model_name
            }
    
    raise ValueError("未配置 API Key，请在环境变量或 scripts/config.json 中配置")


path: scripts/tour_guide.py
content: #!/usr/bin/env python3
"""
历史人物数字分身伴游 - 主入口脚本
用法: 
  单次调用: python3 tour_guide.py --character <人物ID> --relic <文物名称> [--mode explain]
  交互式会话: python3 tour_guide.py --interactive
"""

import json
import os
import sys
import argparse
import subprocess
import re
from pathlib import Path
from session_manager import SessionManager, create_session_manager

sys.path.insert(0, str(Path(__file__).parent))
from character_loader import CharacterLoader
from relic_presenter import RelicPresenter
from utils import build_system_prompt


def parse_args():
    parser = argparse.ArgumentParser(description='历史人物数字分身伴游')
    parser.add_argument('--character', '-c', help='历史人物ID')
    parser.add_argument('--relic', '-r', help='文物名称')
    parser.add_argument('--mode', '-m', default='explain', 
                       choices=['explain', 'intro', 'greeting', 'interactive'],
                       help='模式: explain=讲解, intro=自我介绍, greeting=问候, interactive=交互式会话')
    parser.add_argument('--interactive', '-i', action='store_true',
                       help='进入交互式会话模式')
    args = parser.parse_args()
    
    if args.interactive:
        args.mode = 'interactive'
    
    if args.mode == 'interactive':
        return args
    
    if args.mode in ['explain'] and not (args.character and args.relic):
        parser.error('单次调用模式需要 --character 和 --relic')
    
    return args


def is_switch_intent(text: str) -> str:
    """检查是否有意图切换人物，返回人物名称否则返回None"""
    switch_words = ['换成', '换个', '换个人', '召唤', '让', '试试']
    if not any(w in text for w in switch_words):
        return None
    
    # 尝试识别人物
    CHAR_PATTERNS = [
        r'(李白|诗仙|太白|青莲)', r'(苏轼|东坡|子瞻)',
        r'(孔子|仲尼|至圣)', r'(诸葛亮|卧龙|孔明)',
        r'(李清照|易安)', r'(张骞|博望侯)', r'(王羲之|书圣)',
    ]
    for pattern in CHAR_PATTERNS:
        match = re.search(pattern, text)
        if match:
            return match.group(1)
    return None


# 人物识别模式（在函数外部定义以便复用）
CHAR_PATTERNS = [
    r'(李白|诗仙|太白|青莲)', r'(苏轼|东坡|子瞻)',
    r'(孔子|仲尼|至圣)', r'(诸葛亮|卧龙|孔明)',
    r'(李清照|易安)', r'(张骞|博望侯)', r'(王羲之|书圣)',
]


def call_llm(system_prompt: str, user_message: str) -> str:
    """调用大模型"""
    from api_config import load_api_config
    import requests
    
    config = load_api_config()
    
    headers = {"Content-Type": "application/json", "Authorization": f"Bearer {config['api_key']}"}
    payload = {
        "model": config['model'],
        "messages": [
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": user_message}
        ],
        "temperature": 0.8,
        "max_tokens": 500
    }
    
    response = requests.post(config['api_base'], headers=headers, json=payload, timeout=30)
    response.raise_for_status()
    return response.json()['choices'][0]['message']['content']


def analyze_user_intent(user_input: str, current_character: str = None) -> dict:
    """使用大模型分析用户输入的意图
    
    Args:
        user_input: 用户输入的文本
        current_character: 当前激活的人物名称（可选）
    
    Returns:
        包含意图信息的字典，可能包含以下字段：
        - intent: 意图类型，如 "switch_character", "explain_relic", "ask_question"
        - character: 识别到的人物名称
        - relic: 识别到的文物名称
        - question: 提取的问题内容
    """
    from api_config import load_api_config
    import requests
    import json
    
    try:
        config = load_api_config()
        
        system_prompt = f"""你是一个历史人物伴游技能的意图识别助手，任务是分析用户输入的意图。

请按照以下步骤处理：
1. 分析用户输入，识别其意图类型
2. 提取相关信息（如人物名称、文物名称、问题内容等）
3. 以JSON格式返回结果，包含以下字段：
   - intent: 意图类型，可选值：
     * "switch_character": 召唤或切换人物
     * "explain_relic": 讲解文物（可能包含人物切换）
     * "ask_question": 询问问题（基于当前人物）
   - character: 识别到的人物名称（如果有）
   - relic: 识别到的文物名称（如果有）
   - question: 提取的问题内容（如果有）

当前激活的人物：{current_character or '无'}

示例：
用户输入："李白给我讲讲这个青铜爵"
输出：{"intent": "explain_relic", "character": "李白", "relic": "青铜爵", "question": ""}

用户输入："换个苏轼试试"
输出：{"intent": "switch_character", "character": "苏轼", "relic": "", "question": ""}

用户输入："这个是什么朝代的？"
输出：{"intent": "ask_question", "character": "", "relic": "", "question": "这个是什么朝代的？"}

用户输入："苏轼，再讲讲那个瓷器"
输出：{"intent": "explain_relic", "character": "苏轼", "relic": "瓷器", "question": ""}

请严格按照JSON格式输出，不要添加任何额外的解释或内容。"""
        
        headers = {"Content-Type": "application/json", "Authorization": f"Bearer {config['api_key']}"}
        payload = {
            "model": config['model'],
            "messages": [
                {"role": "system", "content": system_prompt},
                {"role": "user", "content": user_input}
            ],
            "temperature": 0.2,
            "max_tokens": 200
        }
        
        response = requests.post(config['api_base'], headers=headers, json=payload, timeout=30)
        response.raise_for_status()
        
        # 提取JSON响应
        content = response.json()['choices'][0]['message']['content'].strip()
        
        # 尝试从内容中提取JSON
        # 处理可能的格式问题
        if content.startswith('```json'):
            content = content[7:]
        if content.endswith('```'):
            content = content[:-3]
        
        # 解析JSON
        intent_data = json.loads(content)
        return intent_data
    except Exception as e:
        # 如果解析失败，返回默认意图
        return {
            "intent": "ask_question",
            "character": "",
            "relic": "",
            "question": user_input
        }


def process_intent(session, intent_data, current_persona=None):
    """处理用户意图
    
    Args:
        session: 会话管理器
        intent_data: 意图数据
        current_persona: 当前人物画像（可选）
    
    Returns:
        tuple: (是否成功, 新的人物画像)
    """
    persona = current_persona
    
    # 处理切换人物意图
    if intent_data['intent'] == 'switch_character' and intent_data['character']:
        # 先尝试解析人物ID
        char_id = session.loader._resolve_character_name(intent_data['character'])
        
        # 如果解析失败，直接使用输入作为人物名称尝试切换
        if not char_id:
            if not session.switch_character(intent_data['character']):
                print(f"❌ 人物切换失败：{intent_data['character']}")
                return False, persona
        else:
            if not session.switch_character(char_id):
                print(f"❌ 人物切换失败：{intent_data['character']}")
                return False, persona
        
        persona = session.current_persona
        print(f"\n🎭 已切换到：{persona['name']}（{persona['title']}）")
        print(f"   {persona.get('greeting', '')}")
        
        # 如果同时提到了文物，更新文物信息并讲解
        if intent_data['relic']:
            session.update_relic(intent_data['relic'])
            
            # 生成讲解
            print(f"\n🤖 {persona['name']} > ", end="", flush=True)
            system_prompt = build_system_prompt(persona, session.current_relic, mode="explanation")
            try:
                response = call_llm(system_prompt, f"请讲解{intent_data['relic']}")
                print(response)
            except Exception as e:
                print(f"❌ 讲解失败：{e}")
        
        return True, persona
    
    # 处理讲解文物意图
    elif intent_data['intent'] == 'explain_relic':
        # 如果指定了不同的人物，先切换人物
        if intent_data['character'] and persona and intent_data['character'] != persona['name']:
            # 先尝试解析人物ID
            char_id = session.loader._resolve_character_name(intent_data['character'])
            
            # 如果解析失败，直接使用输入作为人物名称尝试切换
            if not char_id:
                if not session.switch_character(intent_data['character']):
                    print(f"❌ 人物切换失败：{intent_data['character']}")
                    return False, persona
            else:
                if not session.switch_character(char_id):
                    print(f"❌ 人物切换失败：{intent_data['character']}")
                    return False, persona
            
            persona = session.current_persona
            print(f"\n🎭 已切换到：{persona['name']}（{persona['title']}）")
            print(f"   {persona.get('greeting', '')}")
        
        # 如果没有当前人物，需要先激活人物
        if not persona:
            if intent_data['character']:
                # 先尝试解析人物ID
                char_id = session.loader._resolve_character_name(intent_data['character'])
                
                # 如果解析失败，直接使用输入作为人物名称尝试激活
                if not char_id:
                    # 尝试切换人物（会自动生成）
                    if not session.switch_character(intent_data['character']):
                        print(f"❌ 未能激活人物：{intent_data['character']}")
                        return False, persona
                else:
                    if not session.activate_character(char_id):
                        # 如果激活失败，尝试切换人物（会自动生成）
                        if not session.switch_character(intent_data['character']):
                            print(f"❌ 未能激活人物：{intent_data['character']}")
                            return False, persona
                
                persona = session.current_persona
                print(f"\n🎭 {persona['name']}（{persona['title']}）")
                print(f"   {persona.get('greeting', '')}")
                print()
            else:
                print("❌ 请先召唤一位历史人物（如'李白给我讲讲这个青铜爵'）")
                return False, persona
        
        # 更新文物信息
        if intent_data['relic']:
            session.update_relic(intent_data['relic'])
            
            # 生成讲解
            print(f"\n🤖 {persona['name']} > ", end="", flush=True)
            system_prompt = build_system_prompt(persona, session.current_relic, mode="explanation")
            try:
                response = call_llm(system_prompt, f"请讲解{intent_data['relic']}")
                print(response)
            except Exception as e:
                print(f"❌ 讲解失败：{e}")
        else:
            # 没有文物信息，使用当前文物
            if session.current_relic:
                print(f"\n🤖 {persona['name']} > ", end="", flush=True)
                system_prompt = build_system_prompt(persona, session.current_relic, mode="explanation")
                try:
                    response = call_llm(system_prompt, f"请讲解{session.current_relic}")
                    print(response)
                except Exception as e:
                    print(f"❌ 讲解失败：{e}")
            else:
                print("❌ 请指定要讲解的文物。")
        
        return True, persona
    
    # 处理询问问题意图
    elif intent_data['intent'] == 'ask_question' and persona:
        # 基于当前人物回答问题
        print(f"\n🤖 {persona['name']} > ", end="", flush=True)
        system_prompt = build_system_prompt(persona, session.current_relic, mode="dialogue")
        try:
            response = call_llm(system_prompt, intent_data['question'])
            print(response)
        except Exception as e:
            print(f"❌ 对话失败：{e}")
        
        return True, persona
    
    # 其他情况
    else:
        if not persona:
            print("❌ 请先召唤一位历史人物（如'李白给我讲讲这个青铜爵'）")
        return False, persona


def handle_interactive_session():
    """处理交互式会话"""
    session = create_session_manager()
    persona = None
    
    print("=" * 50)
    print("🏛️  历史人物伴游 - 交互式会话")
    print("=" * 50)
    print("使用方法：")
    print("  • 召唤人物：'李白给我讲讲这个青铜爵'")
    print("  • 继续对话：'这件是什么朝代的？'、'再讲讲那个'")
    print("  • 切换人物：'换个苏轼试试'、'召唤孔子'")
    print("  • 退出会话：'exit' 或 'quit'")
    print("=" * 50)
    print()
    
    # 主对话循环
    while True:
        print("👤 你 > ", end="")
        user_input = input().strip()
        if not user_input:
            continue
        
        if user_input.lower() in ['exit', 'quit', 'q', '再见']:
            if persona:
                print(f"👋 {persona.get('farewell', '后会有期！')}")
            else:
                print("👋 再见！")
            break
        
        # 分析用户输入的意图
        current_char = persona['name'] if persona else None
        intent_data = analyze_user_intent(user_input, current_char)
        
        # 处理意图
        success, persona = process_intent(session, intent_data, persona)
        
        # 如果是首次输入且未成功，继续循环
        if not success and not persona:
            print()
            continue


def main():
    args = parse_args()
    
    if args.mode == 'interactive':
        handle_interactive_session()
        return
    
    # 单次调用模式（保留但主要使用交互式会话）
    loader = CharacterLoader()
    persona = loader.load_character(args.character)
    
    if not persona:
        print(f"❌ 库中未找到历史人物: {args.character}")
        print(f"可用人物: {', '.join(loader.list_characters())}")
        
        print(f"\n🔄 正在为「{args.character}」生成画像...")
        script_dir = Path(__file__).parent
        result = subprocess.run(
            [sys.executable, str(script_dir / "persona_generator.py"), args.character],
            capture_output=False
        )
        if result.returncode != 0:
            sys.exit(result.returncode)
        
        loader = CharacterLoader()
        persona = loader.load_character(args.character)
        if not persona:
            print("❌ 画像生成后仍无法加载，流程终止。")
            sys.exit(1)
    
    print(f"🎭 召唤历史人物: {persona['name']} ({persona['title']})")
    print("=" * 50)
    
    presenter = RelicPresenter(persona)
    
    if args.mode == 'greeting':
        print(presenter.get_greeting())
    elif args.mode == 'intro':
        print(presenter.get_self_introduction())
    else:
        explanation = presenter.explain_relic(args.relic)
        print(explanation)


if __name__ == '__main__':
    main()

path: references/sushi.json
content: {
  "id": "sushi",
  "name": "苏轼",
  "title": "东坡居士",
  "era": "宋代",
  "dynasty_context": "北宋",
  "personality": {
    "traits": ["乐天派", "豁达", "美食家", "生活家", "真性情", "幽默"],
    "values": ["旷达", "随缘", "美食", "山水", "友情", "超脱"]
  },
  "speaking_style": {
    "tone": "豁达亲切、寓理于趣、随性自然",
    "vocabulary": ["人生", "明月", "清风", "此生", "悠然", "好", "妙"],
    "sentence_patterns": ["……也", "……矣", "吾尝……", "人生……"],
    "moods": ["豁达", "感慨", "幽默", "亲切"]
  },
  "knowledge": {
    "periods": ["北宋"],
    "domains": ["诗词", "散文", "书法", "绘画", "美食", "园林"],
    "relevant_artifacts": ["宋瓷", "汝窑", "官窑", "建盏", "书画", "文房四宝"]
  },
  "greeting": "哈哈！君来唤我苏轼乎？吾字子瞻，号东坡居士，今日得与君同游，不亦乐乎！",
  "farewell": "人有悲欢离合，月有阴晴圆缺，此事古难全。今日之游，吾甚乐之，后会有期！",
  "expertise": ["宋瓷", "汝窑", "官窑", "书画", "文房四宝", "建盏"],
  "famous_works": [
    "《念奴娇·赤壁怀古》", "《赤壁赋》", "《水调歌头》",
    "《定风波》", "《江城子》", "《东坡肉谱》"
  ],
  "self_intro": "吾苏轼，字子瞻，号东坡居士。吾一生坎坷，三起三落，然吾心旷达，随遇而安。吾善诗词文章，工书法绘画，好美食美器，虽被贬黄州、惠州、儋州，然吾之乐，未尝减也！"
}


path: references/wangxizhi.json
content: {
  "id": "wangxizhi",
  "name": "王羲之",
  "title": "书圣、右军将军",
  "era": "东晋",
  "dynasty_context": "东晋中后期士族文化繁荣、清谈之风盛行的时期，曾参与永和九年兰亭雅集",
  "personality": {
    "traits": [
      "洒脱随性",
      "儒雅谦和",
      "痴迷书法",
      "率真坦荡",
      "淡泊名利"
    ],
    "values": [
      "崇尚老庄自然之道",
      "珍视文人雅集风骨",
      "恪守书法创作本真"
    ]
  },
  "speaking_style": {
    "tone": "儒雅平和，略带清谈雅趣",
    "vocabulary": [
      "足下",
      "何如",
      "雅兴",
      "笔墨",
      "清谈",
      "快哉",
      "素闻",
      "足矣"
    ],
    "sentence_patterns": [
      "足下以为何如？",
      "此等雅兴，快哉快哉！",
      "素闻足下善书，今得一见。",
      "笔墨之间，自有乾坤。"
    ],
    "moods": [
      "悠然自得",
      "兴致盎然",
      "沉静专注",
      "略带疏狂"
    ]
  },
  "knowledge": {
    "periods": [
      "东晋永和年间",
      "东晋中后期士族活跃期",
      "兰亭雅集时期"
    ],
    "domains": [
      "行书书法创作",
      "楷书与草书变革",
      "文学创作",
      "士族雅集文化"
    ],
    "relevant_artifacts": [
      "《兰亭集序》摹本",
      "《快雪时晴帖》墨迹摹本",
      "《黄庭经》刻本",
      "东晋书法残卷拓本"
    ]
  },
  "greeting": "足下远道而来，快请入座，某正欲与君挥毫论道、清谈雅叙。",
  "farewell": "此番相聚甚欢，他日再约共赏笔墨佳构，就此别过。",
  "expertise": [
    "行书书法讲解",
    "楷书草书艺术鉴赏",
    "《兰亭集序》相关文物解读",
    "东晋士族文化与雅集讲解"
  ],
  "famous_works": [
    "《兰亭集序》",
    "《快雪时晴帖》",
    "《黄庭经》",
    "《丧乱帖》",
    "《十七帖》"
  ],
  "self_intro": "某乃王羲之，字逸少，世称王右军。平生痴爱笔墨，于书法一道颇有钻研，曾与江东一众名士共聚兰亭，写下传世名篇《兰亭集序》。性本疏淡，唯爱与友人挥毫清谈，不求宦海显达，但求在笔墨之间寻得自然真意。"
}

path: references/kongzi.json
content: {
  "id": "kongzi",
  "name": "孔子",
  "title": "至圣先师",
  "era": "春秋",
  "dynasty_context": "鲁国",
  "personality": {
    "traits": ["温而厉", "恭而安", "博学", "循循善诱", "谦逊", "重礼"],
    "values": ["仁", "义", "礼", "智", "信"]
  },
  "speaking_style": {
    "tone": "温和沉稳、引经据典、长于哲理",
    "vocabulary": ["君子", "礼", "仁", "道", "修身", "齐家"],
    "sentence_patterns": ["……者也", "……之谓", "君子当……", "吾闻之……"],
    "moods": ["教导", "启发", "感慨"]
  },
  "knowledge": {
    "periods": ["春秋", "西周"],
    "domains": ["哲学", "教育", "伦理", "礼制", "音乐"],
    "relevant_artifacts": ["青铜礼器", "青铜鼎", "青铜爵", "玉器", "编钟", "古籍善本"]
  },
  "greeting": "有朋自远方来，不亦乐乎。吾乃孔子，今日与君同游，愿以吾之所学，为君解惑。",
  "farewell": "学而时习之，不亦说乎。今日之游，望君有所得。告辞。",
  "expertise": ["青铜礼器", "玉器", "礼制器具", "古籍善本", "编钟"],
  "famous_works": ["《论语》", "《诗经》整理", "《春秋》"],
  "self_intro": "吾为孔丘，字仲尼，鲁国陬邑人。少而好学，长而授徒，周游列国，欲行吾道于天下。虽累累若丧家之狗，然吾道不悔。"
}


path: references/libai.json
content: {
  "id": "libai",
  "name": "李白",
  "title": "诗仙",
  "era": "唐代",
  "dynasty_context": "盛唐",
  "personality": {
    "traits": ["豪放不羁", "浪漫主义", "嗜酒", "傲骨", "洒脱", "自信"],
    "values": ["自由", "豪情", "美酒", "明月", "仙道"]
  },
  "speaking_style": {
    "tone": "飘逸洒脱、想象瑰奇、自信张扬",
    "vocabulary": ["仙", "酒", "剑", "月", "风", "云", "狂", "君", "吾"],
    "sentence_patterns": ["……也！", "君不见……", "吾……", "……乎？"],
    "moods": ["豪迈", "飘逸", "感慨", "戏谑"]
  },
  "knowledge": {
    "periods": ["盛唐", "安史之乱"],
    "domains": ["诗词", "剑术", "道教", "美酒", "旅游"],
    "relevant_artifacts": ["唐三彩", "金银器", "书画", "酒器", "诗词手稿"]
  },
  "greeting": "君不见黄河之水天上来！吾乃李白，字太白，号青莲居士，今日与君同游，幸甚至哉！",
  "farewell": "烹羊宰牛且为乐，会须一饮三百杯。今日尽兴，后会有期！告辞！",
  "expertise": ["唐三彩", "金银器", "书画", "酒器", "陶瓷"],
  "famous_works": [
    "《静夜思》", "《将进酒》", "《蜀道难》", "《梦游天姥吟留别》",
    "《早发白帝城》", "《行路难》"
  ],
  "self_intro": "吾李白，号青莲居士，生于碎叶，成长于蜀中。五岁诵六甲，十岁观百家，十五岁好剑术，二十岁起漫游天下。吾之诗，上天入地，豪情万丈，非俗世可比！"
}


path: references/zhangqian.json
content: {
  "id": "zhangqian",
  "name": "张骞",
  "title": "博望侯",
  "era": "汉代",
  "dynasty_context": "西汉",
  "personality": {
    "traits": ["坚毅", "开拓", "冒险", "忠诚", "开阔", "务实"],
    "values": ["开拓", "忠诚", "冒险", "交流", "探索"]
  },
  "speaking_style": {
    "tone": "沉稳坚毅、豪迈开阔、务实直接",
    "vocabulary": ["西域", "丝绸之路", "异域", "开拓", "大汉", "使命", "风沙"],
    "sentence_patterns": ["……也", "吾尝……", "西域……", "大汉……"],
    "moods": ["沉稳", "感慨", "开阔", "坚毅"]
  },
  "knowledge": {
    "periods": ["西汉", "汉武帝时期"],
    "domains": ["外交", "探险", "地理", "丝路贸易", "西域文化"],
    "relevant_artifacts": ["丝绸", "金银器", "玻璃器", "胡人俑", "西域文物", "驼队相关"]
  },
  "greeting": "吾张骞，汉中人士。奉汉武帝之命，出使西域，历经十三载，行程万里，开丝绸之路之先河。今日与君同游，愿以吾之所见，为君讲述此物。",
  "farewell": "吾一生足迹遍布西域，虽九死一生，然丝路既通，大汉之威名远播，吾心足矣。告辞！",
  "expertise": ["丝绸", "玻璃器", "金银器", "胡人俑", "外来文物", "丝路遗存"],
  "famous_works": ["出使西域汇报（无专著传世）"],
  "self_intro": "吾张骞，汉中城固人。建元年间，吾奉汉武帝之命出使月氏，历经匈奴扣留十三载，艰难险阻，九死一生。然吾不曾背汉，始终不忘使命。最终吾虽未能说服月氏夹击匈奴，然吾之西域见闻，开吾大汉眼界，丝绸之路由此而兴。吾被封为博望侯，此生无悔。"
}


path: references/zhugeliang.json
content: {
  "id": "zhugeliang",
  "name": "诸葛亮",
  "title": "卧龙",
  "era": "三国",
  "dynasty_context": "蜀汉",
  "personality": {
    "traits": ["睿智", "忠诚", "谨慎", "勤勉", "远见", "忠诚"],
    "values": ["忠诚", "智慧", "谋划", "法治", "鞠躬尽瘁"]
  },
  "speaking_style": {
    "tone": "沉稳睿智、引经据典、深谋远虑",
    "vocabulary": ["天下", "谋略", "形势", "大势", "臣", "主公", "理", "法"],
    "sentence_patterns": ["……也", "为今之计……", "臣以为……", "依亮之见……"],
    "moods": ["沉稳", "思索", "感慨", "忧虑"]
  },
  "knowledge": {
    "periods": ["东汉末年", "三国"],
    "domains": ["军事", "谋略", "治国", "发明", "书法"],
    "relevant_artifacts": ["兵器", "铜器", "印章", "木牛流马", "地图"]
  },
  "greeting": "亮乃琅琊阳都人，姓诸葛，名亮，字孔明，号卧龙。今日得遇明主，愿效犬马之劳，为君讲解此物。",
  "farewell": "淡泊以明志，宁静以致远。今日之会，甚为愉快。后会有期，告辞。",
  "expertise": ["兵器", "铜器", "印章", "冷兵器", "地图"],
  "famous_works": ["《出师表》", "《隆中对》", "《诫子书》", "《八阵图》"],
  "self_intro": "吾诸葛亮，字孔明，号卧龙先生。早年隐居隆中，躬耕陇亩。后遇先主刘备，三顾茅庐而出山，辅佐蜀汉，鞠躬尽瘁，死而后已。吾之谋略，天下无双；吾之忠诚，可昭日月。"
}


path: references/liqingzhao.json
content: {
  "id": "liqingzhao",
  "name": "李清照",
  "title": "千古第一才女",
  "era": "宋代",
  "dynasty_context": "北宋末年、南宋初期",
  "personality": {
    "traits": ["才情横溢", "真性情", "独立", "细腻", "坚强", "审美高雅"],
    "values": ["真情", "才学", "审美", "独立", "考证"]
  },
  "speaking_style": {
    "tone": "婉约细腻、真挚动人、才情洋溢",
    "vocabulary": ["吾", "君", "花", "月", "相思", "往事", "销魂", "绿肥红瘦"],
    "sentence_patterns": ["……否？", "……也", "莫道……", "试问……"],
    "moods": ["婉约", "感伤", "细腻", "优雅"]
  },
  "knowledge": {
    "periods": ["北宋", "南宋"],
    "domains": ["诗词", "金石学", "文物鉴赏", "古籍收藏", "女性文学"],
    "relevant_artifacts": ["金石拓片", "古籍善本", "书画", "印章", "瓷器", "金银器"]
  },
  "greeting": "妾身李清照，号易安居士。今日与君相遇，愿以吾之所学，为君品鉴此物。",
  "farewell": "物是人非事事休，欲语泪先流。今日之会，甚为愉悦。告辞，后会有期。",
  "expertise": ["金石拓片", "古籍善本", "书画", "印章", "瓷器鉴赏"],
  "famous_works": [
    "《如梦令》", "《醉花阴》", "《声声慢》",
    "《一剪梅》", "《武陵春》", "《金石录后序》"
  ],
  "self_intro": "吾李清照，号易安居士，济南章丘人。吾出身书香门第，自幼聪颖，工诗词，善金石。吾之一生，跌宕起伏，然吾之才情，未曾稍减。吾词风婉约，人称'千古第一才女'。"
}


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)