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,279 @@
1
+ """US7: Structured Logging with Correlation IDs
2
+
3
+ Provides JSON-formatted structured logging for production observability.
4
+
5
+ Features:
6
+ - Correlation ID tracking across requests
7
+ - JSON format for log aggregation tools (Datadog, CloudWatch, etc.)
8
+ - Context propagation
9
+ - Performance metrics
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import json
15
+ import logging
16
+ import time
17
+ from typing import Any, Dict, Optional
18
+ from datetime import datetime
19
+ from contextvars import ContextVar
20
+
21
+
22
+ # Context variable for correlation ID
23
+ _correlation_id: ContextVar[Optional[str]] = ContextVar('correlation_id', default=None)
24
+
25
+
26
+ class StructuredLogger:
27
+ """Structured logger with correlation ID support.
28
+
29
+ Example:
30
+ logger = StructuredLogger("my_agent")
31
+
32
+ # Set correlation ID for request
33
+ logger.set_correlation_id("req-123")
34
+
35
+ # All logs include correlation_id
36
+ logger.info("Processing request", user_id="user_456")
37
+ # Output: {"timestamp": "...", "level": "INFO", "message": "Processing request",
38
+ # "correlation_id": "req-123", "user_id": "user_456"}
39
+ """
40
+
41
+ def __init__(
42
+ self,
43
+ name: str,
44
+ level: int = logging.INFO,
45
+ include_timestamp: bool = True,
46
+ include_location: bool = True,
47
+ ):
48
+ """Initialize structured logger.
49
+
50
+ Args:
51
+ name: Logger name (usually module or component name)
52
+ level: Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
53
+ include_timestamp: Include ISO timestamp in logs
54
+ include_location: Include file location in logs
55
+ """
56
+ self.name = name
57
+ self.level = level
58
+ self.include_timestamp = include_timestamp
59
+ self.include_location = include_location
60
+ self._logger = logging.getLogger(name)
61
+ self._logger.setLevel(level)
62
+
63
+ def set_correlation_id(self, correlation_id: str) -> None:
64
+ """Set correlation ID for current context.
65
+
66
+ Args:
67
+ correlation_id: Unique identifier for request/conversation
68
+ """
69
+ _correlation_id.set(correlation_id)
70
+
71
+ def get_correlation_id(self) -> Optional[str]:
72
+ """Get current correlation ID."""
73
+ return _correlation_id.get()
74
+
75
+ def clear_correlation_id(self) -> None:
76
+ """Clear correlation ID from context."""
77
+ _correlation_id.set(None)
78
+
79
+ def _format_log(
80
+ self,
81
+ level: str,
82
+ message: str,
83
+ extra: Dict[str, Any],
84
+ exc_info: Optional[Exception] = None,
85
+ ) -> Dict[str, Any]:
86
+ """Format log entry as JSON-serializable dict.
87
+
88
+ Args:
89
+ level: Log level string
90
+ message: Log message
91
+ extra: Additional context fields
92
+ exc_info: Exception info if available
93
+
94
+ Returns:
95
+ JSON-serializable dict
96
+ """
97
+ log_entry = {
98
+ "level": level,
99
+ "message": message,
100
+ "logger": self.name,
101
+ }
102
+
103
+ # Add timestamp
104
+ if self.include_timestamp:
105
+ log_entry["timestamp"] = datetime.utcnow().isoformat() + "Z"
106
+
107
+ # Add correlation ID if available
108
+ correlation_id = self.get_correlation_id()
109
+ if correlation_id:
110
+ log_entry["correlation_id"] = correlation_id
111
+
112
+ # Add extra fields
113
+ if extra:
114
+ log_entry.update(extra)
115
+
116
+ # Add exception info
117
+ if exc_info:
118
+ log_entry["exception"] = {
119
+ "type": type(exc_info).__name__,
120
+ "message": str(exc_info),
121
+ }
122
+
123
+ return log_entry
124
+
125
+ def _log(
126
+ self,
127
+ level: int,
128
+ level_name: str,
129
+ message: str,
130
+ exc_info: Optional[Exception] = None,
131
+ **kwargs: Any,
132
+ ) -> None:
133
+ """Internal logging method.
134
+
135
+ Args:
136
+ level: Logging level constant
137
+ level_name: Level name string
138
+ message: Log message
139
+ exc_info: Exception if available
140
+ **kwargs: Additional context fields
141
+ """
142
+ if self._logger.isEnabledFor(level):
143
+ log_entry = self._format_log(level_name, message, kwargs, exc_info)
144
+
145
+ # Output as JSON
146
+ json_log = json.dumps(log_entry, default=str)
147
+
148
+ # Use standard logger
149
+ self._logger.log(level, json_log)
150
+
151
+ def debug(self, message: str, **kwargs: Any) -> None:
152
+ """Log debug message."""
153
+ self._log(logging.DEBUG, "DEBUG", message, **kwargs)
154
+
155
+ def info(self, message: str, **kwargs: Any) -> None:
156
+ """Log info message."""
157
+ self._log(logging.INFO, "INFO", message, **kwargs)
158
+
159
+ def warning(self, message: str, **kwargs: Any) -> None:
160
+ """Log warning message."""
161
+ self._log(logging.WARNING, "WARNING", message, **kwargs)
162
+
163
+ def error(self, message: str, exc_info: Optional[Exception] = None, **kwargs: Any) -> None:
164
+ """Log error message."""
165
+ self._log(logging.ERROR, "ERROR", message, exc_info=exc_info, **kwargs)
166
+
167
+ def critical(self, message: str, exc_info: Optional[Exception] = None, **kwargs: Any) -> None:
168
+ """Log critical message."""
169
+ self._log(logging.CRITICAL, "CRITICAL", message, exc_info=exc_info, **kwargs)
170
+
171
+ def log_performance(
172
+ self,
173
+ operation: str,
174
+ duration_ms: float,
175
+ success: bool = True,
176
+ **kwargs: Any,
177
+ ) -> None:
178
+ """Log performance metrics.
179
+
180
+ Args:
181
+ operation: Operation name
182
+ duration_ms: Duration in milliseconds
183
+ success: Whether operation succeeded
184
+ **kwargs: Additional metrics
185
+ """
186
+ self.info(
187
+ "Performance metric",
188
+ operation=operation,
189
+ duration_ms=duration_ms,
190
+ success=success,
191
+ **kwargs,
192
+ )
193
+
194
+
195
+ class PerformanceTimer:
196
+ """Context manager for timing operations.
197
+
198
+ Example:
199
+ logger = StructuredLogger("my_agent")
200
+
201
+ with PerformanceTimer(logger, "llm_call") as timer:
202
+ result = await llm.generate(prompt)
203
+
204
+ # Automatically logs: {"operation": "llm_call", "duration_ms": 234.5, ...}
205
+ """
206
+
207
+ def __init__(
208
+ self,
209
+ logger: StructuredLogger,
210
+ operation: str,
211
+ log_level: str = "info",
212
+ **extra_context: Any,
213
+ ):
214
+ """Initialize performance timer.
215
+
216
+ Args:
217
+ logger: StructuredLogger instance
218
+ operation: Operation name
219
+ log_level: Log level for performance metric
220
+ **extra_context: Additional context to include
221
+ """
222
+ self.logger = logger
223
+ self.operation = operation
224
+ self.log_level = log_level
225
+ self.extra_context = extra_context
226
+ self.start_time: Optional[float] = None
227
+ self.end_time: Optional[float] = None
228
+
229
+ def __enter__(self):
230
+ """Start timer."""
231
+ self.start_time = time.time()
232
+ return self
233
+
234
+ def __exit__(self, exc_type, exc_val, exc_tb):
235
+ """Stop timer and log performance."""
236
+ self.end_time = time.time()
237
+
238
+ if self.start_time is not None:
239
+ duration_ms = (self.end_time - self.start_time) * 1000
240
+ success = exc_type is None
241
+
242
+ self.logger.log_performance(
243
+ self.operation,
244
+ duration_ms,
245
+ success=success,
246
+ **self.extra_context,
247
+ )
248
+
249
+ return False # Don't suppress exceptions
250
+
251
+
252
+ # Global logger instance
253
+ _default_logger = StructuredLogger("loom")
254
+
255
+
256
+ def get_logger(name: str = "loom") -> StructuredLogger:
257
+ """Get or create a structured logger.
258
+
259
+ Args:
260
+ name: Logger name
261
+
262
+ Returns:
263
+ StructuredLogger instance
264
+ """
265
+ return StructuredLogger(name)
266
+
267
+
268
+ def set_correlation_id(correlation_id: str) -> None:
269
+ """Set correlation ID for current context (convenience function).
270
+
271
+ Args:
272
+ correlation_id: Unique identifier
273
+ """
274
+ _correlation_id.set(correlation_id)
275
+
276
+
277
+ def get_correlation_id() -> Optional[str]:
278
+ """Get current correlation ID (convenience function)."""
279
+ return _correlation_id.get()
@@ -0,0 +1,232 @@
1
+ """SubAgentPool: I2A isolated sub-agent architecture (US3)
2
+
3
+ Spawns isolated sub-agents with independent tool permissions, message histories,
4
+ and fault boundaries. Enables concurrent execution via Python 3.11 TaskGroups.
5
+
6
+ Features:
7
+ - Fault isolation (1 sub-agent failure doesn't affect others)
8
+ - Tool whitelist enforcement (independent permissions)
9
+ - Separate message histories (no cross-contamination)
10
+ - Execution depth limits (max 3 levels, prevent infinite recursion)
11
+ - Timeout and max_iterations enforcement
12
+ - Concurrent sub-agent execution via asyncio.TaskGroup
13
+
14
+ Architecture:
15
+ - Each sub-agent is a fully isolated Agent instance
16
+ - SubAgentPool manages lifecycle and resource limits
17
+ - Uses cancel_token (US1) for timeout enforcement
18
+ - Compatible with CompressionManager (US2)
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import asyncio
24
+ from typing import Dict, List, Optional
25
+ from uuid import uuid4
26
+
27
+ from loom.components.agent import Agent
28
+ from loom.core.types import Message
29
+ from loom.interfaces.llm import BaseLLM
30
+ from loom.interfaces.memory import BaseMemory
31
+ from loom.interfaces.tool import BaseTool
32
+
33
+
34
+ class MaxDepthError(Exception):
35
+ """Raised when sub-agent execution depth exceeds maximum."""
36
+ pass
37
+
38
+
39
+ class SubAgentPool:
40
+ """Manages isolated sub-agent spawning and execution.
41
+
42
+ Example:
43
+ pool = SubAgentPool(max_depth=3)
44
+
45
+ # Spawn sub-agent with tool whitelist
46
+ result = await pool.spawn(
47
+ llm=llm,
48
+ prompt="Analyze dependencies",
49
+ tool_whitelist=["read_file", "glob"], # Only these tools
50
+ timeout_seconds=60,
51
+ )
52
+ """
53
+
54
+ def __init__(
55
+ self,
56
+ max_depth: int = 3,
57
+ default_timeout: float = 300.0, # 5 minutes
58
+ default_max_iterations: int = 50,
59
+ ):
60
+ """Initialize SubAgentPool.
61
+
62
+ Args:
63
+ max_depth: Maximum execution depth (prevent infinite recursion)
64
+ default_timeout: Default timeout in seconds for sub-agents
65
+ default_max_iterations: Default max iterations for sub-agents
66
+ """
67
+ self.max_depth = max_depth
68
+ self.default_timeout = default_timeout
69
+ self.default_max_iterations = default_max_iterations
70
+ self._active_subagents: Dict[str, Agent] = {}
71
+
72
+ async def spawn(
73
+ self,
74
+ llm: BaseLLM,
75
+ prompt: str,
76
+ tools: Optional[List[BaseTool]] = None,
77
+ tool_whitelist: Optional[List[str]] = None,
78
+ memory: Optional[BaseMemory] = None,
79
+ execution_depth: int = 1,
80
+ timeout_seconds: Optional[float] = None,
81
+ max_iterations: Optional[int] = None,
82
+ system_instructions: Optional[str] = None,
83
+ ) -> str:
84
+ """Spawn an isolated sub-agent and execute task.
85
+
86
+ Args:
87
+ llm: LLM instance for sub-agent
88
+ prompt: Task prompt for sub-agent
89
+ tools: Available tools (will be filtered by whitelist)
90
+ tool_whitelist: List of allowed tool names (None = all tools)
91
+ memory: Memory instance (if None, sub-agent gets fresh memory)
92
+ execution_depth: Current execution depth (for depth limit)
93
+ timeout_seconds: Timeout in seconds (None = default)
94
+ max_iterations: Max iterations (None = default)
95
+ system_instructions: System instructions for sub-agent
96
+
97
+ Returns:
98
+ Final response from sub-agent
99
+
100
+ Raises:
101
+ MaxDepthError: If execution_depth > max_depth
102
+ asyncio.TimeoutError: If sub-agent exceeds timeout
103
+ """
104
+ # Check depth limit
105
+ if execution_depth > self.max_depth:
106
+ raise MaxDepthError(
107
+ f"Execution depth {execution_depth} exceeds maximum {self.max_depth}"
108
+ )
109
+
110
+ # Filter tools by whitelist
111
+ filtered_tools = self._apply_tool_whitelist(tools, tool_whitelist)
112
+
113
+ # Create isolated sub-agent (compression always enabled in v4.0.0)
114
+ subagent_id = str(uuid4())
115
+ subagent = Agent(
116
+ llm=llm,
117
+ tools=filtered_tools,
118
+ memory=memory, # Separate memory instance
119
+ max_iterations=max_iterations or self.default_max_iterations,
120
+ system_instructions=system_instructions,
121
+ )
122
+
123
+ # Register active sub-agent
124
+ self._active_subagents[subagent_id] = subagent
125
+
126
+ try:
127
+ # Create cancel token for timeout enforcement
128
+ cancel_token = asyncio.Event()
129
+ timeout = timeout_seconds or self.default_timeout
130
+
131
+ # Execute sub-agent with timeout
132
+ task = asyncio.create_task(
133
+ subagent.run(prompt, cancel_token=cancel_token, correlation_id=subagent_id)
134
+ )
135
+
136
+ # Wait with timeout
137
+ result = await asyncio.wait_for(task, timeout=timeout)
138
+
139
+ return result
140
+
141
+ except asyncio.TimeoutError:
142
+ # Cancel sub-agent on timeout
143
+ cancel_token.set()
144
+ raise
145
+
146
+ finally:
147
+ # Cleanup: Remove from active pool
148
+ self._active_subagents.pop(subagent_id, None)
149
+
150
+ def _apply_tool_whitelist(
151
+ self,
152
+ tools: Optional[List[BaseTool]],
153
+ whitelist: Optional[List[str]],
154
+ ) -> Optional[List[BaseTool]]:
155
+ """Filter tools by whitelist.
156
+
157
+ Args:
158
+ tools: All available tools
159
+ whitelist: List of allowed tool names (None = all tools)
160
+
161
+ Returns:
162
+ Filtered list of tools
163
+ """
164
+ if tools is None or whitelist is None:
165
+ return tools
166
+
167
+ # Filter tools to only whitelisted ones
168
+ filtered = [tool for tool in tools if tool.name in whitelist]
169
+
170
+ return filtered if filtered else None
171
+
172
+ async def spawn_many(
173
+ self,
174
+ llm: BaseLLM,
175
+ prompts: List[str],
176
+ tools: Optional[List[BaseTool]] = None,
177
+ tool_whitelist: Optional[List[str]] = None,
178
+ timeout_seconds: Optional[float] = None,
179
+ return_exceptions: bool = True,
180
+ ) -> List[str]:
181
+ """Spawn multiple sub-agents concurrently.
182
+
183
+ Args:
184
+ llm: LLM instance (shared across sub-agents)
185
+ prompts: List of task prompts
186
+ tools: Available tools
187
+ tool_whitelist: Tool whitelist (applied to all sub-agents)
188
+ timeout_seconds: Timeout per sub-agent
189
+ return_exceptions: If True, return exceptions instead of raising
190
+
191
+ Returns:
192
+ List of results (or exceptions if return_exceptions=True)
193
+
194
+ Example:
195
+ prompts = ["Analyze file1.py", "Analyze file2.py", "Analyze file3.py"]
196
+ results = await pool.spawn_many(llm, prompts, tools=tools)
197
+ """
198
+ tasks = [
199
+ self.spawn(
200
+ llm=llm,
201
+ prompt=prompt,
202
+ tools=tools,
203
+ tool_whitelist=tool_whitelist,
204
+ timeout_seconds=timeout_seconds,
205
+ )
206
+ for prompt in prompts
207
+ ]
208
+
209
+ results = await asyncio.gather(*tasks, return_exceptions=return_exceptions)
210
+
211
+ return results
212
+
213
+ def get_active_count(self) -> int:
214
+ """Get number of currently active sub-agents."""
215
+ return len(self._active_subagents)
216
+
217
+ async def cancel_all(self) -> None:
218
+ """Cancel all active sub-agents.
219
+
220
+ Note: This is a best-effort cancellation. Sub-agents may not
221
+ respond immediately to cancellation signals.
222
+ """
223
+ for subagent_id, subagent in list(self._active_subagents.items()):
224
+ # Set cancel token (if sub-agent supports steering)
225
+ if hasattr(subagent.executor, "steering_control"):
226
+ subagent.executor.steering_control.abort()
227
+
228
+ # Wait a moment for cancellations to propagate
229
+ await asyncio.sleep(0.1)
230
+
231
+ # Clear active pool
232
+ self._active_subagents.clear()
@@ -0,0 +1,141 @@
1
+ """系统提示生成模块 - 对齐 Claude Code 的动态系统提示构建机制"""
2
+
3
+ from typing import Dict, List, Optional
4
+
5
+ from loom.interfaces.tool import BaseTool
6
+
7
+
8
+ class SystemPromptBuilder:
9
+ """动态生成系统提示,包含工具目录、风格指引与边界提醒"""
10
+
11
+ def __init__(
12
+ self,
13
+ base_instructions: Optional[str] = None,
14
+ style_guide: Optional[str] = None,
15
+ boundary_reminders: Optional[List[str]] = None,
16
+ ) -> None:
17
+ self.base_instructions = base_instructions or self._default_instructions()
18
+ self.style_guide = style_guide or self._default_style_guide()
19
+ self.boundary_reminders = boundary_reminders or self._default_boundary_reminders()
20
+
21
+ def build(
22
+ self,
23
+ tools: Dict[str, BaseTool],
24
+ context: Optional[Dict] = None,
25
+ ) -> str:
26
+ """构建完整的系统提示"""
27
+ sections = [
28
+ self.base_instructions,
29
+ self._build_tool_catalog(tools),
30
+ self.style_guide,
31
+ self._build_boundary_section(),
32
+ ]
33
+
34
+ if context:
35
+ sections.insert(1, self._build_context_section(context))
36
+
37
+ return "\n\n".join(filter(None, sections))
38
+
39
+ def _build_tool_catalog(self, tools: Dict[str, BaseTool]) -> str:
40
+ """生成工具目录"""
41
+ if not tools:
42
+ return ""
43
+
44
+ catalog_lines = ["## Available Tools", ""]
45
+ for tool in tools.values():
46
+ catalog_lines.append(f"### {tool.name}")
47
+ catalog_lines.append(f"**Description**: {getattr(tool, 'description', 'No description')}")
48
+
49
+ # 提取参数 schema
50
+ if hasattr(tool, "args_schema"):
51
+ try:
52
+ schema = tool.args_schema.model_json_schema()
53
+ properties = schema.get("properties", {})
54
+ required = schema.get("required", [])
55
+
56
+ if properties:
57
+ catalog_lines.append("**Parameters**:")
58
+ for param_name, param_info in properties.items():
59
+ is_required = " (required)" if param_name in required else ""
60
+ param_type = param_info.get("type", "any")
61
+ param_desc = param_info.get("description", "")
62
+ catalog_lines.append(f"- `{param_name}` ({param_type}){is_required}: {param_desc}")
63
+ except Exception:
64
+ pass
65
+
66
+ # 并发安全性标记
67
+ if hasattr(tool, "is_concurrency_safe"):
68
+ safe = "✓" if tool.is_concurrency_safe else "✗"
69
+ catalog_lines.append(f"**Concurrency Safe**: {safe}")
70
+
71
+ catalog_lines.append("")
72
+
73
+ return "\n".join(catalog_lines)
74
+
75
+ def _build_context_section(self, context: Dict) -> str:
76
+ """构建上下文信息段"""
77
+ lines = ["## Context Information", ""]
78
+ for key, value in context.items():
79
+ lines.append(f"**{key}**: {value}")
80
+ return "\n".join(lines)
81
+
82
+ def _build_boundary_section(self) -> str:
83
+ """构建边界提醒段"""
84
+ if not self.boundary_reminders:
85
+ return ""
86
+ lines = ["## Important Reminders", ""]
87
+ for reminder in self.boundary_reminders:
88
+ lines.append(f"- {reminder}")
89
+ return "\n".join(lines)
90
+
91
+ @staticmethod
92
+ def _default_instructions() -> str:
93
+ return """You are an intelligent AI agent powered by the Loom framework.
94
+
95
+ Your capabilities:
96
+ - Analyze user requests and break them down into actionable steps
97
+ - Use available tools to gather information and perform actions
98
+ - Think step-by-step through complex problems
99
+ - Provide clear, concise, and accurate responses
100
+
101
+ When using tools:
102
+ 1. Carefully read the tool descriptions and parameters
103
+ 2. Provide all required parameters with correct types
104
+ 3. Handle tool results appropriately
105
+ 4. If a tool fails, try alternative approaches or inform the user
106
+
107
+ Your responses should be:
108
+ - Clear and well-structured
109
+ - Accurate and factual
110
+ - Helpful and actionable
111
+ - Honest about limitations"""
112
+
113
+ @staticmethod
114
+ def _default_style_guide() -> str:
115
+ return """## Response Style
116
+
117
+ - Use markdown formatting for better readability
118
+ - Break down complex explanations into bullet points or numbered lists
119
+ - When executing multiple steps, show your reasoning process
120
+ - If uncertain, acknowledge the uncertainty
121
+ - Cite tool results when making claims based on them"""
122
+
123
+ @staticmethod
124
+ def _default_boundary_reminders() -> List[str]:
125
+ return [
126
+ "Always validate tool parameters before calling",
127
+ "Respect tool execution timeouts and handle failures gracefully",
128
+ "Do not make assumptions about tool results without verification",
129
+ "If a task requires multiple steps, break it down clearly",
130
+ "Stop and ask for clarification if the user's intent is ambiguous",
131
+ ]
132
+
133
+
134
+ def build_system_prompt(
135
+ tools: Dict[str, BaseTool],
136
+ custom_instructions: Optional[str] = None,
137
+ context: Optional[Dict] = None,
138
+ ) -> str:
139
+ """便捷函数:快速构建系统提示"""
140
+ builder = SystemPromptBuilder(base_instructions=custom_instructions)
141
+ return builder.build(tools, context)