Rohan03 commited on
Commit
c62560e
·
verified ·
1 Parent(s): c110410

refactor: modularity fixes + plugin registry + compiled research

Browse files
Files changed (1) hide show
  1. purpose_agent/registry.py +191 -0
purpose_agent/registry.py ADDED
@@ -0,0 +1,191 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Plugin Registry — Drop-in extension system for backends, tools, callbacks, and evaluators.
3
+
4
+ Makes the framework extensible without editing core code:
5
+ - Register new LLM backends
6
+ - Register new tools
7
+ - Register new callbacks
8
+ - Register new SLM models
9
+ - Register new evaluation metrics
10
+
11
+ Adding a new component = 1 file + 1 register() call.
12
+
13
+ Extension points:
14
+ BackendRegistry — LLM/SLM backends
15
+ ToolRegistry — Already exists in tools.py, re-exported here
16
+ CallbackRegistry — Observability callbacks
17
+ ModelRegistry — SLM model definitions (extends SLM_REGISTRY)
18
+ EmbeddingRegistry — Shared embedding backends (deduplicates embed logic)
19
+
20
+ Usage:
21
+ # In your extension file:
22
+ from purpose_agent.registry import backend_registry, model_registry
23
+
24
+ backend_registry.register("my_backend", MyCustomBackend)
25
+ model_registry.register("my-slm", ollama_name="my-model:latest", context_window=32768, description="My custom SLM")
26
+
27
+ # Then use it:
28
+ backend = backend_registry.create("my_backend", model="my-model")
29
+ slm = model_registry.create_backend("my-slm")
30
+ """
31
+
32
+ from __future__ import annotations
33
+
34
+ import logging
35
+ import math
36
+ from typing import Any, Callable, Type
37
+
38
+ logger = logging.getLogger(__name__)
39
+
40
+
41
+ # ---------------------------------------------------------------------------
42
+ # Generic Plugin Registry
43
+ # ---------------------------------------------------------------------------
44
+
45
+ class PluginRegistry:
46
+ """
47
+ Generic registry for named plugins.
48
+
49
+ Supports:
50
+ - Register by name + class/factory
51
+ - Create instances by name
52
+ - List available plugins
53
+ - Discover plugins via entry points (future)
54
+ """
55
+
56
+ def __init__(self, kind: str):
57
+ self.kind = kind
58
+ self._plugins: dict[str, Type | Callable] = {}
59
+ self._metadata: dict[str, dict[str, Any]] = {}
60
+
61
+ def register(
62
+ self, name: str, cls_or_factory: Type | Callable, **metadata
63
+ ) -> "PluginRegistry":
64
+ """Register a plugin by name."""
65
+ self._plugins[name] = cls_or_factory
66
+ self._metadata[name] = metadata
67
+ logger.debug(f"{self.kind} registry: registered '{name}'")
68
+ return self
69
+
70
+ def create(self, name: str, **kwargs) -> Any:
71
+ """Create an instance of a registered plugin."""
72
+ if name not in self._plugins:
73
+ available = ", ".join(self._plugins.keys())
74
+ raise ValueError(
75
+ f"Unknown {self.kind} '{name}'. Available: {available}"
76
+ )
77
+ return self._plugins[name](**kwargs)
78
+
79
+ def get_class(self, name: str) -> Type | Callable | None:
80
+ """Get the class/factory without instantiating."""
81
+ return self._plugins.get(name)
82
+
83
+ def list(self) -> list[dict[str, Any]]:
84
+ """List all registered plugins with metadata."""
85
+ return [
86
+ {"name": name, **self._metadata.get(name, {})}
87
+ for name in self._plugins
88
+ ]
89
+
90
+ def names(self) -> list[str]:
91
+ return list(self._plugins.keys())
92
+
93
+ def __contains__(self, name: str) -> bool:
94
+ return name in self._plugins
95
+
96
+ def __len__(self) -> int:
97
+ return len(self._plugins)
98
+
99
+
100
+ # ---------------------------------------------------------------------------
101
+ # Shared Embedding Utility — deduplicated from ExperienceReplay + ToolRegistry
102
+ # ---------------------------------------------------------------------------
103
+
104
+ class EmbeddingBackend:
105
+ """
106
+ Abstract embedding backend. Swap in sentence-transformers, OpenAI, etc.
107
+
108
+ Default: lightweight trigram hashing (no dependencies, fast, approximate).
109
+ """
110
+
111
+ def __init__(self, dim: int = 128):
112
+ self.dim = dim
113
+
114
+ def embed(self, text: str) -> list[float]:
115
+ """Compute embedding for text. Override for real embeddings."""
116
+ vec = [0.0] * self.dim
117
+ text_lower = text.lower()
118
+ for i in range(len(text_lower) - 2):
119
+ trigram = text_lower[i:i + 3]
120
+ h = hash(trigram) % self.dim
121
+ vec[h] += 1.0
122
+ magnitude = math.sqrt(sum(x * x for x in vec))
123
+ if magnitude > 0:
124
+ vec = [x / magnitude for x in vec]
125
+ return vec
126
+
127
+ @staticmethod
128
+ def cosine_similarity(a: list[float], b: list[float]) -> float:
129
+ if not a or not b or len(a) != len(b):
130
+ return 0.0
131
+ dot = sum(x * y for x, y in zip(a, b))
132
+ mag_a = math.sqrt(sum(x * x for x in a))
133
+ mag_b = math.sqrt(sum(x * x for x in b))
134
+ if mag_a == 0 or mag_b == 0:
135
+ return 0.0
136
+ return dot / (mag_a * mag_b)
137
+
138
+
139
+ # Singleton shared instance (override with embedding_backend = SentenceTransformerBackend(...))
140
+ default_embedding = EmbeddingBackend(dim=128)
141
+
142
+
143
+ # ---------------------------------------------------------------------------
144
+ # Pre-built Registries
145
+ # ---------------------------------------------------------------------------
146
+
147
+ # Backend registry — for LLM/SLM backends
148
+ backend_registry = PluginRegistry("Backend")
149
+
150
+ # Callback registry — for observability callbacks
151
+ callback_registry = PluginRegistry("Callback")
152
+
153
+ # Model registry — extensible SLM model definitions
154
+ model_registry = PluginRegistry("Model")
155
+
156
+
157
+ def _register_defaults():
158
+ """Register built-in plugins. Called once at import time."""
159
+ # Register built-in backends
160
+ from purpose_agent.llm_backend import (
161
+ MockLLMBackend, HFInferenceBackend, OpenAICompatibleBackend,
162
+ )
163
+ backend_registry.register("mock", MockLLMBackend, description="Deterministic mock for testing")
164
+ backend_registry.register("hf_inference", HFInferenceBackend, description="HuggingFace Inference Providers")
165
+ backend_registry.register("openai", OpenAICompatibleBackend, description="OpenAI-compatible API")
166
+
167
+ from purpose_agent.slm_backends import OllamaBackend, LlamaCppBackend, SLM_REGISTRY
168
+ backend_registry.register("ollama", OllamaBackend, description="Local Ollama serving")
169
+ backend_registry.register("llama_cpp", LlamaCppBackend, description="Direct llama-cpp-python")
170
+
171
+ # Register SLM models from the built-in registry
172
+ for key, (ollama_name, ctx, desc) in SLM_REGISTRY.items():
173
+ model_registry.register(
174
+ key,
175
+ lambda host="http://localhost:11434", _m=ollama_name, _c=ctx: OllamaBackend(
176
+ model=_m, host=host, context_window=_c, compress_prompts=True,
177
+ ),
178
+ ollama_name=ollama_name,
179
+ context_window=ctx,
180
+ description=desc,
181
+ )
182
+
183
+ # Register built-in callbacks
184
+ from purpose_agent.observability import LoggingCallback, MetricsCollector, JSONFileCallback
185
+ callback_registry.register("logging", LoggingCallback, description="Log all events")
186
+ callback_registry.register("metrics", MetricsCollector, description="Collect aggregate metrics")
187
+ callback_registry.register("jsonfile", JSONFileCallback, description="Write events to JSONL file")
188
+
189
+
190
+ # Auto-register defaults on import
191
+ _register_defaults()