outline_agent.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. from dotenv import load_dotenv
  2. load_dotenv()
  3. from hello_agents import SimpleAgent, HelloAgentsLLM
  4. from hello_agents.tools import NoteTool
  5. from prompt import OUTLINE_PROMPT
  6. import re
  7. import os
  8. def extract_note_id(output: str) -> str:
  9. """从 NoteTool 的输出文本中提取 note_id"""
  10. match = re.search(r"ID:\s*(note_[0-9_]+)", output)
  11. if not match:
  12. raise ValueError(f"无法从输出解析 note_id:\n{output}")
  13. return match.group(1)
  14. class OutlineAgent(SimpleAgent):
  15. """小说大纲生成Agent"""
  16. def __init__(self, name: str, llm: HelloAgentsLLM = HelloAgentsLLM(), **kwargs):
  17. self.workspace = kwargs.pop("workspace", "./outputs")
  18. super().__init__(name=name, llm=llm)
  19. self.outline_length = 3000
  20. self.note_tools = {}
  21. def _ensure_tool(self, novel_id: str, title: str = None):
  22. if not self.note_tools.get(novel_id):
  23. if not title:
  24. raise ValueError(f"Tool for novel_id {novel_id} not initialized and title not provided.")
  25. self.note_tools[novel_id] = NoteTool(workspace=os.path.join(self.workspace, f"{title}-{novel_id}", 'outline'))
  26. def run(self, user_input: str, **kwargs) -> str:
  27. """运行 Agent"""
  28. # 小说id用来区分小说,命名可能会重复
  29. novel_id = kwargs.pop("novel_id", None)
  30. assert novel_id, "请提供小说ID"
  31. title = kwargs.pop("title", None)
  32. assert title, "请提供小说标题"
  33. self._ensure_tool(novel_id, title)
  34. # 1. 构建上下文
  35. target_length = kwargs.pop("target_length", self.outline_length)
  36. context = OUTLINE_PROMPT.format(
  37. user_input=user_input,
  38. title=title or "无",
  39. tags=','.join([str(tag) for tag in kwargs.values() if tag]) or '无',
  40. target_length=target_length
  41. )
  42. # 2. 使用上下文调用 LLM
  43. messages = [{"role": "user", "content": context}]
  44. response = self.llm.invoke(messages)
  45. # 3. 保存大纲到笔记
  46. create_output = self.note_tools[novel_id].run({
  47. "action": "create",
  48. "title": f"{novel_id}-大纲",
  49. "content": response,
  50. "note_type": "outline",
  51. "tags": ["outline"]
  52. })
  53. # 获取笔记ID,建立与小说ID的关联
  54. note_id = extract_note_id(create_output)
  55. return response, note_id
  56. def get_outline(self, novel_id: str, note_id: str, title: str = None) -> str:
  57. """获取大纲"""
  58. if title:
  59. self._ensure_tool(novel_id, title)
  60. return self.note_tools[novel_id].run({
  61. "action": "read",
  62. "note_id": note_id
  63. })
  64. def del_outline(self, novel_id: str, note_id: str, title: str = None):
  65. """删除大纲"""
  66. if title:
  67. self._ensure_tool(novel_id, title)
  68. self.note_tools[novel_id].run({
  69. "action": "delete",
  70. "note_id": note_id
  71. })
  72. def update_outline(self, novel_id: str, note_id: str, title: str = None, **kwargs):
  73. """更新大纲"""
  74. if title:
  75. self._ensure_tool(novel_id, title)
  76. self.note_tools[novel_id].run({
  77. "action": "update",
  78. "note_id": note_id,
  79. **kwargs
  80. })
  81. def main():
  82. print("=" * 80)
  83. print("Novel OutlineAgent 示例")
  84. print("=" * 80 + "\n")
  85. llm = HelloAgentsLLM()
  86. novel_id = "demo_novel_001"
  87. title = "记忆之城"
  88. agent = OutlineAgent(
  89. name="小说大纲助手",
  90. llm=llm,
  91. workspace="./outputs",
  92. )
  93. user_idea = "一位能与城市记忆对话的年轻人,在拆迁浪潮中发现一段被刻意抹去的历史。"
  94. # 1. 生成大纲
  95. print(f"\n正在生成大纲...")
  96. response, note_id = agent.run(
  97. user_input=user_idea,
  98. novel_id=novel_id,
  99. title=title,
  100. 风格标签="都市奇幻",
  101. 情感基调="成长与和解",
  102. )
  103. print("生成的大纲(节选):")
  104. print(response[:200] + "...\n")
  105. print(f"大纲已保存到 NoteTool,note_id: {note_id}")
  106. # 2. 读取大纲
  107. print(f"\n正在读取大纲 (Note ID: {note_id})...")
  108. # 注意:get_outline 需要传入 novel_id 和 note_id
  109. stored_outline = agent.get_outline(novel_id, note_id)
  110. print("从 NoteTool 中读取的大纲(节选):")
  111. # 去掉可能存在的 frontmatter 后的内容预览(这里简单展示原始返回)
  112. print(stored_outline[:200] + "...")
  113. # 3. 更新大纲
  114. print(f"\n正在更新大纲...")
  115. # 简单模拟:在原有内容后追加一些信息
  116. # 注意:update_outline 会覆盖 content,所以需要先读取再追加,或者直接传入完整的新内容
  117. # 这里我们演示读取后追加
  118. new_content = stored_outline + "\n\n## 补充设定\n主角的能力在雨天会增强,且能听到建筑物的'呼吸声'。"
  119. agent.update_outline(novel_id, note_id, content=new_content, tags=["outline", "updated"])
  120. print("大纲已更新。")
  121. # 4. 再次读取验证更新
  122. print(f"\n正在验证更新后的内容...")
  123. updated_outline = agent.get_outline(novel_id, note_id)
  124. if "主角的能力在雨天会增强" in updated_outline:
  125. print("验证成功:更新内容已存在。")
  126. else:
  127. print("验证失败:未找到更新内容。")
  128. # 5. 删除大纲(演示,默认注释掉以免误删)
  129. # print(f"\n正在删除大纲...")
  130. # agent.del_outline(novel_id, note_id)
  131. # print("大纲已删除。")
  132. if __name__ == "__main__":
  133. main()