Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9d7ce399c8 | ||
|
|
8e38a568ef | ||
|
|
40663511b4 | ||
|
|
8f008c7fd2 | ||
|
|
d207594e3c | ||
|
|
523c5c43a0 | ||
|
|
822cdc9bf4 | ||
|
|
214dc4e725 |
+7
-2
@@ -21,13 +21,18 @@ SEARXNG_TIMEOUT=30
|
|||||||
# Redis Configuration
|
# Redis Configuration
|
||||||
REDIS_HOST=redis-shared
|
REDIS_HOST=redis-shared
|
||||||
REDIS_PORT=6379
|
REDIS_PORT=6379
|
||||||
REDIS_DB=1
|
REDIS_MEMORY_DB=1
|
||||||
|
REDIS_BENCHMARK_DB=6
|
||||||
REDIS_TIMEOUT=5
|
REDIS_TIMEOUT=5
|
||||||
|
|
||||||
|
# Qdrant Configuraton
|
||||||
|
QDRANT_HOST=qdrant
|
||||||
|
QDRANT_PORT=6333
|
||||||
|
|
||||||
# Logging
|
# Logging
|
||||||
LOG_LEVEL=INFO
|
LOG_LEVEL=INFO
|
||||||
ENABLE_BENCHMARKS=true
|
ENABLE_BENCHMARKS=true
|
||||||
# Note: Log format is auto-selected based on ENVIRONMENT (console for dev, json for production)
|
# Note: Log format is auto-selected based on ENVIRONMENT (console for dev, json for production)
|
||||||
|
|
||||||
# CORS (comma-separated list)
|
# CORS (comma-separated list)
|
||||||
CORS_ORIGINS=*
|
CORS_ORIGINS=["*"]
|
||||||
|
|||||||
@@ -13,16 +13,23 @@ jobs:
|
|||||||
- name: Login to Gitea Registry
|
- name: Login to Gitea Registry
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: git.schweitz.net
|
registry: git.schweitz.internal
|
||||||
username: ${{ secrets.REGISTRY_USER }}
|
username: ${{ secrets.REGISTRY_USER }}
|
||||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: true
|
push: true
|
||||||
provenance: false
|
provenance: false
|
||||||
|
sbom: false
|
||||||
tags: |
|
tags: |
|
||||||
git.schweitz.net/jpmschweitzer/tatlock:latest
|
git.schweitz.internal/jpmschweitzer/tatlock:latest
|
||||||
git.schweitz.net/jpmschweitzer/tatlock:${{ github.ref_name }}
|
git.schweitz.internal/jpmschweitzer/tatlock:${{ github.ref_name }}
|
||||||
|
|
||||||
|
- name: Trigger Watchtower update
|
||||||
|
if: success()
|
||||||
|
run: |
|
||||||
|
curl -sf -H "Authorization: Bearer ${{ secrets.WATCHTOWER_TOKEN }}" \
|
||||||
|
http://watchtower:8080/v1/update
|
||||||
|
|||||||
@@ -7,6 +7,48 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [1.3.2] - 2025-12-14
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **Memory**: Fix biographer tool type hints for Ollama compatibility (remove `| None` union types)
|
||||||
|
|
||||||
|
## [1.3.1] - 2025-12-14
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **Memory**: Add biographer to delegation wrappers (was returning raw tools causing Ollama error)
|
||||||
|
- **Config**: Add Qdrant host/port to .env.example
|
||||||
|
|
||||||
|
## [1.3.0] - 2025-12-14
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **Memory**: Update Qdrant client to use `query_points` API (qdrant-client >= 1.10)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **Config**: Rename `REDIS_DB` to `REDIS_BENCHMARK_DB` for clarity
|
||||||
|
- **Config**: Update Redis defaults to match stack allocation (benchmark=6, memory=1)
|
||||||
|
|
||||||
|
## [1.2.5] - 2025-12-14
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **Dependencies**: Add missing `pydantic-settings` (not included in pydantic-ai-slim)
|
||||||
|
|
||||||
|
## [1.2.4] - 2025-12-14
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **CI**: Trigger Watchtower update after successful image push
|
||||||
|
|
||||||
|
## [1.2.3] - 2025-12-14
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **CI**: Upgrade to build-push-action@v6, disable provenance and sbom for Gitea registry
|
||||||
|
|
||||||
## [1.2.2] - 2025-12-13
|
## [1.2.2] - 2025-12-13
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@@ -1,72 +0,0 @@
|
|||||||
# Dependency Slimming: pydantic-ai → pydantic-ai-slim
|
|
||||||
|
|
||||||
**Date**: 2025-12-13
|
|
||||||
**Version**: Post v1.2.0
|
|
||||||
|
|
||||||
## Change
|
|
||||||
|
|
||||||
Switched from `pydantic-ai` to `pydantic-ai-slim[openai]` to reduce container image size.
|
|
||||||
|
|
||||||
### Before
|
|
||||||
```
|
|
||||||
pydantic-ai>=1.27,<1.28
|
|
||||||
```
|
|
||||||
|
|
||||||
This installs SDKs for ALL LLM providers:
|
|
||||||
- anthropic
|
|
||||||
- boto3 + botocore (AWS Bedrock)
|
|
||||||
- cohere
|
|
||||||
- google-genai + google-auth
|
|
||||||
- groq
|
|
||||||
- huggingface-hub
|
|
||||||
|
|
||||||
Total packages: ~158
|
|
||||||
|
|
||||||
### After
|
|
||||||
```
|
|
||||||
pydantic-ai-slim[openai]>=1.27,<1.28
|
|
||||||
```
|
|
||||||
|
|
||||||
Only installs the OpenAI-compatible SDK. Ollama works through this interface.
|
|
||||||
|
|
||||||
Expected packages: ~80-90 (significant reduction)
|
|
||||||
|
|
||||||
## Why This Works
|
|
||||||
|
|
||||||
Tatlock uses Ollama exclusively, which implements the OpenAI-compatible API. The code uses:
|
|
||||||
```python
|
|
||||||
from pydantic_ai.models.openai import OpenAIChatModel
|
|
||||||
from pydantic_ai.providers.ollama import OllamaProvider
|
|
||||||
|
|
||||||
model = OpenAIChatModel(
|
|
||||||
model_name=config.OLLAMA_DEFAULT_MODEL,
|
|
||||||
provider=OllamaProvider(base_url=f"{config.OLLAMA_HOST}/v1")
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
This pattern only requires the `openai` extra, not the full pydantic-ai package.
|
|
||||||
|
|
||||||
## Rollback Instructions
|
|
||||||
|
|
||||||
If this change breaks things:
|
|
||||||
|
|
||||||
1. Revert requirements.txt:
|
|
||||||
```diff
|
|
||||||
- pydantic-ai-slim[openai]>=1.27,<1.28
|
|
||||||
+ pydantic-ai>=1.27,<1.28
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Reinstall dependencies:
|
|
||||||
```bash
|
|
||||||
pip install -r requirements.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Delete this file once confirmed stable.
|
|
||||||
|
|
||||||
## Testing Checklist
|
|
||||||
|
|
||||||
- [ ] Unit tests pass
|
|
||||||
- [ ] Integration tests pass (with Ollama running)
|
|
||||||
- [ ] Wakeup script e2e test passes
|
|
||||||
- [ ] Container builds successfully
|
|
||||||
- [ ] Container runs correctly
|
|
||||||
@@ -432,7 +432,7 @@ For LLM agent development guidelines and architectural decisions, see [AGENTS.md
|
|||||||
|
|
||||||
## Version
|
## Version
|
||||||
|
|
||||||
Current version: **1.2.2** - CI fix
|
Current version: **1.3.2** - Biographer tool type hints fix
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "tatlock"
|
name = "tatlock"
|
||||||
version = "1.2.2"
|
version = "1.3.2"
|
||||||
description = "OpenAI-compatible API with Ollama backend"
|
description = "OpenAI-compatible API with Ollama backend"
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
dependencies = []
|
dependencies = []
|
||||||
|
|||||||
@@ -14,6 +14,11 @@ uvicorn[standard]>=0.38,<0.39
|
|||||||
# Latest: 2.12.4 (Nov 5, 2025) - No known CVEs
|
# Latest: 2.12.4 (Nov 5, 2025) - No known CVEs
|
||||||
pydantic>=2.11,<2.13
|
pydantic>=2.11,<2.13
|
||||||
|
|
||||||
|
# Pydantic settings for configuration management
|
||||||
|
# Required explicitly since pydantic-ai-slim doesn't include it
|
||||||
|
# Latest: 2.12.0 (Dec 2025) - No known CVEs
|
||||||
|
pydantic-settings>=2.12,<2.13
|
||||||
|
|
||||||
# AI/LLM integration
|
# AI/LLM integration
|
||||||
# PydanticAI: Agent framework for using Pydantic with LLMs
|
# PydanticAI: Agent framework for using Pydantic with LLMs
|
||||||
# Using slim version with only openai extra (Ollama uses OpenAI-compatible API)
|
# Using slim version with only openai extra (Ollama uses OpenAI-compatible API)
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ logger = get_logger(__name__)
|
|||||||
|
|
||||||
async def recall_semantic(
|
async def recall_semantic(
|
||||||
query: str,
|
query: str,
|
||||||
memory_type: str | None = None,
|
memory_type: str = "",
|
||||||
limit: int = 5,
|
limit: int = 5,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""
|
"""
|
||||||
@@ -64,7 +64,7 @@ async def recall_semantic(
|
|||||||
user=user,
|
user=user,
|
||||||
query_vector=query_vector,
|
query_vector=query_vector,
|
||||||
limit=limit,
|
limit=limit,
|
||||||
memory_type=memory_type,
|
memory_type=memory_type if memory_type else None,
|
||||||
)
|
)
|
||||||
|
|
||||||
if not results:
|
if not results:
|
||||||
@@ -111,7 +111,6 @@ async def recall_semantic(
|
|||||||
async def store_insight(
|
async def store_insight(
|
||||||
key: str,
|
key: str,
|
||||||
value: str,
|
value: str,
|
||||||
keywords: list[str] | None = None,
|
|
||||||
importance: float = 0.5,
|
importance: float = 0.5,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""
|
"""
|
||||||
@@ -128,7 +127,6 @@ async def store_insight(
|
|||||||
Args:
|
Args:
|
||||||
key: Short identifier for the memory (e.g., "car", "employer", "pet")
|
key: Short identifier for the memory (e.g., "car", "employer", "pet")
|
||||||
value: The actual information to remember
|
value: The actual information to remember
|
||||||
keywords: Optional keywords for better search (auto-extracted if not provided)
|
|
||||||
importance: How important is this? 0.0 (trivial) to 1.0 (critical)
|
importance: How important is this? 0.0 (trivial) to 1.0 (critical)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -137,15 +135,12 @@ async def store_insight(
|
|||||||
Examples:
|
Examples:
|
||||||
store_insight("car", "User drives a Tesla Model 3")
|
store_insight("car", "User drives a Tesla Model 3")
|
||||||
store_insight("employer", "Works at Acme Corp as software engineer", importance=0.8)
|
store_insight("employer", "Works at Acme Corp as software engineer", importance=0.8)
|
||||||
store_insight("coffee", "Prefers oat milk lattes", keywords=["coffee", "drink", "preference"])
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# Auto-generate keywords if not provided
|
# Auto-generate keywords from key and value
|
||||||
if not keywords:
|
keywords = [key]
|
||||||
keywords = [key]
|
words = value.lower().split()
|
||||||
# Extract simple keywords from value
|
keywords.extend([w for w in words if len(w) > 4][:5])
|
||||||
words = value.lower().split()
|
|
||||||
keywords.extend([w for w in words if len(w) > 4][:5])
|
|
||||||
|
|
||||||
success = await memory_service.store_fact(
|
success = await memory_service.store_fact(
|
||||||
key=key,
|
key=key,
|
||||||
|
|||||||
+5
-5
@@ -101,9 +101,9 @@ class Config(BaseSettings):
|
|||||||
default=6379,
|
default=6379,
|
||||||
description="Redis server port"
|
description="Redis server port"
|
||||||
)
|
)
|
||||||
REDIS_DB: int = Field(
|
REDIS_BENCHMARK_DB: int = Field(
|
||||||
default=1,
|
default=6,
|
||||||
description="Redis database number"
|
description="Redis database number for benchmarks"
|
||||||
)
|
)
|
||||||
REDIS_TIMEOUT: int = Field(
|
REDIS_TIMEOUT: int = Field(
|
||||||
default=5,
|
default=5,
|
||||||
@@ -146,7 +146,7 @@ class Config(BaseSettings):
|
|||||||
|
|
||||||
# Redis Memory Database (separate from benchmarks)
|
# Redis Memory Database (separate from benchmarks)
|
||||||
REDIS_MEMORY_DB: int = Field(
|
REDIS_MEMORY_DB: int = Field(
|
||||||
default=2,
|
default=1,
|
||||||
description="Redis database number for memory cache"
|
description="Redis database number for memory cache"
|
||||||
)
|
)
|
||||||
REDIS_MEMORY_TTL_HOURS: int = Field(
|
REDIS_MEMORY_TTL_HOURS: int = Field(
|
||||||
@@ -170,7 +170,7 @@ class Config(BaseSettings):
|
|||||||
@property
|
@property
|
||||||
def redis_url(self) -> str:
|
def redis_url(self) -> str:
|
||||||
"""Construct Redis connection URL for benchmarks."""
|
"""Construct Redis connection URL for benchmarks."""
|
||||||
return f"redis://{self.REDIS_HOST}:{self.REDIS_PORT}/{self.REDIS_DB}"
|
return f"redis://{self.REDIS_HOST}:{self.REDIS_PORT}/{self.REDIS_BENCHMARK_DB}"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def redis_memory_url(self) -> str:
|
def redis_memory_url(self) -> str:
|
||||||
|
|||||||
@@ -223,12 +223,12 @@ class HouseholdRegistry:
|
|||||||
>>> # Returns: [delegate_to_librarian, calculate, datetime, ...]
|
>>> # Returns: [delegate_to_librarian, calculate, datetime, ...]
|
||||||
>>> # Instead of: [hybrid_search, search_wiki, create_wiki_page, ... (16 tools)]
|
>>> # Instead of: [hybrid_search, search_wiki, create_wiki_page, ... (16 tools)]
|
||||||
"""
|
"""
|
||||||
from src.agents.delegation import delegate_to_librarian
|
from src.agents.delegation import delegate_to_librarian, delegate_to_biographer
|
||||||
|
|
||||||
# Map of expert names to their delegation wrappers
|
# Map of expert names to their delegation wrappers
|
||||||
delegation_wrappers = {
|
delegation_wrappers = {
|
||||||
"librarian": delegate_to_librarian,
|
"librarian": delegate_to_librarian,
|
||||||
# Future: "memory": delegate_to_memory,
|
"biographer": delegate_to_biographer,
|
||||||
# Future: "home_automation": delegate_to_home_automation,
|
# Future: "home_automation": delegate_to_home_automation,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -232,14 +232,14 @@ class MemoryQdrantClient:
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
# Search
|
# Search using new Query API (qdrant-client >= 1.10)
|
||||||
results = self._client.search(
|
results = self._client.query_points(
|
||||||
collection_name=collection_name,
|
collection_name=collection_name,
|
||||||
query_vector=query_vector,
|
query=query_vector,
|
||||||
limit=limit,
|
limit=limit,
|
||||||
query_filter=query_filter,
|
query_filter=query_filter,
|
||||||
score_threshold=score_threshold,
|
score_threshold=score_threshold,
|
||||||
)
|
).points
|
||||||
|
|
||||||
# Format results
|
# Format results
|
||||||
memories = []
|
memories = []
|
||||||
|
|||||||
Reference in New Issue
Block a user