diff --git a/Dockerfile b/Dockerfile index 2f4ec320..7900df47 100644 --- a/Dockerfile +++ b/Dockerfile @@ -28,7 +28,8 @@ WORKDIR /app # Copiar i instal·lar dependències Python (aprofita caché si pyproject.toml no canvia) COPY backend/pyproject.toml backend/uv.lock ./backend/ -RUN cd backend && uv sync --frozen --no-dev +# Install all optional extras so the image supports any GRAPH_BACKEND at runtime +RUN cd backend && uv sync --frozen --no-dev --extra graphiti # Copiar el codi font del backend i els fitxers compartits COPY backend/ ./backend/ diff --git a/azure/2-build-deploy.sh b/azure/2-build-deploy.sh index 024d4c36..9de2c7d4 100755 --- a/azure/2-build-deploy.sh +++ b/azure/2-build-deploy.sh @@ -29,8 +29,18 @@ REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" # ── Validar variables obligatòries ─────────────────────────────────────────── REQUIRED_VARS=( AZURE_SUBSCRIPTION_ID RESOURCE_GROUP PROJECT_NAME - DEMO_PASSWORD SECRET_KEY LLM_API_KEY LLM_BASE_URL LLM_MODEL_NAME ZEP_API_KEY + DEMO_PASSWORD SECRET_KEY LLM_API_KEY LLM_BASE_URL LLM_MODEL_NAME ) +# Validate graph backend config +GRAPH_BACKEND="${GRAPH_BACKEND:-zep}" +if [[ "$GRAPH_BACKEND" == "zep" && -z "${ZEP_API_KEY:-}" ]]; then + echo "ERROR: ZEP_API_KEY is required when GRAPH_BACKEND=zep" + exit 1 +fi +if [[ "$GRAPH_BACKEND" == "graphiti" && -z "${NEO4J_PASSWORD:-}" ]]; then + echo "ERROR: NEO4J_PASSWORD is required when GRAPH_BACKEND=graphiti" + exit 1 +fi for var in "${REQUIRED_VARS[@]}"; do if [[ -z "${!var:-}" ]]; then echo "ERROR: La variable $var no està configurada a config.sh" @@ -124,7 +134,12 @@ DEPLOY_OUTPUT=$(az deployment group create \ demoPassword="$DEMO_PASSWORD" \ llmApiKey="$LLM_API_KEY" \ llmBoostApiKey="${LLM_BOOST_API_KEY:-}" \ - zepApiKey="$ZEP_API_KEY" \ + llmProvider="${LLM_PROVIDER:-}" \ + zepApiKey="${ZEP_API_KEY:-}" \ + neo4jPassword="${NEO4J_PASSWORD:-}" \ + neo4jUri="${NEO4J_URI:-bolt://localhost:7687}" \ + neo4jUser="${NEO4J_USER:-neo4j}" \ + graphBackend="${GRAPH_BACKEND:-zep}" \ secretKey="$SECRET_KEY" \ llmBaseUrl="$LLM_BASE_URL" \ llmModelName="$LLM_MODEL_NAME" \ diff --git a/azure/config.sh.example b/azure/config.sh.example index 7e98bb09..f2a2342f 100644 --- a/azure/config.sh.example +++ b/azure/config.sh.example @@ -34,14 +34,29 @@ LLM_API_KEY="" LLM_BASE_URL="https://dashscope.aliyuncs.com/compatible-mode/v1" LLM_MODEL_NAME="qwen-plus" +# Proveïdor LLM especial (opcional): +# gemini → configura automàticament Google AI Studio (no cal LLM_BASE_URL) +# (buit) → qualsevol API compatible OpenAI +LLM_PROVIDER="" + # ── LLM accelerador (opcional — deixar buit per desactivar) ─────────────────── LLM_BOOST_API_KEY="" LLM_BOOST_BASE_URL="" LLM_BOOST_MODEL_NAME="" -# ── Zep Cloud (graf de memòria) ─────────────────────────────────────────────── +# ── Backend de graf ─────────────────────────────────────────────────────────── +# Opcions: zep (Zep Cloud, per defecte) | graphiti (Neo4j local/Azure) +GRAPH_BACKEND="zep" + +# --- Zep Cloud (si GRAPH_BACKEND=zep) --- ZEP_API_KEY="" +# --- Graphiti + Neo4j (si GRAPH_BACKEND=graphiti) --- +# URI bolt del servidor Neo4j (pot ser una VM Azure, ACI, etc.) +# NEO4J_URI="bolt://:7687" +# NEO4J_USER="neo4j" +# NEO4J_PASSWORD="" + # ── Simulació OASIS (valors per defecte recomanats) ─────────────────────────── OASIS_DEFAULT_MAX_ROUNDS="10" diff --git a/azure/container-app.bicep b/azure/container-app.bicep index 577b5f00..408606a5 100644 --- a/azure/container-app.bicep +++ b/azure/container-app.bicep @@ -49,9 +49,13 @@ param llmApiKey string @secure() param llmBoostApiKey string = '' -@description('Clau de l\'API Zep Cloud') +@description('Clau de l\'API Zep Cloud (obligatori si GRAPH_BACKEND=zep)') @secure() -param zepApiKey string +param zepApiKey string = '' + +@description('Contrasenya de Neo4j (obligatori si GRAPH_BACKEND=graphiti)') +@secure() +param neo4jPassword string = '' @description('SECRET_KEY de Flask per a JWT (python -c "import secrets; print(secrets.token_hex(32))")') @secure() @@ -65,6 +69,20 @@ param llmBaseUrl string = 'https://dashscope.aliyuncs.com/compatible-mode/v1' @description('Nom del model LLM principal') param llmModelName string = 'qwen-plus' +@description('Proveïdor LLM (gemini per a Google AI Studio; buit per a qualsevol compatible OpenAI)') +param llmProvider string = '' + +// ─── Paràmetres del backend de graf ────────────────────────────────────────── + +@description('Backend de graf: zep (Zep Cloud) o graphiti (Neo4j local/Azure)') +param graphBackend string = 'zep' + +@description('URI de connexió bolt de Neo4j (necessari si GRAPH_BACKEND=graphiti)') +param neo4jUri string = 'bolt://localhost:7687' + +@description('Usuari de Neo4j') +param neo4jUser string = 'neo4j' + // ─── Paràmetres LLM accelerador (opcionals) ────────────────────────────────── @description('URL base de l\'API LLM acceleradora (opcional)') @@ -104,6 +122,7 @@ resource containerApp 'Microsoft.App/containerApps@2023-05-01' = { { name: 'llm-api-key', value: llmApiKey } { name: 'llm-boost-api-key', value: llmBoostApiKey } { name: 'zep-api-key', value: zepApiKey } + { name: 'neo4j-password', value: neo4jPassword } { name: 'secret-key', value: secretKey } ] @@ -143,14 +162,21 @@ resource containerApp 'Microsoft.App/containerApps@2023-05-01' = { { name: 'LLM_API_KEY', secretRef: 'llm-api-key' } { name: 'LLM_BOOST_API_KEY', secretRef: 'llm-boost-api-key' } { name: 'ZEP_API_KEY', secretRef: 'zep-api-key' } + { name: 'NEO4J_PASSWORD', secretRef: 'neo4j-password' } { name: 'SECRET_KEY', secretRef: 'secret-key' } // ── Variables no sensibles ── { name: 'LLM_BASE_URL', value: llmBaseUrl } { name: 'LLM_MODEL_NAME', value: llmModelName } + { name: 'LLM_PROVIDER', value: llmProvider } { name: 'LLM_BOOST_BASE_URL', value: llmBoostBaseUrl } { name: 'LLM_BOOST_MODEL_NAME', value: llmBoostModelName } + // ── Backend de graf ── + { name: 'GRAPH_BACKEND', value: graphBackend } + { name: 'NEO4J_URI', value: neo4jUri } + { name: 'NEO4J_USER', value: neo4jUser } + // ── Simulació OASIS ── { name: 'OASIS_DEFAULT_MAX_ROUNDS', value: oasisDefaultMaxRounds } diff --git a/backend/uv.lock b/backend/uv.lock index c562c98e..3ba7c74e 100644 --- a/backend/uv.lock +++ b/backend/uv.lock @@ -475,6 +475,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604, upload-time = "2021-03-08T10:59:24.45Z" }, ] +[[package]] +name = "diskcache" +version = "5.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3f/21/1c1ffc1a039ddcc459db43cc108658f32c57d271d7289a2794e401d0fdb6/diskcache-5.6.3.tar.gz", hash = "sha256:2c3a3fa2743d8535d832ec61c2054a1641f41775aa7c556758a109941e33e4fc", size = 67916, upload-time = "2023-08-31T06:12:00.316Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/27/4570e78fc0bf5ea0ca45eb1de3818a23787af9b390c0b0a0033a1b8236f9/diskcache-5.6.3-py3-none-any.whl", hash = "sha256:5e31b2d5fbad117cc363ebaf6b689474db18a1f6438bc82358b024abd4c2ca19", size = 45550, upload-time = "2023-08-31T06:11:58.822Z" }, +] + [[package]] name = "distlib" version = "0.4.0" @@ -592,6 +601,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/51/c7/b64cae5dba3a1b138d7123ec36bb5ccd39d39939f18454407e5468f4763f/fsspec-2025.12.0-py3-none-any.whl", hash = "sha256:8bf1fe301b7d8acfa6e8571e3b1c3d158f909666642431cc78a1b7b4dbc5ec5b", size = 201422, upload-time = "2025-12-03T15:23:41.434Z" }, ] +[[package]] +name = "graphiti-core" +version = "0.11.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "diskcache" }, + { name = "neo4j" }, + { name = "numpy" }, + { name = "openai" }, + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "tenacity" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/30/94/3f84400e5f02ea8e9dc79784202de4173cbc16f4b3ad1bd4302da888e4d8/graphiti_core-0.11.6.tar.gz", hash = "sha256:31d26621834d7d4b8865059ab749feb18af15937b59c69598a640a5dfabea331", size = 71928, upload-time = "2025-05-15T17:58:02.304Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ac/2e/c8f22f01585bf173d1c82f6d4615511aebc75aeda764c69aa394446fa93c/graphiti_core-0.11.6-py3-none-any.whl", hash = "sha256:6ec4807a884f5ea88b942d0c8b7bcd2e107c7358ab4f98ef2a2092c229929707", size = 111001, upload-time = "2025-05-15T17:58:00.542Z" }, +] + [[package]] name = "gunicorn" version = "25.3.0" @@ -1275,6 +1302,10 @@ dev = [ { name = "pytest" }, { name = "pytest-asyncio" }, ] +graphiti = [ + { name = "graphiti-core" }, + { name = "neo4j" }, +] [package.dev-dependencies] dev = [ @@ -1290,7 +1321,9 @@ requires-dist = [ { name = "charset-normalizer", specifier = ">=3.0.0" }, { name = "flask", specifier = ">=3.0.0" }, { name = "flask-cors", specifier = ">=6.0.0" }, + { name = "graphiti-core", marker = "extra == 'graphiti'", specifier = ">=0.3.0" }, { name = "gunicorn", specifier = ">=22.0.0" }, + { name = "neo4j", marker = "extra == 'graphiti'", specifier = ">=5.23.0" }, { name = "openai", specifier = ">=1.0.0" }, { name = "pipreqs", marker = "extra == 'dev'", specifier = ">=0.5.0" }, { name = "pydantic", specifier = ">=2.0.0" }, @@ -1301,7 +1334,7 @@ requires-dist = [ { name = "python-dotenv", specifier = ">=1.0.0" }, { name = "zep-cloud", specifier = "==3.13.0" }, ] -provides-extras = ["dev"] +provides-extras = ["graphiti", "dev"] [package.metadata.requires-dev] dev = [ @@ -3003,6 +3036,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252, upload-time = "2022-10-06T17:21:44.262Z" }, ] +[[package]] +name = "tenacity" +version = "9.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/47/c6/ee486fd809e357697ee8a44d3d69222b344920433d3b6666ccd9b374630c/tenacity-9.1.4.tar.gz", hash = "sha256:adb31d4c263f2bd041081ab33b498309a57c77f9acf2db65aadf0898179cf93a", size = 49413, upload-time = "2026-02-07T10:45:33.841Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/c1/eb8f9debc45d3b7918a32ab756658a0904732f75e555402972246b0b8e71/tenacity-9.1.4-py3-none-any.whl", hash = "sha256:6095a360c919085f28c6527de529e76a06ad89b23659fa881ae0649b867a9d55", size = 28926, upload-time = "2026-02-07T10:45:32.24Z" }, +] + [[package]] name = "texttable" version = "1.7.0"