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,123 @@
1
+ """Multi-Agent 系统 - 支持多个 Agent 协作完成复杂任务"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Dict, Optional
6
+
7
+ from loom.components.agent import Agent
8
+ from loom.interfaces.llm import BaseLLM
9
+
10
+
11
+ class MultiAgentSystem:
12
+ """
13
+ Multi-Agent 协作系统
14
+
15
+ 支持:
16
+ - 多个专业 Agent 协同工作
17
+ - 协调器 LLM 进行任务分解和结果汇总
18
+ - Agent 间通信与数据传递
19
+ """
20
+
21
+ def __init__(
22
+ self,
23
+ agents: Dict[str, Agent],
24
+ coordinator: Optional[BaseLLM] = None,
25
+ ) -> None:
26
+ self.agents = agents
27
+ self.coordinator = coordinator
28
+
29
+ async def run(self, task: str) -> str:
30
+ """
31
+ 执行多 Agent 任务
32
+
33
+ 工作流程:
34
+ 1. 协调器分解任务 (如果有)
35
+ 2. 分配子任务给不同 Agent
36
+ 3. 汇总结果
37
+ """
38
+ if not self.coordinator:
39
+ # 简单模式:顺序执行所有 Agent
40
+ results = {}
41
+ for name, agent in self.agents.items():
42
+ result = await agent.run(f"As {name}, help with: {task}")
43
+ results[name] = result
44
+
45
+ return self._format_results(results)
46
+
47
+ # 协调模式:使用协调器分解任务
48
+ subtasks = await self._decompose_task(task)
49
+
50
+ results = {}
51
+ for subtask in subtasks:
52
+ agent_name = subtask.get("agent")
53
+ subtask_desc = subtask.get("task", task)
54
+
55
+ if agent_name and agent_name in self.agents:
56
+ result = await self.agents[agent_name].run(subtask_desc)
57
+ results[agent_name] = result
58
+
59
+ # 汇总结果
60
+ return await self._aggregate_results(task, results)
61
+
62
+ async def _decompose_task(self, task: str) -> list[dict]:
63
+ """使用协调器分解任务"""
64
+ if not self.coordinator:
65
+ return [{"agent": list(self.agents.keys())[0], "task": task}]
66
+
67
+ agent_list = ", ".join(self.agents.keys())
68
+ decompose_prompt = f"""You are a task coordinator for a multi-agent system.
69
+
70
+ Available agents: {agent_list}
71
+
72
+ Task: {task}
73
+
74
+ Decompose this task into subtasks and assign each to the most appropriate agent.
75
+ Return a JSON list like:
76
+ [
77
+ {{"agent": "agent_name", "task": "subtask description"}},
78
+ ...
79
+ ]
80
+ """
81
+
82
+ response = await self.coordinator.generate([{"role": "user", "content": decompose_prompt}])
83
+
84
+ # 简单解析 (实际应该用 JSON)
85
+ import json
86
+
87
+ try:
88
+ subtasks = json.loads(response)
89
+ if isinstance(subtasks, list):
90
+ return subtasks
91
+ except Exception:
92
+ pass
93
+
94
+ # 降级:单个任务
95
+ return [{"agent": list(self.agents.keys())[0], "task": task}]
96
+
97
+ async def _aggregate_results(self, original_task: str, results: Dict[str, str]) -> str:
98
+ """汇总各 Agent 的结果"""
99
+ if not self.coordinator:
100
+ return self._format_results(results)
101
+
102
+ results_text = "\n\n".join([f"**{name}**: {result}" for name, result in results.items()])
103
+
104
+ aggregate_prompt = f"""You are a task coordinator for a multi-agent system.
105
+
106
+ Original task: {original_task}
107
+
108
+ Results from agents:
109
+ {results_text}
110
+
111
+ Synthesize these results into a coherent final answer.
112
+ """
113
+
114
+ return await self.coordinator.generate([{"role": "user", "content": aggregate_prompt}])
115
+
116
+ def _format_results(self, results: Dict[str, str]) -> str:
117
+ """格式化结果"""
118
+ lines = ["## Multi-Agent Results\n"]
119
+ for name, result in results.items():
120
+ lines.append(f"### Agent: {name}")
121
+ lines.append(result)
122
+ lines.append("")
123
+ return "\n".join(lines)
loom/patterns/rag.py ADDED
@@ -0,0 +1,262 @@
1
+ """RAG Pattern - 完整的 Retrieval-Augmented Generation 编排"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import List, Optional
6
+
7
+ from loom.components.agent import Agent
8
+ from loom.interfaces.retriever import BaseRetriever, Document
9
+
10
+
11
+ class RAGPattern:
12
+ """
13
+ RAG 编排模式 - 检索增强生成
14
+
15
+ 完整的 RAG Pipeline:
16
+ 1. 检索 (Retrieve) - 从知识库检索相关文档
17
+ 2. 排序 (Rerank) - 可选的重排序步骤
18
+ 3. 生成 (Generate) - 基于检索到的上下文生成答案
19
+
20
+ 适用场景:
21
+ - 需要完整控制 RAG 流程
22
+ - 需要 Re-ranking
23
+ - 需要多轮迭代
24
+
25
+ 示例:
26
+ rag = RAGPattern(
27
+ agent=agent,
28
+ retriever=retriever,
29
+ reranker=cross_encoder_rerank,
30
+ top_k=10,
31
+ rerank_top_k=3
32
+ )
33
+
34
+ result = await rag.run("What is Loom framework?")
35
+ """
36
+
37
+ def __init__(
38
+ self,
39
+ agent: Agent,
40
+ retriever: BaseRetriever,
41
+ reranker: Optional[callable] = None,
42
+ top_k: int = 5,
43
+ rerank_top_k: int = 3,
44
+ ):
45
+ """
46
+ Parameters:
47
+ agent: Agent 实例
48
+ retriever: 检索器实例
49
+ reranker: 可选的重排序函数 (query, docs) -> reranked_docs
50
+ top_k: 初始检索文档数量
51
+ rerank_top_k: 重排序后保留的文档数量
52
+ """
53
+ self.agent = agent
54
+ self.retriever = retriever
55
+ self.reranker = reranker
56
+ self.top_k = top_k
57
+ self.rerank_top_k = rerank_top_k
58
+
59
+ async def run(self, query: str) -> str:
60
+ """
61
+ 执行完整的 RAG 流程
62
+
63
+ Parameters:
64
+ query: 用户查询
65
+
66
+ Returns:
67
+ 生成的答案
68
+ """
69
+ # Step 1: 检索相关文档
70
+ docs = await self.retriever.retrieve(query, top_k=self.top_k)
71
+
72
+ if not docs:
73
+ # 没有检索到文档,直接让 Agent 回答
74
+ return await self.agent.run(query)
75
+
76
+ # Step 2: Re-ranking (可选)
77
+ if self.reranker:
78
+ docs = await self.reranker(query, docs)
79
+ docs = docs[:self.rerank_top_k]
80
+
81
+ # Step 3: 构建增强查询
82
+ augmented_query = self._build_augmented_query(query, docs)
83
+
84
+ # Step 4: Agent 生成答案
85
+ return await self.agent.run(augmented_query)
86
+
87
+ def _build_augmented_query(self, query: str, docs: List[Document]) -> str:
88
+ """
89
+ 构建增强查询 - 将检索到的文档与查询组合
90
+
91
+ Parameters:
92
+ query: 原始查询
93
+ docs: 检索到的文档列表
94
+
95
+ Returns:
96
+ 增强后的查询字符串
97
+ """
98
+ if not docs:
99
+ return query
100
+
101
+ context_parts = []
102
+ for i, doc in enumerate(docs, 1):
103
+ # 格式化文档
104
+ doc_text = f"[Document {i}]"
105
+ if doc.metadata and doc.metadata.get("source"):
106
+ doc_text += f"\nSource: {doc.metadata['source']}"
107
+ doc_text += f"\n{doc.content}"
108
+
109
+ context_parts.append(doc_text)
110
+
111
+ context = "\n\n".join(context_parts)
112
+
113
+ return f"""Based on the following context, please answer the question accurately and concisely.
114
+
115
+ Context:
116
+ {context}
117
+
118
+ Question: {query}
119
+
120
+ Answer:"""
121
+
122
+
123
+ class MultiQueryRAG(RAGPattern):
124
+ """
125
+ 多查询 RAG - 生成多个查询变体并合并结果
126
+
127
+ 适用场景:
128
+ - 复杂查询需要多角度检索
129
+ - 提高召回率
130
+
131
+ 示例:
132
+ rag = MultiQueryRAG(
133
+ agent=agent,
134
+ retriever=retriever,
135
+ query_count=3
136
+ )
137
+
138
+ result = await rag.run("How does context engineering work?")
139
+ # 会生成 3 个查询变体,分别检索后合并
140
+ """
141
+
142
+ def __init__(
143
+ self,
144
+ agent: Agent,
145
+ retriever: BaseRetriever,
146
+ reranker: Optional[callable] = None,
147
+ top_k: int = 5,
148
+ rerank_top_k: int = 3,
149
+ query_count: int = 3,
150
+ ):
151
+ super().__init__(agent, retriever, reranker, top_k, rerank_top_k)
152
+ self.query_count = query_count
153
+
154
+ async def run(self, query: str) -> str:
155
+ """执行多查询 RAG 流程"""
156
+ # Step 1: 生成查询变体
157
+ queries = await self._generate_query_variants(query)
158
+
159
+ # Step 2: 并发检索
160
+ all_docs: List[Document] = []
161
+ for q in queries:
162
+ docs = await self.retriever.retrieve(q, top_k=self.top_k // len(queries))
163
+ all_docs.extend(docs)
164
+
165
+ # Step 3: 去重与排序
166
+ unique_docs = self._deduplicate_docs(all_docs)
167
+
168
+ # Step 4: Re-ranking (可选)
169
+ if self.reranker:
170
+ unique_docs = await self.reranker(query, unique_docs)
171
+
172
+ # Step 5: 生成答案
173
+ augmented_query = self._build_augmented_query(query, unique_docs[:self.rerank_top_k])
174
+ return await self.agent.run(augmented_query)
175
+
176
+ async def _generate_query_variants(self, query: str) -> List[str]:
177
+ """
178
+ 生成查询变体
179
+
180
+ 简单策略:
181
+ - 原始查询
182
+ - 改写查询 (更具体)
183
+ - 相关问题
184
+ """
185
+ variants = [query]
186
+
187
+ # 使用 Agent 生成变体查询
188
+ prompt = f"""Generate {self.query_count - 1} alternative search queries for: "{query}"
189
+
190
+ Return only the queries, one per line, without numbering or explanation."""
191
+
192
+ try:
193
+ response = await self.agent.run(prompt)
194
+ lines = [line.strip() for line in response.split("\n") if line.strip()]
195
+ variants.extend(lines[:self.query_count - 1])
196
+ except Exception:
197
+ # 生成失败,只使用原始查询
198
+ pass
199
+
200
+ return variants[:self.query_count]
201
+
202
+ def _deduplicate_docs(self, docs: List[Document]) -> List[Document]:
203
+ """
204
+ 去重文档
205
+
206
+ 简单策略: 基于内容去重
207
+ """
208
+ seen_content = set()
209
+ unique_docs = []
210
+
211
+ for doc in docs:
212
+ # 使用内容的前 100 个字符作为唯一标识
213
+ key = doc.content[:100]
214
+
215
+ if key not in seen_content:
216
+ seen_content.add(key)
217
+ unique_docs.append(doc)
218
+
219
+ return unique_docs
220
+
221
+
222
+ class HierarchicalRAG(RAGPattern):
223
+ """
224
+ 层次化 RAG - 先检索文档,再检索段落
225
+
226
+ 适用场景:
227
+ - 文档很长,需要两级检索
228
+ - 需要更精确的定位
229
+
230
+ 流程:
231
+ 1. 检索相关文档 (粗粒度)
232
+ 2. 在检索到的文档内检索段落 (细粒度)
233
+ 3. 基于段落生成答案
234
+ """
235
+
236
+ def __init__(
237
+ self,
238
+ agent: Agent,
239
+ document_retriever: BaseRetriever,
240
+ paragraph_retriever: Optional[BaseRetriever] = None,
241
+ doc_top_k: int = 5,
242
+ para_top_k: int = 3,
243
+ ):
244
+ super().__init__(agent, document_retriever, top_k=doc_top_k)
245
+ self.paragraph_retriever = paragraph_retriever or document_retriever
246
+ self.para_top_k = para_top_k
247
+
248
+ async def run(self, query: str) -> str:
249
+ """执行层次化 RAG"""
250
+ # Step 1: 检索文档
251
+ docs = await self.retriever.retrieve(query, top_k=self.top_k)
252
+
253
+ if not docs:
254
+ return await self.agent.run(query)
255
+
256
+ # Step 2: 在检索到的文档内检索段落
257
+ # 简化实现: 直接使用检索到的文档
258
+ paragraphs = docs[:self.para_top_k]
259
+
260
+ # Step 3: 生成答案
261
+ augmented_query = self._build_augmented_query(query, paragraphs)
262
+ return await self.agent.run(augmented_query)
@@ -0,0 +1,55 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Dict, Optional, Type
4
+
5
+ from loom.interfaces.llm import BaseLLM
6
+ from loom.interfaces.memory import BaseMemory
7
+ from loom.interfaces.tool import BaseTool
8
+
9
+
10
+ class PluginRegistry:
11
+ """插件注册中心:注册/获取 LLM、Tool、Memory 实现。"""
12
+
13
+ _llms: Dict[str, Type[BaseLLM]] = {}
14
+ _tools: Dict[str, Type[BaseTool]] = {}
15
+ _memories: Dict[str, Type[BaseMemory]] = {}
16
+
17
+ @classmethod
18
+ def register_llm(cls, name: str):
19
+ def decorator(impl: Type[BaseLLM]):
20
+ cls._llms[name] = impl
21
+ return impl
22
+ return decorator
23
+
24
+ @classmethod
25
+ def register_tool(cls, name: str):
26
+ def decorator(impl: Type[BaseTool]):
27
+ cls._tools[name] = impl
28
+ return impl
29
+ return decorator
30
+
31
+ @classmethod
32
+ def register_memory(cls, name: str):
33
+ def decorator(impl: Type[BaseMemory]):
34
+ cls._memories[name] = impl
35
+ return impl
36
+ return decorator
37
+
38
+ @classmethod
39
+ def get_llm(cls, name: str, **kwargs: Any) -> BaseLLM:
40
+ if name not in cls._llms:
41
+ raise ValueError(f"LLM '{name}' not registered")
42
+ return cls._llms[name](**kwargs)
43
+
44
+ @classmethod
45
+ def get_tool(cls, name: str, **kwargs: Any) -> BaseTool:
46
+ if name not in cls._tools:
47
+ raise ValueError(f"Tool '{name}' not registered")
48
+ return cls._tools[name](**kwargs)
49
+
50
+ @classmethod
51
+ def get_memory(cls, name: str, **kwargs: Any) -> BaseMemory:
52
+ if name not in cls._memories:
53
+ raise ValueError(f"Memory '{name}' not registered")
54
+ return cls._memories[name](**kwargs)
55
+
@@ -0,0 +1,5 @@
1
+ """Resilience subsystem - circuit breakers, retry logic, error handling"""
2
+
3
+ from __future__ import annotations
4
+
5
+ __all__: list[str] = []
loom/tooling.py ADDED
@@ -0,0 +1,72 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import inspect
5
+ from typing import Any, Callable, Optional, Type
6
+
7
+ from pydantic import BaseModel, create_model
8
+
9
+ from .interfaces.tool import BaseTool
10
+
11
+
12
+ def tool(
13
+ name: Optional[str] = None,
14
+ description: Optional[str] = None,
15
+ args_schema: Optional[Type[BaseModel]] = None,
16
+ *,
17
+ concurrency_safe: bool = True,
18
+ ) -> Callable[[Callable[..., Any]], Type[BaseTool]]:
19
+ """Decorator to turn a Python function into a Loom Tool.
20
+
21
+ Example:
22
+ @tool()
23
+ def add(a: int, b: int) -> int:
24
+ # Add two integers
25
+ return a + b
26
+
27
+ AddTool = add # class deriving BaseTool
28
+ my_tool = AddTool()
29
+ """
30
+
31
+ def wrapper(fn: Callable[..., Any]) -> Type[BaseTool]:
32
+ tool_name = name or fn.__name__
33
+ tool_desc = description or (inspect.getdoc(fn) or tool_name)
34
+ schema = args_schema or _infer_args_schema(fn, tool_name)
35
+ is_async = inspect.iscoroutinefunction(fn)
36
+
37
+ class _FuncTool(BaseTool): # type: ignore[override]
38
+ name = tool_name
39
+ description = tool_desc
40
+ args_schema = schema
41
+
42
+ async def run(self, **kwargs) -> Any: # type: ignore[override]
43
+ if is_async:
44
+ return await fn(**kwargs) # type: ignore[misc]
45
+ # Best-effort: offload to default executor to avoid blocking
46
+ loop = asyncio.get_running_loop()
47
+ return await loop.run_in_executor(None, lambda: fn(**kwargs))
48
+
49
+ @property
50
+ def is_concurrency_safe(self) -> bool: # type: ignore[override]
51
+ return concurrency_safe
52
+
53
+ _FuncTool.__name__ = f"{tool_name.capitalize()}Tool"
54
+ return _FuncTool
55
+
56
+ return wrapper
57
+
58
+
59
+ def _infer_args_schema(fn: Callable[..., Any], tool_name: str) -> Type[BaseModel]:
60
+ sig = inspect.signature(fn)
61
+ fields = {}
62
+ for name, param in sig.parameters.items():
63
+ if param.kind in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD):
64
+ continue
65
+ annotation = param.annotation if param.annotation is not inspect._empty else Any
66
+ default = ... if param.default is inspect._empty else param.default
67
+ fields[name] = (annotation, default)
68
+ if not fields:
69
+ # no-arg tool
70
+ return create_model(f"{tool_name}_Args", __base__=BaseModel) # type: ignore
71
+ return create_model(f"{tool_name}_Args", __base__=BaseModel, **fields) # type: ignore
72
+