diff --git a/.env b/.env index 7fc2d86..082d2f4 100644 --- a/.env +++ b/.env @@ -1,50 +1,19 @@ -# Local development environment (.env file) -# Copy to .env and fill in your local/dev URLs -# .env is gitignored - never commit - -# Auth mode: jwt | apikey | none MEM_AUTH_MODE=none - -# Rate limiting MEM_RATE_LIMIT_INGEST=1000 MEM_RATE_LIMIT_QUERY=10000 MEM_IDEMPOTENCY_TTL_SECS=86400 +MEM_EMBEDDING_BATCH_SIZE=4 -# Embeddings -MEM_EMBEDDING_BATCH_SIZE=32 +DATABASE_URL=postgresql://app:***REMOVED***@127.0.0.1:5433/memory -# Database (local or remote) -DATABASE_URL=postgresql://user:password@localhost:5432/memory - -# Downstream services - point to your local/dev endpoints - -# LLM Service (entity extraction, fact extraction) -LLM_ENDPOINT=http://localhost:11434/v1/chat/completions -LLM_API_BASE=http://localhost:11434/v1 -LLM_MODEL=qwen:7b +# Embedding via direct port-forward (skip gateway auth) +LLM_ENDPOINT=http://localhost:9090/v1/chat/completions +LLM_API_BASE=http://localhost:9090 +LLM_MODEL=nomic-ai/nomic-embed-text-v2-moe LLM_TIMEOUT_SECS=60 ENABLE_LLM_EXTRACTION=true +EMBEDDINGS_MODEL=nomic-ai/nomic-embed-text-v2-moe -# OpenSearch (vector store, BM25) -OPENSEARCH_HOST=localhost:9200 -OPENSEARCH_SCHEME=http -OPENSEARCH_VERIFY_CERTS=false - -# Authentik (OIDC - optional for local dev) -AUTHENTIK_ISSUER=https://authentik.riotpiao.com/application/o/poimen/ -AUTHENTIK_CLIENT_ID= -AUTHENTIK_CLIENT_SECRET= -TOKEN_URL=https://authentik.riotpiao.com/application/o/token/ -AUTHENTIK_VERIFY_SSL=false - -# Temporal (workflow orchestration - future) -TEMPORAL_ENDPOINT=localhost:7233 -TEMPORAL_NAMESPACE=poimen - -# API Gateway (route optimization - future) -GATEWAY_URL=http://localhost:8080 - -# Server config -MEM_PORT=8080 +MEM_PORT=8081 MEM_API_KEY=test-key MEM_HOME=/tmp diff --git a/crates/mem-llm/src/embeddings.rs b/crates/mem-llm/src/embeddings.rs index 1883bd1..512f98c 100644 --- a/crates/mem-llm/src/embeddings.rs +++ b/crates/mem-llm/src/embeddings.rs @@ -212,4 +212,73 @@ mod tests { assert_eq!(BATCH_SIZE, 32); assert_eq!(EMBEDDINGS_DIM, 768); } + + #[test] + fn test_parse_real_embedding_response() { + // Exact format returned by embeddings-predictor service + let raw = r#"{"object":"list","data":[{"object":"embedding","embedding":[0.1,0.2,0.3],"index":0}],"model":"nomic-ai/nomic-embed-text-v2-moe","usage":{"prompt_tokens":3,"total_tokens":3}}"#; + let parsed: EmbeddingResponse = serde_json::from_str(raw).expect("should parse"); + match parsed { + EmbeddingResponse::Success { data, .. } => { + assert_eq!(data.len(), 1); + assert_eq!(data[0].embedding.len(), 3); + assert_eq!(data[0].index, 0); + } + EmbeddingResponse::Error { error } => panic!("parsed as error: {:?}", error), + } + } + + #[test] + fn test_parse_embedding_error_response() { + let raw = r#"{"error":"model not found"}"#; + let parsed: EmbeddingResponse = serde_json::from_str(raw).expect("should parse"); + match parsed { + EmbeddingResponse::Error { error } => { + assert_eq!(error.as_str().unwrap(), "model not found"); + } + EmbeddingResponse::Success { .. } => panic!("should be error"), + } + } + + #[test] + fn test_parse_768_dim_response() { + // 768 floats + let embedding: Vec = (0..768).map(|i| i as f32 * 0.001).collect(); + let raw = format!( + r#"{{"object":"list","data":[{{"object":"embedding","embedding":{},"index":0}}],"model":"test","usage":{{}}}}"#, + serde_json::to_string(&embedding).unwrap() + ); + let parsed: EmbeddingResponse = serde_json::from_str(&raw).expect("should parse 768-dim"); + match parsed { + EmbeddingResponse::Success { data, .. } => { + assert_eq!(data[0].embedding.len(), 768); + } + _ => panic!("should be success"), + } + } + + #[test] + fn test_parse_html_fails_gracefully() { + // Simulates gateway returning HTML error page + let raw = "502 Bad Gateway"; + let result: Result = serde_json::from_str(raw); + assert!(result.is_err(), "HTML should fail to parse as JSON"); + let err_msg = result.unwrap_err().to_string(); + assert!(err_msg.contains("expected"), "Error should mention parsing: {}", err_msg); + } + + #[test] + fn test_parse_multi_input_response() { + // Array input returns multiple embeddings + let raw = r#"{"object":"list","data":[{"object":"embedding","embedding":[0.1,0.2,0.3],"index":0},{"object":"embedding","embedding":[0.4,0.5,0.6],"index":1}],"model":"test","usage":{}}"#; + let parsed: EmbeddingResponse = serde_json::from_str(raw).expect("should parse"); + match parsed { + EmbeddingResponse::Success { data, .. } => { + assert_eq!(data.len(), 2); + assert_eq!(data[0].index, 0); + assert_eq!(data[1].index, 1); + } + _ => panic!("should be success"), + } + } }