power-loop 0.2.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 (53) hide show
  1. llm_client/__init__.py +0 -0
  2. llm_client/capabilities.py +162 -0
  3. llm_client/interface.py +470 -0
  4. llm_client/llm_factory.py +981 -0
  5. llm_client/llm_tooling.py +645 -0
  6. llm_client/llm_utils.py +205 -0
  7. llm_client/multimodal.py +237 -0
  8. llm_client/qwen_image.py +576 -0
  9. llm_client/web_search.py +149 -0
  10. power_loop/__init__.py +326 -0
  11. power_loop/agent/__init__.py +6 -0
  12. power_loop/agent/sink.py +247 -0
  13. power_loop/agent/stateful_loop.py +363 -0
  14. power_loop/agent/system_prompt.py +396 -0
  15. power_loop/agent/types.py +41 -0
  16. power_loop/contracts/__init__.py +132 -0
  17. power_loop/contracts/errors.py +140 -0
  18. power_loop/contracts/event_payloads.py +278 -0
  19. power_loop/contracts/events.py +86 -0
  20. power_loop/contracts/handlers.py +45 -0
  21. power_loop/contracts/hook_contexts.py +265 -0
  22. power_loop/contracts/hooks.py +64 -0
  23. power_loop/contracts/messages.py +90 -0
  24. power_loop/contracts/protocols.py +48 -0
  25. power_loop/contracts/tools.py +56 -0
  26. power_loop/core/agent_context.py +94 -0
  27. power_loop/core/events.py +124 -0
  28. power_loop/core/hooks.py +122 -0
  29. power_loop/core/phase.py +217 -0
  30. power_loop/core/pipeline.py +880 -0
  31. power_loop/core/runner.py +60 -0
  32. power_loop/core/state.py +208 -0
  33. power_loop/runtime/budget.py +179 -0
  34. power_loop/runtime/cancellation.py +127 -0
  35. power_loop/runtime/compact.py +300 -0
  36. power_loop/runtime/env.py +103 -0
  37. power_loop/runtime/memory.py +107 -0
  38. power_loop/runtime/provider.py +176 -0
  39. power_loop/runtime/retry.py +182 -0
  40. power_loop/runtime/session_store.py +636 -0
  41. power_loop/runtime/skills.py +201 -0
  42. power_loop/runtime/spec.py +233 -0
  43. power_loop/runtime/structured.py +225 -0
  44. power_loop/tools/__init__.py +51 -0
  45. power_loop/tools/default_manifest.py +244 -0
  46. power_loop/tools/default_tools.py +766 -0
  47. power_loop/tools/registry.py +162 -0
  48. power_loop/tools/spawn_agent.py +173 -0
  49. power_loop-0.2.0.dist-info/METADATA +632 -0
  50. power_loop-0.2.0.dist-info/RECORD +53 -0
  51. power_loop-0.2.0.dist-info/WHEEL +5 -0
  52. power_loop-0.2.0.dist-info/licenses/LICENSE +21 -0
  53. power_loop-0.2.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,149 @@
1
+ """SearXNG Private Gateway web search client."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from collections.abc import Mapping
7
+ from dataclasses import dataclass
8
+ from typing import Any
9
+ from urllib.error import HTTPError, URLError
10
+ from urllib.parse import urlencode
11
+ from urllib.request import Request, urlopen
12
+
13
+
14
+ @dataclass(frozen=True)
15
+ class WebSearchConfig:
16
+ base_url: str
17
+ api_token: str
18
+ max_results: int = 5
19
+ snippet_max_length: int = 240
20
+ timeout_s: float = 30.0
21
+
22
+ @property
23
+ def enabled(self) -> bool:
24
+ return bool(self.base_url and self.api_token)
25
+
26
+ @property
27
+ def agent_endpoint(self) -> str:
28
+ return self.base_url.rstrip("/") + "/search/agent"
29
+
30
+
31
+ class WebSearchError(Exception):
32
+ def __init__(self, message: str, status_code: int | None = None, body: str = ""):
33
+ super().__init__(message)
34
+ self.status_code = status_code
35
+ self.body = body
36
+
37
+
38
+ def web_search_config_from_env(env: Mapping[str, Any]) -> WebSearchConfig | None:
39
+ base_url = str(env.get("SEARXNG_BASE_URL") or "").strip()
40
+ api_token = str(env.get("SEARXNG_API_TOKEN") or "").strip()
41
+ if not (base_url and api_token):
42
+ return None
43
+
44
+ try:
45
+ max_results = int(env.get("SEARXNG_MAX_RESULTS") or 5)
46
+ except Exception:
47
+ max_results = 5
48
+
49
+ try:
50
+ snippet_max_length = int(env.get("SEARXNG_SNIPPET_MAX_LENGTH") or 240)
51
+ except Exception:
52
+ snippet_max_length = 240
53
+
54
+ try:
55
+ timeout_s = float(env.get("SEARXNG_TIMEOUT_S") or 30.0)
56
+ except Exception:
57
+ timeout_s = 30.0
58
+
59
+ return WebSearchConfig(
60
+ base_url=base_url,
61
+ api_token=api_token,
62
+ max_results=max(1, max_results),
63
+ snippet_max_length=max(50, snippet_max_length),
64
+ timeout_s=max(1.0, timeout_s),
65
+ )
66
+
67
+
68
+ def search_web(
69
+ config: WebSearchConfig,
70
+ query: str,
71
+ *,
72
+ max_results: int | None = None,
73
+ language: str | None = None,
74
+ categories: str | None = None,
75
+ ) -> dict[str, Any]:
76
+ """Call SearXNG /search/agent and return the parsed JSON response."""
77
+
78
+ params: dict[str, str] = {"q": query}
79
+ params["max_results"] = str(max_results or config.max_results)
80
+ params["snippet_max_length"] = str(config.snippet_max_length)
81
+ if language:
82
+ params["language"] = language
83
+ if categories:
84
+ params["categories"] = categories
85
+
86
+ url = config.agent_endpoint + "?" + urlencode(params)
87
+ req = Request(url, method="GET")
88
+ req.add_header("Authorization", f"Bearer {config.api_token}")
89
+ req.add_header("Accept", "application/json")
90
+
91
+ try:
92
+ with urlopen(req, timeout=config.timeout_s) as resp:
93
+ data = json.loads(resp.read().decode())
94
+ return data
95
+ except HTTPError as e:
96
+ body = ""
97
+ try:
98
+ body = e.read().decode()
99
+ except Exception:
100
+ pass
101
+ raise WebSearchError(
102
+ f"HTTP {e.code}: {e.reason}",
103
+ status_code=e.code,
104
+ body=body,
105
+ ) from e
106
+ except URLError as e:
107
+ raise WebSearchError(f"Connection error: {e.reason}") from e
108
+ except json.JSONDecodeError as e:
109
+ raise WebSearchError(f"Invalid JSON response: {e}") from e
110
+
111
+
112
+ def summarize_search_result(data: dict[str, Any]) -> dict[str, Any]:
113
+ """Build a structured summary from the /search/agent response."""
114
+ results = data.get("results") or []
115
+ summary: dict[str, Any] = {
116
+ "status": "success",
117
+ "query": data.get("query", ""),
118
+ "result_count": len(results),
119
+ "results": [
120
+ {
121
+ "rank": r.get("rank", i + 1),
122
+ "title": r.get("title", ""),
123
+ "url": r.get("url", ""),
124
+ "snippet": r.get("snippet", ""),
125
+ }
126
+ for i, r in enumerate(results)
127
+ ],
128
+ }
129
+
130
+ answer_box = data.get("answer_box")
131
+ if answer_box:
132
+ summary["answer_box"] = answer_box
133
+
134
+ infobox = data.get("infobox")
135
+ if infobox:
136
+ summary["infobox"] = infobox
137
+
138
+ suggestions = data.get("suggestions")
139
+ if suggestions:
140
+ summary["suggestions"] = suggestions
141
+
142
+ return summary
143
+
144
+
145
+ def summarize_search_error(error: WebSearchError) -> dict[str, Any]:
146
+ result: dict[str, Any] = {"status": "error", "error": str(error)}
147
+ if error.status_code:
148
+ result["http_status"] = error.status_code
149
+ return result
power_loop/__init__.py ADDED
@@ -0,0 +1,326 @@
1
+ """power-loop public API.
2
+
3
+ Stability tiers
4
+ ---------------
5
+
6
+ **STABLE** — 跨 minor 版本保证向后兼容;破坏性变更必须升 minor 版本 + CHANGELOG。
7
+ 业务方(如 DeepTalk `agent` 服务)只应依赖这一层。
8
+
9
+ AgentLoop, AgentLoopConfig, AgentLoopResult,
10
+ AgentHooks, AgentEventBus,
11
+ HookPoint, HookDirective,
12
+ ToolRegistry, ToolDefinition,
13
+
14
+ **PROVISIONAL** — 顶层有 re-export,但 0.x 阶段可能调整。生产代码引用前要确认。
15
+ (其余在 ``__all__`` 中、不在上方 STABLE 列表中的所有符号。)
16
+
17
+ **INTERNAL** — 不从顶层导出;直接 ``from power_loop.core.* import …`` 视为内部 API,
18
+ 无版本承诺,可随时变更或删除。
19
+ """
20
+
21
+ __version__ = "0.2.0"
22
+
23
+ from power_loop.agent.sink import MessageSink, NullSink, SQLiteSink
24
+ from power_loop.agent.stateful_loop import StatefulAgentLoop, StatefulResult
25
+ from power_loop.agent.system_prompt import (
26
+ BUILTIN_SECTIONS,
27
+ DEFAULT_AGENT_SYSTEM_PROMPT,
28
+ DEFAULT_EXPLORE_SUBAGENT_SYSTEM_PROMPT,
29
+ DEFAULT_SUBAGENT_SYSTEM_PROMPT,
30
+ SystemPromptBuilder,
31
+ SystemPromptContext,
32
+ build_agent_system_prompt,
33
+ build_explore_subagent_system_prompt,
34
+ build_subagent_system_prompt,
35
+ )
36
+ from power_loop.agent.types import AgentLoopConfig, AgentLoopResult
37
+ from power_loop.contracts.errors import (
38
+ CancellationRequested,
39
+ CompactionFailed,
40
+ LLMRetryExhausted,
41
+ LLMTimeout,
42
+ PowerLoopError,
43
+ SessionNotFoundError,
44
+ SessionPendingError,
45
+ SpecValidationError,
46
+ ToolNotFound,
47
+ ToolValidationError,
48
+ )
49
+ from power_loop.contracts.event_payloads import (
50
+ AgentErrorPayload,
51
+ AutoCompactStatusPayload,
52
+ BaseEventPayload,
53
+ HitRoundLimitStatusPayload,
54
+ LlmDegradedPayload,
55
+ LlmRetryAttemptedPayload,
56
+ LoopCancelledPayload,
57
+ MemoryFailedPayload,
58
+ MemoryRecalledPayload,
59
+ RoundCompletedPayload,
60
+ RoundStartedPayload,
61
+ RoundToolsPresentPayload,
62
+ RoundUsageStatusPayload,
63
+ SessionEndedPayload,
64
+ SessionStartedPayload,
65
+ StatusChangedPayload,
66
+ StreamCompletedPayload,
67
+ StreamDeltaPayload,
68
+ StreamStartedPayload,
69
+ SubagentCompletedPayload,
70
+ SubagentLimitPayload,
71
+ SubagentTaskStartPayload,
72
+ SubagentTextPayload,
73
+ SystemLogPayload,
74
+ TodoUpdatedPayload,
75
+ ToolCallCompletedPayload,
76
+ ToolCallFailedPayload,
77
+ ToolCallStartedPayload,
78
+ UsageUpdatedPayload,
79
+ UserNotificationPayload,
80
+ )
81
+ from power_loop.contracts.events import AgentEvent, AgentEventType
82
+ from power_loop.contracts.handlers import EventHandler, HookHandler, ToolHandler, ToolHandlerResult
83
+ from power_loop.contracts.hook_contexts import (
84
+ BaseHookCtx,
85
+ CompactAfterCtx,
86
+ CompactBeforeCtx,
87
+ LlmAfterCtx,
88
+ LlmBeforeCtx,
89
+ MemoryRecalledCtx,
90
+ MessageAppendCtx,
91
+ RoundDecideCtx,
92
+ RoundEndCtx,
93
+ RoundStartCtx,
94
+ SessionEndCtx,
95
+ SessionStartCtx,
96
+ ToolAfterCtx,
97
+ ToolBeforeCtx,
98
+ ToolErrorCtx,
99
+ ToolsBatchAfterCtx,
100
+ ToolsBatchBeforeCtx,
101
+ )
102
+ from power_loop.contracts.hooks import HookContext, HookDirective, HookPoint, HookResult
103
+ from power_loop.contracts.messages import AgentMessage, MessageRole, ToolCall
104
+ from power_loop.contracts.protocols import EventBusProtocol, HookManagerProtocol, ToolArgsValidator
105
+ from power_loop.contracts.tools import ToolDefinition, validate_tool_args
106
+ from power_loop.core.events import AgentEventBus
107
+ from power_loop.core.hooks import AgentHooks
108
+ from power_loop.core.phase import PhaseContext, PhaseResult, phase
109
+ from power_loop.core.pipeline import AgentPipeline
110
+ from power_loop.core.runner import AgentRunner
111
+ from power_loop.runtime.budget import estimate_text_tokens, estimate_tokens, trim_history
112
+ from power_loop.runtime.cancellation import CancellationLike, CancellationToken
113
+ from power_loop.runtime.memory import MemoryProvider, MemorySnapshot, tag_as_memory
114
+ from power_loop.runtime.provider import (
115
+ LLMProviderConfig,
116
+ create_llm_service_from_config,
117
+ create_llm_service_from_env,
118
+ )
119
+ from power_loop.runtime.retry import LLMRetryPolicy, with_retry
120
+ from power_loop.runtime.session_store import (
121
+ DEFAULT_DB_PATH,
122
+ MAX_SPAWN_DEPTH,
123
+ MessageRow,
124
+ MessageState,
125
+ SessionKind,
126
+ SessionRow,
127
+ SessionStatus,
128
+ SessionStore,
129
+ SubagentLifecycle,
130
+ )
131
+ from power_loop.runtime.skills import (
132
+ LOAD_SKILL_DEFINITION,
133
+ SkillLoader,
134
+ get_default_loader,
135
+ register_skill_tools,
136
+ )
137
+ from power_loop.runtime.spec import AgentSpec, AgentSpecError, run_agent_spec
138
+ from power_loop.runtime.structured import (
139
+ StructuredOutputError,
140
+ StructuredOutputSpec,
141
+ parse_structured,
142
+ )
143
+ from power_loop.tools import ToolRegistry, build_registry, create_default_tool_registry
144
+ from power_loop.tools.default_manifest import (
145
+ CORE_TOOL_NAMES,
146
+ EXPLORE_TOOL_NAMES,
147
+ FULL_TOOL_NAMES,
148
+ TOOL_PRESETS,
149
+ get_tool_definitions,
150
+ )
151
+ from power_loop.tools.registry import AsyncToolInSyncContext
152
+ from power_loop.tools.spawn_agent import (
153
+ RUN_AGENT_DEFINITION,
154
+ SPAWN_AGENT_DEFINITION,
155
+ register_spawn_agent,
156
+ )
157
+
158
+ STABLE_API = (
159
+ "StatefulAgentLoop",
160
+ "StatefulResult",
161
+ "AgentLoopConfig",
162
+ "AgentLoopResult",
163
+ "SessionStore",
164
+ "SubagentLifecycle",
165
+ "PowerLoopError",
166
+ "SessionPendingError",
167
+ "SessionNotFoundError",
168
+ "LLMTimeout",
169
+ "LLMRetryExhausted",
170
+ "CancellationRequested",
171
+ "LLMRetryPolicy",
172
+ "CancellationToken",
173
+ "AgentHooks",
174
+ "AgentEventBus",
175
+ "HookPoint",
176
+ "HookDirective",
177
+ "ToolRegistry",
178
+ "ToolDefinition",
179
+ )
180
+
181
+ __all__ = [
182
+ "__version__",
183
+ "STABLE_API",
184
+ "StatefulAgentLoop",
185
+ "StatefulResult",
186
+ "AgentLoopConfig",
187
+ "AgentLoopResult",
188
+ "SessionStore",
189
+ "SessionRow",
190
+ "SessionStatus",
191
+ "SessionKind",
192
+ "SubagentLifecycle",
193
+ "MessageRow",
194
+ "MessageState",
195
+ "MAX_SPAWN_DEPTH",
196
+ "DEFAULT_DB_PATH",
197
+ "PowerLoopError",
198
+ "SessionPendingError",
199
+ "SessionNotFoundError",
200
+ "ToolNotFound",
201
+ "ToolValidationError",
202
+ "SpecValidationError",
203
+ "LLMTimeout",
204
+ "LLMRetryExhausted",
205
+ "CancellationRequested",
206
+ "CompactionFailed",
207
+ "LLMRetryPolicy",
208
+ "with_retry",
209
+ "CancellationToken",
210
+ "CancellationLike",
211
+ "LlmRetryAttemptedPayload",
212
+ "LlmDegradedPayload",
213
+ "LoopCancelledPayload",
214
+ "MemoryProvider",
215
+ "MemorySnapshot",
216
+ "tag_as_memory",
217
+ "MemoryRecalledCtx",
218
+ "MemoryRecalledPayload",
219
+ "MemoryFailedPayload",
220
+ "StructuredOutputSpec",
221
+ "StructuredOutputError",
222
+ "parse_structured",
223
+ "estimate_tokens",
224
+ "estimate_text_tokens",
225
+ "trim_history",
226
+ "LLMProviderConfig",
227
+ "create_llm_service_from_config",
228
+ "create_llm_service_from_env",
229
+ "MessageSink",
230
+ "NullSink",
231
+ "SQLiteSink",
232
+ "AgentMessage",
233
+ "MessageRole",
234
+ "ToolCall",
235
+ "AgentEvent",
236
+ "AgentEventType",
237
+ "AgentEventBus",
238
+ "AgentHooks",
239
+ "AgentPipeline",
240
+ "AgentRunner",
241
+ "PhaseContext",
242
+ "PhaseResult",
243
+ "phase",
244
+ "HookContext",
245
+ "HookDirective",
246
+ "HookPoint",
247
+ "HookResult",
248
+ "BaseHookCtx",
249
+ "CompactAfterCtx",
250
+ "CompactBeforeCtx",
251
+ "LlmAfterCtx",
252
+ "LlmBeforeCtx",
253
+ "MessageAppendCtx",
254
+ "RoundDecideCtx",
255
+ "RoundEndCtx",
256
+ "RoundStartCtx",
257
+ "SessionEndCtx",
258
+ "SessionStartCtx",
259
+ "ToolAfterCtx",
260
+ "ToolBeforeCtx",
261
+ "ToolErrorCtx",
262
+ "ToolsBatchAfterCtx",
263
+ "ToolsBatchBeforeCtx",
264
+ "BaseEventPayload",
265
+ "SessionStartedPayload",
266
+ "SessionEndedPayload",
267
+ "RoundStartedPayload",
268
+ "RoundCompletedPayload",
269
+ "RoundToolsPresentPayload",
270
+ "StreamStartedPayload",
271
+ "StreamDeltaPayload",
272
+ "StreamCompletedPayload",
273
+ "ToolCallStartedPayload",
274
+ "ToolCallCompletedPayload",
275
+ "ToolCallFailedPayload",
276
+ "StatusChangedPayload",
277
+ "AutoCompactStatusPayload",
278
+ "RoundUsageStatusPayload",
279
+ "HitRoundLimitStatusPayload",
280
+ "UsageUpdatedPayload",
281
+ "TodoUpdatedPayload",
282
+ "UserNotificationPayload",
283
+ "AgentErrorPayload",
284
+ "SystemLogPayload",
285
+ "SubagentTaskStartPayload",
286
+ "SubagentTextPayload",
287
+ "SubagentLimitPayload",
288
+ "SubagentCompletedPayload",
289
+ "EventHandler",
290
+ "HookHandler",
291
+ "ToolHandler",
292
+ "ToolHandlerResult",
293
+ "EventBusProtocol",
294
+ "HookManagerProtocol",
295
+ "ToolArgsValidator",
296
+ "ToolDefinition",
297
+ "validate_tool_args",
298
+ "ToolRegistry",
299
+ "AsyncToolInSyncContext",
300
+ "build_registry",
301
+ "create_default_tool_registry",
302
+ "get_tool_definitions",
303
+ "CORE_TOOL_NAMES",
304
+ "EXPLORE_TOOL_NAMES",
305
+ "FULL_TOOL_NAMES",
306
+ "TOOL_PRESETS",
307
+ "SystemPromptBuilder",
308
+ "SystemPromptContext",
309
+ "BUILTIN_SECTIONS",
310
+ "DEFAULT_AGENT_SYSTEM_PROMPT",
311
+ "DEFAULT_SUBAGENT_SYSTEM_PROMPT",
312
+ "DEFAULT_EXPLORE_SUBAGENT_SYSTEM_PROMPT",
313
+ "build_agent_system_prompt",
314
+ "build_subagent_system_prompt",
315
+ "build_explore_subagent_system_prompt",
316
+ "register_spawn_agent",
317
+ "SPAWN_AGENT_DEFINITION",
318
+ "RUN_AGENT_DEFINITION",
319
+ "LOAD_SKILL_DEFINITION",
320
+ "SkillLoader",
321
+ "get_default_loader",
322
+ "register_skill_tools",
323
+ "AgentSpec",
324
+ "AgentSpecError",
325
+ "run_agent_spec",
326
+ ]
@@ -0,0 +1,6 @@
1
+ """Agent runtime."""
2
+
3
+ from power_loop.agent.stateful_loop import StatefulAgentLoop, StatefulResult
4
+ from power_loop.agent.types import AgentLoopConfig, AgentLoopResult
5
+
6
+ __all__ = ["StatefulAgentLoop", "StatefulResult", "AgentLoopConfig", "AgentLoopResult"]