Clean and organize project structure

Major reorganization:
- Created scripts/ directory for all utility scripts
- Created config/ directory for configuration files
- Moved all test files to tests/ directory
- Updated all script paths to work with new structure
- Updated README.md with new project structure diagram

New structure:
├── src/          # Source code (API + MCP)
├── scripts/      # Utility scripts (start-*.sh, docs_server.py, etc.)
├── tests/        # All test files and debug utilities
├── config/       # Configuration files (JSON, Caddy config)
├── docs/         # Documentation website
└── logs/         # Log files

All scripts updated to use relative paths from project root.
Documentation updated with new folder structure.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Docker Config Backup
2025-07-17 14:11:08 +02:00
parent b74c7d79ca
commit f0db3e5546
25 changed files with 209 additions and 383 deletions

View File

@@ -34,13 +34,13 @@ LangMem uses a hybrid approach combining:
```bash
git clone <repository>
cd langmem-project
cd langmem
```
### 2. Start Development Environment
```bash
./start-dev.sh
./scripts/start-dev.sh
```
This will:
@@ -52,7 +52,7 @@ This will:
### 3. Test the API
```bash
./test.sh
./scripts/test.sh
```
## API Endpoints
@@ -139,39 +139,60 @@ NEO4J_PASSWORD=langmem_neo4j_password
### Project Structure
```
langmem-project/
├── src/
── api/
── main.py # Main API application
├── tests/
├── test_api.py # API unit tests
── test_integration.py # Integration tests
└── conftest.py # Test configuration
├── docker-compose.yml # Docker services
├── Dockerfile # API container
├── requirements.txt # Python dependencies
├── start-dev.sh # Development startup
├── test.sh # Test runner
└── README.md # This file
langmem/
├── src/ # Source code
── api/ # FastAPI application
── main.py # Main API server
│ │ ├── fact_extraction.py # Fact-based memory logic
│ └── memory_manager.py # Memory management
── mcp/ # Model Context Protocol
├── server.py # MCP server for Claude Code
│ └── requirements.txt
├── scripts/ # Utility scripts
│ ├── start-dev.sh # Development startup
├── start-mcp-server.sh # MCP server startup
│ ├── start-docs-server.sh # Documentation server
│ ├── docs_server.py # Authenticated docs server
│ ├── get-claude-token.py # Matrix setup utility
│ └── test.sh # Test runner
├── tests/ # Test suite
│ ├── test_api.py # API tests
│ ├── test_integration.py # Integration tests
│ ├── test_fact_based_memory.py # Fact extraction tests
│ ├── debug_*.py # Debug utilities
│ └── conftest.py # Test configuration
├── docs/ # Documentation website
│ ├── index.html # Main documentation
│ ├── api/ # API documentation
│ ├── architecture/ # Architecture docs
│ └── implementation/ # Setup guides
├── config/ # Configuration files
│ ├── mcp_config.json # MCP server config
│ ├── claude-matrix-config.json # Matrix setup
│ └── caddyfile-docs-update.txt # Caddy config
├── docker-compose.yml # Docker services
├── Dockerfile # API container
├── requirements.txt # Python dependencies
└── README.md # This file
```
### Running Tests
```bash
# All tests
./test.sh all
./scripts/test.sh all
# Unit tests only
./test.sh unit
./scripts/test.sh unit
# Integration tests only
./test.sh integration
./scripts/test.sh integration
# Quick tests (no slow tests)
./test.sh quick
./scripts/test.sh quick
# With coverage
./test.sh coverage
./scripts/test.sh coverage
```
### Local Development
@@ -289,10 +310,10 @@ Start the authenticated documentation server:
```bash
# Start documentation server on port 8080 (default)
./start-docs-server.sh
./scripts/start-docs-server.sh
# Or specify a custom port
./start-docs-server.sh 8090
./scripts/start-docs-server.sh 8090
```
**Access Credentials:**
@@ -310,7 +331,7 @@ Start the authenticated documentation server:
You can also run the documentation server directly:
```bash
python3 docs_server.py [port]
python3 scripts/docs_server.py [port]
```
Then visit: `http://localhost:8080` (or your specified port)

View File

@@ -79,7 +79,7 @@ echo " - Health Check: http://localhost:8765/health"
echo ""
echo "🔧 Useful Commands:"
echo " - View logs: docker compose logs -f langmem-api"
echo " - Run tests: ./test.sh"
echo " - Run tests: ./scripts/test.sh"
echo " - Stop services: docker compose down"
echo " - Restart API: docker compose restart langmem-api"
echo ""

View File

@@ -5,8 +5,11 @@ echo "🚀 Starting LangMem Documentation Server..."
echo "🔐 Authentication enabled with basic auth"
echo ""
# Change to project root
cd "$(dirname "$0")/.."
# Default port
PORT=${1:-8080}
# Start the authenticated server
python3 docs_server.py $PORT
python3 scripts/docs_server.py $PORT

View File

@@ -5,6 +5,9 @@
echo "🚀 Starting LangMem MCP Server..."
# Change to project root
cd "$(dirname "$0")/.."
# Check if virtual environment exists
if [ ! -d "venv" ]; then
echo "📦 Creating virtual environment..."
@@ -36,8 +39,8 @@ export LANGMEM_API_KEY="langmem_api_key_2025"
# Start MCP server
echo "🏃 Starting MCP server..."
echo "🔗 Connect from Claude Code using:"
echo " Server command: python /home/klas/langmem-project/src/mcp/server.py"
echo " Working directory: /home/klas/langmem-project"
echo " Server command: python /home/klas/langmem/src/mcp/server.py"
echo " Working directory: /home/klas/langmem"
echo ""
echo "📖 Available tools:"
echo " - store_memory: Store memories with AI relationship extraction"

View File

@@ -6,9 +6,12 @@ set -e
echo "🧪 Running LangMem API Tests"
# Change to project root
cd "$(dirname "$0")/.."
# Check if services are running
if ! curl -s http://localhost:8765/health > /dev/null; then
echo "❌ LangMem API is not running. Please start with ./start-dev.sh first."
echo "❌ LangMem API is not running. Please start with ./scripts/start-dev.sh first."
exit 1
fi
@@ -40,7 +43,7 @@ case "${1:-all}" in
;;
*)
echo "❌ Unknown test type: $1"
echo "Usage: ./test.sh [unit|integration|all|quick|coverage]"
echo "Usage: ./scripts/test.sh [unit|integration|all|quick|coverage]"
exit 1
;;
esac

View File

@@ -1,169 +0,0 @@
#!/usr/bin/env python3
"""
Test the LangMem API endpoints
"""
import asyncio
import httpx
import json
from uuid import uuid4
# Configuration
API_BASE_URL = "http://localhost:8765"
API_KEY = "langmem_api_key_2025"
TEST_USER_ID = f"test_user_{uuid4()}"
async def test_api_endpoints():
"""Test all API endpoints"""
print("🧪 Testing LangMem API Endpoints")
print("=" * 50)
headers = {"Authorization": f"Bearer {API_KEY}"}
async with httpx.AsyncClient() as client:
# Test 1: Root endpoint
print("\n1. Testing root endpoint...")
try:
response = await client.get(f"{API_BASE_URL}/")
print(f"✅ Root endpoint: {response.status_code}")
print(f" Response: {response.json()}")
except Exception as e:
print(f"❌ Root endpoint failed: {e}")
# Test 2: Health check
print("\n2. Testing health check...")
try:
response = await client.get(f"{API_BASE_URL}/health")
print(f"✅ Health check: {response.status_code}")
data = response.json()
print(f" Overall status: {data.get('status')}")
for service, status in data.get('services', {}).items():
print(f" {service}: {status}")
except Exception as e:
print(f"❌ Health check failed: {e}")
# Test 3: Store memory
print("\n3. Testing memory storage...")
try:
memory_data = {
"content": "FastAPI is a modern web framework for building APIs with Python",
"user_id": TEST_USER_ID,
"session_id": "test_session_1",
"metadata": {
"category": "programming",
"language": "python",
"framework": "fastapi"
}
}
response = await client.post(
f"{API_BASE_URL}/v1/memories/store",
json=memory_data,
headers=headers,
timeout=30.0
)
if response.status_code == 200:
data = response.json()
memory_id = data["id"]
print(f"✅ Memory stored successfully: {memory_id}")
print(f" Status: {data['status']}")
else:
print(f"❌ Memory storage failed: {response.status_code}")
print(f" Response: {response.text}")
return
except Exception as e:
print(f"❌ Memory storage failed: {e}")
return
# Test 4: Search memories
print("\n4. Testing memory search...")
try:
search_data = {
"query": "Python web framework",
"user_id": TEST_USER_ID,
"limit": 5,
"threshold": 0.5
}
response = await client.post(
f"{API_BASE_URL}/v1/memories/search",
json=search_data,
headers=headers,
timeout=30.0
)
if response.status_code == 200:
data = response.json()
print(f"✅ Memory search successful")
print(f" Found {data['total_count']} memories")
for memory in data['memories']:
print(f" - {memory['content'][:50]}... (similarity: {memory['similarity']:.3f})")
else:
print(f"❌ Memory search failed: {response.status_code}")
print(f" Response: {response.text}")
except Exception as e:
print(f"❌ Memory search failed: {e}")
# Test 5: Retrieve memories for conversation
print("\n5. Testing memory retrieval...")
try:
retrieve_data = {
"messages": [
{"role": "user", "content": "I want to learn about web development"},
{"role": "assistant", "content": "Great! What technology are you interested in?"},
{"role": "user", "content": "I heard Python is good for web APIs"}
],
"user_id": TEST_USER_ID,
"session_id": "test_session_1"
}
response = await client.post(
f"{API_BASE_URL}/v1/memories/retrieve",
json=retrieve_data,
headers=headers,
timeout=30.0
)
if response.status_code == 200:
data = response.json()
print(f"✅ Memory retrieval successful")
print(f" Retrieved {data['total_count']} relevant memories")
for memory in data['memories']:
print(f" - {memory['content'][:50]}... (similarity: {memory['similarity']:.3f})")
else:
print(f"❌ Memory retrieval failed: {response.status_code}")
print(f" Response: {response.text}")
except Exception as e:
print(f"❌ Memory retrieval failed: {e}")
# Test 6: Get user memories
print("\n6. Testing user memory listing...")
try:
response = await client.get(
f"{API_BASE_URL}/v1/memories/users/{TEST_USER_ID}",
headers=headers,
timeout=30.0
)
if response.status_code == 200:
data = response.json()
print(f"✅ User memory listing successful")
print(f" User has {data['total_count']} memories")
for memory in data['memories']:
print(f" - {memory['content'][:50]}... (created: {memory['created_at'][:19]})")
else:
print(f"❌ User memory listing failed: {response.status_code}")
print(f" Response: {response.text}")
except Exception as e:
print(f"❌ User memory listing failed: {e}")
print("\n" + "=" * 50)
print("🎉 API Testing Complete!")
if __name__ == "__main__":
asyncio.run(test_api_endpoints())

View File

@@ -1,204 +1,169 @@
#!/usr/bin/env python3
"""
Test suite for LangMem API
Test the LangMem API endpoints
"""
import asyncio
import json
import pytest
import httpx
import json
from uuid import uuid4
# Configuration
API_BASE_URL = "http://localhost:8765"
API_KEY = "langmem_api_key_2025"
TEST_USER_ID = f"test_user_{uuid4()}"
class TestLangMemAPI:
"""Test suite for LangMem API endpoints"""
async def test_api_endpoints():
"""Test all API endpoints"""
print("🧪 Testing LangMem API Endpoints")
print("=" * 50)
def setup_method(self):
"""Setup test client"""
self.client = httpx.AsyncClient(base_url=API_BASE_URL)
self.headers = {"Authorization": f"Bearer {API_KEY}"}
self.test_user_id = f"test_user_{uuid4()}"
headers = {"Authorization": f"Bearer {API_KEY}"}
async def teardown_method(self):
"""Cleanup test client"""
await self.client.aclose()
async with httpx.AsyncClient() as client:
# Test 1: Root endpoint
print("\n1. Testing root endpoint...")
try:
response = await client.get(f"{API_BASE_URL}/")
print(f"✅ Root endpoint: {response.status_code}")
print(f" Response: {response.json()}")
except Exception as e:
print(f"❌ Root endpoint failed: {e}")
@pytest.mark.asyncio
async def test_root_endpoint(self):
"""Test root endpoint"""
response = await self.client.get("/")
assert response.status_code == 200
data = response.json()
assert data["message"] == "LangMem API - Long-term Memory System"
assert data["version"] == "1.0.0"
# Test 2: Health check
print("\n2. Testing health check...")
try:
response = await client.get(f"{API_BASE_URL}/health")
print(f"✅ Health check: {response.status_code}")
data = response.json()
print(f" Overall status: {data.get('status')}")
for service, status in data.get('services', {}).items():
print(f" {service}: {status}")
except Exception as e:
print(f"❌ Health check failed: {e}")
@pytest.mark.asyncio
async def test_health_check(self):
"""Test health check endpoint"""
response = await self.client.get("/health")
assert response.status_code == 200
data = response.json()
assert "status" in data
assert "services" in data
assert "timestamp" in data
@pytest.mark.asyncio
async def test_store_memory(self):
"""Test storing a memory"""
memory_data = {
"content": "This is a test memory about Python programming",
"user_id": self.test_user_id,
"session_id": "test_session_1",
"metadata": {
"category": "programming",
"language": "python",
"importance": "high"
# Test 3: Store memory
print("\n3. Testing memory storage...")
try:
memory_data = {
"content": "FastAPI is a modern web framework for building APIs with Python",
"user_id": TEST_USER_ID,
"session_id": "test_session_1",
"metadata": {
"category": "programming",
"language": "python",
"framework": "fastapi"
}
}
}
response = await self.client.post(
"/v1/memories/store",
json=memory_data,
headers=self.headers
)
response = await client.post(
f"{API_BASE_URL}/v1/memories/store",
json=memory_data,
headers=headers,
timeout=30.0
)
assert response.status_code == 200
data = response.json()
assert data["status"] == "stored"
assert data["user_id"] == self.test_user_id
assert "id" in data
assert "created_at" in data
if response.status_code == 200:
data = response.json()
memory_id = data["id"]
print(f"✅ Memory stored successfully: {memory_id}")
print(f" Status: {data['status']}")
else:
print(f"❌ Memory storage failed: {response.status_code}")
print(f" Response: {response.text}")
return
return data["id"]
except Exception as e:
print(f"❌ Memory storage failed: {e}")
return
@pytest.mark.asyncio
async def test_search_memories(self):
"""Test searching memories"""
# First store a memory
memory_id = await self.test_store_memory()
# Test 4: Search memories
print("\n4. Testing memory search...")
try:
search_data = {
"query": "Python web framework",
"user_id": TEST_USER_ID,
"limit": 5,
"threshold": 0.5
}
# Wait a moment for indexing
await asyncio.sleep(1)
response = await client.post(
f"{API_BASE_URL}/v1/memories/search",
json=search_data,
headers=headers,
timeout=30.0
)
# Search for the memory
search_data = {
"query": "Python programming",
"user_id": self.test_user_id,
"limit": 10,
"threshold": 0.5,
"include_graph": True
}
if response.status_code == 200:
data = response.json()
print(f"✅ Memory search successful")
print(f" Found {data['total_count']} memories")
for memory in data['memories']:
print(f" - {memory['content'][:50]}... (similarity: {memory['similarity']:.3f})")
else:
print(f"❌ Memory search failed: {response.status_code}")
print(f" Response: {response.text}")
response = await self.client.post(
"/v1/memories/search",
json=search_data,
headers=self.headers
)
except Exception as e:
print(f"❌ Memory search failed: {e}")
assert response.status_code == 200
data = response.json()
assert "memories" in data
assert "context" in data
assert "total_count" in data
assert data["total_count"] > 0
# Test 5: Retrieve memories for conversation
print("\n5. Testing memory retrieval...")
try:
retrieve_data = {
"messages": [
{"role": "user", "content": "I want to learn about web development"},
{"role": "assistant", "content": "Great! What technology are you interested in?"},
{"role": "user", "content": "I heard Python is good for web APIs"}
],
"user_id": TEST_USER_ID,
"session_id": "test_session_1"
}
# Check first memory result
if data["memories"]:
memory = data["memories"][0]
assert "id" in memory
assert "content" in memory
assert "similarity" in memory
assert memory["user_id"] == self.test_user_id
response = await client.post(
f"{API_BASE_URL}/v1/memories/retrieve",
json=retrieve_data,
headers=headers,
timeout=30.0
)
@pytest.mark.asyncio
async def test_retrieve_memories(self):
"""Test retrieving memories for conversation context"""
# Store a memory first
await self.test_store_memory()
if response.status_code == 200:
data = response.json()
print(f"✅ Memory retrieval successful")
print(f" Retrieved {data['total_count']} relevant memories")
for memory in data['memories']:
print(f" - {memory['content'][:50]}... (similarity: {memory['similarity']:.3f})")
else:
print(f"❌ Memory retrieval failed: {response.status_code}")
print(f" Response: {response.text}")
# Wait a moment for indexing
await asyncio.sleep(1)
except Exception as e:
print(f"❌ Memory retrieval failed: {e}")
# Retrieve memories based on conversation
retrieve_data = {
"messages": [
{"role": "user", "content": "I want to learn about Python"},
{"role": "assistant", "content": "Python is a great programming language"},
{"role": "user", "content": "Tell me more about Python programming"}
],
"user_id": self.test_user_id,
"session_id": "test_session_1"
}
# Test 6: Get user memories
print("\n6. Testing user memory listing...")
try:
response = await client.get(
f"{API_BASE_URL}/v1/memories/users/{TEST_USER_ID}",
headers=headers,
timeout=30.0
)
response = await self.client.post(
"/v1/memories/retrieve",
json=retrieve_data,
headers=self.headers
)
if response.status_code == 200:
data = response.json()
print(f"✅ User memory listing successful")
print(f" User has {data['total_count']} memories")
for memory in data['memories']:
print(f" - {memory['content'][:50]}... (created: {memory['created_at'][:19]})")
else:
print(f"❌ User memory listing failed: {response.status_code}")
print(f" Response: {response.text}")
assert response.status_code == 200
data = response.json()
assert "memories" in data
assert "context" in data
assert "total_count" in data
except Exception as e:
print(f"❌ User memory listing failed: {e}")
@pytest.mark.asyncio
async def test_get_user_memories(self):
"""Test getting all memories for a user"""
# Store a memory first
await self.test_store_memory()
response = await self.client.get(
f"/v1/memories/users/{self.test_user_id}",
headers=self.headers
)
assert response.status_code == 200
data = response.json()
assert "memories" in data
assert "total_count" in data
assert data["total_count"] > 0
# Check memory structure
if data["memories"]:
memory = data["memories"][0]
assert "id" in memory
assert "content" in memory
assert "user_id" in memory
assert "created_at" in memory
@pytest.mark.asyncio
async def test_delete_memory(self):
"""Test deleting a memory"""
# Store a memory first
memory_id = await self.test_store_memory()
# Delete the memory
response = await self.client.delete(
f"/v1/memories/{memory_id}",
headers=self.headers
)
assert response.status_code == 200
data = response.json()
assert data["status"] == "deleted"
assert data["id"] == memory_id
@pytest.mark.asyncio
async def test_authentication_required(self):
"""Test that authentication is required"""
response = await self.client.get("/v1/memories/users/test_user")
assert response.status_code == 401
@pytest.mark.asyncio
async def test_invalid_api_key(self):
"""Test invalid API key"""
headers = {"Authorization": "Bearer invalid_key"}
response = await self.client.get("/v1/memories/users/test_user", headers=headers)
assert response.status_code == 401
print("\n" + "=" * 50)
print("🎉 API Testing Complete!")
if __name__ == "__main__":
pytest.main([__file__, "-v"])
asyncio.run(test_api_endpoints())