selectools 0.16.0__tar.gz → 0.16.2__tar.gz
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.
- {selectools-0.16.0/src/selectools.egg-info → selectools-0.16.2}/PKG-INFO +3 -3
- {selectools-0.16.0 → selectools-0.16.2}/README.md +2 -2
- {selectools-0.16.0 → selectools-0.16.2}/pyproject.toml +1 -1
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/__init__.py +1 -1
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/agent/core.py +12 -2
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/entity_memory.py +31 -25
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/knowledge.py +12 -9
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/knowledge_graph.py +5 -3
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/memory.py +1 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/sessions.py +3 -3
- {selectools-0.16.0 → selectools-0.16.2/src/selectools.egg-info}/PKG-INFO +3 -3
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools.egg-info/SOURCES.txt +6 -0
- selectools-0.16.2/tests/test_consolidation_regression.py +414 -0
- selectools-0.16.2/tests/test_memory_async.py +238 -0
- selectools-0.16.2/tests/test_memory_boundary.py +127 -0
- selectools-0.16.2/tests/test_memory_integration.py +271 -0
- selectools-0.16.2/tests/test_sessions_edge_cases.py +245 -0
- selectools-0.16.2/tests/test_sessions_redis.py +354 -0
- {selectools-0.16.0 → selectools-0.16.2}/LICENSE +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/setup.cfg +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/agent/__init__.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/agent/config.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/analytics.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/audit.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/cache.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/cache_redis.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/cli.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/coherence.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/embeddings/__init__.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/embeddings/anthropic.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/embeddings/cohere.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/embeddings/gemini.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/embeddings/openai.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/embeddings/provider.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/env.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/exceptions.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/guardrails/__init__.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/guardrails/base.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/guardrails/format.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/guardrails/length.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/guardrails/pii.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/guardrails/pipeline.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/guardrails/topic.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/guardrails/toxicity.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/models.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/observer.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/parser.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/policy.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/pricing.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/prompt.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/providers/__init__.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/providers/anthropic_provider.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/providers/base.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/providers/fallback.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/providers/gemini_provider.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/providers/ollama_provider.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/providers/openai_provider.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/providers/stubs.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/rag/__init__.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/rag/bm25.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/rag/chunking.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/rag/hybrid.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/rag/loaders.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/rag/reranker.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/rag/stores/__init__.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/rag/stores/chroma.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/rag/stores/memory.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/rag/stores/pinecone.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/rag/stores/sqlite.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/rag/tools.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/rag/vector_store.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/security.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/structured.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/toolbox/__init__.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/toolbox/data_tools.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/toolbox/datetime_tools.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/toolbox/file_tools.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/toolbox/memory_tools.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/toolbox/text_tools.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/toolbox/web_tools.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/tools/__init__.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/tools/base.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/tools/decorators.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/tools/loader.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/tools/registry.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/trace.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/types.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools/usage.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools.egg-info/dependency_links.txt +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools.egg-info/entry_points.txt +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools.egg-info/requires.txt +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/src/selectools.egg-info/top_level.txt +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/tests/test_audit.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/tests/test_cache.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/tests/test_cache_redis.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/tests/test_cli.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/tests/test_coherence.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/tests/test_entity_memory.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/tests/test_env.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/tests/test_guardrails.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/tests/test_knowledge.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/tests/test_knowledge_graph.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/tests/test_memory.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/tests/test_parser.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/tests/test_policy.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/tests/test_prompt.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/tests/test_routing_mode.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/tests/test_security.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/tests/test_sessions.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/tests/test_structured.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/tests/test_summarize_on_trim.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/tests/test_trace.py +0 -0
- {selectools-0.16.0 → selectools-0.16.2}/tests/test_v016_regression.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: selectools
|
|
3
|
-
Version: 0.16.
|
|
3
|
+
Version: 0.16.2
|
|
4
4
|
Summary: Production-ready AI agents with tool calling, structured output, execution traces, and RAG. Provider-agnostic (OpenAI, Anthropic, Gemini, Ollama) with fallback chains, batch processing, tool policies, streaming, caching, and cost tracking.
|
|
5
5
|
Author-email: John <johnnichev@gmail.com>
|
|
6
6
|
License-Expression: LGPL-3.0-or-later
|
|
@@ -65,7 +65,7 @@ Dynamic: license-file
|
|
|
65
65
|
- **Entity Memory** — Auto-extract named entities (person, org, project) across turns with LRU-pruned registry and system prompt injection.
|
|
66
66
|
- **Knowledge Graph** — Relationship triple extraction with in-memory and SQLite storage. Query-relevant triples auto-injected into prompts.
|
|
67
67
|
- **Cross-Session Knowledge** — Daily logs + persistent facts with auto-registered `remember` tool. Give your agent durable memory across conversations.
|
|
68
|
-
- **182 new tests** (total:
|
|
68
|
+
- **182 new tests** (total: 1421 with v0.16.1 consolidation)
|
|
69
69
|
|
|
70
70
|
> Full changelog: [CHANGELOG.md](https://github.com/johnnichev/selectools/blob/main/CHANGELOG.md)
|
|
71
71
|
|
|
@@ -135,7 +135,7 @@ Dynamic: license-file
|
|
|
135
135
|
- **Cross-Session Knowledge**: Daily logs + persistent memory with `remember` tool
|
|
136
136
|
- **37 Examples**: RAG, hybrid search, streaming, structured output, traces, batch, policy, observer, guardrails, audit, sessions, entity memory, knowledge graph, and more
|
|
137
137
|
- **AgentObserver Protocol**: 19 lifecycle events with `run_id` correlation, `LoggingObserver`, OTel export
|
|
138
|
-
- **
|
|
138
|
+
- **1421 Tests**: Unit, integration, regression, and E2E with real API calls
|
|
139
139
|
|
|
140
140
|
## Install
|
|
141
141
|
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
- **Entity Memory** — Auto-extract named entities (person, org, project) across turns with LRU-pruned registry and system prompt injection.
|
|
17
17
|
- **Knowledge Graph** — Relationship triple extraction with in-memory and SQLite storage. Query-relevant triples auto-injected into prompts.
|
|
18
18
|
- **Cross-Session Knowledge** — Daily logs + persistent facts with auto-registered `remember` tool. Give your agent durable memory across conversations.
|
|
19
|
-
- **182 new tests** (total:
|
|
19
|
+
- **182 new tests** (total: 1421 with v0.16.1 consolidation)
|
|
20
20
|
|
|
21
21
|
> Full changelog: [CHANGELOG.md](https://github.com/johnnichev/selectools/blob/main/CHANGELOG.md)
|
|
22
22
|
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
- **Cross-Session Knowledge**: Daily logs + persistent memory with `remember` tool
|
|
87
87
|
- **37 Examples**: RAG, hybrid search, streaming, structured output, traces, batch, policy, observer, guardrails, audit, sessions, entity memory, knowledge graph, and more
|
|
88
88
|
- **AgentObserver Protocol**: 19 lifecycle events with `run_id` correlation, `LoggingObserver`, OTel export
|
|
89
|
-
- **
|
|
89
|
+
- **1421 Tests**: Unit, integration, regression, and E2E with real API calls
|
|
90
90
|
|
|
91
91
|
## Install
|
|
92
92
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "selectools"
|
|
7
|
-
version = "0.16.
|
|
7
|
+
version = "0.16.2"
|
|
8
8
|
description = "Production-ready AI agents with tool calling, structured output, execution traces, and RAG. Provider-agnostic (OpenAI, Anthropic, Gemini, Ollama) with fallback chains, batch processing, tool policies, streaming, caching, and cost tracking."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -1976,6 +1976,7 @@ class Agent:
|
|
|
1976
1976
|
_history_checkpoint = len(self._history)
|
|
1977
1977
|
iteration = 0
|
|
1978
1978
|
|
|
1979
|
+
original_system_prompt = self._system_prompt
|
|
1979
1980
|
self._wire_fallback_observer(run_id)
|
|
1980
1981
|
self._notify_observers("on_run_start", run_id, messages, self._system_prompt)
|
|
1981
1982
|
|
|
@@ -2112,8 +2113,8 @@ class Agent:
|
|
|
2112
2113
|
usage=copy.copy(self.usage),
|
|
2113
2114
|
)
|
|
2114
2115
|
self._notify_observers("on_run_end", run_id, _result)
|
|
2115
|
-
yield _result
|
|
2116
2116
|
self._call_hook("on_agent_end", final_response, self.usage)
|
|
2117
|
+
yield _result
|
|
2117
2118
|
return
|
|
2118
2119
|
|
|
2119
2120
|
if self.config.routing_only:
|
|
@@ -2260,9 +2261,9 @@ class Agent:
|
|
|
2260
2261
|
usage=copy.copy(self.usage),
|
|
2261
2262
|
)
|
|
2262
2263
|
self._notify_observers("on_run_end", run_id, _result)
|
|
2263
|
-
yield _result
|
|
2264
2264
|
self._memory_add(final_msg, run_id)
|
|
2265
2265
|
self._call_hook("on_agent_end", final_msg, self.usage)
|
|
2266
|
+
yield _result
|
|
2266
2267
|
return
|
|
2267
2268
|
except Exception as exc:
|
|
2268
2269
|
if not self.memory:
|
|
@@ -2274,6 +2275,7 @@ class Agent:
|
|
|
2274
2275
|
raise
|
|
2275
2276
|
finally:
|
|
2276
2277
|
self._unwire_fallback_observer()
|
|
2278
|
+
self._system_prompt = original_system_prompt
|
|
2277
2279
|
|
|
2278
2280
|
async def arun(
|
|
2279
2281
|
self,
|
|
@@ -2637,6 +2639,14 @@ class Agent:
|
|
|
2637
2639
|
cost=0.0,
|
|
2638
2640
|
chunk_count=chunk_counter["count"],
|
|
2639
2641
|
)
|
|
2642
|
+
if self.usage.iterations:
|
|
2643
|
+
self.usage.tool_usage[tool.name] = (
|
|
2644
|
+
self.usage.tool_usage.get(tool.name, 0) + 1
|
|
2645
|
+
)
|
|
2646
|
+
self.usage.tool_tokens[tool.name] = (
|
|
2647
|
+
self.usage.tool_tokens.get(tool.name, 0)
|
|
2648
|
+
+ self.usage.iterations[-1].total_tokens
|
|
2649
|
+
)
|
|
2640
2650
|
except Exception as exc:
|
|
2641
2651
|
duration = time.time() - start_time
|
|
2642
2652
|
self._call_hook("on_tool_error", tool.name, exc, parameters)
|
|
@@ -8,6 +8,7 @@ extraction, deduplication, and LRU pruning.
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
10
|
import json
|
|
11
|
+
import threading
|
|
11
12
|
import time
|
|
12
13
|
from dataclasses import dataclass, field
|
|
13
14
|
from typing import Any, Dict, List, Optional
|
|
@@ -92,15 +93,17 @@ class EntityMemory:
|
|
|
92
93
|
self._max_entities = max_entities
|
|
93
94
|
self._relevance_window = relevance_window
|
|
94
95
|
self._entities: Dict[str, Entity] = {}
|
|
96
|
+
self._lock = threading.Lock()
|
|
95
97
|
|
|
96
98
|
@property
|
|
97
99
|
def entities(self) -> List[Entity]:
|
|
98
100
|
"""All tracked entities, sorted by most recently mentioned."""
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
101
|
+
with self._lock:
|
|
102
|
+
return sorted(
|
|
103
|
+
self._entities.values(),
|
|
104
|
+
key=lambda e: e.last_mentioned,
|
|
105
|
+
reverse=True,
|
|
106
|
+
)
|
|
104
107
|
|
|
105
108
|
def extract_entities(
|
|
106
109
|
self,
|
|
@@ -174,28 +177,31 @@ class EntityMemory:
|
|
|
174
177
|
|
|
175
178
|
Deduplicates by name (case-insensitive), updates mention counts
|
|
176
179
|
and attributes, and prunes if over ``max_entities``.
|
|
180
|
+
|
|
181
|
+
Thread-safe: protected by an internal lock.
|
|
177
182
|
"""
|
|
178
183
|
now = time.time()
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
184
|
+
with self._lock:
|
|
185
|
+
for entity in entities:
|
|
186
|
+
key = entity.name.lower()
|
|
187
|
+
if key in self._entities:
|
|
188
|
+
existing = self._entities[key]
|
|
189
|
+
existing.mention_count += 1
|
|
190
|
+
existing.last_mentioned = now
|
|
191
|
+
existing.attributes.update(entity.attributes)
|
|
192
|
+
else:
|
|
193
|
+
entity.last_mentioned = now
|
|
194
|
+
self._entities[key] = entity
|
|
195
|
+
|
|
196
|
+
# LRU prune: remove least recently mentioned
|
|
197
|
+
if len(self._entities) > self._max_entities:
|
|
198
|
+
sorted_keys = sorted(
|
|
199
|
+
self._entities.keys(),
|
|
200
|
+
key=lambda k: self._entities[k].last_mentioned,
|
|
201
|
+
)
|
|
202
|
+
excess = len(self._entities) - self._max_entities
|
|
203
|
+
for k in sorted_keys[:excess]:
|
|
204
|
+
del self._entities[k]
|
|
199
205
|
|
|
200
206
|
def build_context(self) -> str:
|
|
201
207
|
"""Build a context string for prompt injection.
|
|
@@ -9,6 +9,7 @@ for long-term facts.
|
|
|
9
9
|
from __future__ import annotations
|
|
10
10
|
|
|
11
11
|
import os
|
|
12
|
+
import threading
|
|
12
13
|
import time
|
|
13
14
|
from datetime import datetime, timedelta
|
|
14
15
|
from typing import Any, Dict, List, Optional
|
|
@@ -39,6 +40,7 @@ class KnowledgeMemory:
|
|
|
39
40
|
self._directory = directory
|
|
40
41
|
self._recent_days = recent_days
|
|
41
42
|
self._max_context_chars = max_context_chars
|
|
43
|
+
self._lock = threading.Lock()
|
|
42
44
|
os.makedirs(directory, exist_ok=True)
|
|
43
45
|
|
|
44
46
|
@property
|
|
@@ -66,17 +68,18 @@ class KnowledgeMemory:
|
|
|
66
68
|
timestamp = now.strftime("%Y-%m-%d %H:%M:%S")
|
|
67
69
|
entry = f"[{timestamp}] [{category}] {content}"
|
|
68
70
|
|
|
69
|
-
# Write to daily log
|
|
71
|
+
# Write to daily log (thread-safe)
|
|
70
72
|
today = now.strftime("%Y-%m-%d")
|
|
71
73
|
log_path = os.path.join(self._directory, f"{today}.log")
|
|
72
|
-
with
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
74
|
+
with self._lock:
|
|
75
|
+
with open(log_path, "a", encoding="utf-8") as f:
|
|
76
|
+
f.write(entry + "\n")
|
|
77
|
+
|
|
78
|
+
# Optionally write to persistent memory
|
|
79
|
+
if persistent:
|
|
80
|
+
mem_path = os.path.join(self._directory, "MEMORY.md")
|
|
81
|
+
with open(mem_path, "a", encoding="utf-8") as f:
|
|
82
|
+
f.write(f"- [{category}] {content}\n")
|
|
80
83
|
|
|
81
84
|
return f"Remembered: {content}"
|
|
82
85
|
|
|
@@ -133,7 +133,9 @@ class SQLiteTripleStore:
|
|
|
133
133
|
self._ensure_table()
|
|
134
134
|
|
|
135
135
|
def _connect(self) -> sqlite3.Connection:
|
|
136
|
-
|
|
136
|
+
conn = sqlite3.connect(self._db_path)
|
|
137
|
+
conn.execute("PRAGMA journal_mode=WAL")
|
|
138
|
+
return conn
|
|
137
139
|
|
|
138
140
|
def _ensure_table(self) -> None:
|
|
139
141
|
conn = self._connect()
|
|
@@ -249,7 +251,7 @@ class SQLiteTripleStore:
|
|
|
249
251
|
def count(self) -> int:
|
|
250
252
|
conn = self._connect()
|
|
251
253
|
try:
|
|
252
|
-
return conn.execute("SELECT COUNT(*) FROM triples").fetchone()[0]
|
|
254
|
+
return int(conn.execute("SELECT COUNT(*) FROM triples").fetchone()[0])
|
|
253
255
|
finally:
|
|
254
256
|
conn.close()
|
|
255
257
|
|
|
@@ -416,7 +418,7 @@ class KnowledgeGraphMemory:
|
|
|
416
418
|
keywords = [w for w in query.lower().split() if len(w) > 2]
|
|
417
419
|
if not keywords:
|
|
418
420
|
return []
|
|
419
|
-
results = self._store.query(keywords)
|
|
421
|
+
results: List[Triple] = self._store.query(keywords)
|
|
420
422
|
return results[: self._max_context_triples]
|
|
421
423
|
|
|
422
424
|
def build_context(self, query: str = "") -> str:
|
|
@@ -156,6 +156,7 @@ class ConversationMemory:
|
|
|
156
156
|
mem._messages = [Message.from_dict(m) for m in data.get("messages", [])]
|
|
157
157
|
mem._summary = data.get("summary")
|
|
158
158
|
mem._last_trimmed = []
|
|
159
|
+
mem._fix_tool_pair_boundary()
|
|
159
160
|
return mem
|
|
160
161
|
|
|
161
162
|
def _enforce_limits(self) -> None:
|
|
@@ -92,7 +92,7 @@ class JsonFileSessionStore:
|
|
|
92
92
|
def _is_expired(self, data: Dict[str, Any]) -> bool:
|
|
93
93
|
if self._default_ttl is None:
|
|
94
94
|
return False
|
|
95
|
-
updated_at = data.get("updated_at", data.get("created_at", 0))
|
|
95
|
+
updated_at: float = data.get("updated_at", data.get("created_at", 0))
|
|
96
96
|
return (time.time() - updated_at) > self._default_ttl
|
|
97
97
|
|
|
98
98
|
# -- public API --------------------------------------------------------
|
|
@@ -304,7 +304,7 @@ class SQLiteSessionStore:
|
|
|
304
304
|
try:
|
|
305
305
|
cursor = conn.execute("DELETE FROM sessions WHERE session_id = ?", (session_id,))
|
|
306
306
|
conn.commit()
|
|
307
|
-
return cursor.rowcount > 0
|
|
307
|
+
return int(cursor.rowcount) > 0
|
|
308
308
|
finally:
|
|
309
309
|
conn.close()
|
|
310
310
|
|
|
@@ -433,7 +433,7 @@ class RedisSessionStore:
|
|
|
433
433
|
key = self._key(session_id)
|
|
434
434
|
meta_key = self._meta_key(session_id)
|
|
435
435
|
removed = self._client.delete(key, meta_key)
|
|
436
|
-
return removed > 0
|
|
436
|
+
return int(removed) > 0
|
|
437
437
|
|
|
438
438
|
def exists(self, session_id: str) -> bool:
|
|
439
439
|
return bool(self._client.exists(self._key(session_id)))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: selectools
|
|
3
|
-
Version: 0.16.
|
|
3
|
+
Version: 0.16.2
|
|
4
4
|
Summary: Production-ready AI agents with tool calling, structured output, execution traces, and RAG. Provider-agnostic (OpenAI, Anthropic, Gemini, Ollama) with fallback chains, batch processing, tool policies, streaming, caching, and cost tracking.
|
|
5
5
|
Author-email: John <johnnichev@gmail.com>
|
|
6
6
|
License-Expression: LGPL-3.0-or-later
|
|
@@ -65,7 +65,7 @@ Dynamic: license-file
|
|
|
65
65
|
- **Entity Memory** — Auto-extract named entities (person, org, project) across turns with LRU-pruned registry and system prompt injection.
|
|
66
66
|
- **Knowledge Graph** — Relationship triple extraction with in-memory and SQLite storage. Query-relevant triples auto-injected into prompts.
|
|
67
67
|
- **Cross-Session Knowledge** — Daily logs + persistent facts with auto-registered `remember` tool. Give your agent durable memory across conversations.
|
|
68
|
-
- **182 new tests** (total:
|
|
68
|
+
- **182 new tests** (total: 1421 with v0.16.1 consolidation)
|
|
69
69
|
|
|
70
70
|
> Full changelog: [CHANGELOG.md](https://github.com/johnnichev/selectools/blob/main/CHANGELOG.md)
|
|
71
71
|
|
|
@@ -135,7 +135,7 @@ Dynamic: license-file
|
|
|
135
135
|
- **Cross-Session Knowledge**: Daily logs + persistent memory with `remember` tool
|
|
136
136
|
- **37 Examples**: RAG, hybrid search, streaming, structured output, traces, batch, policy, observer, guardrails, audit, sessions, entity memory, knowledge graph, and more
|
|
137
137
|
- **AgentObserver Protocol**: 19 lifecycle events with `run_id` correlation, `LoggingObserver`, OTel export
|
|
138
|
-
- **
|
|
138
|
+
- **1421 Tests**: Unit, integration, regression, and E2E with real API calls
|
|
139
139
|
|
|
140
140
|
## Install
|
|
141
141
|
|
|
@@ -87,18 +87,24 @@ tests/test_cache.py
|
|
|
87
87
|
tests/test_cache_redis.py
|
|
88
88
|
tests/test_cli.py
|
|
89
89
|
tests/test_coherence.py
|
|
90
|
+
tests/test_consolidation_regression.py
|
|
90
91
|
tests/test_entity_memory.py
|
|
91
92
|
tests/test_env.py
|
|
92
93
|
tests/test_guardrails.py
|
|
93
94
|
tests/test_knowledge.py
|
|
94
95
|
tests/test_knowledge_graph.py
|
|
95
96
|
tests/test_memory.py
|
|
97
|
+
tests/test_memory_async.py
|
|
98
|
+
tests/test_memory_boundary.py
|
|
99
|
+
tests/test_memory_integration.py
|
|
96
100
|
tests/test_parser.py
|
|
97
101
|
tests/test_policy.py
|
|
98
102
|
tests/test_prompt.py
|
|
99
103
|
tests/test_routing_mode.py
|
|
100
104
|
tests/test_security.py
|
|
101
105
|
tests/test_sessions.py
|
|
106
|
+
tests/test_sessions_edge_cases.py
|
|
107
|
+
tests/test_sessions_redis.py
|
|
102
108
|
tests/test_structured.py
|
|
103
109
|
tests/test_summarize_on_trim.py
|
|
104
110
|
tests/test_trace.py
|