noesium 0.1.0__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.
- noesium/core/__init__.py +4 -0
- noesium/core/agent/__init__.py +14 -0
- noesium/core/agent/base.py +227 -0
- noesium/core/consts.py +6 -0
- noesium/core/goalith/conflict/conflict.py +104 -0
- noesium/core/goalith/conflict/detector.py +53 -0
- noesium/core/goalith/decomposer/__init__.py +6 -0
- noesium/core/goalith/decomposer/base.py +46 -0
- noesium/core/goalith/decomposer/callable_decomposer.py +65 -0
- noesium/core/goalith/decomposer/llm_decomposer.py +326 -0
- noesium/core/goalith/decomposer/prompts.py +140 -0
- noesium/core/goalith/decomposer/simple_decomposer.py +61 -0
- noesium/core/goalith/errors.py +22 -0
- noesium/core/goalith/goalgraph/graph.py +526 -0
- noesium/core/goalith/goalgraph/node.py +179 -0
- noesium/core/goalith/replanner/base.py +31 -0
- noesium/core/goalith/replanner/replanner.py +36 -0
- noesium/core/goalith/service.py +26 -0
- noesium/core/llm/__init__.py +154 -0
- noesium/core/llm/base.py +152 -0
- noesium/core/llm/litellm.py +528 -0
- noesium/core/llm/llamacpp.py +487 -0
- noesium/core/llm/message.py +184 -0
- noesium/core/llm/ollama.py +459 -0
- noesium/core/llm/openai.py +520 -0
- noesium/core/llm/openrouter.py +89 -0
- noesium/core/llm/prompt.py +551 -0
- noesium/core/memory/__init__.py +11 -0
- noesium/core/memory/base.py +464 -0
- noesium/core/memory/memu/__init__.py +24 -0
- noesium/core/memory/memu/config/__init__.py +26 -0
- noesium/core/memory/memu/config/activity/config.py +46 -0
- noesium/core/memory/memu/config/event/config.py +46 -0
- noesium/core/memory/memu/config/markdown_config.py +241 -0
- noesium/core/memory/memu/config/profile/config.py +48 -0
- noesium/core/memory/memu/llm_adapter.py +129 -0
- noesium/core/memory/memu/memory/__init__.py +31 -0
- noesium/core/memory/memu/memory/actions/__init__.py +40 -0
- noesium/core/memory/memu/memory/actions/add_activity_memory.py +299 -0
- noesium/core/memory/memu/memory/actions/base_action.py +342 -0
- noesium/core/memory/memu/memory/actions/cluster_memories.py +262 -0
- noesium/core/memory/memu/memory/actions/generate_suggestions.py +198 -0
- noesium/core/memory/memu/memory/actions/get_available_categories.py +66 -0
- noesium/core/memory/memu/memory/actions/link_related_memories.py +515 -0
- noesium/core/memory/memu/memory/actions/run_theory_of_mind.py +254 -0
- noesium/core/memory/memu/memory/actions/update_memory_with_suggestions.py +514 -0
- noesium/core/memory/memu/memory/embeddings.py +130 -0
- noesium/core/memory/memu/memory/file_manager.py +306 -0
- noesium/core/memory/memu/memory/memory_agent.py +578 -0
- noesium/core/memory/memu/memory/recall_agent.py +376 -0
- noesium/core/memory/memu/memory_store.py +628 -0
- noesium/core/memory/models.py +149 -0
- noesium/core/msgbus/__init__.py +12 -0
- noesium/core/msgbus/base.py +395 -0
- noesium/core/orchestrix/__init__.py +0 -0
- noesium/core/py.typed +0 -0
- noesium/core/routing/__init__.py +20 -0
- noesium/core/routing/base.py +66 -0
- noesium/core/routing/router.py +241 -0
- noesium/core/routing/strategies/__init__.py +9 -0
- noesium/core/routing/strategies/dynamic_complexity.py +361 -0
- noesium/core/routing/strategies/self_assessment.py +147 -0
- noesium/core/routing/types.py +38 -0
- noesium/core/toolify/__init__.py +39 -0
- noesium/core/toolify/base.py +360 -0
- noesium/core/toolify/config.py +138 -0
- noesium/core/toolify/mcp_integration.py +275 -0
- noesium/core/toolify/registry.py +214 -0
- noesium/core/toolify/toolkits/__init__.py +1 -0
- noesium/core/tracing/__init__.py +37 -0
- noesium/core/tracing/langgraph_hooks.py +308 -0
- noesium/core/tracing/opik_tracing.py +144 -0
- noesium/core/tracing/token_tracker.py +166 -0
- noesium/core/utils/__init__.py +10 -0
- noesium/core/utils/logging.py +172 -0
- noesium/core/utils/statistics.py +12 -0
- noesium/core/utils/typing.py +17 -0
- noesium/core/vector_store/__init__.py +79 -0
- noesium/core/vector_store/base.py +94 -0
- noesium/core/vector_store/pgvector.py +304 -0
- noesium/core/vector_store/weaviate.py +383 -0
- noesium-0.1.0.dist-info/METADATA +525 -0
- noesium-0.1.0.dist-info/RECORD +86 -0
- noesium-0.1.0.dist-info/WHEEL +5 -0
- noesium-0.1.0.dist-info/licenses/LICENSE +21 -0
- noesium-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,551 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Independent Prompts Module
|
|
3
|
+
|
|
4
|
+
A comprehensive and extensible prompt management system for handling various
|
|
5
|
+
prompt templates, dynamic variable injection, and multi-format support.
|
|
6
|
+
|
|
7
|
+
This module is self-contained and doesn't depend on other core modules.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import re
|
|
12
|
+
from abc import ABC, abstractmethod
|
|
13
|
+
from datetime import datetime
|
|
14
|
+
from enum import Enum
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from string import Template
|
|
17
|
+
from typing import Any, Callable, Dict, List, Optional, Union
|
|
18
|
+
|
|
19
|
+
import yaml
|
|
20
|
+
from pydantic import BaseModel, Field
|
|
21
|
+
|
|
22
|
+
# Try to import external dependencies
|
|
23
|
+
try:
|
|
24
|
+
from jinja2 import BaseLoader, Environment, FileSystemLoader
|
|
25
|
+
|
|
26
|
+
HAS_JINJA2 = True
|
|
27
|
+
except ImportError:
|
|
28
|
+
HAS_JINJA2 = False
|
|
29
|
+
|
|
30
|
+
# Import message types from message.py
|
|
31
|
+
from .message import AssistantMessage, BaseMessage, SystemMessage, UserMessage
|
|
32
|
+
|
|
33
|
+
# ============================================================================
|
|
34
|
+
# Prompt Management System
|
|
35
|
+
# ============================================================================
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class TemplateEngine(Enum):
|
|
39
|
+
"""Supported template engines"""
|
|
40
|
+
|
|
41
|
+
STRING = "string" # Python string.Template
|
|
42
|
+
JINJA2 = "jinja2" # Jinja2 templates
|
|
43
|
+
FORMAT = "format" # Python str.format()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class PromptMetadata(BaseModel):
|
|
47
|
+
"""Metadata for a prompt template"""
|
|
48
|
+
|
|
49
|
+
name: str
|
|
50
|
+
description: str = ""
|
|
51
|
+
version: str = "1.0"
|
|
52
|
+
author: str = ""
|
|
53
|
+
created_at: Optional[str] = None
|
|
54
|
+
tags: List[str] = Field(default_factory=list)
|
|
55
|
+
required_variables: List[str] = Field(default_factory=list)
|
|
56
|
+
optional_variables: Dict[str, Any] = Field(default_factory=dict)
|
|
57
|
+
template_engine: TemplateEngine = TemplateEngine.JINJA2
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class MessageTemplate(BaseModel):
|
|
61
|
+
"""Template for a single message"""
|
|
62
|
+
|
|
63
|
+
role: str # "system", "user", "assistant"
|
|
64
|
+
content: str
|
|
65
|
+
name: Optional[str] = None
|
|
66
|
+
cache: bool = False
|
|
67
|
+
condition: Optional[str] = None # Conditional inclusion
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class PromptTemplate(BaseModel):
|
|
71
|
+
"""Complete prompt template with metadata and messages"""
|
|
72
|
+
|
|
73
|
+
metadata: PromptMetadata
|
|
74
|
+
messages: List[MessageTemplate]
|
|
75
|
+
global_variables: Dict[str, Any] = Field(default_factory=dict)
|
|
76
|
+
includes: List[str] = Field(default_factory=list)
|
|
77
|
+
extends: Optional[str] = None
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class TemplateProcessor(ABC):
|
|
81
|
+
"""Abstract base class for template processors"""
|
|
82
|
+
|
|
83
|
+
@abstractmethod
|
|
84
|
+
def render(self, template: str, variables: Dict[str, Any]) -> str:
|
|
85
|
+
"""Render template with variables"""
|
|
86
|
+
|
|
87
|
+
@abstractmethod
|
|
88
|
+
def validate_template(self, template: str) -> bool:
|
|
89
|
+
"""Validate template syntax"""
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class StringTemplateProcessor(TemplateProcessor):
|
|
93
|
+
"""Python string.Template processor"""
|
|
94
|
+
|
|
95
|
+
def render(self, template: str, variables: Dict[str, Any]) -> str:
|
|
96
|
+
try:
|
|
97
|
+
t = Template(template)
|
|
98
|
+
return t.safe_substitute(**variables)
|
|
99
|
+
except Exception as e:
|
|
100
|
+
raise ValueError(f"Template rendering failed: {e}")
|
|
101
|
+
|
|
102
|
+
def validate_template(self, template: str) -> bool:
|
|
103
|
+
try:
|
|
104
|
+
Template(template)
|
|
105
|
+
return True
|
|
106
|
+
except Exception:
|
|
107
|
+
return False
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class FormatTemplateProcessor(TemplateProcessor):
|
|
111
|
+
"""Python str.format() processor"""
|
|
112
|
+
|
|
113
|
+
def render(self, template: str, variables: Dict[str, Any]) -> str:
|
|
114
|
+
try:
|
|
115
|
+
return template.format(**variables)
|
|
116
|
+
except Exception as e:
|
|
117
|
+
raise ValueError(f"Template rendering failed: {e}")
|
|
118
|
+
|
|
119
|
+
def validate_template(self, template: str) -> bool:
|
|
120
|
+
try:
|
|
121
|
+
# Try to format with empty dict to check syntax
|
|
122
|
+
template.format()
|
|
123
|
+
return True
|
|
124
|
+
except (ValueError, KeyError):
|
|
125
|
+
# KeyError is expected if variables are missing
|
|
126
|
+
return True
|
|
127
|
+
except Exception:
|
|
128
|
+
return False
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class Jinja2TemplateProcessor(TemplateProcessor):
|
|
132
|
+
"""Jinja2 template processor"""
|
|
133
|
+
|
|
134
|
+
def __init__(self, template_dir: Optional[str] = None):
|
|
135
|
+
if not HAS_JINJA2:
|
|
136
|
+
raise ImportError("Jinja2 not available. Install with: pip install jinja2")
|
|
137
|
+
|
|
138
|
+
if template_dir:
|
|
139
|
+
loader = FileSystemLoader(template_dir)
|
|
140
|
+
else:
|
|
141
|
+
loader = BaseLoader()
|
|
142
|
+
|
|
143
|
+
self.env = Environment(loader=loader, trim_blocks=True, lstrip_blocks=True)
|
|
144
|
+
|
|
145
|
+
# Add custom filters
|
|
146
|
+
self.env.filters["jsonify"] = json.dumps
|
|
147
|
+
self.env.filters["yamlify"] = yaml.dump
|
|
148
|
+
self.env.globals["datetime"] = datetime
|
|
149
|
+
|
|
150
|
+
def render(self, template: str, variables: Dict[str, Any]) -> str:
|
|
151
|
+
try:
|
|
152
|
+
t = self.env.from_string(template)
|
|
153
|
+
return t.render(**variables)
|
|
154
|
+
except Exception as e:
|
|
155
|
+
raise ValueError(f"Template rendering failed: {e}")
|
|
156
|
+
|
|
157
|
+
def validate_template(self, template: str) -> bool:
|
|
158
|
+
try:
|
|
159
|
+
self.env.from_string(template)
|
|
160
|
+
return True
|
|
161
|
+
except Exception:
|
|
162
|
+
return False
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class PromptLoader:
|
|
166
|
+
"""Load prompts from various sources"""
|
|
167
|
+
|
|
168
|
+
@staticmethod
|
|
169
|
+
def from_markdown(file_path: Union[str, Path]) -> PromptTemplate:
|
|
170
|
+
"""Load prompt from markdown file with YAML frontmatter"""
|
|
171
|
+
path = Path(file_path)
|
|
172
|
+
if not path.exists():
|
|
173
|
+
raise FileNotFoundError(f"Prompt file not found: {file_path}")
|
|
174
|
+
|
|
175
|
+
content = path.read_text(encoding="utf-8")
|
|
176
|
+
return PromptLoader.from_markdown_string(content, name=path.stem)
|
|
177
|
+
|
|
178
|
+
@staticmethod
|
|
179
|
+
def from_markdown_string(content: str, name: str = "inline") -> PromptTemplate:
|
|
180
|
+
"""Load prompt from markdown string with YAML frontmatter"""
|
|
181
|
+
# Check for YAML frontmatter
|
|
182
|
+
if content.startswith("---"):
|
|
183
|
+
parts = content.split("---", 2)
|
|
184
|
+
if len(parts) >= 3:
|
|
185
|
+
yaml_content = parts[1].strip()
|
|
186
|
+
markdown_content = parts[2].strip()
|
|
187
|
+
else:
|
|
188
|
+
yaml_content = ""
|
|
189
|
+
markdown_content = content
|
|
190
|
+
else:
|
|
191
|
+
yaml_content = ""
|
|
192
|
+
markdown_content = content
|
|
193
|
+
|
|
194
|
+
# Parse metadata
|
|
195
|
+
metadata_dict = yaml.safe_load(yaml_content) if yaml_content else {}
|
|
196
|
+
metadata = PromptMetadata(
|
|
197
|
+
name=metadata_dict.get("name", name),
|
|
198
|
+
description=metadata_dict.get("description", ""),
|
|
199
|
+
version=metadata_dict.get("version", "1.0"),
|
|
200
|
+
author=metadata_dict.get("author", ""),
|
|
201
|
+
created_at=metadata_dict.get("created_at"),
|
|
202
|
+
tags=metadata_dict.get("tags", []),
|
|
203
|
+
required_variables=metadata_dict.get("required_variables", []),
|
|
204
|
+
optional_variables=metadata_dict.get("optional_variables", {}),
|
|
205
|
+
template_engine=TemplateEngine(metadata_dict.get("template_engine", "jinja2")),
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
# Parse messages from markdown
|
|
209
|
+
messages = PromptLoader._parse_markdown_messages(markdown_content, metadata_dict)
|
|
210
|
+
|
|
211
|
+
return PromptTemplate(
|
|
212
|
+
metadata=metadata,
|
|
213
|
+
messages=messages,
|
|
214
|
+
global_variables=metadata_dict.get("global_variables", {}),
|
|
215
|
+
includes=metadata_dict.get("includes", []),
|
|
216
|
+
extends=metadata_dict.get("extends"),
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
@staticmethod
|
|
220
|
+
def from_yaml(file_path: Union[str, Path]) -> PromptTemplate:
|
|
221
|
+
"""Load prompt from YAML file"""
|
|
222
|
+
path = Path(file_path)
|
|
223
|
+
if not path.exists():
|
|
224
|
+
raise FileNotFoundError(f"Prompt file not found: {file_path}")
|
|
225
|
+
|
|
226
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
227
|
+
data = yaml.safe_load(f)
|
|
228
|
+
|
|
229
|
+
return PromptLoader._dict_to_prompt_template(data)
|
|
230
|
+
|
|
231
|
+
@staticmethod
|
|
232
|
+
def from_json(file_path: Union[str, Path]) -> PromptTemplate:
|
|
233
|
+
"""Load prompt from JSON file"""
|
|
234
|
+
path = Path(file_path)
|
|
235
|
+
if not path.exists():
|
|
236
|
+
raise FileNotFoundError(f"Prompt file not found: {file_path}")
|
|
237
|
+
|
|
238
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
239
|
+
data = json.load(f)
|
|
240
|
+
|
|
241
|
+
return PromptLoader._dict_to_prompt_template(data)
|
|
242
|
+
|
|
243
|
+
@staticmethod
|
|
244
|
+
def from_string(content: str, name: str = "inline", role: str = "system", **metadata_kwargs) -> PromptTemplate:
|
|
245
|
+
"""Create prompt from string content"""
|
|
246
|
+
metadata = PromptMetadata(name=name, **metadata_kwargs)
|
|
247
|
+
|
|
248
|
+
# Simple string becomes a message with specified role
|
|
249
|
+
messages = [MessageTemplate(role=role, content=content)]
|
|
250
|
+
|
|
251
|
+
return PromptTemplate(metadata=metadata, messages=messages)
|
|
252
|
+
|
|
253
|
+
@staticmethod
|
|
254
|
+
def _parse_markdown_messages(content: str, metadata: Dict[str, Any]) -> List[MessageTemplate]:
|
|
255
|
+
"""Parse message blocks from markdown content"""
|
|
256
|
+
messages = []
|
|
257
|
+
|
|
258
|
+
# Look for message blocks: ## role: content or ### role: content
|
|
259
|
+
message_pattern = r"^#+\s*(system|user|assistant)(?:\s*:\s*(.*))?$"
|
|
260
|
+
lines = content.split("\n")
|
|
261
|
+
|
|
262
|
+
current_message = None
|
|
263
|
+
current_content = []
|
|
264
|
+
|
|
265
|
+
for line in lines:
|
|
266
|
+
match = re.match(message_pattern, line.strip(), re.IGNORECASE)
|
|
267
|
+
if match:
|
|
268
|
+
# Save previous message
|
|
269
|
+
if current_message:
|
|
270
|
+
current_message.content = "\n".join(current_content).strip()
|
|
271
|
+
messages.append(current_message)
|
|
272
|
+
|
|
273
|
+
# Start new message
|
|
274
|
+
role = match.group(1).lower()
|
|
275
|
+
title_content = match.group(2) or ""
|
|
276
|
+
current_message = MessageTemplate(role=role, content="", cache=metadata.get("cache", False))
|
|
277
|
+
current_content = [title_content] if title_content else []
|
|
278
|
+
else:
|
|
279
|
+
if current_message:
|
|
280
|
+
current_content.append(line)
|
|
281
|
+
|
|
282
|
+
# Save last message
|
|
283
|
+
if current_message:
|
|
284
|
+
current_message.content = "\n".join(current_content).strip()
|
|
285
|
+
messages.append(current_message)
|
|
286
|
+
|
|
287
|
+
# If no structured messages found, treat entire content as system message
|
|
288
|
+
if not messages:
|
|
289
|
+
messages = [MessageTemplate(role="system", content=content)]
|
|
290
|
+
|
|
291
|
+
return messages
|
|
292
|
+
|
|
293
|
+
@staticmethod
|
|
294
|
+
def _dict_to_prompt_template(data: Dict[str, Any]) -> PromptTemplate:
|
|
295
|
+
"""Convert dictionary to PromptTemplate"""
|
|
296
|
+
metadata_dict = data.get("metadata", {})
|
|
297
|
+
metadata = PromptMetadata(
|
|
298
|
+
name=metadata_dict.get("name", "unnamed"),
|
|
299
|
+
description=metadata_dict.get("description", ""),
|
|
300
|
+
version=metadata_dict.get("version", "1.0"),
|
|
301
|
+
author=metadata_dict.get("author", ""),
|
|
302
|
+
created_at=metadata_dict.get("created_at"),
|
|
303
|
+
tags=metadata_dict.get("tags", []),
|
|
304
|
+
required_variables=metadata_dict.get("required_variables", []),
|
|
305
|
+
optional_variables=metadata_dict.get("optional_variables", {}),
|
|
306
|
+
template_engine=TemplateEngine(metadata_dict.get("template_engine", "jinja2")),
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
messages_data = data.get("messages", [])
|
|
310
|
+
messages = [
|
|
311
|
+
MessageTemplate(
|
|
312
|
+
role=msg.get("role", "user"),
|
|
313
|
+
content=msg.get("content", ""),
|
|
314
|
+
name=msg.get("name"),
|
|
315
|
+
cache=msg.get("cache", False),
|
|
316
|
+
condition=msg.get("condition"),
|
|
317
|
+
)
|
|
318
|
+
for msg in messages_data
|
|
319
|
+
]
|
|
320
|
+
|
|
321
|
+
return PromptTemplate(
|
|
322
|
+
metadata=metadata,
|
|
323
|
+
messages=messages,
|
|
324
|
+
global_variables=data.get("global_variables", {}),
|
|
325
|
+
includes=data.get("includes", []),
|
|
326
|
+
extends=data.get("extends"),
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
class PromptManager:
|
|
331
|
+
"""Main prompt management system"""
|
|
332
|
+
|
|
333
|
+
def __init__(
|
|
334
|
+
self,
|
|
335
|
+
template_dirs: Optional[List[str]] = None,
|
|
336
|
+
default_engine: TemplateEngine = TemplateEngine.JINJA2,
|
|
337
|
+
enable_cache: bool = True,
|
|
338
|
+
):
|
|
339
|
+
self.template_dirs = [Path(d) for d in (template_dirs or [])]
|
|
340
|
+
self.default_engine = default_engine
|
|
341
|
+
self.enable_cache = enable_cache
|
|
342
|
+
|
|
343
|
+
# Template cache
|
|
344
|
+
self._template_cache: Dict[str, PromptTemplate] = {}
|
|
345
|
+
|
|
346
|
+
# Processors
|
|
347
|
+
self._processors = {
|
|
348
|
+
TemplateEngine.STRING: StringTemplateProcessor(),
|
|
349
|
+
TemplateEngine.FORMAT: FormatTemplateProcessor(),
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if HAS_JINJA2:
|
|
353
|
+
template_dir = str(self.template_dirs[0]) if self.template_dirs else None
|
|
354
|
+
self._processors[TemplateEngine.JINJA2] = Jinja2TemplateProcessor(template_dir)
|
|
355
|
+
|
|
356
|
+
# Custom functions for templates
|
|
357
|
+
self._custom_functions: Dict[str, Callable] = {}
|
|
358
|
+
|
|
359
|
+
def register_custom_function(self, name: str, func: Callable):
|
|
360
|
+
"""Register custom function for use in templates"""
|
|
361
|
+
self._custom_functions[name] = func
|
|
362
|
+
|
|
363
|
+
def load_prompt(
|
|
364
|
+
self,
|
|
365
|
+
file_path: Optional[Union[str, Path]] = None,
|
|
366
|
+
content: Optional[str] = None,
|
|
367
|
+
name: Optional[str] = None,
|
|
368
|
+
**metadata_kwargs,
|
|
369
|
+
) -> PromptTemplate:
|
|
370
|
+
"""Load a prompt template"""
|
|
371
|
+
|
|
372
|
+
if file_path:
|
|
373
|
+
path = Path(file_path)
|
|
374
|
+
|
|
375
|
+
# Try to find in template directories
|
|
376
|
+
if not path.is_absolute():
|
|
377
|
+
for template_dir in self.template_dirs:
|
|
378
|
+
candidate = template_dir / path
|
|
379
|
+
if candidate.exists():
|
|
380
|
+
path = candidate
|
|
381
|
+
break
|
|
382
|
+
|
|
383
|
+
cache_key = str(path)
|
|
384
|
+
|
|
385
|
+
# Check cache
|
|
386
|
+
if self.enable_cache and cache_key in self._template_cache:
|
|
387
|
+
return self._template_cache[cache_key]
|
|
388
|
+
|
|
389
|
+
# Load based on file extension
|
|
390
|
+
if path.suffix.lower() == ".md":
|
|
391
|
+
template = PromptLoader.from_markdown(path)
|
|
392
|
+
elif path.suffix.lower() in [".yaml", ".yml"]:
|
|
393
|
+
template = PromptLoader.from_yaml(path)
|
|
394
|
+
elif path.suffix.lower() == ".json":
|
|
395
|
+
template = PromptLoader.from_json(path)
|
|
396
|
+
else:
|
|
397
|
+
# Try markdown first, then treat as plain text
|
|
398
|
+
try:
|
|
399
|
+
template = PromptLoader.from_markdown(path)
|
|
400
|
+
except:
|
|
401
|
+
content = path.read_text(encoding="utf-8")
|
|
402
|
+
template = PromptLoader.from_string(content, name or path.stem)
|
|
403
|
+
|
|
404
|
+
# Cache template
|
|
405
|
+
if self.enable_cache:
|
|
406
|
+
self._template_cache[cache_key] = template
|
|
407
|
+
|
|
408
|
+
return template
|
|
409
|
+
|
|
410
|
+
elif content:
|
|
411
|
+
return PromptLoader.from_string(content, name or "inline", **metadata_kwargs)
|
|
412
|
+
|
|
413
|
+
else:
|
|
414
|
+
raise ValueError("Either file_path or content must be provided")
|
|
415
|
+
|
|
416
|
+
def render_prompt(
|
|
417
|
+
self, template: Union[PromptTemplate, str, Path], variables: Optional[Dict[str, Any]] = None, **kwargs
|
|
418
|
+
) -> List[BaseMessage]:
|
|
419
|
+
"""Render a prompt template to messages"""
|
|
420
|
+
|
|
421
|
+
if not isinstance(template, PromptTemplate):
|
|
422
|
+
template = self.load_prompt(template)
|
|
423
|
+
|
|
424
|
+
variables = variables or {}
|
|
425
|
+
variables.update(kwargs)
|
|
426
|
+
|
|
427
|
+
# Add global variables and custom functions
|
|
428
|
+
variables.update(template.global_variables)
|
|
429
|
+
variables.update(self._custom_functions)
|
|
430
|
+
|
|
431
|
+
# Validate required variables
|
|
432
|
+
missing_vars = set(template.metadata.required_variables) - set(variables.keys())
|
|
433
|
+
if missing_vars:
|
|
434
|
+
raise ValueError(f"Missing required variables: {missing_vars}")
|
|
435
|
+
|
|
436
|
+
# Add optional variables with defaults
|
|
437
|
+
for var, default in template.metadata.optional_variables.items():
|
|
438
|
+
if var not in variables:
|
|
439
|
+
variables[var] = default
|
|
440
|
+
|
|
441
|
+
# Get template processor
|
|
442
|
+
engine = template.metadata.template_engine
|
|
443
|
+
if engine not in self._processors:
|
|
444
|
+
engine = self.default_engine
|
|
445
|
+
|
|
446
|
+
processor = self._processors[engine]
|
|
447
|
+
|
|
448
|
+
# Render messages
|
|
449
|
+
messages = []
|
|
450
|
+
for msg_template in template.messages:
|
|
451
|
+
# Check condition
|
|
452
|
+
if msg_template.condition:
|
|
453
|
+
try:
|
|
454
|
+
condition_result = eval(msg_template.condition, {"__builtins__": {}}, variables)
|
|
455
|
+
if not condition_result:
|
|
456
|
+
continue
|
|
457
|
+
except Exception as e:
|
|
458
|
+
print(f"Warning: Condition evaluation failed for message: {e}")
|
|
459
|
+
continue
|
|
460
|
+
|
|
461
|
+
# Render content
|
|
462
|
+
rendered_content = processor.render(msg_template.content, variables)
|
|
463
|
+
|
|
464
|
+
# Create message object
|
|
465
|
+
if msg_template.role == "system":
|
|
466
|
+
message = SystemMessage(content=rendered_content, name=msg_template.name, cache=msg_template.cache)
|
|
467
|
+
elif msg_template.role == "user":
|
|
468
|
+
message = UserMessage(content=rendered_content, name=msg_template.name)
|
|
469
|
+
elif msg_template.role == "assistant":
|
|
470
|
+
message = AssistantMessage(content=rendered_content, name=msg_template.name)
|
|
471
|
+
else:
|
|
472
|
+
raise ValueError(f"Unknown role: {msg_template.role}")
|
|
473
|
+
|
|
474
|
+
messages.append(message)
|
|
475
|
+
|
|
476
|
+
return messages
|
|
477
|
+
|
|
478
|
+
def list_templates(self, tag: Optional[str] = None) -> List[str]:
|
|
479
|
+
"""List available templates"""
|
|
480
|
+
templates = []
|
|
481
|
+
|
|
482
|
+
for template_dir in self.template_dirs:
|
|
483
|
+
if not template_dir.exists():
|
|
484
|
+
continue
|
|
485
|
+
|
|
486
|
+
for file_path in template_dir.rglob("*"):
|
|
487
|
+
if file_path.is_file() and file_path.suffix.lower() in [".md", ".yaml", ".yml", ".json"]:
|
|
488
|
+
try:
|
|
489
|
+
template = self.load_prompt(file_path)
|
|
490
|
+
if not tag or tag in template.metadata.tags:
|
|
491
|
+
templates.append(str(file_path.relative_to(template_dir)))
|
|
492
|
+
except Exception:
|
|
493
|
+
continue
|
|
494
|
+
|
|
495
|
+
return sorted(templates)
|
|
496
|
+
|
|
497
|
+
def get_template_info(self, template_path: Union[str, Path]) -> PromptMetadata:
|
|
498
|
+
"""Get metadata for a template"""
|
|
499
|
+
template = self.load_prompt(template_path)
|
|
500
|
+
return template.metadata
|
|
501
|
+
|
|
502
|
+
def validate_template(self, template_path: Union[str, Path]) -> Dict[str, Any]:
|
|
503
|
+
"""Validate a template"""
|
|
504
|
+
result = {"valid": True, "errors": [], "warnings": []}
|
|
505
|
+
|
|
506
|
+
try:
|
|
507
|
+
template = self.load_prompt(template_path)
|
|
508
|
+
|
|
509
|
+
# Check engine availability
|
|
510
|
+
engine = template.metadata.template_engine
|
|
511
|
+
if engine not in self._processors:
|
|
512
|
+
result["warnings"].append(
|
|
513
|
+
f"Template engine {engine.value} not available, using {self.default_engine.value}"
|
|
514
|
+
)
|
|
515
|
+
engine = self.default_engine
|
|
516
|
+
|
|
517
|
+
processor = self._processors[engine]
|
|
518
|
+
|
|
519
|
+
# Validate each message template
|
|
520
|
+
for i, msg_template in enumerate(template.messages):
|
|
521
|
+
if not processor.validate_template(msg_template.content):
|
|
522
|
+
result["valid"] = False
|
|
523
|
+
result["errors"].append(f"Invalid template syntax in message {i+1}")
|
|
524
|
+
|
|
525
|
+
except Exception as e:
|
|
526
|
+
result["valid"] = False
|
|
527
|
+
result["errors"].append(str(e))
|
|
528
|
+
|
|
529
|
+
return result
|
|
530
|
+
|
|
531
|
+
def clear_cache(self):
|
|
532
|
+
"""Clear template cache"""
|
|
533
|
+
self._template_cache.clear()
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
# ============================================================================
|
|
537
|
+
# Convenience Functions
|
|
538
|
+
# ============================================================================
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
def create_simple_prompt(content: str, role: str = "system", **kwargs) -> List[BaseMessage]:
|
|
542
|
+
"""Quick way to create a simple prompt"""
|
|
543
|
+
manager = PromptManager()
|
|
544
|
+
template = PromptLoader.from_string(content, role=role)
|
|
545
|
+
return manager.render_prompt(template, **kwargs)
|
|
546
|
+
|
|
547
|
+
|
|
548
|
+
def load_and_render(file_path: Union[str, Path], **variables) -> List[BaseMessage]:
|
|
549
|
+
"""Quick way to load and render a prompt file"""
|
|
550
|
+
manager = PromptManager()
|
|
551
|
+
return manager.render_prompt(file_path, variables)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from .base import BaseMemoryManager, BaseMemoryStore
|
|
2
|
+
from .models import MemoryFilter, MemoryItem, MemoryStats, SearchResult
|
|
3
|
+
|
|
4
|
+
__all__ = [
|
|
5
|
+
"BaseMemoryStore",
|
|
6
|
+
"BaseMemoryManager",
|
|
7
|
+
"MemoryItem",
|
|
8
|
+
"MemoryFilter",
|
|
9
|
+
"MemoryStats",
|
|
10
|
+
"SearchResult",
|
|
11
|
+
]
|