reorder files

This commit is contained in:
Nicolò Boschi 2025-11-10 15:45:30 +01:00
parent ada9562cc2
commit 646dea89d9
85 changed files with 312 additions and 111 deletions

1
.sesskey Normal file
View file

@ -0,0 +1 @@
49ed5f3e-d51f-4fbe-abdb-c909287df6a0

View file

@ -9,9 +9,6 @@ from pathlib import Path
from benchmarks.common.benchmark_runner import BenchmarkRunner
from memora import TemporalSemanticMemory
# Add parent directory to path for imports
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
import json
from datetime import datetime, timezone
from typing import List, Dict, Any, Tuple, Optional
@ -20,9 +17,7 @@ import pydantic
from openai import AsyncOpenAI
import os
# Import common framework
sys.path.insert(0, str(Path(__file__).parent.parent))
from common.benchmark_runner import BenchmarkDataset, LLMAnswerGenerator, LLMAnswerEvaluator
from benchmarks.common.benchmark_runner import BenchmarkDataset, LLMAnswerGenerator, LLMAnswerEvaluator
from memora.llm_wrapper import LLMConfig
class LoComoDataset(BenchmarkDataset):

View file

@ -9,9 +9,6 @@ from pathlib import Path
from benchmarks.common.benchmark_runner import BenchmarkRunner
from memora import TemporalSemanticMemory
# Add parent directory to path for imports
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
import json
from datetime import datetime, timezone
from typing import List, Dict, Any, Tuple, Optional
@ -20,9 +17,7 @@ import pydantic
from openai import AsyncOpenAI
import os
# Import common framework
sys.path.insert(0, str(Path(__file__).parent.parent))
from common.benchmark_runner import BenchmarkDataset, LLMAnswerGenerator, LLMAnswerEvaluator
from benchmarks.common.benchmark_runner import BenchmarkDataset, LLMAnswerGenerator, LLMAnswerEvaluator
from memora.llm_wrapper import LLMConfig

23
benchmarks/pyproject.toml Normal file
View file

@ -0,0 +1,23 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "benchmarks"
version = "0.1.0"
description = "Benchmarks for Memora memory system"
requires-python = ">=3.11"
dependencies = [
"memora",
"python-fasthtml>=0.12.33",
"streamlit>=1.51.0",
"openai>=1.0.0",
"rich>=13.0.0",
"pydantic>=2.0.0",
]
[tool.hatch.build.targets.wheel]
packages = ["benchmarks"]
[tool.uv.sources]
memora = { workspace = true }

View file

@ -0,0 +1,12 @@
.next
node_modules
npm-debug.log
.git
.gitignore
.env.local
.env*.local
README.md
Dockerfile
.dockerignore
build-docker.sh
docker-compose.yml

View file

@ -1,3 +1,3 @@
# Dataplane API Configuration
# URL of the Python FastAPI dataplane server
NEXT_PUBLIC_DATAPLANE_API_URL=http://localhost:8080
# URL of the Python FastAPI dataplane server (server-side only, not exposed to browser)
DATAPLANE_API_URL=http://localhost:8080

39
control-plane/Dockerfile Normal file
View file

@ -0,0 +1,39 @@
FROM node:20-alpine AS base
# Install dependencies only when needed
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
# Automatically leverage output traces to reduce image size
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"
CMD ["node", "server.js"]

View file

@ -84,7 +84,7 @@ cp .env.local.example .env.local
Edit `.env.local`:
```env
NEXT_PUBLIC_DATAPLANE_API_URL=http://localhost:8080
DATAPLANE_API_URL=http://localhost:8080
```
### Development

74
control-plane/build-docker.sh Executable file
View file

@ -0,0 +1,74 @@
#!/bin/bash
set -e
# Default values
IMAGE_NAME="memora-control-plane"
IMAGE_TAG="latest"
REGISTRY=""
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
--name)
IMAGE_NAME="$2"
shift 2
;;
--tag)
IMAGE_TAG="$2"
shift 2
;;
--registry)
REGISTRY="$2"
shift 2
;;
--help)
echo "Usage: $0 [OPTIONS]"
echo ""
echo "Options:"
echo " --name NAME Docker image name (default: control-plane)"
echo " --tag TAG Docker image tag (default: latest)"
echo " --registry REG Docker registry URL (optional)"
echo " --help Show this help message"
echo ""
echo "Example:"
echo " $0 --name myapp --tag v1.0.0"
echo " $0 --registry docker.io/myuser --name control-plane --tag v1.0.0"
exit 0
;;
*)
echo "Unknown option: $1"
echo "Use --help for usage information"
exit 1
;;
esac
done
# Construct full image name
if [ -n "$REGISTRY" ]; then
FULL_IMAGE_NAME="${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}"
else
FULL_IMAGE_NAME="${IMAGE_NAME}:${IMAGE_TAG}"
fi
echo "Building Docker image: ${FULL_IMAGE_NAME}"
echo "========================================"
# Build the Docker image
docker build -t "${FULL_IMAGE_NAME}" .
echo ""
echo "Build completed successfully!"
echo "Image: ${FULL_IMAGE_NAME}"
echo ""
echo "To run the container:"
echo " docker run -p 3000:3000 \\"
echo " -e DATAPLANE_API_URL=http://your-api-url:8080 \\"
echo " ${FULL_IMAGE_NAME}"
echo ""
echo "Or with an env file:"
echo " docker run -p 3000:3000 --env-file .env.local ${FULL_IMAGE_NAME}"
echo ""
echo "To push to registry (if registry specified):"
if [ -n "$REGISTRY" ]; then
echo " docker push ${FULL_IMAGE_NAME}"
fi

View file

@ -1,7 +1,7 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
/* config options here */
output: 'standalone',
};
export default nextConfig;

View file

@ -1,6 +1,6 @@
import { NextResponse } from 'next/server';
const DATAPLANE_URL = process.env.NEXT_PUBLIC_DATAPLANE_API_URL || 'http://localhost:8080';
const DATAPLANE_URL = process.env.DATAPLANE_API_URL || 'http://localhost:8080';
export async function GET() {
try {

View file

@ -1,6 +1,6 @@
import { NextRequest, NextResponse } from 'next/server';
const DATAPLANE_URL = process.env.NEXT_PUBLIC_DATAPLANE_API_URL || 'http://localhost:8080';
const DATAPLANE_URL = process.env.DATAPLANE_API_URL || 'http://localhost:8080';
export async function GET(
request: NextRequest,

View file

@ -1,6 +1,6 @@
import { NextRequest, NextResponse } from 'next/server';
const DATAPLANE_URL = process.env.NEXT_PUBLIC_DATAPLANE_API_URL || 'http://localhost:8080';
const DATAPLANE_URL = process.env.DATAPLANE_API_URL || 'http://localhost:8080';
export async function GET(request: NextRequest) {
try {

View file

@ -1,6 +1,6 @@
import { NextRequest, NextResponse } from 'next/server';
const DATAPLANE_URL = process.env.NEXT_PUBLIC_DATAPLANE_API_URL || 'http://localhost:8080';
const DATAPLANE_URL = process.env.DATAPLANE_API_URL || 'http://localhost:8080';
export async function GET(request: NextRequest) {
try {

View file

@ -1,6 +1,6 @@
import { NextRequest, NextResponse } from 'next/server';
const DATAPLANE_URL = process.env.NEXT_PUBLIC_DATAPLANE_API_URL || 'http://localhost:8080';
const DATAPLANE_URL = process.env.DATAPLANE_API_URL || 'http://localhost:8080';
export async function GET(request: NextRequest) {
try {

View file

@ -1,6 +1,6 @@
import { NextRequest, NextResponse } from 'next/server';
const DATAPLANE_URL = process.env.NEXT_PUBLIC_DATAPLANE_API_URL || 'http://localhost:8080';
const DATAPLANE_URL = process.env.DATAPLANE_API_URL || 'http://localhost:8080';
export async function POST(request: NextRequest) {
try {

View file

@ -1,6 +1,6 @@
import { NextRequest, NextResponse } from 'next/server';
const DATAPLANE_URL = process.env.NEXT_PUBLIC_DATAPLANE_API_URL || 'http://localhost:8080';
const DATAPLANE_URL = process.env.DATAPLANE_API_URL || 'http://localhost:8080';
export async function POST(request: NextRequest) {
try {

View file

@ -1,6 +1,6 @@
import { NextRequest, NextResponse } from 'next/server';
const DATAPLANE_URL = process.env.NEXT_PUBLIC_DATAPLANE_API_URL || 'http://localhost:8080';
const DATAPLANE_URL = process.env.DATAPLANE_API_URL || 'http://localhost:8080';
export async function GET(
request: NextRequest,

View file

@ -1,6 +1,6 @@
import { NextRequest, NextResponse } from 'next/server';
const DATAPLANE_URL = process.env.NEXT_PUBLIC_DATAPLANE_API_URL || 'http://localhost:8080';
const DATAPLANE_URL = process.env.DATAPLANE_API_URL || 'http://localhost:8080';
export async function POST(request: NextRequest) {
try {

View file

@ -1,6 +1,6 @@
import { NextRequest, NextResponse } from 'next/server';
const DATAPLANE_URL = process.env.NEXT_PUBLIC_DATAPLANE_API_URL || 'http://localhost:8080';
const DATAPLANE_URL = process.env.DATAPLANE_API_URL || 'http://localhost:8080';
export async function GET(
request: NextRequest,

View file

@ -1,6 +1,6 @@
import { NextRequest, NextResponse } from 'next/server';
const DATAPLANE_URL = process.env.NEXT_PUBLIC_DATAPLANE_API_URL || 'http://localhost:8080';
const DATAPLANE_URL = process.env.DATAPLANE_API_URL || 'http://localhost:8080';
export async function POST(request: NextRequest) {
try {

View file

@ -213,7 +213,7 @@ export class ServerDataplaneClient {
private baseUrl: string;
constructor() {
this.baseUrl = process.env.NEXT_PUBLIC_DATAPLANE_API_URL || 'http://localhost:8080';
this.baseUrl = process.env.DATAPLANE_API_URL || 'http://localhost:8080';
}
async fetchDataplane<T>(

View file

@ -0,0 +1 @@
"""Utility scripts for Memora development."""

View file

@ -9,9 +9,6 @@ import sys
import os
from pathlib import Path
# Add parent directory to path to import memory module
sys.path.insert(0, str(Path(__file__).parent))
from memora.api import create_app
from memora import TemporalSemanticMemory

18
memora-dev/pyproject.toml Normal file
View file

@ -0,0 +1,18 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "memora-dev"
version = "0.1.0"
description = "Development utilities for Memora"
requires-python = ">=3.11"
dependencies = [
"memora",
]
[tool.hatch.build.targets.wheel]
packages = ["memora_dev"]
[tool.uv.sources]
memora = { workspace = true }

View file

@ -23,14 +23,17 @@ def load_env():
if os.getenv("DATABASE_URL"):
return
# Look for .env files in the parent directory (root of the workspace)
root_dir = Path(__file__).parent.parent.parent
# Default to local environment
env_file = ".env.local"
if Path(env_file).exists():
env_file = root_dir / ".env.local"
if env_file.exists():
load_dotenv(env_file)
else:
# Fallback to dev
env_file = ".env.dev"
if Path(env_file).exists():
env_file = root_dir / ".env.dev"
if env_file.exists():
load_dotenv(env_file)
load_env()

43
memora/pyproject.toml Normal file
View file

@ -0,0 +1,43 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "memora"
version = "0.1.0"
description = "Temporal + Semantic + Entity Memory System for AI agents using PostgreSQL"
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"asyncpg>=0.29.0",
"python-dotenv>=1.0.0",
"openai>=1.0.0",
"pydantic>=2.0.0",
"rich>=13.0.0",
"sentence-transformers>=2.2.0",
"pytest>=7.0.0",
"pytest-asyncio>=0.21.0",
"langchain-text-splitters>=0.3.0",
"fastapi[standard]>=0.120.3",
"uvicorn>=0.38.0",
"sqlalchemy>=2.0.44",
"alembic>=1.17.1",
"pgvector>=0.4.1",
"greenlet>=3.2.4",
"psycopg2-binary>=2.9.11",
"pytest-timeout>=2.4.0",
"dateparser>=1.2.0",
"tiktoken>=0.12.0",
"httpx>=0.27.0",
]
[tool.hatch.build.targets.wheel]
packages = ["memora"]
[tool.pytest.ini_options]
log_cli = true
log_cli_level = "INFO"
log_cli_format = "%(asctime)s %(levelname)s %(message)s"
log_cli_date_format = "%Y-%m-%d %H:%M:%S"
addopts = "--timeout 60 -p no:warnings"
asyncio_default_fixture_loop_scope = "function"

View file

@ -19,7 +19,8 @@ LOCAL_DB_URL = "postgresql://memora:memora_dev@localhost:5432/memora"
# Load environment variables from .env.local at the start of test session
def pytest_configure(config):
"""Load environment variables before running tests."""
env_file = Path(__file__).parent.parent / ".env.local"
# Look for .env.local in the workspace root (two levels up from tests dir)
env_file = Path(__file__).parent.parent.parent / ".env.local"
if env_file.exists():
load_dotenv(env_file)
else:

View file

@ -472,7 +472,7 @@
"Memory Storage"
],
"summary": "Store multiple memories",
"description": "Store multiple memory items in batch with automatic fact extraction.\n\n Features:\n - Efficient batch processing\n - Automatic fact extraction from natural language\n - Entity recognition and linking\n - Document tracking with optional upsert\n - Temporal and semantic linking\n\n The system automatically:\n 1. Extracts semantic facts from the content\n 2. Generates embeddings\n 3. Deduplicates similar facts\n 4. Creates temporal, semantic, and entity links\n 5. Tracks document metadata",
"description": "Store multiple memory items in batch with automatic fact extraction.\n\n Features:\n - Efficient batch processing\n - Automatic fact extraction from natural language\n - Entity recognition and linking\n - Document tracking with automatic upsert (when document_id is provided)\n - Temporal and semantic linking\n\n The system automatically:\n 1. Extracts semantic facts from the content\n 2. Generates embeddings\n 3. Deduplicates similar facts\n 4. Creates temporal, semantic, and entity links\n 5. Tracks document metadata\n\n Note: If document_id is provided and already exists, the old document and its memory units will be deleted before creating new ones (upsert behavior).",
"operationId": "api_batch_put_api_memories_batch_post",
"requestBody": {
"content": {
@ -514,7 +514,7 @@
"Memory Storage"
],
"summary": "Store multiple memories asynchronously",
"description": "Store multiple memory items in batch asynchronously using the task backend.\n\n This endpoint returns immediately after queuing the task, without waiting for completion.\n The actual processing happens in the background.\n\n Features:\n - Immediate response (non-blocking)\n - Background processing via task queue\n - Efficient batch processing\n - Automatic fact extraction from natural language\n - Entity recognition and linking\n - Document tracking with optional upsert\n - Temporal and semantic linking\n\n The system automatically:\n 1. Queues the batch put task\n 2. Returns immediately with success=True, queued=True\n 3. Processes in background: extracts facts, generates embeddings, creates links",
"description": "Store multiple memory items in batch asynchronously using the task backend.\n\n This endpoint returns immediately after queuing the task, without waiting for completion.\n The actual processing happens in the background.\n\n Features:\n - Immediate response (non-blocking)\n - Background processing via task queue\n - Efficient batch processing\n - Automatic fact extraction from natural language\n - Entity recognition and linking\n - Document tracking with automatic upsert (when document_id is provided)\n - Temporal and semantic linking\n\n The system automatically:\n 1. Queues the batch put task\n 2. Returns immediately with success=True, queued=True\n 3. Processes in background: extracts facts, generates embeddings, creates links\n\n Note: If document_id is provided and already exists, the old document and its memory units will be deleted before creating new ones (upsert behavior).",
"operationId": "api_batch_put_async_api_memories_batch_async_post",
"requestBody": {
"content": {
@ -776,23 +776,6 @@
}
],
"title": "Document Id"
},
"document_metadata": {
"anyOf": [
{
"additionalProperties": true,
"type": "object"
},
{
"type": "null"
}
],
"title": "Document Metadata"
},
"upsert": {
"type": "boolean",
"title": "Upsert",
"default": false
}
},
"type": "object",
@ -814,8 +797,7 @@
"content": "Bob went hiking yesterday",
"event_date": "2024-01-15T10:00:00Z"
}
],
"upsert": false
]
}
},
"BatchPutResponse": {
@ -1188,7 +1170,10 @@
"title": "Query"
},
"fact_type": {
"type": "string",
"items": {
"type": "string"
},
"type": "array",
"title": "Fact Type"
},
"agent_id": {
@ -1215,6 +1200,17 @@
"type": "boolean",
"title": "Trace",
"default": false
},
"question_date": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Question Date"
}
},
"type": "object",
@ -1226,9 +1222,13 @@
"description": "Request model for search endpoint.",
"example": {
"agent_id": "user123",
"fact_type": "world",
"fact_type": [
"world",
"agent"
],
"max_tokens": 4096,
"query": "What did Alice say about machine learning?",
"question_date": "2023-05-30T23:40:00",
"reranker": "heuristic",
"thinking_budget": 100,
"trace": true

View file

@ -1,45 +1,5 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.uv.workspace]
members = ["memora", "benchmarks", "memora-dev"]
[project]
name = "memora"
version = "0.1.0"
description = "Temporal + Semantic + Entity Memory System for AI agents using PostgreSQL"
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"asyncpg>=0.29.0",
"python-dotenv>=1.0.0",
"openai>=1.0.0",
"pydantic>=2.0.0",
"rich>=13.0.0",
"sentence-transformers>=2.2.0",
"pytest>=7.0.0",
"pytest-asyncio>=0.21.0",
"langchain-text-splitters>=0.3.0",
"fastapi[standard]>=0.120.3",
"uvicorn>=0.38.0",
"sqlalchemy>=2.0.44",
"alembic>=1.17.1",
"pgvector>=0.4.1",
"greenlet>=3.2.4",
"psycopg2-binary>=2.9.11",
"pytest-timeout>=2.4.0",
"dateparser>=1.2.0",
"tiktoken>=0.12.0",
"httpx>=0.27.0",
"streamlit>=1.51.0",
"python-fasthtml>=0.12.33",
]
[tool.hatch.build.targets.wheel]
packages = ["memora"]
[tool.pytest.ini_options]
log_cli = true
log_cli_level = "INFO"
log_cli_format = "%(asctime)s %(levelname)s %(message)s"
log_cli_date_format = "%Y-%m-%d %H:%M:%S"
addopts = "--timeout 60 -p no:warnings"
asyncio_default_fixture_loop_scope = "function"
[tool.uv]
dev-dependencies = []

View file

@ -7,7 +7,7 @@ echo "🛑 Stopping and erasing local PostgreSQL..."
echo ""
# Stop and remove containers, networks, volumes
cd docker
cd local-db
docker-compose down -v
echo ""

View file

@ -1,7 +1,7 @@
#!/bin/bash
set -e
cd "$(dirname "$0")/.."
cd "$(dirname "$0")/../memora"
echo "🔄 Database Migration Script"
echo "============================"

View file

@ -7,7 +7,7 @@ echo "🚀 Starting local PostgreSQL..."
echo ""
# Start docker compose
cd docker
cd local-db
docker-compose up -d
echo ""
@ -25,7 +25,9 @@ cd ..
export DATABASE_URL="postgresql://memora:memora_dev@localhost:5432/memora"
echo "📊 Running database migrations..."
cd memora
uv run alembic upgrade head
cd ..
echo ""
echo "✅ Database initialized successfully!"

47
uv.lock
View file

@ -6,6 +6,13 @@ resolution-markers = [
"python_full_version < '3.12'",
]
[manifest]
members = [
"benchmarks",
"memora",
"memora-dev",
]
[[package]]
name = "alembic"
version = "1.17.1"
@ -208,6 +215,29 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl", hash = "sha256:5ef6fa3a8cbece8488d66985560f97ed091e22bbc4e9c2338508a9d5de6d4515", size = 106392 },
]
[[package]]
name = "benchmarks"
version = "0.1.0"
source = { editable = "benchmarks" }
dependencies = [
{ name = "memora" },
{ name = "openai" },
{ name = "pydantic" },
{ name = "python-fasthtml" },
{ name = "rich" },
{ name = "streamlit" },
]
[package.metadata]
requires-dist = [
{ name = "memora", editable = "memora" },
{ name = "openai", specifier = ">=1.0.0" },
{ name = "pydantic", specifier = ">=2.0.0" },
{ name = "python-fasthtml", specifier = ">=0.12.33" },
{ name = "rich", specifier = ">=13.0.0" },
{ name = "streamlit", specifier = ">=1.51.0" },
]
[[package]]
name = "blinker"
version = "1.9.0"
@ -1015,7 +1045,7 @@ wheels = [
[[package]]
name = "memora"
version = "0.1.0"
source = { editable = "." }
source = { editable = "memora" }
dependencies = [
{ name = "alembic" },
{ name = "asyncpg" },
@ -1032,11 +1062,9 @@ dependencies = [
{ name = "pytest-asyncio" },
{ name = "pytest-timeout" },
{ name = "python-dotenv" },
{ name = "python-fasthtml" },
{ name = "rich" },
{ name = "sentence-transformers" },
{ name = "sqlalchemy" },
{ name = "streamlit" },
{ name = "tiktoken" },
{ name = "uvicorn" },
]
@ -1058,15 +1086,24 @@ requires-dist = [
{ name = "pytest-asyncio", specifier = ">=0.21.0" },
{ name = "pytest-timeout", specifier = ">=2.4.0" },
{ name = "python-dotenv", specifier = ">=1.0.0" },
{ name = "python-fasthtml", specifier = ">=0.12.33" },
{ name = "rich", specifier = ">=13.0.0" },
{ name = "sentence-transformers", specifier = ">=2.2.0" },
{ name = "sqlalchemy", specifier = ">=2.0.44" },
{ name = "streamlit", specifier = ">=1.51.0" },
{ name = "tiktoken", specifier = ">=0.12.0" },
{ name = "uvicorn", specifier = ">=0.38.0" },
]
[[package]]
name = "memora-dev"
version = "0.1.0"
source = { editable = "memora-dev" }
dependencies = [
{ name = "memora" },
]
[package.metadata]
requires-dist = [{ name = "memora", editable = "memora" }]
[[package]]
name = "mpmath"
version = "1.3.0"