bolt-boot 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. bolt/__init__.py +23 -0
  2. bolt/__main__.py +18 -0
  3. bolt/agent/__init__.py +39 -0
  4. bolt/agent/base.py +282 -0
  5. bolt/agent/example.py +94 -0
  6. bolt/agent/memory.py +375 -0
  7. bolt/agent/messages.py +48 -0
  8. bolt/agent/oxy/__init__.py +24 -0
  9. bolt/agent/oxy/engine.py +285 -0
  10. bolt/agent/oxy/llm_compat.py +132 -0
  11. bolt/agent/oxy/mapping.py +275 -0
  12. bolt/agent/oxy/mas_api.py +390 -0
  13. bolt/agent/oxy/runtime.py +588 -0
  14. bolt/agent/tools.py +125 -0
  15. bolt/api/__init__.py +85 -0
  16. bolt/api/deps.py +155 -0
  17. bolt/api/health.py +501 -0
  18. bolt/api/openapi.py +291 -0
  19. bolt/api/rate_limit.py +421 -0
  20. bolt/cli/commands/__init__.py +1 -0
  21. bolt/cli/commands/agent.py +114 -0
  22. bolt/cli/commands/docs.py +56 -0
  23. bolt/cli/commands/generate.py +535 -0
  24. bolt/cli/commands/init.py +277 -0
  25. bolt/cli/commands/migrate.py +277 -0
  26. bolt/cli/commands/rag.py +137 -0
  27. bolt/cli/console.py +8 -0
  28. bolt/cli/main.py +375 -0
  29. bolt/core/__init__.py +84 -0
  30. bolt/core/app.py +497 -0
  31. bolt/core/config.py +332 -0
  32. bolt/core/database.py +167 -0
  33. bolt/core/di.py +463 -0
  34. bolt/core/exceptions.py +419 -0
  35. bolt/core/lifecycle.py +144 -0
  36. bolt/core/logging.py +310 -0
  37. bolt/core/middleware.py +411 -0
  38. bolt/core/plugin.py +108 -0
  39. bolt/core/propagation.py +72 -0
  40. bolt/core/protocols.py +79 -0
  41. bolt/core/response.py +190 -0
  42. bolt/core/telemetry.py +214 -0
  43. bolt/domain/__init__.py +34 -0
  44. bolt/domain/events.py +220 -0
  45. bolt/domain/model.py +140 -0
  46. bolt/domain/repository/__init__.py +13 -0
  47. bolt/domain/repository/base.py +695 -0
  48. bolt/domain/repository/bulk.py +209 -0
  49. bolt/domain/repository/cache.py +134 -0
  50. bolt/domain/repository/filters.py +86 -0
  51. bolt/domain/repository/pagination.py +61 -0
  52. bolt/domain/schema.py +138 -0
  53. bolt/domain/service.py +251 -0
  54. bolt/domain/transaction.py +387 -0
  55. bolt/infrastructure/__init__.py +99 -0
  56. bolt/infrastructure/cache.py +252 -0
  57. bolt/infrastructure/cache_backends.py +237 -0
  58. bolt/infrastructure/config_center/__init__.py +349 -0
  59. bolt/infrastructure/distributed_lock.py +199 -0
  60. bolt/infrastructure/mq/__init__.py +242 -0
  61. bolt/infrastructure/mq/kafka.py +274 -0
  62. bolt/infrastructure/mq/rabbitmq.py +247 -0
  63. bolt/infrastructure/mq/rocketmq.py +254 -0
  64. bolt/infrastructure/observability/__init__.py +410 -0
  65. bolt/infrastructure/registry/__init__.py +73 -0
  66. bolt/infrastructure/registry/consul.py +209 -0
  67. bolt/infrastructure/registry/nacos.py +204 -0
  68. bolt/infrastructure/resilience/__init__.py +530 -0
  69. bolt/infrastructure/scheduler.py +444 -0
  70. bolt/infrastructure/scheduler_backends.py +186 -0
  71. bolt/infrastructure/scheduler_models.py +277 -0
  72. bolt/py.typed +0 -0
  73. bolt/rag/__init__.py +51 -0
  74. bolt/rag/advanced/__init__.py +40 -0
  75. bolt/rag/advanced/advanced_retriever.py +146 -0
  76. bolt/rag/advanced/citation.py +69 -0
  77. bolt/rag/advanced/compression.py +88 -0
  78. bolt/rag/advanced/reranker.py +215 -0
  79. bolt/rag/advanced/retriever.py +154 -0
  80. bolt/rag/advanced/search.py +149 -0
  81. bolt/rag/documents.py +28 -0
  82. bolt/rag/embeddings.py +241 -0
  83. bolt/rag/knowledge_graph/__init__.py +79 -0
  84. bolt/rag/knowledge_graph/extractor.py +114 -0
  85. bolt/rag/knowledge_graph/graph_rag.py +158 -0
  86. bolt/rag/knowledge_graph/models.py +69 -0
  87. bolt/rag/knowledge_graph/stores.py +462 -0
  88. bolt/rag/llm.py +159 -0
  89. bolt/rag/loaders.py +725 -0
  90. bolt/rag/multimodal/__init__.py +42 -0
  91. bolt/rag/multimodal/audio.py +118 -0
  92. bolt/rag/multimodal/base.py +38 -0
  93. bolt/rag/multimodal/image.py +186 -0
  94. bolt/rag/multimodal/loader.py +190 -0
  95. bolt/rag/multimodal/table.py +138 -0
  96. bolt/rag/multimodal/video.py +234 -0
  97. bolt/rag/retriever.py +382 -0
  98. bolt/rag/splitters.py +276 -0
  99. bolt/rag/vectorstore.py +448 -0
  100. bolt/security/__init__.py +135 -0
  101. bolt/security/api_key.py +250 -0
  102. bolt/security/audit.py +204 -0
  103. bolt/security/brute_force.py +177 -0
  104. bolt/security/jwt.py +575 -0
  105. bolt/security/jwt_blacklist.py +108 -0
  106. bolt/security/jwt_keys.py +80 -0
  107. bolt/security/password.py +174 -0
  108. bolt/security/permissions.py +272 -0
  109. bolt/security/tenant.py +489 -0
  110. bolt_boot-0.1.0.dist-info/METADATA +492 -0
  111. bolt_boot-0.1.0.dist-info/RECORD +114 -0
  112. bolt_boot-0.1.0.dist-info/WHEEL +4 -0
  113. bolt_boot-0.1.0.dist-info/entry_points.txt +2 -0
  114. bolt_boot-0.1.0.dist-info/licenses/LICENSE +21 -0
bolt/__init__.py ADDED
@@ -0,0 +1,23 @@
1
+ """Bolt - AI-Native FastAPI Framework"""
2
+
3
+ __version__ = "0.1.0"
4
+
5
+ from typing import Any
6
+
7
+ __all__ = ["App", "create_app", "get_settings"]
8
+
9
+
10
+ def __getattr__(name: str) -> Any:
11
+ if name == "App":
12
+ from bolt.core.app import App
13
+
14
+ return App
15
+ if name == "create_app":
16
+ from bolt.core.app import create_app
17
+
18
+ return create_app
19
+ if name == "get_settings":
20
+ from bolt.core.config import get_settings
21
+
22
+ return get_settings
23
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
bolt/__main__.py ADDED
@@ -0,0 +1,18 @@
1
+ """内置演示应用,``python -m bolt`` / 镜像默认 CMD 的可运行入口。
2
+
3
+ 提供一个最小可用的 Bolt 实例(健康检查、指标等基础端点),
4
+ 用于容器镜像冒烟与快速验证。业务应用请自行 ``create_app``
5
+ 并覆盖容器的 CMD。
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from bolt.core.app import create_app
11
+
12
+ app = create_app(title="Bolt Demo")
13
+
14
+
15
+ if __name__ == "__main__":
16
+ import uvicorn
17
+
18
+ uvicorn.run(app, host="0.0.0.0", port=8000)
bolt/agent/__init__.py ADDED
@@ -0,0 +1,39 @@
1
+ """Agent 模块 - 底层由 OxyGent MAS 驱动。
2
+
3
+ 提供 AI Agent 开发能力:
4
+ - BoltAgent: Agent 基类(对话/工具循环/流式,OxyGent ReAct 驱动)
5
+ - ToolRegistry: 工具注册表
6
+ - ConversationMemory: 会话记忆(内存/Redis/数据库)
7
+ - MultiAgentSystem: 多 Agent 编排(master 委派 sub_agents)
8
+ """
9
+
10
+ from bolt.agent.base import AgentResult, BoltAgent, tool
11
+ from bolt.agent.memory import (
12
+ ConversationMemory,
13
+ DatabaseConversationMemory,
14
+ RedisConversationMemory,
15
+ create_memory,
16
+ )
17
+ from bolt.agent.messages import ChatMessage
18
+ from bolt.agent.oxy.mas_api import AgentSpec, AgentStreamEvent, MultiAgentSystem
19
+ from bolt.agent.tools import Tool, ToolRegistry
20
+
21
+ __all__ = [
22
+ # Agent
23
+ "BoltAgent",
24
+ "AgentResult",
25
+ "tool",
26
+ "ChatMessage",
27
+ # 工具
28
+ "ToolRegistry",
29
+ "Tool",
30
+ # 多 Agent 编排
31
+ "MultiAgentSystem",
32
+ "AgentSpec",
33
+ "AgentStreamEvent",
34
+ # 记忆
35
+ "ConversationMemory",
36
+ "RedisConversationMemory",
37
+ "DatabaseConversationMemory",
38
+ "create_memory",
39
+ ]
bolt/agent/base.py ADDED
@@ -0,0 +1,282 @@
1
+ """Agent 基类 - 底层由 OxyGent MAS 驱动。
2
+
3
+ BoltAgent 的对话/工具循环/多 Agent 委派全部由内嵌的 OxyGent 引擎完成:
4
+ - 有工具的 Agent 映射为 OxyGent ``ReActAgent``(完整的工具调用循环)
5
+ - 无工具的 Agent 映射为 ``ChatAgent``
6
+ - MAS 实例进程级单例,懒启动(首次调用时初始化)
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from abc import ABC, abstractmethod
12
+ from collections import OrderedDict
13
+ from collections.abc import AsyncIterator, Callable
14
+ from typing import TYPE_CHECKING, Any, ParamSpec, TypeVar
15
+
16
+ from pydantic import BaseModel, Field
17
+
18
+ from bolt.agent.messages import ChatMessage
19
+ from bolt.agent.tools import Tool
20
+ from bolt.core.config import get_settings
21
+
22
+ if TYPE_CHECKING:
23
+ from bolt.agent.memory import ConversationMemory
24
+ from bolt.agent.oxy.engine import OxyEngine
25
+
26
+ __all__ = [
27
+ "BoltAgent",
28
+ "AgentResult",
29
+ "tool",
30
+ ]
31
+
32
+ P = ParamSpec("P")
33
+ R = TypeVar("R")
34
+
35
+
36
+ # ============ 工具装饰器 ============
37
+
38
+
39
+ def tool(
40
+ name: str | None = None,
41
+ description: str = "",
42
+ ) -> Callable[..., Any]:
43
+ """Tool 装饰器 - 标记方法为可被 Agent 调用的工具。
44
+
45
+ 装饰后原函数原样返回(可直接调用),仅附加工具元信息;
46
+ 元信息在 MAS 启动时桥接为 OxyGent FunctionTool。
47
+
48
+ 用法:
49
+ @tool(description="搜索知识库")
50
+ async def search_kb(self, query: str) -> str:
51
+ return await self.kb.search(query)
52
+ """
53
+
54
+ def decorator(func: Callable[P, R]) -> Callable[P, R]:
55
+ tool_name = name or func.__name__
56
+ tool_desc = description or func.__doc__ or ""
57
+
58
+ # 附加 Tool 元信息(MAS 启动时桥接为 FunctionTool)。
59
+ # 动态属性经 setattr 注入:ruff B010 豁免 + 规避函数对象属性声明
60
+ setattr(func, "_bolt_is_tool", True) # noqa: B010
61
+ setattr(func, "_bolt_tool_name", tool_name) # noqa: B010
62
+ setattr(func, "_bolt_tool_description", tool_desc) # noqa: B010
63
+
64
+ return func
65
+
66
+ return decorator
67
+
68
+
69
+ # ============ Agent 结果 ============
70
+
71
+
72
+ class AgentResult(BaseModel):
73
+ """Agent 执行结果"""
74
+
75
+ success: bool = Field(default=True, description="是否成功")
76
+ output: str = Field(default="", description="输出内容")
77
+ error: str | None = Field(default=None, description="错误信息")
78
+ messages: list[ChatMessage] = Field(default_factory=list, description="消息历史")
79
+ tool_calls: list[dict[str, Any]] = Field(default_factory=list, description="工具调用记录")
80
+ token_usage: dict[str, int] = Field(default_factory=dict, description="Token 使用")
81
+
82
+
83
+ # ============ Agent 基类 ============
84
+
85
+
86
+ class BoltAgent(ABC):
87
+ """Agent 基类 - 由 OxyGent 引擎驱动。
88
+
89
+ 用法:
90
+ class CommentAgent(BoltAgent):
91
+ def __init__(self):
92
+ super().__init__(
93
+ name="comment_agent",
94
+ model="gpt-4o",
95
+ system_prompt="你是一个评论生成专家",
96
+ )
97
+
98
+ @tool(description="搜索相关笔记内容")
99
+ async def search_notes(self, query: str) -> str:
100
+ return await self.note_service.search(query)
101
+
102
+ async def run(self, note_url: str) -> AgentResult:
103
+ # invoke 内部由 OxyGent ReAct 循环驱动工具调用
104
+ comment = await self.invoke(f"为笔记生成评论: {note_url}")
105
+ return AgentResult(output=comment)
106
+ """
107
+
108
+ def __init__(
109
+ self,
110
+ name: str,
111
+ *,
112
+ model: str | None = None,
113
+ provider: str | None = None,
114
+ system_prompt: str | None = None,
115
+ temperature: float = 0.7,
116
+ max_tokens: int = 4096,
117
+ ) -> None:
118
+ self.name = name
119
+ self.model = model or get_settings().llm_default_model or "gpt-4o"
120
+ self.provider = provider or get_settings().llm_default_provider or "openai"
121
+ self.system_prompt = system_prompt
122
+ self.temperature = temperature
123
+ self.max_tokens = max_tokens
124
+
125
+ # 工具注册表(OxyGent MAS 启动时桥接为 FunctionHub)
126
+ self._tools: dict[str, Tool] = {}
127
+ self._register_tools()
128
+
129
+ # 会话记忆缓存(session_id -> ConversationMemory)。
130
+ # 有界(LRU 语义):对话型 Agent 的 session_id 是用户规模级的,
131
+ # 不设上限会随会话数慢性泄漏内存
132
+ self._memories: OrderedDict[str, ConversationMemory] = OrderedDict()
133
+ self._memories_maxsize = 1000
134
+
135
+ # 引擎句柄(懒创建)
136
+ self._engine: OxyEngine | None = None
137
+
138
+ # 登记到 OxyGent 运行时(纯同步登记;MAS 懒启动时物化)
139
+ from bolt.agent.oxy.runtime import get_oxy_runtime
140
+
141
+ get_oxy_runtime().register_agent(self)
142
+
143
+ def _register_tools(self) -> None:
144
+ """自动注册带 @tool 装饰器的方法(沿 MRO 收集,父类工具可继承)。
145
+
146
+ 反向遍历 MRO 使子类对同名工具的覆盖优先生效。
147
+ """
148
+ seen: set[str] = set()
149
+ for klass in reversed(type(self).__mro__):
150
+ for attr_name, attr in klass.__dict__.items():
151
+ if not (callable(attr) and getattr(attr, "_bolt_is_tool", False)):
152
+ continue
153
+ if attr._bolt_tool_name in seen:
154
+ continue
155
+ seen.add(attr._bolt_tool_name)
156
+ self._tools[attr._bolt_tool_name] = Tool(
157
+ name=attr._bolt_tool_name,
158
+ description=attr._bolt_tool_description,
159
+ func=getattr(self, attr_name),
160
+ )
161
+
162
+ def get_tools(self) -> list[Tool]:
163
+ """获取所有注册的工具"""
164
+ return list(self._tools.values())
165
+
166
+ @property
167
+ def has_tools(self) -> bool:
168
+ """是否注册了工具(决定 ReAct / Chat 映射与流式策略)"""
169
+ return bool(self._tools)
170
+
171
+ def get_memory(self, session_id: str = "default") -> ConversationMemory:
172
+ """获取(或创建)指定会话的记忆(同一 session_id 复用同一实例,LRU 淘汰最旧)。"""
173
+ if session_id not in self._memories:
174
+ self._memories[session_id] = get_memory_backend(session_id=session_id)
175
+ self._memories.move_to_end(session_id)
176
+ while len(self._memories) > self._memories_maxsize:
177
+ self._memories.popitem(last=False)
178
+ return self._memories[session_id]
179
+
180
+ # ============ OxyGent 引擎调用 ============
181
+
182
+ def _get_oxy_engine(self) -> OxyEngine:
183
+ from bolt.agent.oxy.engine import OxyEngine
184
+ from bolt.agent.oxy.runtime import get_oxy_runtime
185
+
186
+ if self._engine is None:
187
+ self._engine = OxyEngine(agent=self, runtime=get_oxy_runtime())
188
+ return self._engine
189
+
190
+ async def chat(
191
+ self,
192
+ messages: str | list[ChatMessage | dict[str, str]],
193
+ *,
194
+ use_tools: bool = True,
195
+ session_id: str | None = None,
196
+ timeout: float | None = None,
197
+ ) -> ChatMessage:
198
+ """调用 Agent 进行一轮对话。
199
+
200
+ Args:
201
+ messages: 单条消息或消息列表(ChatMessage / {"role","content"} dict)
202
+ use_tools: 是否允许工具调用(无工具 Agent 忽略)
203
+ session_id: 会话 ID;提供时启用会话记忆(多轮上下文)
204
+ timeout: 单轮超时秒数(None = 不限时)
205
+
206
+ Returns:
207
+ assistant 消息
208
+ """
209
+ return await self._get_oxy_engine().chat(messages, use_tools=use_tools, session_id=session_id, timeout=timeout)
210
+
211
+ async def invoke(
212
+ self,
213
+ message: str | list[ChatMessage | dict[str, str]],
214
+ *,
215
+ use_tools: bool = True,
216
+ session_id: str | None = None,
217
+ timeout: float | None = None,
218
+ ) -> str:
219
+ """简化调用 - 输入文本,返回文本。"""
220
+ return await self._get_oxy_engine().invoke(message, use_tools=use_tools, session_id=session_id, timeout=timeout)
221
+
222
+ async def stream(
223
+ self,
224
+ message: str | list[ChatMessage | dict[str, str]],
225
+ *,
226
+ use_tools: bool = True,
227
+ session_id: str | None = None,
228
+ timeout: float | None = None,
229
+ ) -> AsyncIterator[str]:
230
+ """流式输出文本块。
231
+
232
+ 有工具的 Agent 采用缓冲策略(中间轮工具调用 JSON 不外漏,
233
+ 最终以完整答案输出);无工具 Agent 逐段透传。
234
+ """
235
+ async for chunk in self._get_oxy_engine().stream(
236
+ message, use_tools=use_tools, session_id=session_id, timeout=timeout
237
+ ):
238
+ yield chunk
239
+
240
+ # ============ 抽象方法 ============
241
+
242
+ @abstractmethod
243
+ async def run(self, *args: Any, **kwargs: Any) -> AgentResult:
244
+ """执行 Agent(子类实现)"""
245
+ pass
246
+
247
+ async def execute(self, *args: Any, **kwargs: Any) -> AgentResult:
248
+ """执行 Agent(带错误处理,不吞没不可恢复异常)"""
249
+ try:
250
+ result = await self.run(*args, **kwargs)
251
+ return result
252
+ except (KeyboardInterrupt, SystemExit):
253
+ raise
254
+ except Exception as e:
255
+ from bolt.core.logging import get_logger
256
+
257
+ get_logger(f"bolt.agent.{self.name}").exception("Agent execution failed", exc=e)
258
+ return AgentResult(
259
+ success=False,
260
+ error=str(e),
261
+ )
262
+
263
+
264
+ def get_memory_backend(session_id: str) -> ConversationMemory:
265
+ """按 Settings 构建默认记忆后端。独立函数避免 base↔memory 模块级循环。
266
+
267
+ - ``memory``(默认):进程内存储
268
+ - ``redis``:跨 worker 共享(复用 ``settings.redis_url`` 与 ``settings.memory_ttl``)
269
+ """
270
+ settings = get_settings()
271
+ backend = settings.memory_backend
272
+ if backend == "redis":
273
+ from bolt.agent.memory import RedisConversationMemory
274
+
275
+ return RedisConversationMemory(
276
+ session_id=session_id,
277
+ redis_url=settings.redis_url,
278
+ ttl=settings.memory_ttl,
279
+ )
280
+ from bolt.agent.memory import ConversationMemory
281
+
282
+ return ConversationMemory(session_id=session_id)
bolt/agent/example.py ADDED
@@ -0,0 +1,94 @@
1
+ """示例 Agent - 评论生成 Agent。
2
+
3
+ 演示如何使用 Bolt Agent 框架(OxyGent 引擎驱动)构建 AI Agent。
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import logging
9
+
10
+ from bolt.agent.base import AgentResult, BoltAgent, tool
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class CommentAgent(BoltAgent):
16
+ """评论生成 Agent 示例。
17
+
18
+ 用法:
19
+ agent = CommentAgent()
20
+
21
+ # 执行(invoke 内部由 OxyGent ReAct 循环驱动工具调用)
22
+ result = await agent.execute(note_url="https://xiaohongshu.com/note/123")
23
+ print(result.output)
24
+ """
25
+
26
+ def __init__(
27
+ self,
28
+ name: str = "comment_agent",
29
+ model: str = "gpt-4o",
30
+ provider: str = "openai",
31
+ ) -> None:
32
+ super().__init__(
33
+ name=name,
34
+ model=model,
35
+ provider=provider,
36
+ system_prompt="""你是一个专业的评论生成专家。
37
+ 你的任务是根据笔记内容生成有深度、有见解的评论。
38
+ 评论应该:
39
+ 1. 紧扣笔记主题
40
+ 2. 有独特的观点或见解
41
+ 3. 语言自然、有亲和力""",
42
+ )
43
+
44
+ @tool(description="搜索小红书笔记内容")
45
+ async def search_note(self, note_url: str) -> str:
46
+ """搜索小红书笔记内容。"""
47
+ # 这里应该调用实际的小红书 API
48
+ return "笔记内容摘要: 这是一篇关于美食探店的笔记..."
49
+
50
+ @tool(description="获取热门评论作为参考")
51
+ async def get_hot_comments(self, note_url: str, limit: int = 5) -> list[str]:
52
+ """获取热门评论。"""
53
+ return [
54
+ "这家店我也去过!真的很好吃!",
55
+ "感谢分享,已经收藏了~",
56
+ ]
57
+
58
+ async def run(self, note_url: str, style: str = "friendly") -> AgentResult:
59
+ """执行评论生成(工具调用由 ReAct 循环自动编排)。"""
60
+ prompt = f"""请为笔记 {note_url} 生成一条评论。
61
+
62
+ 步骤:
63
+ 1. 用工具搜索笔记内容
64
+ 2. 用工具获取热门评论参考
65
+ 3. 生成一条风格为「{style}」的评论,直接输出评论内容"""
66
+
67
+ response = await self.invoke(prompt)
68
+ return AgentResult(
69
+ success=True,
70
+ output=response,
71
+ )
72
+
73
+
74
+ # ============ 使用示例 ============
75
+
76
+
77
+ async def example_simple_agent() -> None:
78
+ """简单 Agent 示例"""
79
+ logger.info("简单 Agent 示例")
80
+
81
+ agent = CommentAgent()
82
+
83
+ # 检查工具注册
84
+ tools = agent.get_tools()
85
+ logger.info("注册的工具: %s", [t.name for t in tools])
86
+
87
+ result = await agent.execute(note_url="https://xiaohongshu.com/note/123")
88
+ logger.info("结果: %s", result)
89
+
90
+
91
+ if __name__ == "__main__":
92
+ import asyncio
93
+
94
+ asyncio.run(example_simple_agent())