loom-agent 0.0.1__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.

Potentially problematic release.


This version of loom-agent might be problematic. Click here for more details.

Files changed (89) hide show
  1. loom/__init__.py +77 -0
  2. loom/agent.py +217 -0
  3. loom/agents/__init__.py +10 -0
  4. loom/agents/refs.py +28 -0
  5. loom/agents/registry.py +50 -0
  6. loom/builtin/compression/__init__.py +4 -0
  7. loom/builtin/compression/structured.py +79 -0
  8. loom/builtin/embeddings/__init__.py +9 -0
  9. loom/builtin/embeddings/openai_embedding.py +135 -0
  10. loom/builtin/embeddings/sentence_transformers_embedding.py +145 -0
  11. loom/builtin/llms/__init__.py +8 -0
  12. loom/builtin/llms/mock.py +34 -0
  13. loom/builtin/llms/openai.py +168 -0
  14. loom/builtin/llms/rule.py +102 -0
  15. loom/builtin/memory/__init__.py +5 -0
  16. loom/builtin/memory/in_memory.py +21 -0
  17. loom/builtin/memory/persistent_memory.py +278 -0
  18. loom/builtin/retriever/__init__.py +9 -0
  19. loom/builtin/retriever/chroma_store.py +265 -0
  20. loom/builtin/retriever/in_memory.py +106 -0
  21. loom/builtin/retriever/milvus_store.py +307 -0
  22. loom/builtin/retriever/pinecone_store.py +237 -0
  23. loom/builtin/retriever/qdrant_store.py +274 -0
  24. loom/builtin/retriever/vector_store.py +128 -0
  25. loom/builtin/retriever/vector_store_config.py +217 -0
  26. loom/builtin/tools/__init__.py +32 -0
  27. loom/builtin/tools/calculator.py +49 -0
  28. loom/builtin/tools/document_search.py +111 -0
  29. loom/builtin/tools/glob.py +27 -0
  30. loom/builtin/tools/grep.py +56 -0
  31. loom/builtin/tools/http_request.py +86 -0
  32. loom/builtin/tools/python_repl.py +73 -0
  33. loom/builtin/tools/read_file.py +32 -0
  34. loom/builtin/tools/task.py +158 -0
  35. loom/builtin/tools/web_search.py +64 -0
  36. loom/builtin/tools/write_file.py +31 -0
  37. loom/callbacks/base.py +9 -0
  38. loom/callbacks/logging.py +12 -0
  39. loom/callbacks/metrics.py +27 -0
  40. loom/callbacks/observability.py +248 -0
  41. loom/components/agent.py +107 -0
  42. loom/core/agent_executor.py +450 -0
  43. loom/core/circuit_breaker.py +178 -0
  44. loom/core/compression_manager.py +329 -0
  45. loom/core/context_retriever.py +185 -0
  46. loom/core/error_classifier.py +193 -0
  47. loom/core/errors.py +66 -0
  48. loom/core/message_queue.py +167 -0
  49. loom/core/permission_store.py +62 -0
  50. loom/core/permissions.py +69 -0
  51. loom/core/scheduler.py +125 -0
  52. loom/core/steering_control.py +47 -0
  53. loom/core/structured_logger.py +279 -0
  54. loom/core/subagent_pool.py +232 -0
  55. loom/core/system_prompt.py +141 -0
  56. loom/core/system_reminders.py +283 -0
  57. loom/core/tool_pipeline.py +113 -0
  58. loom/core/types.py +269 -0
  59. loom/interfaces/compressor.py +59 -0
  60. loom/interfaces/embedding.py +51 -0
  61. loom/interfaces/llm.py +33 -0
  62. loom/interfaces/memory.py +29 -0
  63. loom/interfaces/retriever.py +179 -0
  64. loom/interfaces/tool.py +27 -0
  65. loom/interfaces/vector_store.py +80 -0
  66. loom/llm/__init__.py +14 -0
  67. loom/llm/config.py +228 -0
  68. loom/llm/factory.py +111 -0
  69. loom/llm/model_health.py +235 -0
  70. loom/llm/model_pool_advanced.py +305 -0
  71. loom/llm/pool.py +170 -0
  72. loom/llm/registry.py +201 -0
  73. loom/mcp/__init__.py +4 -0
  74. loom/mcp/client.py +86 -0
  75. loom/mcp/registry.py +58 -0
  76. loom/mcp/tool_adapter.py +48 -0
  77. loom/observability/__init__.py +5 -0
  78. loom/patterns/__init__.py +5 -0
  79. loom/patterns/multi_agent.py +123 -0
  80. loom/patterns/rag.py +262 -0
  81. loom/plugins/registry.py +55 -0
  82. loom/resilience/__init__.py +5 -0
  83. loom/tooling.py +72 -0
  84. loom/utils/agent_loader.py +218 -0
  85. loom/utils/token_counter.py +19 -0
  86. loom_agent-0.0.1.dist-info/METADATA +457 -0
  87. loom_agent-0.0.1.dist-info/RECORD +89 -0
  88. loom_agent-0.0.1.dist-info/WHEEL +4 -0
  89. loom_agent-0.0.1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,27 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import Dict
5
+
6
+
7
+ @dataclass
8
+ class PerformanceMetrics:
9
+ total_iterations: int = 0
10
+ llm_calls: int = 0
11
+ tool_calls: int = 0
12
+ total_errors: int = 0
13
+ extras: Dict[str, float] = field(default_factory=dict)
14
+
15
+
16
+ class MetricsCollector:
17
+ def __init__(self) -> None:
18
+ self.metrics = PerformanceMetrics()
19
+
20
+ def summary(self) -> Dict:
21
+ return {
22
+ "iterations": self.metrics.total_iterations,
23
+ "llm_calls": self.metrics.llm_calls,
24
+ "tool_calls": self.metrics.tool_calls,
25
+ "errors": self.metrics.total_errors,
26
+ }
27
+
@@ -0,0 +1,248 @@
1
+ """US7: Observability Callbacks
2
+
3
+ Enhanced callbacks for production observability and monitoring.
4
+
5
+ New event types:
6
+ - compression_start: Before context compression
7
+ - compression_complete: After compression with metrics
8
+ - subagent_spawned: When sub-agent is created
9
+ - subagent_completed: When sub-agent finishes
10
+ - retry_attempt: When operation is retried
11
+ - circuit_breaker_opened: When circuit opens
12
+ - circuit_breaker_closed: When circuit closes
13
+ - performance_metric: General performance tracking
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ from typing import Any, Dict, List, Optional
19
+ from datetime import datetime
20
+
21
+ from loom.callbacks.base import BaseCallback
22
+ from loom.core.structured_logger import StructuredLogger
23
+
24
+
25
+ class ObservabilityCallback(BaseCallback):
26
+ """Callback for structured logging and observability.
27
+
28
+ Logs all agent events in JSON format for aggregation.
29
+
30
+ Example:
31
+ logger = StructuredLogger("my_agent")
32
+ callback = ObservabilityCallback(logger)
33
+
34
+ agent = Agent(llm=llm, callbacks=[callback])
35
+ """
36
+
37
+ def __init__(
38
+ self,
39
+ logger: Optional[StructuredLogger] = None,
40
+ log_all_events: bool = True,
41
+ log_performance: bool = True,
42
+ ):
43
+ """Initialize observability callback.
44
+
45
+ Args:
46
+ logger: StructuredLogger instance (creates one if None)
47
+ log_all_events: Log all events (default True)
48
+ log_performance: Log performance metrics separately (default True)
49
+ """
50
+ if logger is None:
51
+ logger = StructuredLogger("loom.observability")
52
+
53
+ self.logger = logger
54
+ self.log_all_events = log_all_events
55
+ self.log_performance = log_performance
56
+
57
+ async def on_event(self, event_type: str, payload: Dict[str, Any]) -> None:
58
+ """Handle agent events.
59
+
60
+ Args:
61
+ event_type: Event type string
62
+ payload: Event payload
63
+ """
64
+ # Extract correlation ID if available
65
+ correlation_id = payload.get("correlation_id")
66
+ if correlation_id and not self.logger.get_correlation_id():
67
+ self.logger.set_correlation_id(correlation_id)
68
+
69
+ # Log all events if enabled
70
+ if self.log_all_events:
71
+ self.logger.info(
72
+ f"Agent event: {event_type}",
73
+ event_type=event_type,
74
+ **payload
75
+ )
76
+
77
+ # Special handling for specific events
78
+ if event_type == "compression_start":
79
+ self._log_compression_start(payload)
80
+ elif event_type == "compression_complete":
81
+ self._log_compression_complete(payload)
82
+ elif event_type == "subagent_spawned":
83
+ self._log_subagent_spawned(payload)
84
+ elif event_type == "retry_attempt":
85
+ self._log_retry_attempt(payload)
86
+ elif event_type == "error":
87
+ self._log_error(payload)
88
+ elif event_type in ["llm_call", "tool_call"] and self.log_performance:
89
+ self._log_performance(event_type, payload)
90
+
91
+ def _log_compression_start(self, payload: Dict[str, Any]) -> None:
92
+ """Log compression start event."""
93
+ self.logger.info(
94
+ "Context compression starting",
95
+ token_count=payload.get("token_count"),
96
+ message_count=payload.get("message_count"),
97
+ )
98
+
99
+ def _log_compression_complete(self, payload: Dict[str, Any]) -> None:
100
+ """Log compression completion with metrics."""
101
+ before_tokens = payload.get("before_tokens", 0)
102
+ after_tokens = payload.get("after_tokens", 0)
103
+ ratio = payload.get("compression_ratio", 0)
104
+
105
+ self.logger.log_performance(
106
+ "context_compression",
107
+ duration_ms=payload.get("duration_ms", 0),
108
+ success=True,
109
+ before_tokens=before_tokens,
110
+ after_tokens=after_tokens,
111
+ compression_ratio=ratio,
112
+ tokens_saved=before_tokens - after_tokens,
113
+ )
114
+
115
+ def _log_subagent_spawned(self, payload: Dict[str, Any]) -> None:
116
+ """Log sub-agent spawn event."""
117
+ self.logger.info(
118
+ "Sub-agent spawned",
119
+ subagent_id=payload.get("subagent_id"),
120
+ execution_depth=payload.get("execution_depth"),
121
+ tool_whitelist=payload.get("tool_whitelist"),
122
+ )
123
+
124
+ def _log_retry_attempt(self, payload: Dict[str, Any]) -> None:
125
+ """Log retry attempt."""
126
+ self.logger.warning(
127
+ "Retry attempt",
128
+ attempt=payload.get("attempt"),
129
+ max_attempts=payload.get("max_attempts"),
130
+ operation=payload.get("operation"),
131
+ error=payload.get("error"),
132
+ )
133
+
134
+ def _log_error(self, payload: Dict[str, Any]) -> None:
135
+ """Log error with context."""
136
+ self.logger.error(
137
+ f"Agent error in {payload.get('stage', 'unknown')}",
138
+ stage=payload.get("stage"),
139
+ message=payload.get("message"),
140
+ iteration=payload.get("iteration"),
141
+ )
142
+
143
+ def _log_performance(self, operation: str, payload: Dict[str, Any]) -> None:
144
+ """Log performance metric."""
145
+ duration_ms = payload.get("duration_ms", 0)
146
+ if duration_ms > 0:
147
+ self.logger.log_performance(
148
+ operation,
149
+ duration_ms,
150
+ success=True,
151
+ **payload
152
+ )
153
+
154
+
155
+ class MetricsAggregator(BaseCallback):
156
+ """Aggregates metrics for monitoring dashboards.
157
+
158
+ Example:
159
+ aggregator = MetricsAggregator()
160
+ agent = Agent(llm=llm, callbacks=[aggregator])
161
+
162
+ # After running agent
163
+ summary = aggregator.get_summary()
164
+ print(f"Total LLM calls: {summary['llm_calls']}")
165
+ print(f"Avg LLM latency: {summary['avg_llm_latency_ms']:.2f}ms")
166
+ """
167
+
168
+ def __init__(self):
169
+ """Initialize metrics aggregator."""
170
+ self.metrics = {
171
+ "llm_calls": 0,
172
+ "llm_total_ms": 0.0,
173
+ "tool_calls": 0,
174
+ "tool_total_ms": 0.0,
175
+ "compressions": 0,
176
+ "compression_total_ms": 0.0,
177
+ "errors": 0,
178
+ "subagents_spawned": 0,
179
+ "retry_attempts": 0,
180
+ }
181
+ self.start_time = datetime.now()
182
+
183
+ async def on_event(self, event_type: str, payload: Dict[str, Any]) -> None:
184
+ """Aggregate metrics from events."""
185
+ if event_type == "llm_call":
186
+ self.metrics["llm_calls"] += 1
187
+ self.metrics["llm_total_ms"] += payload.get("duration_ms", 0)
188
+
189
+ elif event_type == "tool_call":
190
+ self.metrics["tool_calls"] += 1
191
+ self.metrics["tool_total_ms"] += payload.get("duration_ms", 0)
192
+
193
+ elif event_type == "compression_complete":
194
+ self.metrics["compressions"] += 1
195
+ self.metrics["compression_total_ms"] += payload.get("duration_ms", 0)
196
+
197
+ elif event_type == "error":
198
+ self.metrics["errors"] += 1
199
+
200
+ elif event_type == "subagent_spawned":
201
+ self.metrics["subagents_spawned"] += 1
202
+
203
+ elif event_type == "retry_attempt":
204
+ self.metrics["retry_attempts"] += 1
205
+
206
+ def get_summary(self) -> Dict[str, Any]:
207
+ """Get aggregated metrics summary.
208
+
209
+ Returns:
210
+ Dict with summary statistics
211
+ """
212
+ elapsed_seconds = (datetime.now() - self.start_time).total_seconds()
213
+
214
+ summary = {
215
+ **self.metrics,
216
+ "uptime_seconds": elapsed_seconds,
217
+ }
218
+
219
+ # Calculate averages
220
+ if self.metrics["llm_calls"] > 0:
221
+ summary["avg_llm_latency_ms"] = (
222
+ self.metrics["llm_total_ms"] / self.metrics["llm_calls"]
223
+ )
224
+
225
+ if self.metrics["tool_calls"] > 0:
226
+ summary["avg_tool_latency_ms"] = (
227
+ self.metrics["tool_total_ms"] / self.metrics["tool_calls"]
228
+ )
229
+
230
+ if self.metrics["compressions"] > 0:
231
+ summary["avg_compression_ms"] = (
232
+ self.metrics["compression_total_ms"] / self.metrics["compressions"]
233
+ )
234
+
235
+ # Calculate rates (per minute)
236
+ if elapsed_seconds > 0:
237
+ minutes = elapsed_seconds / 60
238
+ summary["llm_calls_per_minute"] = self.metrics["llm_calls"] / minutes
239
+ summary["errors_per_minute"] = self.metrics["errors"] / minutes
240
+
241
+ return summary
242
+
243
+ def reset(self) -> None:
244
+ """Reset all metrics."""
245
+ for key in self.metrics:
246
+ if isinstance(self.metrics[key], (int, float)):
247
+ self.metrics[key] = 0
248
+ self.start_time = datetime.now()
@@ -0,0 +1,107 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ from typing import AsyncGenerator, Dict, List, Optional
5
+
6
+ from loom.core.agent_executor import AgentExecutor
7
+ from loom.core.types import StreamEvent
8
+ from loom.interfaces.llm import BaseLLM
9
+ from loom.interfaces.memory import BaseMemory
10
+ from loom.interfaces.tool import BaseTool
11
+ from loom.interfaces.compressor import BaseCompressor
12
+ from loom.callbacks.base import BaseCallback
13
+ from loom.callbacks.metrics import MetricsCollector
14
+ from loom.core.steering_control import SteeringControl
15
+
16
+
17
+ class Agent:
18
+ """高层 Agent 组件:对外暴露 run/stream,内部委托 AgentExecutor。"""
19
+
20
+ def __init__(
21
+ self,
22
+ llm: BaseLLM,
23
+ tools: List[BaseTool] | None = None,
24
+ memory: Optional[BaseMemory] = None,
25
+ compressor: Optional[BaseCompressor] = None,
26
+ max_iterations: int = 50,
27
+ max_context_tokens: int = 16000,
28
+ permission_policy: Optional[Dict[str, str]] = None,
29
+ ask_handler=None,
30
+ safe_mode: bool = False,
31
+ permission_store=None,
32
+ # Advanced options
33
+ context_retriever=None,
34
+ system_instructions: Optional[str] = None,
35
+ callbacks: Optional[List[BaseCallback]] = None,
36
+ steering_control: Optional[SteeringControl] = None,
37
+ metrics: Optional[MetricsCollector] = None,
38
+ ) -> None:
39
+ # v4.0.0: Auto-instantiate CompressionManager (always enabled)
40
+ if compressor is None:
41
+ from loom.core.compression_manager import CompressionManager
42
+ compressor = CompressionManager(
43
+ llm=llm,
44
+ max_retries=3,
45
+ compression_threshold=0.92,
46
+ target_reduction=0.75,
47
+ sliding_window_size=20,
48
+ )
49
+
50
+ tools_map = {t.name: t for t in (tools or [])}
51
+ self.executor = AgentExecutor(
52
+ llm=llm,
53
+ tools=tools_map,
54
+ memory=memory,
55
+ compressor=compressor,
56
+ context_retriever=context_retriever,
57
+ steering_control=steering_control,
58
+ max_iterations=max_iterations,
59
+ max_context_tokens=max_context_tokens,
60
+ metrics=metrics,
61
+ permission_manager=None,
62
+ system_instructions=system_instructions,
63
+ callbacks=callbacks,
64
+ enable_steering=True, # v4.0.0: Always enabled
65
+ )
66
+
67
+ # 始终构造 PermissionManager(以便支持 safe_mode/持久化);保持默认语义
68
+ from loom.core.permissions import PermissionManager
69
+
70
+ pm = PermissionManager(
71
+ policy=permission_policy or {},
72
+ default="allow", # 保持默认放行语义
73
+ ask_handler=ask_handler,
74
+ safe_mode=safe_mode,
75
+ permission_store=permission_store,
76
+ )
77
+ self.executor.permission_manager = pm
78
+ self.executor.tool_pipeline.permission_manager = pm
79
+
80
+ async def run(
81
+ self,
82
+ input: str,
83
+ cancel_token: Optional[asyncio.Event] = None, # 🆕 US1
84
+ correlation_id: Optional[str] = None, # 🆕 US1
85
+ ) -> str:
86
+ return await self.executor.execute(input, cancel_token=cancel_token, correlation_id=correlation_id)
87
+
88
+ async def stream(self, input: str) -> AsyncGenerator[StreamEvent, None]:
89
+ async for ev in self.executor.stream(input):
90
+ yield ev
91
+
92
+ # LangChain 风格的别名,便于迁移/调用
93
+ async def ainvoke(
94
+ self,
95
+ input: str,
96
+ cancel_token: Optional[asyncio.Event] = None, # 🆕 US1
97
+ correlation_id: Optional[str] = None, # 🆕 US1
98
+ ) -> str:
99
+ return await self.run(input, cancel_token=cancel_token, correlation_id=correlation_id)
100
+
101
+ async def astream(self, input: str) -> AsyncGenerator[StreamEvent, None]:
102
+ async for ev in self.stream(input):
103
+ yield ev
104
+
105
+ def get_metrics(self) -> Dict:
106
+ """返回当前指标摘要。"""
107
+ return self.executor.metrics.summary()