run.py 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. """
  2. NanoBanana 工具测试脚本
  3. 测试场景:
  4. 1. 纯文本生成图像
  5. 2. 基于第一个测试生成的图像,转换为不同风格
  6. """
  7. import asyncio
  8. import sys
  9. from pathlib import Path
  10. from dotenv import load_dotenv
  11. # 添加项目根目录到 Python 路径
  12. project_root = Path(__file__).parent.parent.parent
  13. sys.path.insert(0, str(project_root))
  14. # 从 Agent 根目录加载 .env 文件
  15. env_path = project_root / ".env"
  16. if env_path.exists():
  17. load_dotenv(env_path)
  18. print(f"✅ 已加载环境变量: {env_path}")
  19. else:
  20. print(f"⚠️ 未找到 .env 文件: {env_path}")
  21. from agent.core.runner import AgentRunner, RunConfig
  22. from agent.trace.store import FileSystemTraceStore
  23. from agent.trace.models import Trace, Message
  24. from agent.llm.openrouter import create_openrouter_llm_call
  25. async def test_text_to_image(output_dir: Path) -> Path:
  26. """测试1: 纯文本生成图像
  27. Returns:
  28. 生成的图片路径
  29. """
  30. print("\n" + "="*60)
  31. print("测试1: 纯文本生成图像")
  32. print("="*60)
  33. # 导入自定义工具
  34. import examples.test_nanobanana.tool
  35. store = FileSystemTraceStore(base_path=".trace")
  36. llm_call = create_openrouter_llm_call(model="anthropic/claude-sonnet-4.5")
  37. runner = AgentRunner(
  38. trace_store=store,
  39. llm_call=llm_call,
  40. )
  41. config = RunConfig(
  42. model="anthropic/claude-sonnet-4.5",
  43. temperature=0.3,
  44. max_iterations=10,
  45. )
  46. # 使用绝对路径
  47. output_path = output_dir / "cat.png"
  48. task = f"""
  49. 请使用 nanobanana 工具生成一张图片:
  50. - 内容:一只可爱的橙色小猫,坐在窗台上看着外面的雨
  51. - 风格:水彩画风格
  52. - 保存到:{output_path}
  53. 注意:路径必须使用绝对路径。
  54. """
  55. print(f"\n任务: 生成小猫图片")
  56. print(f"输出路径: {output_path}\n")
  57. # 将任务转换为消息格式
  58. messages = [{"role": "user", "content": task}]
  59. generated_image = None
  60. try:
  61. async for item in runner.run(messages=messages, config=config):
  62. # 处理 Trace 对象(整体状态变化)
  63. if isinstance(item, Trace):
  64. if item.status == "running":
  65. print(f"[Trace] 开始: {item.trace_id[:8]}...")
  66. elif item.status == "completed":
  67. print(f"\n[Trace] ✅ 完成")
  68. print(f" - Total messages: {item.total_messages}")
  69. print(f" - Total tokens: {item.total_tokens}")
  70. print(f" - Total cost: ${item.total_cost:.4f}")
  71. if output_path.exists():
  72. generated_image = output_path
  73. print(f" - 生成的图片: {generated_image}")
  74. elif item.status == "failed":
  75. print(f"\n[Trace] ❌ 失败: {item.error_message}")
  76. elif item.status == "stopped":
  77. print(f"\n[Trace] ⏸️ 已停止")
  78. # 处理 Message 对象(执行过程)
  79. elif isinstance(item, Message):
  80. if item.role == "assistant":
  81. content = item.content
  82. if isinstance(content, dict):
  83. text = content.get("text", "")
  84. tool_calls = content.get("tool_calls")
  85. if text and not tool_calls:
  86. # 纯文本回复(最终响应)
  87. print(f"\n[Response] {text}")
  88. elif tool_calls:
  89. # 工具调用
  90. tool_names = [tc.get("function", {}).get("name") for tc in tool_calls]
  91. print(f"[Tool Call] {', '.join(tool_names)}")
  92. elif item.role == "tool":
  93. # 工具结果
  94. if isinstance(item.content, dict):
  95. tool_name = item.content.get("tool_name", "unknown")
  96. print(f"[Tool Result] {tool_name}")
  97. except Exception as e:
  98. print(f"\n❌ 测试失败: {e}")
  99. import traceback
  100. traceback.print_exc()
  101. return generated_image
  102. async def test_image_to_image(input_image: Path, output_dir: Path):
  103. """测试2: 基于参考图像生成新图像
  104. Args:
  105. input_image: 参考图像路径(来自测试1)
  106. output_dir: 输出目录
  107. """
  108. print("\n" + "="*60)
  109. print("测试2: 基于参考图像生成新图像")
  110. print("="*60)
  111. if not input_image or not input_image.exists():
  112. print("⚠️ 跳过测试2: 测试1没有成功生成图片")
  113. return
  114. print(f"参考图像: {input_image}")
  115. # 导入自定义工具
  116. import examples.test_nanobanana.tool
  117. store = FileSystemTraceStore(base_path=".trace")
  118. llm_call = create_openrouter_llm_call(model="anthropic/claude-sonnet-4.5")
  119. runner = AgentRunner(
  120. trace_store=store,
  121. llm_call=llm_call,
  122. )
  123. config = RunConfig(
  124. model="anthropic/claude-sonnet-4.5",
  125. temperature=0.3,
  126. max_iterations=10,
  127. )
  128. # 使用绝对路径
  129. output_path = output_dir / "cat_oil_painting.png"
  130. task = f"""
  131. 请使用 nanobanana 工具,基于参考图像生成一张新图片:
  132. - 参考图像:{input_image}
  133. - 要求:保持小猫和窗台的元素,但改变风格为油画风格
  134. - 保存到:{output_path}
  135. 注意:路径必须使用绝对路径。
  136. """
  137. print(f"\n任务: 将小猫图片转换为油画风格")
  138. print(f"输出路径: {output_path}\n")
  139. # 将任务转换为消息格式
  140. messages = [{"role": "user", "content": task}]
  141. try:
  142. async for item in runner.run(messages=messages, config=config):
  143. # 处理 Trace 对象(整体状态变化)
  144. if isinstance(item, Trace):
  145. if item.status == "running":
  146. print(f"[Trace] 开始: {item.trace_id[:8]}...")
  147. elif item.status == "completed":
  148. print(f"\n[Trace] ✅ 完成")
  149. print(f" - Total messages: {item.total_messages}")
  150. print(f" - Total tokens: {item.total_tokens}")
  151. print(f" - Total cost: ${item.total_cost:.4f}")
  152. if output_path.exists():
  153. print(f" - 生成的图片: {output_path}")
  154. elif item.status == "failed":
  155. print(f"\n[Trace] ❌ 失败: {item.error_message}")
  156. elif item.status == "stopped":
  157. print(f"\n[Trace] ⏸️ 已停止")
  158. # 处理 Message 对象(执行过程)
  159. elif isinstance(item, Message):
  160. if item.role == "assistant":
  161. content = item.content
  162. if isinstance(content, dict):
  163. text = content.get("text", "")
  164. tool_calls = content.get("tool_calls")
  165. if text and not tool_calls:
  166. # 纯文本回复(最终响应)
  167. print(f"\n[Response] {text}")
  168. elif tool_calls:
  169. # 工具调用
  170. tool_names = [tc.get("function", {}).get("name") for tc in tool_calls]
  171. print(f"[Tool Call] {', '.join(tool_names)}")
  172. elif item.role == "tool":
  173. # 工具结果
  174. if isinstance(item.content, dict):
  175. tool_name = item.content.get("tool_name", "unknown")
  176. print(f"[Tool Result] {tool_name}")
  177. except Exception as e:
  178. print(f"\n❌ 测试失败: {e}")
  179. import traceback
  180. traceback.print_exc()
  181. async def main():
  182. """运行所有测试"""
  183. print("\n" + "="*60)
  184. print("NanoBanana 工具测试")
  185. print("="*60)
  186. # 创建输出目录(使用绝对路径)
  187. output_dir = Path(__file__).parent / "output"
  188. output_dir.mkdir(exist_ok=True)
  189. print(f"输出目录: {output_dir.absolute()}\n")
  190. try:
  191. # 测试1: 纯文本生成
  192. generated_image = await test_text_to_image(output_dir)
  193. # 测试2: 图像转换(基于测试1的结果)
  194. if generated_image:
  195. await test_image_to_image(generated_image, output_dir)
  196. else:
  197. print("\n⚠️ 跳过测试2: 测试1未成功生成图片")
  198. print("\n" + "="*60)
  199. print("测试完成!")
  200. print("="*60)
  201. print(f"\n查看生成的图片:")
  202. if generated_image and generated_image.exists():
  203. print(f" 1. {generated_image}")
  204. oil_painting = output_dir / "cat_oil_painting.png"
  205. if oil_painting.exists():
  206. print(f" 2. {oil_painting}")
  207. except KeyboardInterrupt:
  208. print("\n\n⚠️ 测试被用户中断")
  209. except Exception as e:
  210. print(f"\n\n❌ 测试失败: {e}")
  211. import traceback
  212. traceback.print_exc()
  213. if __name__ == "__main__":
  214. asyncio.run(main())