add updates
This commit is contained in:
parent
c1aabbb7ee
commit
a57cc024c6
@ -16,7 +16,7 @@ Cognee is an AI memory framework that:
|
|||||||
- Extracts entities (people, places, concepts) from documents
|
- Extracts entities (people, places, concepts) from documents
|
||||||
- Builds a knowledge graph of relationships
|
- Builds a knowledge graph of relationships
|
||||||
- Enables semantic search with LLM-powered reasoning
|
- Enables semantic search with LLM-powered reasoning
|
||||||
- Supports multiple search modes (insights, chunks, summaries)
|
- Supports multiple search modes (GRAPH_COMPLETION, chunks, summaries)
|
||||||
|
|
||||||
Learn more at [docs.cognee.ai](https://docs.cognee.ai/).
|
Learn more at [docs.cognee.ai](https://docs.cognee.ai/).
|
||||||
|
|
||||||
@ -24,57 +24,18 @@ Learn more at [docs.cognee.ai](https://docs.cognee.ai/).
|
|||||||
|
|
||||||
### Option 1: Local Docker (Recommended)
|
### Option 1: Local Docker (Recommended)
|
||||||
|
|
||||||
Run Cognee locally using Docker Compose:
|
Use the repo example compose file:
|
||||||
|
|
||||||
**Step 1: Create docker-compose.yml**
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '3.8'
|
|
||||||
|
|
||||||
services:
|
|
||||||
cognee:
|
|
||||||
image: topoteretes/cognee:latest
|
|
||||||
container_name: cognee
|
|
||||||
ports:
|
|
||||||
- "8000:8000"
|
|
||||||
environment:
|
|
||||||
# Optional: Set API key for authentication
|
|
||||||
- COGNEE_API_KEY=your-local-api-key
|
|
||||||
# Database configuration
|
|
||||||
- DATABASE_URL=postgresql://cognee:cognee@postgres:5432/cognee
|
|
||||||
volumes:
|
|
||||||
- cognee_data:/app/data
|
|
||||||
depends_on:
|
|
||||||
- postgres
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
postgres:
|
|
||||||
image: postgres:15-alpine
|
|
||||||
container_name: cognee-postgres
|
|
||||||
environment:
|
|
||||||
- POSTGRES_USER=cognee
|
|
||||||
- POSTGRES_PASSWORD=cognee
|
|
||||||
- POSTGRES_DB=cognee
|
|
||||||
volumes:
|
|
||||||
- postgres_data:/var/lib/postgresql/data
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
cognee_data:
|
|
||||||
postgres_data:
|
|
||||||
```
|
|
||||||
|
|
||||||
**Step 2: Start Cognee**
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker-compose up -d
|
docker compose -f examples/cognee-docker-compose.yaml up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
**Step 3: Verify**
|
This binds to `127.0.0.1:8000`, so Cognee is only reachable from your machine.
|
||||||
|
|
||||||
|
**Verify:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl http://localhost:8000/status
|
curl http://localhost:8000/health
|
||||||
# Should return: {"status":"healthy"}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Option 2: Cognee Cloud
|
### Option 2: Cognee Cloud
|
||||||
@ -87,26 +48,49 @@ Use the hosted Cognee service:
|
|||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
Add Cognee memory configuration to your `~/.clawdbot/config.yaml`:
|
Clawdbot reads `~/.clawdbot/clawdbot.json` (JSON5). For local + secure setup, use an env var for the Cognee token and reference it in config.
|
||||||
|
|
||||||
### Basic Configuration (Docker Local)
|
### Step 1: Put tokens in `examples/.env`
|
||||||
|
|
||||||
```yaml
|
```bash
|
||||||
agents:
|
LLM_API_KEY="your-llm-api-key"
|
||||||
defaults:
|
COGNEE_API_KEY="your-cognee-access-token"
|
||||||
memorySearch:
|
CLAWDBOT_GATEWAY_TOKEN="your-random-gateway-token"
|
||||||
enabled: true
|
|
||||||
provider: cognee
|
|
||||||
sources: [memory] # or [memory, sessions]
|
|
||||||
cognee:
|
|
||||||
baseUrl: http://localhost:8000
|
|
||||||
datasetName: clawdbot
|
|
||||||
searchType: insights # or "chunks", "summaries"
|
|
||||||
maxResults: 6
|
|
||||||
autoCognify: true
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Cloud Configuration
|
### Step 2: Configure Cognee in `~/.clawdbot/clawdbot.json`
|
||||||
|
|
||||||
|
```json5
|
||||||
|
{
|
||||||
|
agents: {
|
||||||
|
defaults: {
|
||||||
|
memorySearch: {
|
||||||
|
enabled: true,
|
||||||
|
provider: "cognee",
|
||||||
|
sources: ["memory", "sessions"],
|
||||||
|
experimental: { sessionMemory: true },
|
||||||
|
cognee: {
|
||||||
|
baseUrl: "http://localhost:8000",
|
||||||
|
apiKey: "${COGNEE_API_KEY}",
|
||||||
|
datasetName: "clawdbot",
|
||||||
|
searchType: "GRAPH_COMPLETION",
|
||||||
|
maxResults: 6,
|
||||||
|
autoCognify: true,
|
||||||
|
timeoutSeconds: 180
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: Start the gateway with env loaded
|
||||||
|
|
||||||
|
```zsh
|
||||||
|
set -a; source "examples/.env"; set +a; pnpm clawdbot gateway --port 18789 --token "$CLAWDBOT_GATEWAY_TOKEN" --verbose
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cloud Configuration (tbt)
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
agents:
|
agents:
|
||||||
@ -119,7 +103,7 @@ agents:
|
|||||||
baseUrl: https://cognee--cognee-saas-backend-serve.modal.run
|
baseUrl: https://cognee--cognee-saas-backend-serve.modal.run
|
||||||
apiKey: your-api-key-here # Required for cloud
|
apiKey: your-api-key-here # Required for cloud
|
||||||
datasetName: clawdbot
|
datasetName: clawdbot
|
||||||
searchType: insights
|
searchType: GRAPH_COMPLETION
|
||||||
maxResults: 8
|
maxResults: 8
|
||||||
autoCognify: true
|
autoCognify: true
|
||||||
timeoutSeconds: 60
|
timeoutSeconds: 60
|
||||||
@ -130,22 +114,21 @@ agents:
|
|||||||
| Option | Type | Default | Description |
|
| Option | Type | Default | Description |
|
||||||
|--------|------|---------|-------------|
|
|--------|------|---------|-------------|
|
||||||
| `baseUrl` | string | `http://localhost:8000` | Cognee API endpoint |
|
| `baseUrl` | string | `http://localhost:8000` | Cognee API endpoint |
|
||||||
| `apiKey` | string | - | API key (required for cloud, optional for local) |
|
| `apiKey` | string | - | Access token (required if Cognee auth is enabled) |
|
||||||
| `datasetName` | string | `"clawdbot"` | Dataset name for organizing memories |
|
| `datasetName` | string | `"clawdbot"` | Dataset name for organizing memories |
|
||||||
| `searchType` | string | `"insights"` | Search mode: `insights`, `chunks`, or `summaries` |
|
| `searchType` | string | `"GRAPH_COMPLETION"` | Search mode: `GRAPH_COMPLETION`, `chunks`, or `summaries` |
|
||||||
| `maxResults` | number | `6` | Maximum search results returned |
|
| `maxResults` | number | `6` | Maximum search results returned |
|
||||||
| `autoCognify` | boolean | `true` | Auto-process documents after adding |
|
| `autoCognify` | boolean | `true` | Auto-process documents after adding |
|
||||||
| `cognifyBatchSize` | number | `100` | Batch size for processing |
|
| `cognifyBatchSize` | number | `100` | Batch size for processing |
|
||||||
| `timeoutSeconds` | number | `30` | Request timeout in seconds |
|
| `timeoutSeconds` | number | `180` | Request timeout in seconds |
|
||||||
|
|
||||||
## Search Types
|
## Search Types
|
||||||
|
|
||||||
Cognee offers three search modes:
|
Cognee offers these modes:
|
||||||
|
|
||||||
### Insights (Recommended)
|
### GRAPH_COMPLETION
|
||||||
Best for: **High-level understanding and reasoning**
|
Best for: **High-level understanding and reasoning**
|
||||||
- Returns AI-generated insights from knowledge graph
|
- Returns graph-completion outputs over the knowledge graph
|
||||||
- Combines multiple related facts
|
|
||||||
- Good for: "What projects am I working on?" or "Summarize my notes about X"
|
- Good for: "What projects am I working on?" or "Summarize my notes about X"
|
||||||
|
|
||||||
### Chunks
|
### Chunks
|
||||||
@ -181,20 +164,11 @@ agents:
|
|||||||
sessionMemory: true
|
sessionMemory: true
|
||||||
```
|
```
|
||||||
|
|
||||||
### Manual Sync
|
### Manual Sync and Status
|
||||||
|
|
||||||
Force a memory sync:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Not yet implemented - coming soon
|
# Force sync + cognify for the current agent
|
||||||
clawdbot memory sync --provider cognee
|
clawdbot memory status --index --json
|
||||||
```
|
|
||||||
|
|
||||||
### Check Status
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Not yet implemented - coming soon
|
|
||||||
clawdbot memory status --provider cognee
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## How It Works
|
## How It Works
|
||||||
@ -231,7 +205,7 @@ clawdbot memory status --provider cognee
|
|||||||
**Solutions**:
|
**Solutions**:
|
||||||
1. Verify Docker is running: `docker ps | grep cognee`
|
1. Verify Docker is running: `docker ps | grep cognee`
|
||||||
2. Check Cognee logs: `docker logs cognee`
|
2. Check Cognee logs: `docker logs cognee`
|
||||||
3. Test manually: `curl http://localhost:8000/status`
|
3. Test manually: `curl http://localhost:8000/health`
|
||||||
4. Ensure port 8000 is not blocked
|
4. Ensure port 8000 is not blocked
|
||||||
|
|
||||||
### Slow Performance
|
### Slow Performance
|
||||||
@ -250,6 +224,15 @@ clawdbot memory status --provider cognee
|
|||||||
3. Process fewer files at once
|
3. Process fewer files at once
|
||||||
4. Clear old datasets via Cognee API
|
4. Clear old datasets via Cognee API
|
||||||
|
|
||||||
|
### 401 Unauthorized (Cognee auth)
|
||||||
|
|
||||||
|
**Cause**: Cognee auth is enabled, but Clawdbot is missing/using an invalid `COGNEE_API_KEY`.
|
||||||
|
|
||||||
|
**Fix**:
|
||||||
|
1. Log in to Cognee and get a fresh access token.
|
||||||
|
2. Update `COGNEE_API_KEY` in `examples/.env`.
|
||||||
|
3. Restart the gateway with env loaded (see Step 3 above).
|
||||||
|
|
||||||
## Advanced Configuration
|
## Advanced Configuration
|
||||||
|
|
||||||
### Per-Agent Override
|
### Per-Agent Override
|
||||||
@ -265,7 +248,7 @@ agents:
|
|||||||
memorySearch:
|
memorySearch:
|
||||||
provider: cognee # Override for this agent
|
provider: cognee # Override for this agent
|
||||||
cognee:
|
cognee:
|
||||||
searchType: insights
|
searchType: GRAPH_COMPLETION
|
||||||
maxResults: 10
|
maxResults: 10
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -285,7 +268,7 @@ Add health checks to docker-compose.yml:
|
|||||||
services:
|
services:
|
||||||
cognee:
|
cognee:
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "curl", "-f", "http://localhost:8000/status"]
|
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
|
||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
@ -313,15 +296,13 @@ Mount volumes for persistence:
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
volumes:
|
volumes:
|
||||||
- ./cognee_data:/app/data
|
- ./cognee_data:/app/cognee/.cognee_system
|
||||||
- ./cognee_logs:/app/logs
|
- ./cognee_logs:/app/logs
|
||||||
```
|
```
|
||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
|
|
||||||
Planned features:
|
Planned features:
|
||||||
- [ ] `clawdbot memory status --provider cognee` command
|
|
||||||
- [ ] `clawdbot memory sync --provider cognee` command
|
|
||||||
- [ ] Hybrid mode (Cognee + SQLite)
|
- [ ] Hybrid mode (Cognee + SQLite)
|
||||||
- [ ] Graph visualization export
|
- [ ] Graph visualization export
|
||||||
- [ ] Manual entity management
|
- [ ] Manual entity management
|
||||||
|
|||||||
7
examples/.env.template
Normal file
7
examples/.env.template
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
LLM_API_KEY=
|
||||||
|
ENABLE_BACKEND_ACCESS_CONTROL="false"
|
||||||
|
|
||||||
|
|
||||||
|
COGNEE_API_KEY=
|
||||||
|
|
||||||
|
CLAWDBOT_GATEWAY_TOKEN=
|
||||||
35
examples/cognee-clawdbot-config.yaml
Normal file
35
examples/cognee-clawdbot-config.yaml
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
# Sample Clawdbot config for testing Cognee integration
|
||||||
|
# Copy this to ~/.clawdbot/config.yaml inside the dev container
|
||||||
|
#
|
||||||
|
# Inside container:
|
||||||
|
# mkdir -p ~/.clawdbot
|
||||||
|
# cp /app/examples/cognee-clawdbot-config.yaml ~/.clawdbot/config.yaml
|
||||||
|
|
||||||
|
agents:
|
||||||
|
defaults:
|
||||||
|
# Enable Cognee as the memory provider
|
||||||
|
memorySearch:
|
||||||
|
enabled: true
|
||||||
|
provider: cognee
|
||||||
|
sources: [memory] # Start with just memory files
|
||||||
|
|
||||||
|
cognee:
|
||||||
|
# Inside Docker network, Cognee is at this address
|
||||||
|
baseUrl: http://cognee:8000
|
||||||
|
# No API key needed for local Docker setup
|
||||||
|
# apiKey: ""
|
||||||
|
|
||||||
|
# Dataset name for organizing memories
|
||||||
|
datasetName: clawdbot-test
|
||||||
|
|
||||||
|
# Search mode: "insights" (recommended), "chunks", or "summaries"
|
||||||
|
searchType: insights
|
||||||
|
|
||||||
|
# Max results per search
|
||||||
|
maxResults: 6
|
||||||
|
|
||||||
|
# Auto-process documents after adding
|
||||||
|
autoCognify: true
|
||||||
|
|
||||||
|
# Request timeout
|
||||||
|
timeoutSeconds: 30
|
||||||
@ -21,8 +21,8 @@ agents:
|
|||||||
# Dataset name for organizing memories
|
# Dataset name for organizing memories
|
||||||
datasetName: clawdbot
|
datasetName: clawdbot
|
||||||
|
|
||||||
# Search mode: "insights" (recommended), "chunks", or "summaries"
|
# Search mode: "GRAPH_COMPLETION" (recommended), "chunks", or "summaries"
|
||||||
searchType: insights
|
searchType: GRAPH_COMPLETION
|
||||||
|
|
||||||
# Maximum search results to return
|
# Maximum search results to return
|
||||||
maxResults: 6
|
maxResults: 6
|
||||||
|
|||||||
@ -1,77 +1,36 @@
|
|||||||
# Docker Compose configuration for running Cognee locally with Clawdbot
|
# Minimal Docker Compose for Cognee (local-only)
|
||||||
#
|
#
|
||||||
# Usage:
|
# Usage:
|
||||||
# 1. Copy this file to your preferred location
|
# 1. Export your LLM key: export LLM_API_KEY="your-openai-api-key"
|
||||||
# 2. Run: docker-compose -f cognee-docker-compose.yaml up -d
|
# 2. Run: docker compose -f examples/cognee-docker-compose.yaml up -d
|
||||||
# 3. Verify: curl http://localhost:8000/status
|
# 3. Verify: curl http://localhost:8000/health
|
||||||
# 4. Configure Clawdbot with baseUrl: http://localhost:8000
|
# 4. Configure Clawdbot with baseUrl: http://localhost:8000
|
||||||
#
|
#
|
||||||
# For production, see docs/memory-cognee.md for additional configuration
|
# Defaults (no extra DB setup):
|
||||||
|
# - Relational DB: SQLite (file-based)
|
||||||
|
# - Vector DB: LanceDB (file-based)
|
||||||
|
# - Graph DB: Kuzu (file-based)
|
||||||
|
|
||||||
version: '3.8'
|
version: "3.8"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
# Cognee API server
|
|
||||||
cognee:
|
cognee:
|
||||||
image: topoteretes/cognee:latest
|
image: cognee/cognee:latest
|
||||||
container_name: cognee
|
container_name: cognee
|
||||||
ports:
|
ports:
|
||||||
- "8000:8000"
|
- "127.0.0.1:8000:8000"
|
||||||
environment:
|
environment:
|
||||||
# Optional: Set API key for authentication
|
- LLM_API_KEY=${LLM_API_KEY}
|
||||||
# Remove or comment out for local development without auth
|
|
||||||
- COGNEE_API_KEY=${COGNEE_API_KEY:-}
|
|
||||||
|
|
||||||
# Database connection
|
|
||||||
- DATABASE_URL=postgresql://cognee:cognee@postgres:5432/cognee
|
|
||||||
|
|
||||||
# Optional: Configure LLM provider for entity extraction
|
|
||||||
# Uncomment and set if you want to use specific providers
|
|
||||||
# - OPENAI_API_KEY=${OPENAI_API_KEY:-}
|
|
||||||
# - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-}
|
|
||||||
volumes:
|
volumes:
|
||||||
- cognee_data:/app/data
|
- cognee_data:/app/cognee/.cognee_system
|
||||||
- cognee_logs:/app/logs
|
|
||||||
depends_on:
|
|
||||||
postgres:
|
|
||||||
condition: service_healthy
|
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "curl", "-f", "http://localhost:8000/status"]
|
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
|
||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
start_period: 40s
|
start_period: 40s
|
||||||
networks:
|
|
||||||
- cognee-network
|
|
||||||
|
|
||||||
# PostgreSQL database for Cognee
|
|
||||||
postgres:
|
|
||||||
image: postgres:15-alpine
|
|
||||||
container_name: cognee-postgres
|
|
||||||
environment:
|
|
||||||
- POSTGRES_USER=cognee
|
|
||||||
- POSTGRES_PASSWORD=cognee
|
|
||||||
- POSTGRES_DB=cognee
|
|
||||||
volumes:
|
|
||||||
- postgres_data:/var/lib/postgresql/data
|
|
||||||
restart: unless-stopped
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "pg_isready -U cognee"]
|
|
||||||
interval: 10s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 5
|
|
||||||
networks:
|
|
||||||
- cognee-network
|
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
cognee_data:
|
cognee_data:
|
||||||
driver: local
|
driver: local
|
||||||
cognee_logs:
|
|
||||||
driver: local
|
|
||||||
postgres_data:
|
|
||||||
driver: local
|
|
||||||
|
|
||||||
networks:
|
|
||||||
cognee-network:
|
|
||||||
driver: bridge
|
|
||||||
|
|||||||
117
examples/cognee-test-compose.yaml
Normal file
117
examples/cognee-test-compose.yaml
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
# Docker Compose for testing Clawdbot + Cognee integration
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# 1. cd examples
|
||||||
|
# 2. docker-compose -f cognee-test-compose.yaml up -d
|
||||||
|
# 3. docker-compose -f cognee-test-compose.yaml exec dev bash
|
||||||
|
# 4. Inside container:
|
||||||
|
# - pnpm install # Install dependencies
|
||||||
|
# - pnpm build # Build Clawdbot
|
||||||
|
# - pnpm test src/memory/cognee-* # Run unit tests
|
||||||
|
# - pnpm clawdbot agent --message "test" # Run Clawdbot CLI
|
||||||
|
#
|
||||||
|
# To tear down: docker-compose -f cognee-test-compose.yaml down -v
|
||||||
|
|
||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
# Cognee API server
|
||||||
|
cognee:
|
||||||
|
image: topoteretes/cognee:latest
|
||||||
|
container_name: cognee-test
|
||||||
|
ports:
|
||||||
|
- "8000:8000"
|
||||||
|
environment:
|
||||||
|
# No API key for local testing
|
||||||
|
- COGNEE_API_KEY=
|
||||||
|
# Database connection
|
||||||
|
- DATABASE_URL=postgresql://cognee:cognee@postgres:5432/cognee
|
||||||
|
volumes:
|
||||||
|
- cognee_data:/app/data
|
||||||
|
depends_on:
|
||||||
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-f", "http://localhost:8000/status"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 60s
|
||||||
|
networks:
|
||||||
|
- cognee-test-network
|
||||||
|
|
||||||
|
# PostgreSQL database for Cognee
|
||||||
|
postgres:
|
||||||
|
image: postgres:15-alpine
|
||||||
|
container_name: cognee-test-postgres
|
||||||
|
environment:
|
||||||
|
- POSTGRES_USER=cognee
|
||||||
|
- POSTGRES_PASSWORD=cognee
|
||||||
|
- POSTGRES_DB=cognee
|
||||||
|
volumes:
|
||||||
|
- postgres_data:/var/lib/postgresql/data
|
||||||
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U cognee"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
networks:
|
||||||
|
- cognee-test-network
|
||||||
|
|
||||||
|
# Development container for running Clawdbot + tests
|
||||||
|
dev:
|
||||||
|
image: node:22-bookworm
|
||||||
|
container_name: clawdbot-cognee-dev
|
||||||
|
working_dir: /app
|
||||||
|
volumes:
|
||||||
|
# Mount the entire clawdbot repo
|
||||||
|
- ..:/app
|
||||||
|
# Use a separate node_modules volume to avoid conflicts
|
||||||
|
- node_modules:/app/node_modules
|
||||||
|
# Clawdbot config directory (persisted)
|
||||||
|
- clawdbot_config:/root/.clawdbot
|
||||||
|
environment:
|
||||||
|
- HOME=/root
|
||||||
|
- TERM=xterm-256color
|
||||||
|
# Point to Cognee running in Docker network
|
||||||
|
- COGNEE_BASE_URL=http://cognee:8000
|
||||||
|
# Optional: Add your API keys here for full testing
|
||||||
|
# - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-}
|
||||||
|
# - OPENAI_API_KEY=${OPENAI_API_KEY:-}
|
||||||
|
depends_on:
|
||||||
|
cognee:
|
||||||
|
condition: service_healthy
|
||||||
|
stdin_open: true
|
||||||
|
tty: true
|
||||||
|
# Install pnpm and keep container running
|
||||||
|
command: >
|
||||||
|
bash -c "
|
||||||
|
corepack enable &&
|
||||||
|
echo '=== Clawdbot + Cognee Test Environment ===' &&
|
||||||
|
echo 'Cognee is available at: http://cognee:8000' &&
|
||||||
|
echo '' &&
|
||||||
|
echo 'Quick start:' &&
|
||||||
|
echo ' pnpm install && pnpm build' &&
|
||||||
|
echo ' pnpm test src/memory/cognee-client.test.ts' &&
|
||||||
|
echo ' pnpm clawdbot --help' &&
|
||||||
|
echo '' &&
|
||||||
|
sleep infinity
|
||||||
|
"
|
||||||
|
networks:
|
||||||
|
- cognee-test-network
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
cognee_data:
|
||||||
|
driver: local
|
||||||
|
postgres_data:
|
||||||
|
driver: local
|
||||||
|
node_modules:
|
||||||
|
driver: local
|
||||||
|
clawdbot_config:
|
||||||
|
driver: local
|
||||||
|
|
||||||
|
networks:
|
||||||
|
cognee-test-network:
|
||||||
|
driver: bridge
|
||||||
66
examples/test-cognee.sh
Normal file
66
examples/test-cognee.sh
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Test script for Cognee integration
|
||||||
|
# Run this inside the dev container after starting docker-compose
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "=== Clawdbot + Cognee Integration Test ==="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 1. Check if Cognee is reachable
|
||||||
|
echo "1. Testing Cognee connection..."
|
||||||
|
if curl -sf http://cognee:8000/status > /dev/null 2>&1; then
|
||||||
|
echo " ✓ Cognee is healthy at http://cognee:8000"
|
||||||
|
curl -s http://cognee:8000/status | head -c 200
|
||||||
|
echo ""
|
||||||
|
else
|
||||||
|
echo " ✗ Cannot reach Cognee at http://cognee:8000"
|
||||||
|
echo " Make sure Cognee container is running and healthy"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 2. Install dependencies if needed
|
||||||
|
if [ ! -d "node_modules/.pnpm" ]; then
|
||||||
|
echo "2. Installing dependencies..."
|
||||||
|
pnpm install
|
||||||
|
else
|
||||||
|
echo "2. Dependencies already installed"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 3. Run unit tests
|
||||||
|
echo "3. Running Cognee unit tests..."
|
||||||
|
pnpm test src/memory/cognee-client.test.ts src/memory/cognee-provider.test.ts
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 4. Quick integration check (manual API test)
|
||||||
|
echo "4. Quick API smoke test..."
|
||||||
|
echo " Adding test data to Cognee..."
|
||||||
|
|
||||||
|
# Add test data
|
||||||
|
RESULT=$(curl -sf -X POST http://cognee:8000/add \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"data": "Test memory entry: The capital of France is Paris. This is a test document for Clawdbot integration.",
|
||||||
|
"dataset_name": "clawdbot-test"
|
||||||
|
}' 2>&1) || true
|
||||||
|
|
||||||
|
if echo "$RESULT" | grep -q "dataset"; then
|
||||||
|
echo " ✓ Data added successfully"
|
||||||
|
echo " Response: $RESULT"
|
||||||
|
else
|
||||||
|
echo " ⚠ Add may have failed (this is OK if Cognee API differs)"
|
||||||
|
echo " Response: $RESULT"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "=== Test Complete ==="
|
||||||
|
echo ""
|
||||||
|
echo "Next steps:"
|
||||||
|
echo " - The unit tests verify the client/provider logic"
|
||||||
|
echo " - For full integration, configure Clawdbot with:"
|
||||||
|
echo " memorySearch:"
|
||||||
|
echo " provider: cognee"
|
||||||
|
echo " cognee:"
|
||||||
|
echo " baseUrl: http://cognee:8000 # or http://localhost:8000 from host"
|
||||||
@ -35,7 +35,7 @@ export type ResolvedMemorySearchConfig = {
|
|||||||
baseUrl?: string;
|
baseUrl?: string;
|
||||||
apiKey?: string;
|
apiKey?: string;
|
||||||
datasetName?: string;
|
datasetName?: string;
|
||||||
searchType?: "insights" | "chunks" | "summaries";
|
searchType?: "GRAPH_COMPLETION" | "chunks" | "summaries";
|
||||||
maxResults?: number;
|
maxResults?: number;
|
||||||
timeoutSeconds?: number;
|
timeoutSeconds?: number;
|
||||||
autoCognify?: boolean;
|
autoCognify?: boolean;
|
||||||
|
|||||||
@ -501,7 +501,8 @@ const FIELD_HELP: Record<string, string> = {
|
|||||||
'Sources to index for memory search (default: ["memory"]; add "sessions" to include session transcripts).',
|
'Sources to index for memory search (default: ["memory"]; add "sessions" to include session transcripts).',
|
||||||
"agents.defaults.memorySearch.experimental.sessionMemory":
|
"agents.defaults.memorySearch.experimental.sessionMemory":
|
||||||
"Enable experimental session transcript indexing for memory search (default: false).",
|
"Enable experimental session transcript indexing for memory search (default: false).",
|
||||||
"agents.defaults.memorySearch.provider": 'Embedding provider ("openai", "gemini", or "local").',
|
"agents.defaults.memorySearch.provider":
|
||||||
|
'Embedding provider ("openai", "gemini", "local", or "cognee").',
|
||||||
"agents.defaults.memorySearch.remote.baseUrl":
|
"agents.defaults.memorySearch.remote.baseUrl":
|
||||||
"Custom base URL for remote embeddings (OpenAI-compatible proxies or Gemini overrides).",
|
"Custom base URL for remote embeddings (OpenAI-compatible proxies or Gemini overrides).",
|
||||||
"agents.defaults.memorySearch.remote.apiKey": "Custom API key for the remote embedding provider.",
|
"agents.defaults.memorySearch.remote.apiKey": "Custom API key for the remote embedding provider.",
|
||||||
@ -517,10 +518,12 @@ const FIELD_HELP: Record<string, string> = {
|
|||||||
"Polling interval in ms for batch status (default: 2000).",
|
"Polling interval in ms for batch status (default: 2000).",
|
||||||
"agents.defaults.memorySearch.remote.batch.timeoutMinutes":
|
"agents.defaults.memorySearch.remote.batch.timeoutMinutes":
|
||||||
"Timeout in minutes for batch indexing (default: 60).",
|
"Timeout in minutes for batch indexing (default: 60).",
|
||||||
|
"agents.defaults.memorySearch.cognee":
|
||||||
|
"Cognee provider configuration (baseUrl, apiKey, datasetName, searchType, maxResults, autoCognify).",
|
||||||
"agents.defaults.memorySearch.local.modelPath":
|
"agents.defaults.memorySearch.local.modelPath":
|
||||||
"Local GGUF model path or hf: URI (node-llama-cpp).",
|
"Local GGUF model path or hf: URI (node-llama-cpp).",
|
||||||
"agents.defaults.memorySearch.fallback":
|
"agents.defaults.memorySearch.fallback":
|
||||||
'Fallback provider when embeddings fail ("openai", "gemini", "local", or "none").',
|
'Fallback provider when embeddings fail ("openai", "gemini", "local", "cognee", or "none").',
|
||||||
"agents.defaults.memorySearch.store.path":
|
"agents.defaults.memorySearch.store.path":
|
||||||
"SQLite index path (default: ~/.clawdbot/memory/{agentId}.sqlite).",
|
"SQLite index path (default: ~/.clawdbot/memory/{agentId}.sqlite).",
|
||||||
"agents.defaults.memorySearch.store.vector.enabled":
|
"agents.defaults.memorySearch.store.vector.enabled":
|
||||||
|
|||||||
@ -269,8 +269,8 @@ export type MemorySearchConfig = {
|
|||||||
apiKey?: string;
|
apiKey?: string;
|
||||||
/** Dataset name for organizing memories (default: "clawdbot"). */
|
/** Dataset name for organizing memories (default: "clawdbot"). */
|
||||||
datasetName?: string;
|
datasetName?: string;
|
||||||
/** Search type: "insights", "chunks", or "summaries" (default: "insights"). */
|
/** Search type: "GRAPH_COMPLETION", "chunks", or "summaries" (default: "GRAPH_COMPLETION"). */
|
||||||
searchType?: "insights" | "chunks" | "summaries";
|
searchType?: "GRAPH_COMPLETION" | "chunks" | "summaries";
|
||||||
/** Max results per search query (default: 6). */
|
/** Max results per search query (default: 6). */
|
||||||
maxResults?: number;
|
maxResults?: number;
|
||||||
/** Timeout for API requests in seconds (default: 30). */
|
/** Timeout for API requests in seconds (default: 30). */
|
||||||
|
|||||||
@ -310,7 +310,9 @@ export const MemorySearchSchema = z
|
|||||||
})
|
})
|
||||||
.strict()
|
.strict()
|
||||||
.optional(),
|
.optional(),
|
||||||
provider: z.union([z.literal("openai"), z.literal("local"), z.literal("gemini")]).optional(),
|
provider: z
|
||||||
|
.union([z.literal("openai"), z.literal("local"), z.literal("gemini"), z.literal("cognee")])
|
||||||
|
.optional(),
|
||||||
remote: z
|
remote: z
|
||||||
.object({
|
.object({
|
||||||
baseUrl: z.string().optional(),
|
baseUrl: z.string().optional(),
|
||||||
@ -330,9 +332,30 @@ export const MemorySearchSchema = z
|
|||||||
.strict()
|
.strict()
|
||||||
.optional(),
|
.optional(),
|
||||||
fallback: z
|
fallback: z
|
||||||
.union([z.literal("openai"), z.literal("gemini"), z.literal("local"), z.literal("none")])
|
.union([
|
||||||
|
z.literal("openai"),
|
||||||
|
z.literal("gemini"),
|
||||||
|
z.literal("local"),
|
||||||
|
z.literal("cognee"),
|
||||||
|
z.literal("none"),
|
||||||
|
])
|
||||||
.optional(),
|
.optional(),
|
||||||
model: z.string().optional(),
|
model: z.string().optional(),
|
||||||
|
cognee: z
|
||||||
|
.object({
|
||||||
|
baseUrl: z.string().optional(),
|
||||||
|
apiKey: z.string().optional(),
|
||||||
|
datasetName: z.string().optional(),
|
||||||
|
searchType: z
|
||||||
|
.union([z.literal("GRAPH_COMPLETION"), z.literal("CHUNKS"), z.literal("SUMMARIES")])
|
||||||
|
.optional(),
|
||||||
|
maxResults: z.number().int().positive().optional(),
|
||||||
|
autoCognify: z.boolean().optional(),
|
||||||
|
cognifyBatchSize: z.number().int().positive().optional(),
|
||||||
|
timeoutSeconds: z.number().int().positive().optional(),
|
||||||
|
})
|
||||||
|
.strict()
|
||||||
|
.optional(),
|
||||||
local: z
|
local: z
|
||||||
.object({
|
.object({
|
||||||
modelPath: z.string().optional(),
|
modelPath: z.string().optional(),
|
||||||
|
|||||||
@ -2,21 +2,21 @@ import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
|
|||||||
import { CogneeClient } from "./cognee-client.js";
|
import { CogneeClient } from "./cognee-client.js";
|
||||||
import { request } from "undici";
|
import { request } from "undici";
|
||||||
|
|
||||||
vi.mock("undici", () => ({
|
vi.mock("undici", () => ({
|
||||||
request: vi.fn(),
|
request: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe("CogneeClient", () => {
|
describe("CogneeClient", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
vi.restoreAllMocks();
|
vi.restoreAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("add", () => {
|
describe("add", () => {
|
||||||
it("should add data successfully", async () => {
|
it("should add data successfully", async () => {
|
||||||
const mockResponse = {
|
const mockResponse = {
|
||||||
statusCode: 200,
|
statusCode: 200,
|
||||||
body: {
|
body: {
|
||||||
@ -46,18 +46,18 @@ describe("CogneeClient", () => {
|
|||||||
message: "Data added successfully",
|
message: "Data added successfully",
|
||||||
});
|
});
|
||||||
expect(request).toHaveBeenCalledWith(
|
expect(request).toHaveBeenCalledWith(
|
||||||
"http://localhost:8000/add",
|
"http://localhost:8000/api/v1/add",
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: expect.objectContaining({
|
headers: expect.objectContaining({
|
||||||
"Content-Type": "application/json",
|
Authorization: "Bearer test-key",
|
||||||
"X-Api-Key": "test-key",
|
"X-Api-Key": "test-key",
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should handle errors", async () => {
|
it("should handle errors", async () => {
|
||||||
const mockResponse = {
|
const mockResponse = {
|
||||||
statusCode: 500,
|
statusCode: 500,
|
||||||
body: {
|
body: {
|
||||||
@ -77,8 +77,8 @@ describe("CogneeClient", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("cognify", () => {
|
describe("cognify", () => {
|
||||||
it("should run cognify successfully", async () => {
|
it("should run cognify successfully", async () => {
|
||||||
const mockResponse = {
|
const mockResponse = {
|
||||||
statusCode: 200,
|
statusCode: 200,
|
||||||
body: {
|
body: {
|
||||||
@ -106,8 +106,8 @@ describe("CogneeClient", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("search", () => {
|
describe("search", () => {
|
||||||
it("should search successfully", async () => {
|
it("should search successfully", async () => {
|
||||||
const mockResponse = {
|
const mockResponse = {
|
||||||
statusCode: 200,
|
statusCode: 200,
|
||||||
body: {
|
body: {
|
||||||
@ -121,7 +121,7 @@ describe("CogneeClient", () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
query: "test query",
|
query: "test query",
|
||||||
search_type: "insights",
|
search_type: "GRAPH_COMPLETION",
|
||||||
}),
|
}),
|
||||||
text: vi.fn(),
|
text: vi.fn(),
|
||||||
},
|
},
|
||||||
@ -132,7 +132,7 @@ describe("CogneeClient", () => {
|
|||||||
|
|
||||||
const result = await client.search({
|
const result = await client.search({
|
||||||
queryText: "test query",
|
queryText: "test query",
|
||||||
searchType: "insights",
|
searchType: "GRAPH_COMPLETION",
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result.results).toHaveLength(1);
|
expect(result.results).toHaveLength(1);
|
||||||
@ -145,14 +145,14 @@ describe("CogneeClient", () => {
|
|||||||
expect(result.query).toBe("test query");
|
expect(result.query).toBe("test query");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should use default search type", async () => {
|
it("should use default search type", async () => {
|
||||||
const mockResponse = {
|
const mockResponse = {
|
||||||
statusCode: 200,
|
statusCode: 200,
|
||||||
body: {
|
body: {
|
||||||
json: vi.fn().mockResolvedValue({
|
json: vi.fn().mockResolvedValue({
|
||||||
results: [],
|
results: [],
|
||||||
query: "test",
|
query: "test",
|
||||||
search_type: "insights",
|
search_type: "GRAPH_COMPLETION",
|
||||||
}),
|
}),
|
||||||
text: vi.fn(),
|
text: vi.fn(),
|
||||||
},
|
},
|
||||||
@ -165,28 +165,18 @@ describe("CogneeClient", () => {
|
|||||||
expect(request).toHaveBeenCalledWith(
|
expect(request).toHaveBeenCalledWith(
|
||||||
expect.any(String),
|
expect.any(String),
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
body: expect.stringContaining('"search_type":"insights"'),
|
body: expect.stringContaining('"searchType":"GRAPH_COMPLETION"'),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("status", () => {
|
describe("status", () => {
|
||||||
it("should get status successfully", async () => {
|
it("should get status successfully", async () => {
|
||||||
const mockResponse = {
|
const mockResponse = {
|
||||||
statusCode: 200,
|
statusCode: 200,
|
||||||
body: {
|
body: {
|
||||||
json: vi.fn().mockResolvedValue({
|
json: vi.fn().mockResolvedValue({ status: "healthy" }),
|
||||||
status: "healthy",
|
|
||||||
version: "1.0.0",
|
|
||||||
datasets: [
|
|
||||||
{
|
|
||||||
id: "dataset-1",
|
|
||||||
name: "test-dataset",
|
|
||||||
document_count: 10,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
text: vi.fn(),
|
text: vi.fn(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -198,20 +188,12 @@ describe("CogneeClient", () => {
|
|||||||
|
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
status: "healthy",
|
status: "healthy",
|
||||||
version: "1.0.0",
|
|
||||||
datasets: [
|
|
||||||
{
|
|
||||||
id: "dataset-1",
|
|
||||||
name: "test-dataset",
|
|
||||||
documentCount: 10,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("healthCheck", () => {
|
describe("healthCheck", () => {
|
||||||
it("should return true when status is successful", async () => {
|
it("should return true when status is successful", async () => {
|
||||||
const mockResponse = {
|
const mockResponse = {
|
||||||
statusCode: 200,
|
statusCode: 200,
|
||||||
body: {
|
body: {
|
||||||
@ -227,7 +209,7 @@ describe("CogneeClient", () => {
|
|||||||
expect(result).toBe(true);
|
expect(result).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return false when status fails", async () => {
|
it("should return false when status fails", async () => {
|
||||||
vi.mocked(request).mockRejectedValue(new Error("Connection failed"));
|
vi.mocked(request).mockRejectedValue(new Error("Connection failed"));
|
||||||
|
|
||||||
const client = new CogneeClient();
|
const client = new CogneeClient();
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
import { request } from "undici";
|
import { Blob } from "buffer";
|
||||||
|
import { FormData, request } from "undici";
|
||||||
import { createSubsystemLogger } from "../logging/subsystem.js";
|
import { createSubsystemLogger } from "../logging/subsystem.js";
|
||||||
|
|
||||||
const log = createSubsystemLogger("cognee");
|
const log = createSubsystemLogger("cognee");
|
||||||
|
|
||||||
const DEFAULT_BASE_URL = "http://localhost:8000";
|
const DEFAULT_BASE_URL = "http://localhost:8000";
|
||||||
const DEFAULT_TIMEOUT_MS = 30_000;
|
const DEFAULT_TIMEOUT_MS = 30_000;
|
||||||
|
const API_PREFIX = "/api/v1";
|
||||||
|
|
||||||
export type CogneeClientConfig = {
|
export type CogneeClientConfig = {
|
||||||
baseUrl?: string;
|
baseUrl?: string;
|
||||||
@ -35,7 +37,7 @@ export type CogneeCognifyResponse = {
|
|||||||
|
|
||||||
export type CogneeSearchRequest = {
|
export type CogneeSearchRequest = {
|
||||||
queryText: string;
|
queryText: string;
|
||||||
searchType?: "insights" | "chunks" | "summaries";
|
searchType?: "GRAPH_COMPLETION" | "chunks" | "summaries";
|
||||||
datasetIds?: string[];
|
datasetIds?: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -43,7 +45,7 @@ export type CogneeSearchResult = {
|
|||||||
id: string;
|
id: string;
|
||||||
text: string;
|
text: string;
|
||||||
score: number;
|
score: number;
|
||||||
metadata?: Record<string, unknown>;
|
metadata?: Record<string, unknown>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CogneeSearchResponse = {
|
export type CogneeSearchResponse = {
|
||||||
@ -55,13 +57,15 @@ export type CogneeSearchResponse = {
|
|||||||
export type CogneeStatusResponse = {
|
export type CogneeStatusResponse = {
|
||||||
status: string;
|
status: string;
|
||||||
version?: string;
|
version?: string;
|
||||||
datasets?: Array<{
|
datasets?: Array<{
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
documentCount?: number;
|
documentCount?: number;
|
||||||
}>;
|
}>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type CogneeSearchApiType = "SUMMARIES" | "CHUNKS" | "GRAPH_COMPLETION";
|
||||||
|
|
||||||
export class CogneeClient {
|
export class CogneeClient {
|
||||||
private readonly baseUrl: string;
|
private readonly baseUrl: string;
|
||||||
private readonly apiKey?: string;
|
private readonly apiKey?: string;
|
||||||
@ -73,12 +77,11 @@ export class CogneeClient {
|
|||||||
this.timeoutMs = config.timeoutMs || DEFAULT_TIMEOUT_MS;
|
this.timeoutMs = config.timeoutMs || DEFAULT_TIMEOUT_MS;
|
||||||
}
|
}
|
||||||
|
|
||||||
async add(req: CogneeAddRequest): Promise<CogneeAddResponse> {
|
async add(req: CogneeAddRequest): Promise<CogneeAddResponse> {
|
||||||
const url = `${this.baseUrl}/add`;
|
const url = `${this.baseUrl}${API_PREFIX}/add`;
|
||||||
const headers: Record<string, string> = {
|
const headers: Record<string, string> = {};
|
||||||
"Content-Type": "application/json",
|
|
||||||
};
|
|
||||||
if (this.apiKey) {
|
if (this.apiKey) {
|
||||||
|
headers.Authorization = `Bearer ${this.apiKey}`;
|
||||||
headers["X-Api-Key"] = this.apiKey;
|
headers["X-Api-Key"] = this.apiKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,23 +92,27 @@ export class CogneeClient {
|
|||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const formData = new FormData();
|
||||||
|
const blob = new Blob([req.data], { type: "text/plain" });
|
||||||
|
formData.append("data", blob, "clawdbot-memory.txt");
|
||||||
|
if (req.datasetName) {
|
||||||
|
formData.append("datasetName", req.datasetName);
|
||||||
|
}
|
||||||
|
if (req.datasetId) {
|
||||||
|
formData.append("datasetId", req.datasetId);
|
||||||
|
}
|
||||||
|
|
||||||
const response = await request(url, {
|
const response = await request(url, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers,
|
headers,
|
||||||
body: JSON.stringify({
|
body: formData,
|
||||||
data: req.data,
|
|
||||||
dataset_name: req.datasetName,
|
|
||||||
dataset_id: req.datasetId,
|
|
||||||
}),
|
|
||||||
bodyTimeout: this.timeoutMs,
|
bodyTimeout: this.timeoutMs,
|
||||||
headersTimeout: this.timeoutMs,
|
headersTimeout: this.timeoutMs,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.statusCode !== 200) {
|
if (response.statusCode !== 200) {
|
||||||
const errorText = await response.body.text();
|
const errorText = await response.body.text();
|
||||||
throw new Error(
|
throw new Error(`Cognee add failed with status ${response.statusCode}: ${errorText}`);
|
||||||
`Cognee add failed with status ${response.statusCode}: ${errorText}`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = (await response.body.json()) as {
|
const data = (await response.body.json()) as {
|
||||||
@ -127,12 +134,13 @@ export class CogneeClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async cognify(req: CogneeCognifyRequest = {}): Promise<CogneeCognifyResponse> {
|
async cognify(req: CogneeCognifyRequest = {}): Promise<CogneeCognifyResponse> {
|
||||||
const url = `${this.baseUrl}/cognify`;
|
const url = `${this.baseUrl}${API_PREFIX}/cognify`;
|
||||||
const headers: Record<string, string> = {
|
const headers: Record<string, string> = {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
};
|
};
|
||||||
if (this.apiKey) {
|
if (this.apiKey) {
|
||||||
|
headers.Authorization = `Bearer ${this.apiKey}`;
|
||||||
headers["X-Api-Key"] = this.apiKey;
|
headers["X-Api-Key"] = this.apiKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,7 +151,7 @@ export class CogneeClient {
|
|||||||
method: "POST",
|
method: "POST",
|
||||||
headers,
|
headers,
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
dataset_ids: req.datasetIds,
|
datasetIds: req.datasetIds,
|
||||||
}),
|
}),
|
||||||
bodyTimeout: this.timeoutMs,
|
bodyTimeout: this.timeoutMs,
|
||||||
headersTimeout: this.timeoutMs,
|
headersTimeout: this.timeoutMs,
|
||||||
@ -151,9 +159,7 @@ export class CogneeClient {
|
|||||||
|
|
||||||
if (response.statusCode !== 200) {
|
if (response.statusCode !== 200) {
|
||||||
const errorText = await response.body.text();
|
const errorText = await response.body.text();
|
||||||
throw new Error(
|
throw new Error(`Cognee cognify failed with status ${response.statusCode}: ${errorText}`);
|
||||||
`Cognee cognify failed with status ${response.statusCode}: ${errorText}`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = (await response.body.json()) as {
|
const data = (await response.body.json()) as {
|
||||||
@ -173,12 +179,13 @@ export class CogneeClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async search(req: CogneeSearchRequest): Promise<CogneeSearchResponse> {
|
async search(req: CogneeSearchRequest): Promise<CogneeSearchResponse> {
|
||||||
const url = `${this.baseUrl}/search`;
|
const url = `${this.baseUrl}${API_PREFIX}/search`;
|
||||||
const headers: Record<string, string> = {
|
const headers: Record<string, string> = {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
};
|
};
|
||||||
if (this.apiKey) {
|
if (this.apiKey) {
|
||||||
|
headers.Authorization = `Bearer ${this.apiKey}`;
|
||||||
headers["X-Api-Key"] = this.apiKey;
|
headers["X-Api-Key"] = this.apiKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -193,9 +200,9 @@ export class CogneeClient {
|
|||||||
method: "POST",
|
method: "POST",
|
||||||
headers,
|
headers,
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
query_text: req.queryText,
|
query: req.queryText,
|
||||||
search_type: req.searchType || "insights",
|
searchType: this.mapSearchType(req.searchType),
|
||||||
dataset_ids: req.datasetIds,
|
datasetIds: req.datasetIds,
|
||||||
}),
|
}),
|
||||||
bodyTimeout: this.timeoutMs,
|
bodyTimeout: this.timeoutMs,
|
||||||
headersTimeout: this.timeoutMs,
|
headersTimeout: this.timeoutMs,
|
||||||
@ -203,31 +210,16 @@ export class CogneeClient {
|
|||||||
|
|
||||||
if (response.statusCode !== 200) {
|
if (response.statusCode !== 200) {
|
||||||
const errorText = await response.body.text();
|
const errorText = await response.body.text();
|
||||||
throw new Error(
|
throw new Error(`Cognee search failed with status ${response.statusCode}: ${errorText}`);
|
||||||
`Cognee search failed with status ${response.statusCode}: ${errorText}`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = (await response.body.json()) as {
|
const data = (await response.body.json()) as unknown;
|
||||||
results: Array<{
|
const results = this.normalizeSearchResults(data);
|
||||||
id: string;
|
|
||||||
text: string;
|
|
||||||
score: number;
|
|
||||||
metadata?: Record<string, unknown>;
|
|
||||||
}>;
|
|
||||||
query: string;
|
|
||||||
search_type: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
results: data.results.map((r) => ({
|
results,
|
||||||
id: r.id,
|
query: req.queryText,
|
||||||
text: r.text,
|
searchType: req.searchType || "GRAPH_COMPLETION",
|
||||||
score: r.score,
|
|
||||||
metadata: r.metadata,
|
|
||||||
})),
|
|
||||||
query: data.query,
|
|
||||||
searchType: data.search_type,
|
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error("Failed to search Cognee", { error });
|
log.error("Failed to search Cognee", { error });
|
||||||
@ -237,10 +229,11 @@ export class CogneeClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async status(): Promise<CogneeStatusResponse> {
|
async status(): Promise<CogneeStatusResponse> {
|
||||||
const url = `${this.baseUrl}/status`;
|
const url = `${this.baseUrl}/health`;
|
||||||
const headers: Record<string, string> = {};
|
const headers: Record<string, string> = {};
|
||||||
if (this.apiKey) {
|
if (this.apiKey) {
|
||||||
|
headers.Authorization = `Bearer ${this.apiKey}`;
|
||||||
headers["X-Api-Key"] = this.apiKey;
|
headers["X-Api-Key"] = this.apiKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -256,29 +249,13 @@ export class CogneeClient {
|
|||||||
|
|
||||||
if (response.statusCode !== 200) {
|
if (response.statusCode !== 200) {
|
||||||
const errorText = await response.body.text();
|
const errorText = await response.body.text();
|
||||||
throw new Error(
|
throw new Error(`Cognee status failed with status ${response.statusCode}: ${errorText}`);
|
||||||
`Cognee status failed with status ${response.statusCode}: ${errorText}`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = (await response.body.json()) as {
|
const data = (await response.body.json()) as { status?: string };
|
||||||
status: string;
|
|
||||||
version?: string;
|
|
||||||
datasets?: Array<{
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
document_count?: number;
|
|
||||||
}>;
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status: data.status,
|
status: data.status || "healthy",
|
||||||
version: data.version,
|
|
||||||
datasets: data.datasets?.map((d) => ({
|
|
||||||
id: d.id,
|
|
||||||
name: d.name,
|
|
||||||
documentCount: d.document_count,
|
|
||||||
})),
|
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error("Failed to get Cognee status", { error });
|
log.error("Failed to get Cognee status", { error });
|
||||||
@ -288,7 +265,7 @@ export class CogneeClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async healthCheck(): Promise<boolean> {
|
async healthCheck(): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
await this.status();
|
await this.status();
|
||||||
return true;
|
return true;
|
||||||
@ -296,4 +273,56 @@ export class CogneeClient {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private mapSearchType(type?: CogneeSearchRequest["searchType"]): CogneeSearchApiType {
|
||||||
|
switch (type) {
|
||||||
|
case "chunks":
|
||||||
|
return "CHUNKS";
|
||||||
|
case "summaries":
|
||||||
|
return "SUMMARIES";
|
||||||
|
case "GRAPH_COMPLETION":
|
||||||
|
default:
|
||||||
|
return "GRAPH_COMPLETION";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private normalizeSearchResults(data: unknown): CogneeSearchResult[] {
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
return data.map((item, index) => {
|
||||||
|
if (typeof item === "string") {
|
||||||
|
return { id: `result-${index}`, text: item, score: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item && typeof item === "object") {
|
||||||
|
const record = item as Record<string, unknown>;
|
||||||
|
const raw =
|
||||||
|
record.search_result ?? record.result ?? record.context ?? record.text ?? record;
|
||||||
|
const text = typeof raw === "string" ? raw : JSON.stringify(raw, null, 2);
|
||||||
|
const metadata =
|
||||||
|
record.dataset_name || record.dataset_id
|
||||||
|
? {
|
||||||
|
datasetName: record.dataset_name,
|
||||||
|
datasetId: record.dataset_id,
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
return { id: `result-${index}`, text, score: 0, metadata };
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: `result-${index}`,
|
||||||
|
text: String(item),
|
||||||
|
score: 0,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data && typeof data === "object" && "results" in data) {
|
||||||
|
const results = (data as { results?: unknown }).results;
|
||||||
|
if (Array.isArray(results)) {
|
||||||
|
return this.normalizeSearchResults(results);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,8 +2,8 @@ import { describe, expect, it, vi, beforeEach } from "vitest";
|
|||||||
import { CogneeMemoryProvider } from "./cognee-provider.js";
|
import { CogneeMemoryProvider } from "./cognee-provider.js";
|
||||||
import type { ClawdbotConfig } from "../config/config.js";
|
import type { ClawdbotConfig } from "../config/config.js";
|
||||||
|
|
||||||
vi.mock("./cognee-client.js", () => ({
|
vi.mock("./cognee-client.js", () => ({
|
||||||
CogneeClient: vi.fn().mockImplementation(() => ({
|
CogneeClient: vi.fn().mockImplementation(() => ({
|
||||||
healthCheck: vi.fn().mockResolvedValue(true),
|
healthCheck: vi.fn().mockResolvedValue(true),
|
||||||
add: vi.fn().mockResolvedValue({
|
add: vi.fn().mockResolvedValue({
|
||||||
datasetId: "test-dataset-id",
|
datasetId: "test-dataset-id",
|
||||||
@ -27,7 +27,7 @@ vi.mock("./cognee-client.js", () => ({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
query: "test query",
|
query: "test query",
|
||||||
searchType: "insights",
|
searchType: "GRAPH_COMPLETION",
|
||||||
}),
|
}),
|
||||||
status: vi.fn().mockResolvedValue({
|
status: vi.fn().mockResolvedValue({
|
||||||
status: "healthy",
|
status: "healthy",
|
||||||
@ -43,13 +43,13 @@ vi.mock("./cognee-client.js", () => ({
|
|||||||
})),
|
})),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock("./internal.js", () => ({
|
vi.mock("./internal.js", () => ({
|
||||||
listMemoryFiles: vi.fn().mockResolvedValue([]),
|
listMemoryFiles: vi.fn().mockResolvedValue([]),
|
||||||
buildFileEntry: vi.fn(),
|
buildFileEntry: vi.fn(),
|
||||||
hashText: vi.fn().mockReturnValue("test-hash"),
|
hashText: vi.fn().mockReturnValue("test-hash"),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock("node:fs/promises", () => ({
|
vi.mock("node:fs/promises", () => ({
|
||||||
default: {
|
default: {
|
||||||
readdir: vi.fn().mockResolvedValue([]),
|
readdir: vi.fn().mockResolvedValue([]),
|
||||||
readFile: vi.fn().mockResolvedValue("Test file content"),
|
readFile: vi.fn().mockResolvedValue("Test file content"),
|
||||||
@ -57,7 +57,7 @@ vi.mock("node:fs/promises", () => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe("CogneeMemoryProvider", () => {
|
describe("CogneeMemoryProvider", () => {
|
||||||
const mockConfig: ClawdbotConfig = {
|
const mockConfig: ClawdbotConfig = {
|
||||||
agents: {
|
agents: {
|
||||||
defaults: {
|
defaults: {
|
||||||
@ -66,18 +66,18 @@ describe("CogneeMemoryProvider", () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("constructor", () => {
|
describe("constructor", () => {
|
||||||
it("should initialize with default configuration", () => {
|
it("should initialize with default configuration", () => {
|
||||||
const provider = new CogneeMemoryProvider(mockConfig, "test-agent", ["memory"]);
|
const provider = new CogneeMemoryProvider(mockConfig, "test-agent", ["memory"]);
|
||||||
|
|
||||||
expect(provider).toBeDefined();
|
expect(provider).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should initialize with custom configuration", () => {
|
it("should initialize with custom configuration", () => {
|
||||||
const provider = new CogneeMemoryProvider(mockConfig, "test-agent", ["memory"], {
|
const provider = new CogneeMemoryProvider(mockConfig, "test-agent", ["memory"], {
|
||||||
baseUrl: "http://custom:8000",
|
baseUrl: "http://custom:8000",
|
||||||
apiKey: "custom-key",
|
apiKey: "custom-key",
|
||||||
@ -90,8 +90,8 @@ describe("CogneeMemoryProvider", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("healthCheck", () => {
|
describe("healthCheck", () => {
|
||||||
it("should perform health check", async () => {
|
it("should perform health check", async () => {
|
||||||
const provider = new CogneeMemoryProvider(mockConfig, "test-agent", ["memory"]);
|
const provider = new CogneeMemoryProvider(mockConfig, "test-agent", ["memory"]);
|
||||||
|
|
||||||
const healthy = await provider.healthCheck();
|
const healthy = await provider.healthCheck();
|
||||||
@ -100,8 +100,8 @@ describe("CogneeMemoryProvider", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("search", () => {
|
describe("search", () => {
|
||||||
it("should search and transform results", async () => {
|
it("should search and transform results", async () => {
|
||||||
const provider = new CogneeMemoryProvider(mockConfig, "test-agent", ["memory"]);
|
const provider = new CogneeMemoryProvider(mockConfig, "test-agent", ["memory"]);
|
||||||
|
|
||||||
const results = await provider.search("test query");
|
const results = await provider.search("test query");
|
||||||
@ -115,13 +115,10 @@ describe("CogneeMemoryProvider", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should respect maxResults setting", async () => {
|
it("should respect maxResults setting", async () => {
|
||||||
const provider = new CogneeMemoryProvider(
|
const provider = new CogneeMemoryProvider(mockConfig, "test-agent", ["memory"], {
|
||||||
mockConfig,
|
maxResults: 5,
|
||||||
"test-agent",
|
});
|
||||||
["memory"],
|
|
||||||
{ maxResults: 5 },
|
|
||||||
);
|
|
||||||
|
|
||||||
const results = await provider.search("test query");
|
const results = await provider.search("test query");
|
||||||
|
|
||||||
@ -129,16 +126,16 @@ describe("CogneeMemoryProvider", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("cognify", () => {
|
describe("cognify", () => {
|
||||||
it("should run cognify", async () => {
|
it("should run cognify", async () => {
|
||||||
const provider = new CogneeMemoryProvider(mockConfig, "test-agent", ["memory"]);
|
const provider = new CogneeMemoryProvider(mockConfig, "test-agent", ["memory"]);
|
||||||
|
|
||||||
await expect(provider.cognify()).resolves.not.toThrow();
|
await expect(provider.cognify()).resolves.not.toThrow();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("getStatus", () => {
|
describe("getStatus", () => {
|
||||||
it("should return status information", async () => {
|
it("should return status information", async () => {
|
||||||
const provider = new CogneeMemoryProvider(mockConfig, "test-agent", ["memory"]);
|
const provider = new CogneeMemoryProvider(mockConfig, "test-agent", ["memory"]);
|
||||||
|
|
||||||
const status = await provider.getStatus();
|
const status = await provider.getStatus();
|
||||||
|
|||||||
@ -5,22 +5,20 @@ import { resolveAgentWorkspaceDir } from "../agents/agent-scope.js";
|
|||||||
import { resolveSessionTranscriptsDirForAgent } from "../config/sessions/paths.js";
|
import { resolveSessionTranscriptsDirForAgent } from "../config/sessions/paths.js";
|
||||||
import { createSubsystemLogger } from "../logging/subsystem.js";
|
import { createSubsystemLogger } from "../logging/subsystem.js";
|
||||||
import type { MemorySearchResult } from "./index.js";
|
import type { MemorySearchResult } from "./index.js";
|
||||||
import {
|
import { CogneeClient, type CogneeClientConfig, type CogneeSearchResult } from "./cognee-client.js";
|
||||||
CogneeClient,
|
|
||||||
type CogneeClientConfig,
|
|
||||||
type CogneeSearchResult,
|
|
||||||
} from "./cognee-client.js";
|
|
||||||
import {
|
import {
|
||||||
buildFileEntry,
|
buildFileEntry,
|
||||||
hashText,
|
hashText,
|
||||||
listMemoryFiles,
|
listMemoryFiles,
|
||||||
|
isMemoryPath,
|
||||||
|
normalizeRelPath,
|
||||||
type MemoryFileEntry,
|
type MemoryFileEntry,
|
||||||
} from "./internal.js";
|
} from "./internal.js";
|
||||||
|
|
||||||
const log = createSubsystemLogger("cognee-provider");
|
const log = createSubsystemLogger("cognee-provider");
|
||||||
|
|
||||||
const DEFAULT_DATASET_NAME = "clawdbot";
|
const DEFAULT_DATASET_NAME = "clawdbot";
|
||||||
const DEFAULT_SEARCH_TYPE = "insights";
|
const DEFAULT_SEARCH_TYPE = "GRAPH_COMPLETION";
|
||||||
const DEFAULT_MAX_RESULTS = 6;
|
const DEFAULT_MAX_RESULTS = 6;
|
||||||
const DEFAULT_TIMEOUT_SECONDS = 30;
|
const DEFAULT_TIMEOUT_SECONDS = 30;
|
||||||
const DEFAULT_AUTO_COGNIFY = true;
|
const DEFAULT_AUTO_COGNIFY = true;
|
||||||
@ -31,7 +29,7 @@ export type CogneeProviderConfig = {
|
|||||||
baseUrl?: string;
|
baseUrl?: string;
|
||||||
apiKey?: string;
|
apiKey?: string;
|
||||||
datasetName?: string;
|
datasetName?: string;
|
||||||
searchType?: "insights" | "chunks" | "summaries";
|
searchType?: "GRAPH_COMPLETION" | "chunks" | "summaries";
|
||||||
maxResults?: number;
|
maxResults?: number;
|
||||||
timeoutSeconds?: number;
|
timeoutSeconds?: number;
|
||||||
autoCognify?: boolean;
|
autoCognify?: boolean;
|
||||||
@ -46,18 +44,18 @@ export class CogneeMemoryProvider {
|
|||||||
private readonly agentId: string;
|
private readonly agentId: string;
|
||||||
private readonly workspaceDir: string;
|
private readonly workspaceDir: string;
|
||||||
private readonly datasetName: string;
|
private readonly datasetName: string;
|
||||||
private readonly searchType: "insights" | "chunks" | "summaries";
|
private readonly searchType: "GRAPH_COMPLETION" | "chunks" | "summaries";
|
||||||
private readonly maxResults: number;
|
private readonly maxResults: number;
|
||||||
private readonly autoCognify: boolean;
|
private readonly autoCognify: boolean;
|
||||||
private readonly cognifyBatchSize: number;
|
private readonly cognifyBatchSize: number;
|
||||||
private readonly sources: Set<CogneeMemorySource>;
|
private readonly sources: Set<CogneeMemorySource>;
|
||||||
private datasetId?: string;
|
private datasetId?: string;
|
||||||
private syncedFiles = new Map<string, string>(); // path -> hash
|
private syncedFiles = new Map<string, string>(); // path -> hash
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
cfg: ClawdbotConfig,
|
cfg: ClawdbotConfig,
|
||||||
agentId: string,
|
agentId: string,
|
||||||
sources: Array<CogneeMemorySource>,
|
sources: Array<CogneeMemorySource>,
|
||||||
config: CogneeProviderConfig = {},
|
config: CogneeProviderConfig = {},
|
||||||
) {
|
) {
|
||||||
const timeoutMs = (config.timeoutSeconds || DEFAULT_TIMEOUT_SECONDS) * 1000;
|
const timeoutMs = (config.timeoutSeconds || DEFAULT_TIMEOUT_SECONDS) * 1000;
|
||||||
@ -86,11 +84,15 @@ export class CogneeMemoryProvider {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async healthCheck(): Promise<boolean> {
|
async healthCheck(): Promise<boolean> {
|
||||||
return await this.client.healthCheck();
|
return await this.client.healthCheck();
|
||||||
}
|
}
|
||||||
|
|
||||||
async sync(): Promise<void> {
|
async sync(params?: {
|
||||||
|
reason?: string;
|
||||||
|
force?: boolean;
|
||||||
|
progress?: (update: { completed: number; total: number; label?: string }) => void;
|
||||||
|
}): Promise<void> {
|
||||||
log.info("Starting Cognee memory sync", { agentId: this.agentId });
|
log.info("Starting Cognee memory sync", { agentId: this.agentId });
|
||||||
|
|
||||||
let addedCount = 0;
|
let addedCount = 0;
|
||||||
@ -108,7 +110,7 @@ export class CogneeMemoryProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Run cognify if auto-enabled and files were added
|
// Run cognify if auto-enabled and files were added
|
||||||
if (this.autoCognify && addedCount > 0) {
|
if (this.autoCognify && addedCount > 0) {
|
||||||
log.info("Running cognify after sync", { addedCount });
|
log.info("Running cognify after sync", { addedCount });
|
||||||
await this.cognify();
|
await this.cognify();
|
||||||
}
|
}
|
||||||
@ -117,9 +119,20 @@ export class CogneeMemoryProvider {
|
|||||||
agentId: this.agentId,
|
agentId: this.agentId,
|
||||||
addedCount,
|
addedCount,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (params?.progress) {
|
||||||
|
params.progress({
|
||||||
|
completed: addedCount,
|
||||||
|
total: addedCount,
|
||||||
|
label: params.reason ? `Synced (${params.reason})` : "Synced",
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async search(query: string): Promise<MemorySearchResult[]> {
|
async search(
|
||||||
|
query: string,
|
||||||
|
opts?: { maxResults?: number; minScore?: number; sessionKey?: string },
|
||||||
|
): Promise<MemorySearchResult[]> {
|
||||||
log.debug("Searching Cognee memory", { query, searchType: this.searchType });
|
log.debug("Searching Cognee memory", { query, searchType: this.searchType });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -129,9 +142,12 @@ export class CogneeMemoryProvider {
|
|||||||
datasetIds: this.datasetId ? [this.datasetId] : undefined,
|
datasetIds: this.datasetId ? [this.datasetId] : undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const maxResults = opts?.maxResults ?? this.maxResults;
|
||||||
|
const minScore = opts?.minScore ?? 0;
|
||||||
const results: MemorySearchResult[] = response.results
|
const results: MemorySearchResult[] = response.results
|
||||||
.slice(0, this.maxResults)
|
.map((r) => this.transformResult(r))
|
||||||
.map((r) => this.transformResult(r));
|
.filter((r) => r.score >= minScore)
|
||||||
|
.slice(0, maxResults);
|
||||||
|
|
||||||
log.debug("Cognee search completed", { query, resultCount: results.length });
|
log.debug("Cognee search completed", { query, resultCount: results.length });
|
||||||
return results;
|
return results;
|
||||||
@ -141,7 +157,7 @@ export class CogneeMemoryProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async cognify(): Promise<void> {
|
async cognify(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const response = await this.client.cognify({
|
const response = await this.client.cognify({
|
||||||
datasetIds: this.datasetId ? [this.datasetId] : undefined,
|
datasetIds: this.datasetId ? [this.datasetId] : undefined,
|
||||||
@ -153,16 +169,16 @@ export class CogneeMemoryProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getStatus(): Promise<{
|
async getStatus(): Promise<{
|
||||||
connected: boolean;
|
connected: boolean;
|
||||||
datasetId?: string;
|
datasetId?: string;
|
||||||
datasetName: string;
|
datasetName: string;
|
||||||
syncedFileCount: number;
|
syncedFileCount: number;
|
||||||
version?: string;
|
version?: string;
|
||||||
}> {
|
}> {
|
||||||
try {
|
try {
|
||||||
const status = await this.client.status();
|
const status = await this.client.status();
|
||||||
const dataset = status.datasets?.find((d) => d.name === this.datasetName);
|
const dataset = status.datasets?.find((d) => d.name === this.datasetName);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
connected: true,
|
connected: true,
|
||||||
@ -181,7 +197,98 @@ export class CogneeMemoryProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async collectMemoryFiles(): Promise<MemoryFileEntry[]> {
|
status(): {
|
||||||
|
files: number;
|
||||||
|
chunks: number;
|
||||||
|
dirty: boolean;
|
||||||
|
workspaceDir: string;
|
||||||
|
dbPath: string;
|
||||||
|
provider: string;
|
||||||
|
model: string;
|
||||||
|
requestedProvider: string;
|
||||||
|
sources: Array<CogneeMemorySource>;
|
||||||
|
sourceCounts: Array<{ source: CogneeMemorySource; files: number; chunks: number }>;
|
||||||
|
cache?: { enabled: boolean; entries?: number; maxEntries?: number };
|
||||||
|
fts?: { enabled: boolean; available: boolean; error?: string };
|
||||||
|
fallback?: { from: string; reason?: string };
|
||||||
|
vector?: {
|
||||||
|
enabled: boolean;
|
||||||
|
available?: boolean;
|
||||||
|
extensionPath?: string;
|
||||||
|
loadError?: string;
|
||||||
|
dims?: number;
|
||||||
|
};
|
||||||
|
batch?: {
|
||||||
|
enabled: boolean;
|
||||||
|
failures: number;
|
||||||
|
limit: number;
|
||||||
|
wait: boolean;
|
||||||
|
concurrency: number;
|
||||||
|
pollIntervalMs: number;
|
||||||
|
timeoutMs: number;
|
||||||
|
lastError?: string;
|
||||||
|
lastProvider?: string;
|
||||||
|
};
|
||||||
|
} {
|
||||||
|
const sources = Array.from(this.sources);
|
||||||
|
const files = this.syncedFiles.size;
|
||||||
|
return {
|
||||||
|
files,
|
||||||
|
chunks: 0,
|
||||||
|
dirty: false,
|
||||||
|
workspaceDir: this.workspaceDir,
|
||||||
|
dbPath: "cognee",
|
||||||
|
provider: "cognee",
|
||||||
|
model: this.searchType,
|
||||||
|
requestedProvider: "cognee",
|
||||||
|
sources,
|
||||||
|
sourceCounts: sources.map((source) => ({ source, files, chunks: 0 })),
|
||||||
|
vector: {
|
||||||
|
enabled: false,
|
||||||
|
available: false,
|
||||||
|
},
|
||||||
|
fts: {
|
||||||
|
enabled: false,
|
||||||
|
available: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async readFile(params: {
|
||||||
|
relPath: string;
|
||||||
|
from?: number;
|
||||||
|
lines?: number;
|
||||||
|
}): Promise<{ text: string; path: string }> {
|
||||||
|
const relPath = normalizeRelPath(params.relPath);
|
||||||
|
if (!relPath || !isMemoryPath(relPath)) {
|
||||||
|
throw new Error("path required");
|
||||||
|
}
|
||||||
|
const absPath = path.resolve(this.workspaceDir, relPath);
|
||||||
|
if (!absPath.startsWith(this.workspaceDir)) {
|
||||||
|
throw new Error("path escapes workspace");
|
||||||
|
}
|
||||||
|
const content = await fs.readFile(absPath, "utf-8");
|
||||||
|
if (!params.from && !params.lines) {
|
||||||
|
return { text: content, path: relPath };
|
||||||
|
}
|
||||||
|
const lines = content.split("\n");
|
||||||
|
const start = Math.max(1, params.from ?? 1);
|
||||||
|
const count = Math.max(1, params.lines ?? lines.length);
|
||||||
|
const slice = lines.slice(start - 1, start - 1 + count);
|
||||||
|
return { text: slice.join("\n"), path: relPath };
|
||||||
|
}
|
||||||
|
|
||||||
|
async probeEmbeddingAvailability(): Promise<{ ok: boolean; error?: string }> {
|
||||||
|
return { ok: false, error: "Cognee provider does not use embeddings." };
|
||||||
|
}
|
||||||
|
|
||||||
|
async probeVectorAvailability(): Promise<boolean> {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async close(): Promise<void> {}
|
||||||
|
|
||||||
|
private async collectMemoryFiles(): Promise<MemoryFileEntry[]> {
|
||||||
const files: MemoryFileEntry[] = [];
|
const files: MemoryFileEntry[] = [];
|
||||||
const memoryPaths = await listMemoryFiles(this.workspaceDir);
|
const memoryPaths = await listMemoryFiles(this.workspaceDir);
|
||||||
|
|
||||||
@ -197,12 +304,9 @@ export class CogneeMemoryProvider {
|
|||||||
return files;
|
return files;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async collectSessionFiles(): Promise<MemoryFileEntry[]> {
|
private async collectSessionFiles(): Promise<MemoryFileEntry[]> {
|
||||||
const files: MemoryFileEntry[] = [];
|
const files: MemoryFileEntry[] = [];
|
||||||
const transcriptsDir = resolveSessionTranscriptsDirForAgent(
|
const transcriptsDir = resolveSessionTranscriptsDirForAgent(this.agentId);
|
||||||
this.cfg,
|
|
||||||
this.agentId,
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const entries = await fs.readdir(transcriptsDir, { withFileTypes: true });
|
const entries = await fs.readdir(transcriptsDir, { withFileTypes: true });
|
||||||
@ -227,20 +331,17 @@ export class CogneeMemoryProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.debug("No session transcripts directory", { transcriptsDir });
|
log.debug("No session transcripts directory", { transcriptsDir, error });
|
||||||
}
|
}
|
||||||
|
|
||||||
return files;
|
return files;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async syncFiles(
|
private async syncFiles(files: MemoryFileEntry[], source: CogneeMemorySource): Promise<number> {
|
||||||
files: MemoryFileEntry[],
|
|
||||||
source: CogneeMemorySource,
|
|
||||||
): Promise<number> {
|
|
||||||
let addedCount = 0;
|
let addedCount = 0;
|
||||||
const batchSize = this.cognifyBatchSize;
|
const batchSize = this.cognifyBatchSize;
|
||||||
|
|
||||||
for (let i = 0; i < files.length; i += batchSize) {
|
for (let i = 0; i < files.length; i += batchSize) {
|
||||||
const batch = files.slice(i, i + batchSize);
|
const batch = files.slice(i, i + batchSize);
|
||||||
|
|
||||||
for (const file of batch) {
|
for (const file of batch) {
|
||||||
@ -295,7 +396,7 @@ export class CogneeMemoryProvider {
|
|||||||
|
|
||||||
// Truncate snippet to max chars
|
// Truncate snippet to max chars
|
||||||
let snippet = result.text;
|
let snippet = result.text;
|
||||||
if (snippet.length > SNIPPET_MAX_CHARS) {
|
if (snippet.length > SNIPPET_MAX_CHARS) {
|
||||||
snippet = snippet.slice(0, SNIPPET_MAX_CHARS) + "...";
|
snippet = snippet.slice(0, SNIPPET_MAX_CHARS) + "...";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -313,9 +414,9 @@ export class CogneeMemoryProvider {
|
|||||||
export async function createCogneeProvider(
|
export async function createCogneeProvider(
|
||||||
cfg: ClawdbotConfig,
|
cfg: ClawdbotConfig,
|
||||||
agentId: string,
|
agentId: string,
|
||||||
sources: Array<CogneeMemorySource>,
|
sources: Array<CogneeMemorySource>,
|
||||||
config: CogneeProviderConfig = {},
|
config: CogneeProviderConfig = {},
|
||||||
): Promise<CogneeMemoryProvider> {
|
): Promise<CogneeMemoryProvider> {
|
||||||
const provider = new CogneeMemoryProvider(cfg, agentId, sources, config);
|
const provider = new CogneeMemoryProvider(cfg, agentId, sources, config);
|
||||||
|
|
||||||
// Verify connection
|
// Verify connection
|
||||||
|
|||||||
@ -183,13 +183,15 @@ export class MemoryIndexManager {
|
|||||||
const key = `${agentId}:${workspaceDir}:${JSON.stringify(settings)}`;
|
const key = `${agentId}:${workspaceDir}:${JSON.stringify(settings)}`;
|
||||||
const existing = INDEX_CACHE.get(key);
|
const existing = INDEX_CACHE.get(key);
|
||||||
if (existing) return existing;
|
if (existing) return existing;
|
||||||
|
const provider = settings.provider === "cognee" ? "auto" : settings.provider;
|
||||||
|
const fallback = settings.fallback === "cognee" ? "none" : settings.fallback;
|
||||||
const providerResult = await createEmbeddingProvider({
|
const providerResult = await createEmbeddingProvider({
|
||||||
config: cfg,
|
config: cfg,
|
||||||
agentDir: resolveAgentDir(cfg, agentId),
|
agentDir: resolveAgentDir(cfg, agentId),
|
||||||
provider: settings.provider,
|
provider,
|
||||||
remote: settings.remote,
|
remote: settings.remote,
|
||||||
model: settings.model,
|
model: settings.model,
|
||||||
fallback: settings.fallback,
|
fallback,
|
||||||
local: settings.local,
|
local: settings.local,
|
||||||
});
|
});
|
||||||
const manager = new MemoryIndexManager({
|
const manager = new MemoryIndexManager({
|
||||||
@ -197,7 +199,7 @@ export class MemoryIndexManager {
|
|||||||
cfg,
|
cfg,
|
||||||
agentId,
|
agentId,
|
||||||
workspaceDir,
|
workspaceDir,
|
||||||
settings,
|
settings: { ...settings, provider, fallback },
|
||||||
providerResult,
|
providerResult,
|
||||||
});
|
});
|
||||||
INDEX_CACHE.set(key, manager);
|
INDEX_CACHE.set(key, manager);
|
||||||
@ -1261,7 +1263,7 @@ export class MemoryIndexManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async activateFallbackProvider(reason: string): Promise<boolean> {
|
private async activateFallbackProvider(reason: string): Promise<boolean> {
|
||||||
const fallback = this.settings.fallback;
|
const fallback = this.settings.fallback === "cognee" ? "none" : this.settings.fallback;
|
||||||
if (!fallback || fallback === "none" || fallback === this.provider.id) return false;
|
if (!fallback || fallback === "none" || fallback === this.provider.id) return false;
|
||||||
if (this.fallbackFrom) return false;
|
if (this.fallbackFrom) return false;
|
||||||
const fallbackFrom = this.provider.id as "openai" | "gemini" | "local";
|
const fallbackFrom = this.provider.id as "openai" | "gemini" | "local";
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user