chuk-ai-session-manager 0.7.1__py3-none-any.whl → 0.8.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.
- chuk_ai_session_manager/__init__.py +84 -40
- chuk_ai_session_manager/api/__init__.py +1 -1
- chuk_ai_session_manager/api/simple_api.py +53 -59
- chuk_ai_session_manager/exceptions.py +31 -17
- chuk_ai_session_manager/guards/__init__.py +118 -0
- chuk_ai_session_manager/guards/bindings.py +217 -0
- chuk_ai_session_manager/guards/cache.py +163 -0
- chuk_ai_session_manager/guards/manager.py +819 -0
- chuk_ai_session_manager/guards/models.py +498 -0
- chuk_ai_session_manager/guards/ungrounded.py +159 -0
- chuk_ai_session_manager/infinite_conversation.py +86 -79
- chuk_ai_session_manager/memory/__init__.py +247 -0
- chuk_ai_session_manager/memory/artifacts_bridge.py +469 -0
- chuk_ai_session_manager/memory/context_packer.py +347 -0
- chuk_ai_session_manager/memory/fault_handler.py +507 -0
- chuk_ai_session_manager/memory/manifest.py +307 -0
- chuk_ai_session_manager/memory/models.py +1084 -0
- chuk_ai_session_manager/memory/mutation_log.py +186 -0
- chuk_ai_session_manager/memory/pack_cache.py +206 -0
- chuk_ai_session_manager/memory/page_table.py +275 -0
- chuk_ai_session_manager/memory/prefetcher.py +192 -0
- chuk_ai_session_manager/memory/tlb.py +247 -0
- chuk_ai_session_manager/memory/vm_prompts.py +238 -0
- chuk_ai_session_manager/memory/working_set.py +574 -0
- chuk_ai_session_manager/models/__init__.py +21 -9
- chuk_ai_session_manager/models/event_source.py +3 -1
- chuk_ai_session_manager/models/event_type.py +10 -1
- chuk_ai_session_manager/models/session.py +103 -68
- chuk_ai_session_manager/models/session_event.py +69 -68
- chuk_ai_session_manager/models/session_metadata.py +9 -10
- chuk_ai_session_manager/models/session_run.py +21 -22
- chuk_ai_session_manager/models/token_usage.py +76 -76
- chuk_ai_session_manager/procedural_memory/__init__.py +70 -0
- chuk_ai_session_manager/procedural_memory/formatter.py +407 -0
- chuk_ai_session_manager/procedural_memory/manager.py +523 -0
- chuk_ai_session_manager/procedural_memory/models.py +371 -0
- chuk_ai_session_manager/sample_tools.py +79 -46
- chuk_ai_session_manager/session_aware_tool_processor.py +27 -16
- chuk_ai_session_manager/session_manager.py +259 -232
- chuk_ai_session_manager/session_prompt_builder.py +163 -111
- chuk_ai_session_manager/session_storage.py +45 -52
- {chuk_ai_session_manager-0.7.1.dist-info → chuk_ai_session_manager-0.8.1.dist-info}/METADATA +80 -4
- chuk_ai_session_manager-0.8.1.dist-info/RECORD +45 -0
- {chuk_ai_session_manager-0.7.1.dist-info → chuk_ai_session_manager-0.8.1.dist-info}/WHEEL +1 -1
- chuk_ai_session_manager-0.7.1.dist-info/RECORD +0 -22
- {chuk_ai_session_manager-0.7.1.dist-info → chuk_ai_session_manager-0.8.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
# chuk_ai_session_manager/procedural_memory/models.py
|
|
2
|
+
"""
|
|
3
|
+
Data models for procedural memory.
|
|
4
|
+
|
|
5
|
+
These models represent:
|
|
6
|
+
- Individual tool invocations (traces)
|
|
7
|
+
- Aggregated tool patterns (recipes)
|
|
8
|
+
- Fix relationships (how failures were resolved)
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from datetime import datetime, timezone
|
|
14
|
+
from enum import Enum
|
|
15
|
+
from typing import Any, Optional
|
|
16
|
+
|
|
17
|
+
from pydantic import BaseModel, Field
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ToolOutcome(str, Enum):
|
|
21
|
+
"""Outcome of a tool invocation."""
|
|
22
|
+
|
|
23
|
+
SUCCESS = "success"
|
|
24
|
+
FAILURE = "failure"
|
|
25
|
+
PARTIAL = "partial" # Completed but with warnings/limitations
|
|
26
|
+
TIMEOUT = "timeout"
|
|
27
|
+
CANCELLED = "cancelled"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ToolFixRelation(BaseModel):
|
|
31
|
+
"""Describes how one call fixed a prior failure."""
|
|
32
|
+
|
|
33
|
+
failed_call_id: str
|
|
34
|
+
success_call_id: str
|
|
35
|
+
delta_args: dict[str, Any] = Field(default_factory=dict)
|
|
36
|
+
# delta_args structure:
|
|
37
|
+
# {
|
|
38
|
+
# "added": {"key": value, ...},
|
|
39
|
+
# "removed": ["key1", "key2"],
|
|
40
|
+
# "changed": {"key": {"from": old, "to": new}}
|
|
41
|
+
# }
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class ToolLogEntry(BaseModel):
|
|
45
|
+
"""
|
|
46
|
+
Single tool invocation record (the 'trace').
|
|
47
|
+
|
|
48
|
+
This captures everything about a single tool call:
|
|
49
|
+
- What was called and with what arguments
|
|
50
|
+
- The context/goal at the time
|
|
51
|
+
- The outcome and any errors
|
|
52
|
+
- Links to fixes if this was a failure that got resolved
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
# Identity
|
|
56
|
+
id: str # call-001, call-002, ...
|
|
57
|
+
timestamp: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
58
|
+
|
|
59
|
+
# Tool call details
|
|
60
|
+
tool_name: str
|
|
61
|
+
arguments: dict[str, Any] = Field(default_factory=dict)
|
|
62
|
+
arguments_hash: str = "" # For quick comparison
|
|
63
|
+
|
|
64
|
+
# Context at call time
|
|
65
|
+
context_goal: Optional[str] = None # What was user trying to do?
|
|
66
|
+
preceding_call_id: Optional[str] = None # Chain of calls in this turn
|
|
67
|
+
|
|
68
|
+
# Outcome
|
|
69
|
+
outcome: ToolOutcome
|
|
70
|
+
result_summary: str # Compact summary, not full result
|
|
71
|
+
result_type: Optional[str] = None # "sat", "list", "object", etc.
|
|
72
|
+
execution_time_ms: Optional[int] = None
|
|
73
|
+
|
|
74
|
+
# Error details (if failure)
|
|
75
|
+
error_type: Optional[str] = None # "unsat", "timeout", "validation", etc.
|
|
76
|
+
error_message: Optional[str] = None
|
|
77
|
+
|
|
78
|
+
# Fix tracking
|
|
79
|
+
fixed_by: Optional[str] = None # ID of call that fixed this failure
|
|
80
|
+
fix_for: Optional[str] = None # ID of call this fixed
|
|
81
|
+
delta_args: Optional[dict[str, Any]] = None # What changed to fix it
|
|
82
|
+
|
|
83
|
+
model_config = {"frozen": False}
|
|
84
|
+
|
|
85
|
+
def is_failure(self) -> bool:
|
|
86
|
+
"""Check if this was a failed call."""
|
|
87
|
+
return self.outcome in (ToolOutcome.FAILURE, ToolOutcome.TIMEOUT)
|
|
88
|
+
|
|
89
|
+
def is_success(self) -> bool:
|
|
90
|
+
"""Check if this was a successful call."""
|
|
91
|
+
return self.outcome == ToolOutcome.SUCCESS
|
|
92
|
+
|
|
93
|
+
def was_fixed(self) -> bool:
|
|
94
|
+
"""Check if this failure was subsequently fixed."""
|
|
95
|
+
return self.fixed_by is not None
|
|
96
|
+
|
|
97
|
+
def is_fix(self) -> bool:
|
|
98
|
+
"""Check if this call fixed a prior failure."""
|
|
99
|
+
return self.fix_for is not None
|
|
100
|
+
|
|
101
|
+
def format_compact(self) -> str:
|
|
102
|
+
"""Format as compact string for logging."""
|
|
103
|
+
status = "✓" if self.is_success() else "✗"
|
|
104
|
+
return f"[{status}] {self.tool_name}: {self.result_summary}"
|
|
105
|
+
|
|
106
|
+
def format_for_context(self, include_args: bool = False) -> str:
|
|
107
|
+
"""Format for injection into model context."""
|
|
108
|
+
status = "✓" if self.is_success() else "✗"
|
|
109
|
+
lines = [f"[{status}] {self.result_summary}"]
|
|
110
|
+
|
|
111
|
+
if include_args and self.arguments:
|
|
112
|
+
# Show abbreviated args
|
|
113
|
+
args_str = ", ".join(f"{k}={_abbrev(v)}" for k, v in self.arguments.items())
|
|
114
|
+
lines.append(f" args: {args_str}")
|
|
115
|
+
|
|
116
|
+
if self.is_fix() and self.delta_args:
|
|
117
|
+
lines.append(f" (fixed prior failure by: {self.delta_args})")
|
|
118
|
+
|
|
119
|
+
if self.is_failure() and self.was_fixed():
|
|
120
|
+
lines.append(f" (later fixed by {self.fixed_by})")
|
|
121
|
+
|
|
122
|
+
return "\n".join(lines)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class ErrorPattern(BaseModel):
|
|
126
|
+
"""A pattern of errors for a tool."""
|
|
127
|
+
|
|
128
|
+
error_type: str
|
|
129
|
+
count: int = 1
|
|
130
|
+
contexts: list[str] = Field(
|
|
131
|
+
default_factory=list
|
|
132
|
+
) # Goal contexts where this occurred
|
|
133
|
+
example_args: Optional[dict[str, Any]] = None
|
|
134
|
+
typical_fix: Optional[str] = None # Description of how it's usually fixed
|
|
135
|
+
fix_delta: Optional[dict[str, Any]] = None # Actual arg changes that fixed it
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class SuccessPattern(BaseModel):
|
|
139
|
+
"""A pattern of successful usage for a tool."""
|
|
140
|
+
|
|
141
|
+
goal_match: Optional[str] = None # What kind of goal this pattern works for
|
|
142
|
+
arg_hints: dict[str, Any] = Field(default_factory=dict)
|
|
143
|
+
# arg_hints: {"must_include": [...], "typical_values": {...}}
|
|
144
|
+
notes: Optional[str] = None
|
|
145
|
+
example_call_id: Optional[str] = None
|
|
146
|
+
delta_that_fixed: Optional[dict[str, Any]] = None # If this was a fix
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class ToolPattern(BaseModel):
|
|
150
|
+
"""
|
|
151
|
+
Aggregated knowledge about a tool (the 'recipe').
|
|
152
|
+
|
|
153
|
+
This accumulates across calls to learn:
|
|
154
|
+
- What arguments typically work
|
|
155
|
+
- What errors occur and how to fix them
|
|
156
|
+
- Success patterns for different goals
|
|
157
|
+
"""
|
|
158
|
+
|
|
159
|
+
tool_name: str
|
|
160
|
+
|
|
161
|
+
# Statistics
|
|
162
|
+
total_calls: int = 0
|
|
163
|
+
success_count: int = 0
|
|
164
|
+
failure_count: int = 0
|
|
165
|
+
|
|
166
|
+
@property
|
|
167
|
+
def success_rate(self) -> float:
|
|
168
|
+
"""Calculate success rate."""
|
|
169
|
+
if self.total_calls == 0:
|
|
170
|
+
return 0.0
|
|
171
|
+
return self.success_count / self.total_calls
|
|
172
|
+
|
|
173
|
+
# Argument statistics
|
|
174
|
+
common_args: dict[str, list[Any]] = Field(default_factory=dict)
|
|
175
|
+
# common_args: {"param_name": [most_common_values]}
|
|
176
|
+
|
|
177
|
+
# Success patterns
|
|
178
|
+
success_patterns: list[SuccessPattern] = Field(default_factory=list)
|
|
179
|
+
|
|
180
|
+
# Error patterns
|
|
181
|
+
error_patterns: list[ErrorPattern] = Field(default_factory=list)
|
|
182
|
+
|
|
183
|
+
# Timing
|
|
184
|
+
avg_execution_ms: Optional[float] = None
|
|
185
|
+
updated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
186
|
+
|
|
187
|
+
model_config = {"frozen": False}
|
|
188
|
+
|
|
189
|
+
def record_call(self, entry: ToolLogEntry) -> None:
|
|
190
|
+
"""Update pattern statistics from a call."""
|
|
191
|
+
self.total_calls += 1
|
|
192
|
+
|
|
193
|
+
if entry.is_success():
|
|
194
|
+
self.success_count += 1
|
|
195
|
+
elif entry.is_failure():
|
|
196
|
+
self.failure_count += 1
|
|
197
|
+
|
|
198
|
+
# Update timing
|
|
199
|
+
if entry.execution_time_ms:
|
|
200
|
+
if self.avg_execution_ms is None:
|
|
201
|
+
self.avg_execution_ms = float(entry.execution_time_ms)
|
|
202
|
+
else:
|
|
203
|
+
# Running average
|
|
204
|
+
self.avg_execution_ms = (
|
|
205
|
+
self.avg_execution_ms * (self.total_calls - 1)
|
|
206
|
+
+ entry.execution_time_ms
|
|
207
|
+
) / self.total_calls
|
|
208
|
+
|
|
209
|
+
# Track common args
|
|
210
|
+
for key, value in entry.arguments.items():
|
|
211
|
+
if key not in self.common_args:
|
|
212
|
+
self.common_args[key] = []
|
|
213
|
+
# Keep last few values (dedup)
|
|
214
|
+
if value not in self.common_args[key]:
|
|
215
|
+
self.common_args[key].append(value)
|
|
216
|
+
if len(self.common_args[key]) > 5:
|
|
217
|
+
self.common_args[key].pop(0)
|
|
218
|
+
|
|
219
|
+
self.updated_at = datetime.now(timezone.utc)
|
|
220
|
+
|
|
221
|
+
def add_error_pattern(
|
|
222
|
+
self,
|
|
223
|
+
error_type: str,
|
|
224
|
+
context: Optional[str] = None,
|
|
225
|
+
example_args: Optional[dict] = None,
|
|
226
|
+
) -> ErrorPattern:
|
|
227
|
+
"""Add or update an error pattern."""
|
|
228
|
+
# Find existing
|
|
229
|
+
for pattern in self.error_patterns:
|
|
230
|
+
if pattern.error_type == error_type:
|
|
231
|
+
pattern.count += 1
|
|
232
|
+
if context and context not in pattern.contexts:
|
|
233
|
+
pattern.contexts.append(context)
|
|
234
|
+
if len(pattern.contexts) > 5:
|
|
235
|
+
pattern.contexts.pop(0)
|
|
236
|
+
return pattern
|
|
237
|
+
|
|
238
|
+
# Create new
|
|
239
|
+
pattern = ErrorPattern(
|
|
240
|
+
error_type=error_type,
|
|
241
|
+
contexts=[context] if context else [],
|
|
242
|
+
example_args=example_args,
|
|
243
|
+
)
|
|
244
|
+
self.error_patterns.append(pattern)
|
|
245
|
+
return pattern
|
|
246
|
+
|
|
247
|
+
def add_success_pattern(
|
|
248
|
+
self,
|
|
249
|
+
goal_match: Optional[str] = None,
|
|
250
|
+
arg_hints: Optional[dict] = None,
|
|
251
|
+
delta_that_fixed: Optional[dict] = None,
|
|
252
|
+
example_call_id: Optional[str] = None,
|
|
253
|
+
) -> SuccessPattern:
|
|
254
|
+
"""Add a success pattern."""
|
|
255
|
+
pattern = SuccessPattern(
|
|
256
|
+
goal_match=goal_match,
|
|
257
|
+
arg_hints=arg_hints or {},
|
|
258
|
+
delta_that_fixed=delta_that_fixed,
|
|
259
|
+
example_call_id=example_call_id,
|
|
260
|
+
)
|
|
261
|
+
self.success_patterns.append(pattern)
|
|
262
|
+
|
|
263
|
+
# Keep limited history
|
|
264
|
+
if len(self.success_patterns) > 10:
|
|
265
|
+
self.success_patterns.pop(0)
|
|
266
|
+
|
|
267
|
+
return pattern
|
|
268
|
+
|
|
269
|
+
def record_fix(self, error_type: str, fix_delta: dict[str, Any]) -> None:
|
|
270
|
+
"""Record that an error was fixed with specific arg changes."""
|
|
271
|
+
for pattern in self.error_patterns:
|
|
272
|
+
if pattern.error_type == error_type:
|
|
273
|
+
pattern.fix_delta = fix_delta
|
|
274
|
+
pattern.typical_fix = _describe_fix(fix_delta)
|
|
275
|
+
return
|
|
276
|
+
|
|
277
|
+
def format_for_context(self, max_errors: int = 3, max_successes: int = 3) -> str:
|
|
278
|
+
"""Format pattern for injection into model context."""
|
|
279
|
+
lines = []
|
|
280
|
+
|
|
281
|
+
# Stats summary
|
|
282
|
+
lines.append(
|
|
283
|
+
f"Success rate: {self.success_rate:.0%} ({self.total_calls} calls)"
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
# Error patterns
|
|
287
|
+
if self.error_patterns:
|
|
288
|
+
lines.append("Common errors:")
|
|
289
|
+
for ep in self.error_patterns[-max_errors:]:
|
|
290
|
+
fix_hint = f" -> {ep.typical_fix}" if ep.typical_fix else ""
|
|
291
|
+
lines.append(f" - {ep.error_type} (x{ep.count}){fix_hint}")
|
|
292
|
+
|
|
293
|
+
# Success hints
|
|
294
|
+
if self.success_patterns:
|
|
295
|
+
lines.append("Success hints:")
|
|
296
|
+
for sp in self.success_patterns[-max_successes:]:
|
|
297
|
+
if sp.delta_that_fixed:
|
|
298
|
+
lines.append(f" - Fixed by: {sp.delta_that_fixed}")
|
|
299
|
+
elif sp.arg_hints:
|
|
300
|
+
lines.append(f" - Args: {sp.arg_hints}")
|
|
301
|
+
|
|
302
|
+
return "\n".join(lines)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
class ProceduralMemory(BaseModel):
|
|
306
|
+
"""
|
|
307
|
+
Container for all procedural memory state.
|
|
308
|
+
|
|
309
|
+
This is the "L2/L3" memory that persists beyond the hot cache.
|
|
310
|
+
"""
|
|
311
|
+
|
|
312
|
+
session_id: str
|
|
313
|
+
|
|
314
|
+
# L2: Session tool log (append-only trace)
|
|
315
|
+
tool_log: list[ToolLogEntry] = Field(default_factory=list)
|
|
316
|
+
next_call_id: int = 1
|
|
317
|
+
|
|
318
|
+
# L3: Per-tool patterns (aggregated knowledge)
|
|
319
|
+
tool_patterns: dict[str, ToolPattern] = Field(default_factory=dict)
|
|
320
|
+
|
|
321
|
+
# Fix relationships
|
|
322
|
+
fix_relations: list[ToolFixRelation] = Field(default_factory=list)
|
|
323
|
+
|
|
324
|
+
# Metadata
|
|
325
|
+
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
326
|
+
updated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
327
|
+
|
|
328
|
+
model_config = {"frozen": False}
|
|
329
|
+
|
|
330
|
+
def get_pattern(self, tool_name: str) -> ToolPattern:
|
|
331
|
+
"""Get or create pattern for a tool."""
|
|
332
|
+
if tool_name not in self.tool_patterns:
|
|
333
|
+
self.tool_patterns[tool_name] = ToolPattern(tool_name=tool_name)
|
|
334
|
+
return self.tool_patterns[tool_name]
|
|
335
|
+
|
|
336
|
+
def allocate_call_id(self) -> str:
|
|
337
|
+
"""Allocate next call ID."""
|
|
338
|
+
call_id = f"call-{self.next_call_id:04d}"
|
|
339
|
+
self.next_call_id += 1
|
|
340
|
+
return call_id
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
# --- Helpers ---
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def _abbrev(value: Any, max_len: int = 20) -> str:
|
|
347
|
+
"""Abbreviate a value for display."""
|
|
348
|
+
s = str(value)
|
|
349
|
+
if len(s) > max_len:
|
|
350
|
+
return s[: max_len - 3] + "..."
|
|
351
|
+
return s
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def _describe_fix(delta: dict[str, Any]) -> str:
|
|
355
|
+
"""Generate human-readable description of a fix."""
|
|
356
|
+
parts = []
|
|
357
|
+
|
|
358
|
+
if "added" in delta:
|
|
359
|
+
added_keys = list(delta["added"].keys())
|
|
360
|
+
parts.append(f"add {', '.join(added_keys)}")
|
|
361
|
+
|
|
362
|
+
if "removed" in delta:
|
|
363
|
+
parts.append(f"remove {', '.join(delta['removed'])}")
|
|
364
|
+
|
|
365
|
+
if "changed" in delta:
|
|
366
|
+
changed = []
|
|
367
|
+
for k, v in delta["changed"].items():
|
|
368
|
+
changed.append(f"{k}: {_abbrev(v['from'])} -> {_abbrev(v['to'])}")
|
|
369
|
+
parts.append(f"change {'; '.join(changed)}")
|
|
370
|
+
|
|
371
|
+
return "; ".join(parts) if parts else "unknown changes"
|
|
@@ -11,24 +11,28 @@ from typing import Dict, Any
|
|
|
11
11
|
from chuk_tool_processor.registry import register_tool
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
@register_tool(
|
|
14
|
+
@register_tool(
|
|
15
|
+
name="calculator",
|
|
16
|
+
namespace="default",
|
|
17
|
+
description="Perform basic arithmetic operations",
|
|
18
|
+
)
|
|
15
19
|
class CalculatorTool:
|
|
16
20
|
"""Calculator tool for basic arithmetic."""
|
|
17
|
-
|
|
21
|
+
|
|
18
22
|
async def execute(self, operation: str, a: float, b: float) -> Dict[str, Any]:
|
|
19
23
|
"""
|
|
20
24
|
Perform a basic arithmetic operation.
|
|
21
|
-
|
|
25
|
+
|
|
22
26
|
Args:
|
|
23
27
|
operation: One of "add", "subtract", "multiply", "divide"
|
|
24
28
|
a: First operand
|
|
25
29
|
b: Second operand
|
|
26
|
-
|
|
30
|
+
|
|
27
31
|
Returns:
|
|
28
32
|
Dictionary with the result
|
|
29
33
|
"""
|
|
30
34
|
print(f"🧮 Calculator executing: {a} {operation} {b}")
|
|
31
|
-
|
|
35
|
+
|
|
32
36
|
if operation == "add":
|
|
33
37
|
result = a + b
|
|
34
38
|
elif operation == "subtract":
|
|
@@ -41,60 +45,84 @@ class CalculatorTool:
|
|
|
41
45
|
result = a / b
|
|
42
46
|
else:
|
|
43
47
|
raise ValueError(f"Unknown operation: {operation}")
|
|
44
|
-
|
|
48
|
+
|
|
45
49
|
return {
|
|
46
50
|
"operation": operation,
|
|
47
51
|
"a": a,
|
|
48
52
|
"b": b,
|
|
49
53
|
"result": result,
|
|
50
|
-
"timestamp": datetime.now().isoformat()
|
|
54
|
+
"timestamp": datetime.now().isoformat(),
|
|
51
55
|
}
|
|
52
56
|
|
|
53
57
|
|
|
54
|
-
@register_tool(
|
|
58
|
+
@register_tool(
|
|
59
|
+
name="weather",
|
|
60
|
+
namespace="default",
|
|
61
|
+
description="Get current weather information for a location",
|
|
62
|
+
)
|
|
55
63
|
class WeatherTool:
|
|
56
64
|
"""Weather tool that returns mock weather data."""
|
|
57
|
-
|
|
65
|
+
|
|
58
66
|
async def execute(self, location: str) -> Dict[str, Any]:
|
|
59
67
|
"""
|
|
60
68
|
Get weather information for a specific location.
|
|
61
|
-
|
|
69
|
+
|
|
62
70
|
Args:
|
|
63
71
|
location: The city or location to get weather for
|
|
64
|
-
|
|
72
|
+
|
|
65
73
|
Returns:
|
|
66
74
|
Dictionary with weather information
|
|
67
75
|
"""
|
|
68
76
|
print(f"🌤️ Weather tool executing for: {location}")
|
|
69
77
|
await asyncio.sleep(0.1) # Simulate API delay
|
|
70
|
-
|
|
78
|
+
|
|
71
79
|
# Mock realistic weather based on location
|
|
72
80
|
base_temp = 15 # Default moderate temperature
|
|
73
|
-
if any(
|
|
81
|
+
if any(
|
|
82
|
+
city in location.lower()
|
|
83
|
+
for city in ["miami", "phoenix", "dubai", "singapore"]
|
|
84
|
+
):
|
|
74
85
|
base_temp = 28
|
|
75
|
-
elif any(
|
|
86
|
+
elif any(
|
|
87
|
+
city in location.lower()
|
|
88
|
+
for city in ["moscow", "montreal", "oslo", "anchorage"]
|
|
89
|
+
):
|
|
76
90
|
base_temp = -5
|
|
77
|
-
elif any(
|
|
91
|
+
elif any(
|
|
92
|
+
city in location.lower() for city in ["london", "seattle", "vancouver"]
|
|
93
|
+
):
|
|
78
94
|
base_temp = 12
|
|
79
|
-
elif any(
|
|
95
|
+
elif any(
|
|
96
|
+
city in location.lower()
|
|
97
|
+
for city in ["tokyo", "new york", "paris", "berlin"]
|
|
98
|
+
):
|
|
80
99
|
base_temp = 18
|
|
81
|
-
|
|
100
|
+
|
|
82
101
|
# Add some randomness
|
|
83
102
|
temperature = base_temp + random.randint(-8, 12)
|
|
84
|
-
conditions = [
|
|
103
|
+
conditions = [
|
|
104
|
+
"Sunny",
|
|
105
|
+
"Partly Cloudy",
|
|
106
|
+
"Cloudy",
|
|
107
|
+
"Light Rain",
|
|
108
|
+
"Heavy Rain",
|
|
109
|
+
"Snow",
|
|
110
|
+
"Thunderstorm",
|
|
111
|
+
"Foggy",
|
|
112
|
+
]
|
|
85
113
|
condition = random.choice(conditions)
|
|
86
114
|
humidity = random.randint(35, 85)
|
|
87
115
|
wind_speed = random.uniform(2.0, 25.0)
|
|
88
116
|
feels_like = temperature + random.randint(-3, 3)
|
|
89
|
-
|
|
117
|
+
|
|
90
118
|
# Adjust conditions based on temperature
|
|
91
119
|
if temperature < 0:
|
|
92
120
|
condition = random.choice(["Snow", "Cloudy", "Partly Cloudy"])
|
|
93
121
|
elif temperature > 30:
|
|
94
122
|
condition = random.choice(["Sunny", "Partly Cloudy", "Hot"])
|
|
95
|
-
|
|
123
|
+
|
|
96
124
|
description = f"Current weather in {location} is {condition.lower()} with temperature {temperature}°C"
|
|
97
|
-
|
|
125
|
+
|
|
98
126
|
return {
|
|
99
127
|
"location": location,
|
|
100
128
|
"temperature": float(temperature),
|
|
@@ -103,62 +131,65 @@ class WeatherTool:
|
|
|
103
131
|
"wind_speed": round(wind_speed, 1),
|
|
104
132
|
"description": description,
|
|
105
133
|
"feels_like": float(feels_like),
|
|
106
|
-
"timestamp": datetime.now().isoformat()
|
|
134
|
+
"timestamp": datetime.now().isoformat(),
|
|
107
135
|
}
|
|
108
136
|
|
|
109
137
|
|
|
110
|
-
@register_tool(
|
|
138
|
+
@register_tool(
|
|
139
|
+
name="search",
|
|
140
|
+
namespace="default",
|
|
141
|
+
description="Search for information on the internet",
|
|
142
|
+
)
|
|
111
143
|
class SearchTool:
|
|
112
144
|
"""Search tool that returns mock search results."""
|
|
113
|
-
|
|
145
|
+
|
|
114
146
|
async def execute(self, query: str, max_results: int = 3) -> Dict[str, Any]:
|
|
115
147
|
"""
|
|
116
148
|
Search for information on the internet.
|
|
117
|
-
|
|
149
|
+
|
|
118
150
|
Args:
|
|
119
151
|
query: Search query
|
|
120
152
|
max_results: Maximum number of results to return
|
|
121
|
-
|
|
153
|
+
|
|
122
154
|
Returns:
|
|
123
155
|
Dictionary with search results
|
|
124
156
|
"""
|
|
125
157
|
print(f"🔍 Search tool executing for: {query}")
|
|
126
158
|
await asyncio.sleep(0.2) # Simulate API delay
|
|
127
|
-
|
|
128
|
-
results = []
|
|
159
|
+
|
|
129
160
|
query_lower = query.lower()
|
|
130
|
-
|
|
161
|
+
|
|
131
162
|
# Generate contextually relevant mock results based on query
|
|
132
163
|
if "climate" in query_lower or "environment" in query_lower:
|
|
133
164
|
result_templates = [
|
|
134
165
|
{
|
|
135
166
|
"title": "Climate Change Adaptation Strategies - IPCC Report",
|
|
136
167
|
"url": "https://www.ipcc.ch/adaptation-strategies",
|
|
137
|
-
"snippet": "Comprehensive guide to climate change adaptation strategies for communities, businesses, and governments. Includes resilience planning and risk assessment."
|
|
168
|
+
"snippet": "Comprehensive guide to climate change adaptation strategies for communities, businesses, and governments. Includes resilience planning and risk assessment.",
|
|
138
169
|
},
|
|
139
170
|
{
|
|
140
|
-
"title": "Environmental Adaptation Solutions | Climate.gov",
|
|
171
|
+
"title": "Environmental Adaptation Solutions | Climate.gov",
|
|
141
172
|
"url": "https://www.climate.gov/adaptation-solutions",
|
|
142
|
-
"snippet": "Evidence-based climate adaptation solutions including infrastructure improvements, ecosystem restoration, and community planning approaches."
|
|
173
|
+
"snippet": "Evidence-based climate adaptation solutions including infrastructure improvements, ecosystem restoration, and community planning approaches.",
|
|
143
174
|
},
|
|
144
175
|
{
|
|
145
176
|
"title": "Building Climate Resilience: A Practical Guide",
|
|
146
|
-
"url": "https://www.resilience.org/climate-guide",
|
|
147
|
-
"snippet": "Practical steps for building climate resilience in your community. Covers early warning systems, green infrastructure, and adaptation planning."
|
|
148
|
-
}
|
|
177
|
+
"url": "https://www.resilience.org/climate-guide",
|
|
178
|
+
"snippet": "Practical steps for building climate resilience in your community. Covers early warning systems, green infrastructure, and adaptation planning.",
|
|
179
|
+
},
|
|
149
180
|
]
|
|
150
181
|
elif "weather" in query_lower:
|
|
151
182
|
result_templates = [
|
|
152
183
|
{
|
|
153
184
|
"title": "Weather Forecast and Current Conditions",
|
|
154
185
|
"url": "https://weather.com/forecast",
|
|
155
|
-
"snippet": "Get accurate weather forecasts, current conditions, and severe weather alerts for your location."
|
|
186
|
+
"snippet": "Get accurate weather forecasts, current conditions, and severe weather alerts for your location.",
|
|
156
187
|
},
|
|
157
188
|
{
|
|
158
189
|
"title": "Climate and Weather Patterns Explained",
|
|
159
190
|
"url": "https://www.weatherpatterns.org",
|
|
160
|
-
"snippet": "Understanding weather patterns, climate systems, and meteorological phenomena that affect daily weather."
|
|
161
|
-
}
|
|
191
|
+
"snippet": "Understanding weather patterns, climate systems, and meteorological phenomena that affect daily weather.",
|
|
192
|
+
},
|
|
162
193
|
]
|
|
163
194
|
else:
|
|
164
195
|
# Generic results for other queries
|
|
@@ -166,29 +197,31 @@ class SearchTool:
|
|
|
166
197
|
{
|
|
167
198
|
"title": f"Everything You Need to Know About {query.title()}",
|
|
168
199
|
"url": f"https://encyclopedia.com/{query.lower().replace(' ', '-')}",
|
|
169
|
-
"snippet": f"Comprehensive information and resources about {query}. Expert insights, latest research, and practical applications."
|
|
200
|
+
"snippet": f"Comprehensive information and resources about {query}. Expert insights, latest research, and practical applications.",
|
|
170
201
|
},
|
|
171
202
|
{
|
|
172
203
|
"title": f"{query.title()} - Latest News and Updates",
|
|
173
204
|
"url": f"https://news.example.com/{query.lower().replace(' ', '-')}",
|
|
174
|
-
"snippet": f"Stay up to date with the latest news, trends, and developments related to {query}."
|
|
205
|
+
"snippet": f"Stay up to date with the latest news, trends, and developments related to {query}.",
|
|
175
206
|
},
|
|
176
207
|
{
|
|
177
208
|
"title": f"Guide to {query.title()} - Best Practices",
|
|
178
209
|
"url": f"https://guides.com/{query.lower().replace(' ', '-')}",
|
|
179
|
-
"snippet": f"Expert guide covering best practices, tips, and strategies for {query}. Includes real-world examples and case studies."
|
|
180
|
-
}
|
|
210
|
+
"snippet": f"Expert guide covering best practices, tips, and strategies for {query}. Includes real-world examples and case studies.",
|
|
211
|
+
},
|
|
181
212
|
]
|
|
182
|
-
|
|
213
|
+
|
|
183
214
|
# Select results up to max_results
|
|
184
215
|
selected_results = result_templates[:max_results]
|
|
185
|
-
|
|
216
|
+
|
|
186
217
|
return {
|
|
187
218
|
"query": query,
|
|
188
219
|
"results_count": len(selected_results),
|
|
189
220
|
"results": selected_results,
|
|
190
|
-
"timestamp": datetime.now().isoformat()
|
|
221
|
+
"timestamp": datetime.now().isoformat(),
|
|
191
222
|
}
|
|
192
223
|
|
|
193
224
|
|
|
194
|
-
print(
|
|
225
|
+
print(
|
|
226
|
+
"✅ sample_tools.py: 3 tools defined with @register_tool decorator (corrected version)"
|
|
227
|
+
)
|