loom-agent 0.0.2__py3-none-any.whl → 0.0.4__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.

@@ -11,7 +11,7 @@ import asyncio
11
11
  import json
12
12
  import time
13
13
  from pathlib import Path
14
- from typing import AsyncGenerator, Dict, List, Optional
14
+ from typing import AsyncGenerator, Dict, List, Optional, Any
15
15
  from uuid import uuid4
16
16
 
17
17
  from loom.callbacks.base import BaseCallback
@@ -37,6 +37,52 @@ try:
37
37
  except ImportError:
38
38
  ContextRetriever = None # type: ignore
39
39
 
40
+ # Unified coordination support
41
+ try:
42
+ from loom.core.unified_coordination import UnifiedExecutionContext, IntelligentCoordinator
43
+ except ImportError:
44
+ UnifiedExecutionContext = None # type: ignore
45
+ IntelligentCoordinator = None # type: ignore
46
+
47
+
48
+ class TaskHandler:
49
+ """
50
+ 任务处理器基类
51
+
52
+ 开发者可以继承此类来实现自定义的任务处理逻辑
53
+ """
54
+
55
+ def can_handle(self, task: str) -> bool:
56
+ """
57
+ 判断是否能处理给定的任务
58
+
59
+ Args:
60
+ task: 任务描述
61
+
62
+ Returns:
63
+ bool: 是否能处理此任务
64
+ """
65
+ raise NotImplementedError
66
+
67
+ def generate_guidance(
68
+ self,
69
+ original_task: str,
70
+ result_analysis: Dict[str, Any],
71
+ recursion_depth: int
72
+ ) -> str:
73
+ """
74
+ 生成递归指导消息
75
+
76
+ Args:
77
+ original_task: 原始任务
78
+ result_analysis: 工具结果分析
79
+ recursion_depth: 递归深度
80
+
81
+ Returns:
82
+ str: 生成的指导消息
83
+ """
84
+ raise NotImplementedError
85
+
40
86
 
41
87
  class AgentExecutor:
42
88
  """
@@ -78,6 +124,9 @@ class AgentExecutor:
78
124
  system_instructions: Optional[str] = None,
79
125
  callbacks: Optional[List[BaseCallback]] = None,
80
126
  enable_steering: bool = False,
127
+ task_handlers: Optional[List[TaskHandler]] = None,
128
+ unified_context: Optional["UnifiedExecutionContext"] = None,
129
+ enable_unified_coordination: bool = True,
81
130
  ) -> None:
82
131
  self.llm = llm
83
132
  self.tools = tools or {}
@@ -94,6 +143,15 @@ class AgentExecutor:
94
143
  self.system_instructions = system_instructions
95
144
  self.callbacks = callbacks or []
96
145
  self.enable_steering = enable_steering
146
+ self.task_handlers = task_handlers or []
147
+
148
+ # Unified coordination
149
+ self.unified_context = unified_context
150
+ self.enable_unified_coordination = enable_unified_coordination
151
+
152
+ # Initialize unified coordination if enabled
153
+ if self.enable_unified_coordination and UnifiedExecutionContext and IntelligentCoordinator:
154
+ self._setup_unified_coordination()
97
155
 
98
156
  # Tool execution (legacy pipeline for backward compatibility)
99
157
  self.tool_pipeline = ToolExecutionPipeline(
@@ -102,6 +160,80 @@ class AgentExecutor:
102
160
  metrics=self.metrics,
103
161
  )
104
162
 
163
+ def _setup_unified_coordination(self):
164
+ """设置统一协调机制"""
165
+ if not self.unified_context:
166
+ # 创建默认的统一执行上下文
167
+ from loom.core.unified_coordination import CoordinationConfig
168
+ self.unified_context = UnifiedExecutionContext(
169
+ execution_id=f"exec_{int(time.time())}",
170
+ config=CoordinationConfig() # 使用默认配置
171
+ )
172
+
173
+ # 集成四大核心能力
174
+ self._integrate_core_capabilities()
175
+
176
+ # 创建智能协调器
177
+ self.coordinator = IntelligentCoordinator(self.unified_context)
178
+
179
+ # 设置跨组件引用
180
+ self._setup_cross_component_references()
181
+
182
+ def _integrate_core_capabilities(self):
183
+ """集成四大核心能力到统一上下文"""
184
+
185
+ config = self.unified_context.config
186
+
187
+ # 1. 集成 ContextAssembler
188
+ if not self.unified_context.context_assembler:
189
+ from loom.core.context_assembly import ContextAssembler
190
+ self.unified_context.context_assembler = ContextAssembler(
191
+ max_tokens=self.max_context_tokens,
192
+ enable_caching=True,
193
+ cache_size=config.context_cache_size
194
+ )
195
+
196
+ # 2. 集成 TaskTool
197
+ if "task" in self.tools and not self.unified_context.task_tool:
198
+ task_tool = self.tools["task"]
199
+ # 使用配置更新 TaskTool
200
+ task_tool.pool_size = config.subagent_pool_size
201
+ task_tool.enable_pooling = True
202
+ self.unified_context.task_tool = task_tool
203
+
204
+ # 3. 集成 EventProcessor
205
+ if not self.unified_context.event_processor:
206
+ from loom.core.events import EventFilter, EventProcessor, AgentEventType
207
+
208
+ # 创建智能事件过滤器,使用配置值
209
+ llm_filter = EventFilter(
210
+ allowed_types=[
211
+ AgentEventType.LLM_DELTA,
212
+ AgentEventType.TOOL_RESULT,
213
+ AgentEventType.AGENT_FINISH
214
+ ],
215
+ enable_batching=True,
216
+ batch_size=config.event_batch_size,
217
+ batch_timeout=config.event_batch_timeout
218
+ )
219
+
220
+ self.unified_context.event_processor = EventProcessor(
221
+ filters=[llm_filter],
222
+ enable_stats=True
223
+ )
224
+
225
+ # 4. 集成 TaskHandlers
226
+ if not self.unified_context.task_handlers:
227
+ self.unified_context.task_handlers = self.task_handlers or []
228
+
229
+ def _setup_cross_component_references(self):
230
+ """
231
+ 设置跨组件引用(已简化)
232
+
233
+ 移除了魔法属性注入,改为通过协调器处理所有跨组件通信
234
+ """
235
+ pass # 跨组件通信现在通过 IntelligentCoordinator 处理
236
+
105
237
  # Tool orchestration (Loom 2.0 - intelligent parallel/sequential execution)
106
238
  self.tool_orchestrator = ToolOrchestrator(
107
239
  tools=self.tools,
@@ -269,40 +401,50 @@ class AgentExecutor:
269
401
  },
270
402
  )
271
403
 
272
- # Assemble system prompt using ContextAssembler
273
- assembler = ContextAssembler(max_tokens=self.max_context_tokens)
274
-
275
- # Add base instructions (critical priority)
276
- if self.system_instructions:
277
- assembler.add_component(
278
- name="base_instructions",
279
- content=self.system_instructions,
280
- priority=ComponentPriority.CRITICAL,
281
- truncatable=False,
404
+ # 使用统一协调的智能上下文组装
405
+ if self.enable_unified_coordination and hasattr(self, 'coordinator'):
406
+ # 使用智能协调器进行上下文组装
407
+ execution_plan = self.coordinator.coordinate_tt_recursion(
408
+ messages, turn_state, context
282
409
  )
410
+ final_system_prompt = execution_plan.get("context", "")
411
+ # 使用统一协调器的 assembler
412
+ assembler = self.unified_context.context_assembler
413
+ else:
414
+ # 传统方式组装系统提示
415
+ assembler = ContextAssembler(max_tokens=self.max_context_tokens)
416
+
417
+ # Add base instructions (critical priority)
418
+ if self.system_instructions:
419
+ assembler.add_component(
420
+ name="base_instructions",
421
+ content=self.system_instructions,
422
+ priority=ComponentPriority.CRITICAL,
423
+ truncatable=False,
424
+ )
283
425
 
284
- # Add RAG context (high priority)
285
- if rag_context:
286
- assembler.add_component(
287
- name="retrieved_context",
288
- content=rag_context,
289
- priority=ComponentPriority.HIGH,
290
- truncatable=True,
291
- )
426
+ # Add RAG context (high priority)
427
+ if rag_context:
428
+ assembler.add_component(
429
+ name="retrieved_context",
430
+ content=rag_context,
431
+ priority=ComponentPriority.HIGH,
432
+ truncatable=True,
433
+ )
292
434
 
293
- # Add tool definitions (medium priority)
294
- if self.tools:
295
- tools_spec = self._serialize_tools()
296
- tools_prompt = f"Available tools:\n{json.dumps(tools_spec, indent=2)}"
297
- assembler.add_component(
298
- name="tool_definitions",
299
- content=tools_prompt,
300
- priority=ComponentPriority.MEDIUM,
301
- truncatable=False,
302
- )
435
+ # Add tool definitions (medium priority)
436
+ if self.tools:
437
+ tools_spec = self._serialize_tools()
438
+ tools_prompt = f"Available tools:\n{json.dumps(tools_spec, indent=2)}"
439
+ assembler.add_component(
440
+ name="tool_definitions",
441
+ content=tools_prompt,
442
+ priority=ComponentPriority.MEDIUM,
443
+ truncatable=False,
444
+ )
303
445
 
304
- # Assemble final system prompt
305
- final_system_prompt = assembler.assemble()
446
+ # Assemble final system prompt
447
+ final_system_prompt = assembler.assemble()
306
448
 
307
449
  # Inject system prompt into history
308
450
  if history and history[0].role == "system":
@@ -442,15 +584,20 @@ class AgentExecutor:
442
584
  # Prepare next turn state
443
585
  next_state = turn_state.next_turn(compacted=compacted_this_turn)
444
586
 
445
- # Prepare next turn messages (only new messages, not full history)
446
- next_messages = [
447
- Message(
448
- role="tool",
449
- content=r.content,
450
- tool_call_id=r.tool_call_id,
587
+ # Prepare next turn messages with intelligent context guidance
588
+ next_messages = self._prepare_recursive_messages(
589
+ messages, tool_results, turn_state, context
590
+ )
591
+
592
+ # Add tool results
593
+ for r in tool_results:
594
+ next_messages.append(
595
+ Message(
596
+ role="tool",
597
+ content=r.content,
598
+ tool_call_id=r.tool_call_id,
599
+ )
451
600
  )
452
- for r in tool_results
453
- ]
454
601
 
455
602
  # Emit recursion event
456
603
  yield AgentEvent(
@@ -466,6 +613,130 @@ class AgentExecutor:
466
613
  async for event in self.tt(next_messages, next_state, context):
467
614
  yield event
468
615
 
616
+ # ==========================================
617
+ # Intelligent Recursion Methods
618
+ # ==========================================
619
+
620
+ def _prepare_recursive_messages(
621
+ self,
622
+ messages: List[Message],
623
+ tool_results: List[ToolResult],
624
+ turn_state: TurnState,
625
+ context: ExecutionContext,
626
+ ) -> List[Message]:
627
+ """
628
+ 智能准备递归调用的消息
629
+
630
+ 基于工具结果类型、任务上下文和递归深度,生成合适的用户指导消息
631
+ """
632
+ # 分析工具结果
633
+ result_analysis = self._analyze_tool_results(tool_results)
634
+
635
+ # 获取原始任务
636
+ original_task = self._extract_original_task(messages)
637
+
638
+ # 生成智能指导消息
639
+ guidance_message = self._generate_recursion_guidance(
640
+ original_task, result_analysis, turn_state.turn_counter
641
+ )
642
+
643
+ return [Message(role="user", content=guidance_message)]
644
+
645
+ def _analyze_tool_results(self, tool_results: List[ToolResult]) -> Dict[str, Any]:
646
+ """分析工具结果类型和质量"""
647
+ analysis = {
648
+ "has_data": False,
649
+ "has_errors": False,
650
+ "suggests_completion": False,
651
+ "result_types": [],
652
+ "completeness_score": 0.0
653
+ }
654
+
655
+ for result in tool_results:
656
+ content = result.content.lower()
657
+
658
+ # 检查数据类型
659
+ if any(keyword in content for keyword in ["data", "found", "retrieved", "table", "schema", "获取到", "表结构", "结构"]):
660
+ analysis["has_data"] = True
661
+ analysis["result_types"].append("data")
662
+ analysis["completeness_score"] += 0.3
663
+
664
+ # 检查错误
665
+ if any(keyword in content for keyword in ["error", "failed", "exception", "not found"]):
666
+ analysis["has_errors"] = True
667
+ analysis["result_types"].append("error")
668
+
669
+ # 检查完成建议
670
+ if any(keyword in content for keyword in ["complete", "finished", "done", "ready"]):
671
+ analysis["suggests_completion"] = True
672
+ analysis["result_types"].append("completion")
673
+ analysis["completeness_score"] += 0.5
674
+
675
+ # 检查分析结果
676
+ if any(keyword in content for keyword in ["analysis", "summary", "conclusion", "insights"]):
677
+ analysis["result_types"].append("analysis")
678
+ analysis["completeness_score"] += 0.4
679
+
680
+ analysis["completeness_score"] = min(analysis["completeness_score"], 1.0)
681
+ return analysis
682
+
683
+ def _extract_original_task(self, messages: List[Message]) -> str:
684
+ """从消息历史中提取原始任务"""
685
+ # 查找第一个用户消息作为原始任务
686
+ for message in messages:
687
+ if message.role == "user" and message.content:
688
+ # 过滤掉系统生成的递归消息
689
+ if not any(keyword in message.content.lower() for keyword in [
690
+ "工具调用已完成", "请基于工具返回的结果", "不要继续调用工具"
691
+ ]):
692
+ return message.content
693
+ return "处理用户请求"
694
+
695
+ def _generate_recursion_guidance(
696
+ self,
697
+ original_task: str,
698
+ result_analysis: Dict[str, Any],
699
+ recursion_depth: int
700
+ ) -> str:
701
+ """生成递归指导消息"""
702
+
703
+ # 使用可扩展的任务处理器
704
+ if hasattr(self, 'task_handlers') and self.task_handlers:
705
+ for handler in self.task_handlers:
706
+ if handler.can_handle(original_task):
707
+ return handler.generate_guidance(original_task, result_analysis, recursion_depth)
708
+
709
+ # 默认处理
710
+ return self._generate_default_guidance(original_task, result_analysis, recursion_depth)
711
+
712
+
713
+ def _generate_default_guidance(
714
+ self,
715
+ original_task: str,
716
+ result_analysis: Dict[str, Any],
717
+ recursion_depth: int
718
+ ) -> str:
719
+ """生成默认的递归指导"""
720
+
721
+ if result_analysis["suggests_completion"] or recursion_depth >= 6:
722
+ return f"""工具调用已完成。请基于返回的结果完成任务:{original_task}
723
+
724
+ 请提供完整、准确的最终答案。"""
725
+
726
+ elif result_analysis["has_errors"]:
727
+ return f"""工具执行遇到问题。请重新尝试完成任务:{original_task}
728
+
729
+ 建议:
730
+ - 检查工具参数是否正确
731
+ - 尝试使用不同的工具或方法
732
+ - 如果问题持续,请说明具体错误"""
733
+
734
+ else:
735
+ return f"""继续处理任务:{original_task}
736
+
737
+ 当前进度:{result_analysis['completeness_score']:.0%}
738
+ 建议:使用更多工具收集信息或分析已获得的结果"""
739
+
469
740
  # ==========================================
470
741
  # Helper Methods
471
742
  # ==========================================
@@ -10,8 +10,9 @@ overwritten by system prompts.
10
10
 
11
11
  from __future__ import annotations
12
12
 
13
+ import hashlib
13
14
  from dataclasses import dataclass
14
- from typing import Dict, List, Optional, Callable
15
+ from typing import Dict, List, Optional, Callable, Any
15
16
  from enum import IntEnum
16
17
 
17
18
 
@@ -56,6 +57,9 @@ class ContextAssembler:
56
57
  - Token budget management
57
58
  - Smart truncation of low-priority components
58
59
  - Guarantee high-priority component integrity
60
+ - Component caching for performance
61
+ - Dynamic priority adjustment
62
+ - Context reuse optimization
59
63
 
60
64
  Example:
61
65
  ```python
@@ -85,7 +89,9 @@ class ContextAssembler:
85
89
  self,
86
90
  max_tokens: int = 16000,
87
91
  token_counter: Optional[Callable[[str], int]] = None,
88
- token_buffer: float = 0.9 # Use 90% of budget for safety
92
+ token_buffer: float = 0.9, # Use 90% of budget for safety
93
+ enable_caching: bool = True,
94
+ cache_size: int = 100
89
95
  ):
90
96
  """
91
97
  Initialize the context assembler.
@@ -94,10 +100,19 @@ class ContextAssembler:
94
100
  max_tokens: Maximum token budget
95
101
  token_counter: Custom token counting function (defaults to simple estimation)
96
102
  token_buffer: Safety buffer ratio (0.9 = use 90% of max_tokens)
103
+ enable_caching: Enable component caching for performance
104
+ cache_size: Maximum number of cached components
97
105
  """
98
106
  self.max_tokens = int(max_tokens * token_buffer)
99
107
  self.token_counter = token_counter or self._estimate_tokens
100
108
  self.components: List[ContextComponent] = []
109
+
110
+ # Performance optimizations
111
+ self.enable_caching = enable_caching
112
+ self._component_cache: Dict[str, ContextComponent] = {}
113
+ self._cache_size = cache_size
114
+ self._assembly_cache: Optional[str] = None
115
+ self._last_components_hash: Optional[str] = None
101
116
 
102
117
  def add_component(
103
118
  self,
@@ -127,16 +142,26 @@ class ContextAssembler:
127
142
  truncatable=truncatable
128
143
  )
129
144
  self.components.append(component)
145
+
146
+ # Update cache if enabled
147
+ if self.enable_caching:
148
+ self._component_cache[name] = component
149
+ # Maintain cache size limit
150
+ if len(self._component_cache) > self._cache_size:
151
+ # Remove oldest entries (simple LRU)
152
+ oldest_key = next(iter(self._component_cache))
153
+ del self._component_cache[oldest_key]
130
154
 
131
155
  def assemble(self) -> str:
132
156
  """
133
157
  Assemble the final context from all components.
134
158
 
135
159
  Strategy:
136
- 1. Sort components by priority (descending)
137
- 2. Add components until budget is reached
138
- 3. Truncate low-priority components if needed
139
- 4. Merge all components into final string
160
+ 1. Check cache for identical component configuration
161
+ 2. Sort components by priority (descending)
162
+ 3. Add components until budget is reached
163
+ 4. Truncate low-priority components if needed
164
+ 5. Merge all components into final string
140
165
 
141
166
  Returns:
142
167
  Assembled context string
@@ -144,6 +169,13 @@ class ContextAssembler:
144
169
  if not self.components:
145
170
  return ""
146
171
 
172
+ # Check cache if enabled
173
+ if self.enable_caching:
174
+ current_hash = self._get_components_hash()
175
+ if (self._assembly_cache is not None and
176
+ self._last_components_hash == current_hash):
177
+ return self._assembly_cache
178
+
147
179
  # Sort by priority (highest first)
148
180
  sorted_components = sorted(
149
181
  self.components,
@@ -165,7 +197,83 @@ class ContextAssembler:
165
197
  header = f"# {component.name.replace('_', ' ').upper()}"
166
198
  sections.append(f"{header}\n{component.content}")
167
199
 
168
- return "\n\n".join(sections)
200
+ result = "\n\n".join(sections)
201
+
202
+ # Update cache if enabled
203
+ if self.enable_caching:
204
+ self._assembly_cache = result
205
+ self._last_components_hash = self._get_components_hash()
206
+
207
+ return result
208
+
209
+ def _get_components_hash(self) -> str:
210
+ """
211
+ Generate hash for current component configuration
212
+
213
+ 优化版本:
214
+ - 使用 blake2b 替代 MD5(更快)
215
+ - 直接update字节而非拼接字符串
216
+ - 移除不必要的排序
217
+ """
218
+ # 使用 blake2b,比 MD5 更快且安全
219
+ hasher = hashlib.blake2b(digest_size=16)
220
+
221
+ # 直接更新hasher,避免字符串拼接
222
+ for comp in self.components:
223
+ hasher.update(comp.name.encode())
224
+ hasher.update(str(comp.priority).encode())
225
+ hasher.update(str(comp.token_count).encode())
226
+ hasher.update(b'1' if comp.truncatable else b'0')
227
+
228
+ return hasher.hexdigest()
229
+
230
+ def adjust_priority(self, component_name: str, new_priority: int) -> bool:
231
+ """
232
+ Dynamically adjust component priority.
233
+
234
+ Args:
235
+ component_name: Name of the component to adjust
236
+ new_priority: New priority value
237
+
238
+ Returns:
239
+ True if component was found and adjusted, False otherwise
240
+ """
241
+ for component in self.components:
242
+ if component.name == component_name:
243
+ component.priority = new_priority
244
+ # Clear cache since configuration changed
245
+ if self.enable_caching:
246
+ self._assembly_cache = None
247
+ self._last_components_hash = None
248
+ return True
249
+ return False
250
+
251
+ def get_component_stats(self) -> Dict[str, Any]:
252
+ """Get statistics about current components"""
253
+ if not self.components:
254
+ return {"total_components": 0, "total_tokens": 0}
255
+
256
+ total_tokens = sum(c.token_count for c in self.components)
257
+ priority_distribution = {}
258
+
259
+ for comp in self.components:
260
+ priority_distribution[comp.priority] = priority_distribution.get(comp.priority, 0) + 1
261
+
262
+ return {
263
+ "total_components": len(self.components),
264
+ "total_tokens": total_tokens,
265
+ "budget_utilization": total_tokens / self.max_tokens if self.max_tokens > 0 else 0,
266
+ "priority_distribution": priority_distribution,
267
+ "cache_enabled": self.enable_caching,
268
+ "cache_size": len(self._component_cache) if self.enable_caching else 0
269
+ }
270
+
271
+ def clear_cache(self) -> None:
272
+ """Clear all caches"""
273
+ if self.enable_caching:
274
+ self._component_cache.clear()
275
+ self._assembly_cache = None
276
+ self._last_components_hash = None
169
277
 
170
278
  def _truncate_components(
171
279
  self,