Repository: opea-project/GenAIComps
Pull request #1319: add text2cypher component
Author: login: jeanyu-habana
Created: 2025-02-21T22:58:08Z | Merged: 2025-03-10T03:46:10Z

## Description

Implement Cypher Query based on input text**: Transform user-provided natural language into Cypher queries, subsequently executing them to retrieve data from Graph databases.

## Issues

n/a

## Type of change

List the type of change like below. Please delete options that are not relevant.

- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds new functionality)
- [ ] Breaking change (fix or feature that would break existing design and interface)
- [ ] Others (enhancement, documentation, validation, etc.)

## Dependencies

neo4j microservice

## Tests

user-provided natural language transformed into Cypher queries, subsequently executing them to retrieve data from Neo4J database.



Diff:
diff --git a/.github/workflows/docker/compose/text2cypher-compose.yaml b/.github/workflows/docker/compose/text2cypher-compose.yaml
new file mode 100644
index 0000000000..f3b82e5049
--- /dev/null
+++ b/.github/workflows/docker/compose/text2cypher-compose.yaml
@@ -0,0 +1,9 @@
+# Copyright (C) 2024 Intel Corporation
+# SPDX-License-Identifier: Apache-2.0
+
+# this file should be run in the root of the repo
+services:
+  text2cypher-gaudi:
+    build:
+      dockerfile: comps/text2cypher/src/Dockerfile.intel_hpu
+    image: ${REGISTRY:-opea}/text2cypher-gaudi:${TAG:-latest}
diff --git a/comps/cores/mega/constants.py b/comps/cores/mega/constants.py
index e36693f5ac..b0639b9b29 100644
--- a/comps/cores/mega/constants.py
+++ b/comps/cores/mega/constants.py
@@ -35,6 +35,7 @@ class ServiceType(Enum):
     IMAGE2IMAGE = 18
     TEXT2SQL = 19
     TEXT2GRAPH = 20
+    TEXT2CYPHER = 21
 
 
 class MegaServiceEndpoint(Enum):
@@ -54,6 +55,7 @@ class MegaServiceEndpoint(Enum):
     RETRIEVALTOOL = "/v1/retrievaltool"
     FAQ_GEN = "/v1/faqgen"
     GRAPH_RAG = "/v1/graphrag"
+    HYBRID_RAG = "/v1/hybridrag"
     # Follow OPENAI
     EMBEDDINGS = "/v1/embeddings"
     TTS = "/v1/audio/speech"
diff --git a/comps/text2cypher/deployment/docker_compose/README.md b/comps/text2cypher/deployment/docker_compose/README.md
new file mode 100644
index 0000000000..d81ddbcf9d
--- /dev/null
+++ b/comps/text2cypher/deployment/docker_compose/README.md
@@ -0,0 +1,22 @@
+# Deploy text2cypher microservice using docker-compose
+
+## Deploy on Intel Gaudi
+
+```
+unset http_proxy
+service_name="neo4j-apoc text2cypher-gaudi"
+export ip_address=$(hostname -I | awk '{print $1}')
+export host_ip=${ip_address}
+export TAG="comps"
+export NEO4J_AUTH="neo4j/neo4jtest"
+export NEO4J_URL="bolt://${ip_address}:7687"
+export NEO4J_USERNAME="neo4j"
+export NEO4J_PASSWORD="neo4jtest"
+export NEO4J_apoc_export_file_enabled=true
+export NEO4J_apoc_import_file_use__neo4j__config=true
+export NEO4J_PLUGINS=\[\"apoc\"\]
+
+cd $WORKPATH/comps/text2cypher/deployment/docker_compose/
+docker compose up ${service_name} -d
+
+```
diff --git a/comps/text2cypher/deployment/docker_compose/compose.yaml b/comps/text2cypher/deployment/docker_compose/compose.yaml
new file mode 100644
index 0000000000..40f243271d
--- /dev/null
+++ b/comps/text2cypher/deployment/docker_compose/compose.yaml
@@ -0,0 +1,50 @@
+﻿# Copyright (C) 2025 Intel Corporation
+# SPDX-License-Identifier: Apache-2.0
+
+include:
+  - ../../../third_parties/neo4j/deployment/docker_compose/compose.yaml
+
+services:
+  text2cypher:
+    image: opea/text2cypher:${TAG:-latest}
+    container_name: text2cypher-container
+    ports:
+      - ${TEXT2CYPHER_PORT:-9097}:9097
+    depends_on:
+      neo4j-apoc:
+        condition: service_healthy
+
+  text2cypher-gaudi:
+    image: opea/text2cypher-gaudi:${TAG:-latest}
+    #pull_policy: never
+    container_name: text2cypher-gaudi-container
+    ports:
+      - ${TEXT2CYPHER_PORT:-9097}:9097
+    depends_on:
+      neo4j-apoc:
+        condition: service_healthy
+    ipc: host
+    environment:
+      no_proxy: ${no_proxy}
+      http_proxy: ${http_proxy}
+      https_proxy: ${https_proxy}
+      INDEX_NAME: ${INDEX_NAME}
+      HUGGINGFACEHUB_API_TOKEN: ${HF_TOKEN}
+      HF_TOKEN: ${HF_TOKEN}
+      LOGFLAG: ${LOGFLAG:-False}
+      HABANA_VISIBLE_DEVICES: all
+      OMPI_MCA_btl_vader_single_copy_mechanism: none
+      TOKENIZERS_PARALLELISM: False
+      NEO4J_URI: ${NEO4J_URI}
+      NEO4J_URL: ${NEO4J_URI}
+      NEO4J_USERNAME: ${NEO4J_USERNAME}
+      NEO4J_PASSWORD: ${NEO4J_PASSWORD}
+      host_ip: ${host_ip}
+    runtime: habana
+    cap_add:
+      - SYS_NICE
+    restart: unless-stopped
+
+networks:
+  default:
+    driver: bridge
diff --git a/comps/text2cypher/src/Dockerfile.intel_hpu b/comps/text2cypher/src/Dockerfile.intel_hpu
new file mode 100644
index 0000000000..c044670b93
--- /dev/null
+++ b/comps/text2cypher/src/Dockerfile.intel_hpu
@@ -0,0 +1,48 @@
+# Copyright (C) 2025 Intel Corporation
+# SPDX-License-Identifier: Apache-2.0
+
+# HABANA environment
+FROM vault.habana.ai/gaudi-docker/1.19.0/ubuntu22.04/habanalabs/pytorch-installer-2.5.1 AS hpu
+
+ENV LANG=en_US.UTF-8
+ARG REPO=https://github.com/huggingface/optimum-habana.git
+ARG REPO_VER=v1.15.0
+
+RUN apt-get update && apt-get install -y --no-install-recommends --fix-missing \
+    git-lfs \
+    libgl1-mesa-glx \
+    libjemalloc-dev
+
+#RUN useradd -m -s /bin/bash user && \
+#    mkdir -p /home/user && \
+#    chown -R user /home/user/
+
+RUN git lfs install
+
+COPY comps /root/comps
+#RUN chown -R user /home/user/comps/text2cypher
+
+#RUN rm -rf /etc/ssh/ssh_host*
+
+RUN pip install --no-cache-dir --upgrade pip setuptools && \
+    pip install --no-cache-dir --upgrade-strategy eager optimum[habana] && \
+    pip install --no-cache-dir git+https://github.com/HabanaAI/DeepSpeed.git@1.19.0
+
+RUN git clone ${REPO} /root/optimum-habana && \
+    cd /root/optimum-habana && git checkout ${REPO_VER} && \
+    cd /root/comps/text2cypher/src && pip install --no-cache-dir -r requirements.txt && \
+    pip install --no-cache-dir --upgrade --force-reinstall pydantic numpy==1.26.3
+
+# Set environment variables
+ENV PYTHONPATH=/root:/usr/lib/habanalabs/:/root/optimum-habana
+ENV HABANA_VISIBLE_DEVICES=all
+ENV OMPI_MCA_btl_vader_single_copy_mechanism=none
+ENV DEBIAN_FRONTEND="noninteractive"  TZ=Etc/UTC
+
+#USER user
+WORKDIR /root/comps/text2cypher/src
+
+RUN echo python opea_text2cypher_microservice.py --device hpu --use_hpu_graphs --bf16 >> run.sh
+
+CMD bash run.sh
+
diff --git a/comps/text2cypher/src/README.md b/comps/text2cypher/src/README.md
new file mode 100644
index 0000000000..b3ca98afcf
--- /dev/null
+++ b/comps/text2cypher/src/README.md
@@ -0,0 +1,127 @@
+# 🛢 Text-to-Cypher Microservice
+
+The microservice enables a wide range of use cases, making it a versatile tool for businesses, researchers, and individuals alike. Users can generate queries based on natural language questions, enabling them to quickly retrieve relevant data from graph databases. This service executes locally on Intel Gaudi.
+
+---
+
+## 🛠️ Features
+
+**Implement Cypher Query based on input text**: Transform user-provided natural language into Cypher queries, subsequently executing them to retrieve data from Graph databases.
+
+---
+
+## ⚙️ Implementation
+
+The text-to-cypher microservice able to implement with various framework and support various types of Graph databases.
+
+### 🔗 Utilizing Text-to-Cypher with Langchain framework
+
+The follow guide provides set-up instructions and comprehensive details regarding the Text-to-Cypher microservices via LangChain. In this configuration, we will employ Neo4J DB as our example database to showcase this microservice.
+
+---
+
+### Start Neo4J Service
+
+### 🚀 Start Text2Cypher Microservice with Python（Option 1）
+
+#### Install Requirements
+
+```bash
+pip install -r requirements.txt
+```
+
+#### Start Text-to-Cypher Microservice with Python Script
+
+Start Text-to-Cypher microservice with below command.
+
+```bash
+python3 opea_text2cypher_microservice.py
+```
+
+---
+
+### 🚀 Start Microservice with Docker (Option 2)
+
+#### Build Docker Image
+
+```bash
+cd GenAIComps/
+docker build -t opea/text2cypher:latest -f comps/text2cypher/src/Dockerfile.intel_hpu .
+```
+
+#### Run Docker with CLI (Option A)
+
+```bash
+docker run  --name="comps-langchain-text2cypher"  -p 9097:8080 --ipc=host opea/text2cypher:latest
+```
+
+#### Run via docker compose (Option B)
+
+##### Setup Environment Variables.
+
+```bash
+ip_address=$(hostname -I | awk '{print $1}')
+export HF_TOKEN=${HUGGINGFACEHUB_API_TOKEN}
+export NEO4J_USER=neo4j
+export NEO4J_PASSWORD=neo4jtest
+export NEO4J_URL="bolt://${ip_address}:7687"
+export TEXT2CYPHER_PORT=11801
+```
+
+##### Start the services.
+
+- Gaudi2 HPU
+
+```bash
+cd comps/text2cypher/deployment/docker_compose
+docker compose -f compose.yaml up text2cypher-gaudi -d
+```
+
+---
+
+### ✅ Invoke the microservice.
+
+The Text-to-Cypher microservice exposes the following API endpoints:
+
+- Execute Cypher Query with Pre-seeded Data and Schema:
+
+  ```bash
+  curl http://${ip_address}:${TEXT2CYPHER_PORT}/v1/text2cypher\
+        -X POST \
+        -d '{"input_text": "what are the symptoms for Diabetes?","conn_str": {"user": "'${NEO4J_USERNAME}'","password": "'${NEO4J_PASSWPORD}'","url": "'${NEO4J_URL}'" }}' \
+        -H 'Content-Type: application/json'
+  ```
+
+- Execute Cypher Query with User Data and Schema:
+
+Define customized cypher_insert statements:
+
+```bash
+export cypher_insert='
+ LOAD CSV WITH HEADERS FROM "https://docs.google.com/spreadsheets/d/e/2PACX-1vQCEUxVlMZwwI2sn2T1aulBrRzJYVpsM9no8AEsYOOklCDTljoUIBHItGnqmAez62wwLpbvKMr7YoHI/pub?gid=0&single=true&output=csv" AS rows
+ MERGE (d:disease {name:rows.Disease})
+ MERGE (dt:diet {name:rows.Diet})
+ MERGE (d)-[:HOME_REMEDY]->(dt)
+
+ MERGE (m:medication {name:rows.Medication})
+ MERGE (d)-[:TREATMENT]->(m)
+
+ MERGE (s:symptoms {name:rows.Symptom})
+ MERGE (d)-[:MANIFESTATION]->(s)
+
+ MERGE (p:precaution {name:rows.Precaution})
+ MERGE (d)-[:PREVENTION]->(p)
+'
+```
+
+Pass the cypher_insert to the cypher2text service. The user can also specify whether to refresh the Neo4j database using the refresh_db option.
+
+```bash
+ curl http://${ip_address}:${TEXT2CYPHER_PORT}/v1/text2cypher \
+        -X POST \
+        -d '{"input_text": "what are the symptoms for Diabetes?", \
+             "conn_str": {"user": "'${NEO4J_USERNAME}'","password": "'${NEO4J_PASSWPORD}'","url": "'${NEO4J_URL}'" } \
+             "seeding": {"cypher_insert": "'${cypher_insert}'","refresh_db": "True" }}' \
+        -H 'Content-Type: application/json'
+
+```
diff --git a/comps/text2cypher/src/__init__.py b/comps/text2cypher/src/__init__.py
new file mode 100644
index 0000000000..4057dc0163
--- /dev/null
+++ b/comps/text2cypher/src/__init__.py
@@ -0,0 +1,2 @@
+# Copyright (C) 2025 Intel Corporation
+# SPDX-License-Identifier: Apache-2.0
diff --git a/comps/text2cypher/src/integrations/__init__.py b/comps/text2cypher/src/integrations/__init__.py
new file mode 100644
index 0000000000..4057dc0163
--- /dev/null
+++ b/comps/text2cypher/src/integrations/__init__.py
@@ -0,0 +1,2 @@
+# Copyright (C) 2025 Intel Corporation
+# SPDX-License-Identifier: Apache-2.0
diff --git a/comps/text2cypher/src/integrations/cypher_utils.py b/comps/text2cypher/src/integrations/cypher_utils.py
new file mode 100644
index 0000000000..737688acd5
--- /dev/null
+++ b/comps/text2cypher/src/integrations/cypher_utils.py
@@ -0,0 +1,228 @@
+# Copyright (C) 2025 Intel Corporation
+# SPDX-License-Identifier: Apache-2.0
+
+import re
+from collections import namedtuple
+from string import Template
+from typing import Any, Dict, List, Optional, Union
+
+from langchain_community.chains.graph_qa.cypher_utils import CypherQueryCorrector, Schema
+
+from comps import CustomLogger
+
+logger = CustomLogger("opea_text2cypher_cypher_utils")
+Relationship = namedtuple("Relationship", ["entity1", "relationship", "entity2"])
+
+cypher_cleanup = """
+ MATCH (n) DETACH DELETE n
+"""
+
+cypher_insert = """
+ LOAD CSV WITH HEADERS FROM 'https://docs.google.com/spreadsheets/d/e/2PACX-1vQCEUxVlMZwwI2sn2T1aulBrRzJYVpsM9no8AEsYOOklCDTljoUIBHItGnqmAez62wwLpbvKMr7YoHI/pub?gid=0&single=true&output=csv' AS rows
+ MERGE (d:disease {name:rows.Disease})
+ MERGE (dt:diet {name:rows.Diet})
+ //SET dt.Diet = rows.Diet
+ MERGE (d)-[:HOME_REMEDY]->(dt)
+
+ MERGE (m:medication {name:rows.Medication})
+ //SET m.Medication = rows.Medication
+ MERGE (d)-[:TREATMENT]->(m)
+
+ MERGE (s:symptom {name:rows.Symptom})
+ //SET s.Symptoms = rows.Symptom
+ MERGE (d)-[:MANIFESTATION]->(s)
+
+ MERGE (p:precaution {name:rows.Precaution})
+ //SET p.precaution = rows.Precaution
+ MERGE (d)-[:PREVENTION]->(p)
+
+ //MATCH (n:Description) RETURN count(n) AS count
+"""
+
+
+# in the original string, change the first character of the substring into lower case
+def replace_with_lowercase(s, sub):
+    index = s.lower().find(sub)
+    if index < 0 or index >= len(s):
+        raise ValueError("Index out of range")
+    # Replace the character at the index with its lowercase version
+    return s[:index] + s[index].lower() + s[index + 1 :]
+
+
+def parse_relationships(input_str):
+    # Split by comma to get each relationship
+    substrings = input_str.split(",")
+
+    relationships = []
+
+    for substring in substrings:
+        # Remove unnecessary characters and split by spaces
+        parts = (
+            substring.replace("(", "")
+            .replace(")", "")
+            .replace(">", "")
+            .replace("[", "")
+            .replace("]", "")
+            .replace(":", "")
+            .split("-")
+        )
+
+        entity1 = parts[0].strip()
+        relationship = parts[1].strip()
+        entity2 = parts[2].strip()
+
+        # Create a named tuple and add it to the list
+        relationships.append(Relationship(entity1, relationship, entity2))
+
+    return relationships
+
+
+def swap(cypher_string, relations):
+    # Regular expression pattern to match substrings
+    pattern = r"\(([^()]*)\)"
+
+    # Find all matches
+    matches = re.findall(pattern, cypher_string)
+    new_string = cypher_string
+    # Check if there are exactly two matches
+    if len(matches) == 2:
+        first = matches[0]
+        second = matches[1]
+        for rel in relations:
+            e1 = rel.entity1.lower()
+            r = rel.relationship.lower()
+            e2 = rel.entity2.lower()
+            if e1 in first.lower() and e2 in second.lower():
+                new_string = replace_with_lowercase(new_string, e1)
+                new_string = replace_with_lowercase(new_string, e2)
+                break
+            elif e1 in second.lower() and e2 in first.lower():
+                new_string = new_string.replace(f"({second})", "tmp", 1)
+                new_string = new_string.replace(f"({first})", f"({second})", 1)
+                new_string = new_string.replace("tmp", f"({first})", 1)
+                new_string = replace_with_lowercase(new_string, e1)
+                new_string = replace_with_lowercase(new_string, e2)
+                break
+            elif e1 in first.lower() and r in second.lower():
+                new_string = new_string.replace(f"{second[2:]}", f"{e2}", 1)
+                new_string = replace_with_lowercase(new_string, e1)
+                new_string = replace_with_lowercase(new_string, e2)
+                break
+            elif e1 in second.lower() and r in first.lower():
+                new_string = new_string.replace(f"({second})", "tmp", 1)
+                new_string = new_string.replace(f"({first})", f"({second})", 1)
+                new_string = new_string.replace("tmp", f"({first})", 1)
+                # now replace r with e2
+                new_string = new_string.replace(f"{first[2:]}", f"{e2}", 1)
+                new_string = replace_with_lowercase(new_string, e1)
+                new_string = replace_with_lowercase(new_string, e2)
+                break
+    return new_string
+
+
+def prepare_chat_template(question):
+    template = Template(
+        """
+Generate Cypher statement to query a graph database to answer "$question"
+Instructions:
+Use only the provided relationship types and properties in the schema.
+Do not use any other relationship types or properties that are not provided.
+Schema:
+{schema}
+Note: Do not include any explanations or apologies in your responses.
+Use only the user's question to construct a Cypher statement.
+Do not include any text except the generated Cypher statement.
+
+Cypher output:
+    """
+    )
+    temp_str = template.substitute(question=question)
+    return temp_str
+
+
+def construct_schema(
+    structured_schema: Dict[str, Any],
+    include_types: List[str],
+    exclude_types: List[str],
+) -> str:
+    """Filter the schema based on included or excluded types."""
+
+    def filter_func(x: str) -> bool:
+        return x in include_types if include_types else x not in exclude_types
+
+    filtered_schema: Dict[str, Any] = {
+        "node_props": {k: v for k, v in structured_schema.get("node_props", {}).items() if filter_func(k)},
+        "rel_props": {k: v for k, v in structured_schema.get("rel_props", {}).items() if filter_func(k)},
+        "relationships": [
+            r
+            for r in structured_schema.get("relationships", [])
+            if all(filter_func(r[t]) for t in ["start", "end", "type"])
+        ],
+    }
+
+    # Format node properties
+    formatted_node_props = []
+    for label, properties in filtered_schema["node_props"].items():
+        props_str = ", ".join([f"{prop['property']}: {prop['type']}" for prop in properties])
+        formatted_node_props.append(f"{label} {{{props_str}}}")
+
+    # Format relationship properties
+    formatted_rel_props = []
+    for rel_type, properties in filtered_schema["rel_props"].items():
+        props_str = ", ".join([f"{prop['property']}: {prop['type']}" for prop in properties])
+        formatted_rel_props.append(f"{rel_type} {{{props_str}}}")
+
+    # Format relationships
+    formatted_rels = [f"(:{el['start']})-[:{el['type']}]->(:{el['end']})" for el in filtered_schema["relationships"]]
+
+    return "\n".join(
+        [
+            "Node properties are the following:",
+            ",".join(formatted_node_props),
+            "Relationship properties are the following:",
+            ",".join(formatted_rel_props),
+            "The relationships are the following:",
+            ",".join(formatted_rels),
+        ]
+    )
+
+
+class CypherQueryCorrectorExt(CypherQueryCorrector):
+    """Extension of CypherQueryCorrector. CypherQueryCorrector corrects
+    relationship direction in generated Cypher statements.
+
+    CypherQueryCorrectorExt can perform additional checks based on a schema.
+    For example,
+         1. remove any extra information from the cypher statement.
+         2. any user-defined and/or application-specific transformation.
+    """
+
+    def __init__(self, schemas: List[Schema], schema_str: str):
+        self.schemas = schemas
+        self.schema_str = schema_str
+
+    def correct_query(self, query: str) -> str:
+        logger.info(f"[ correct_query ] query: {query}")
+        start_index = query.find("MATCH ")
+        if start_index == -1:
+            raise ValueError("Generated cypher does not contain `MATCH `.")
+        tmp1 = query[start_index:]
+        pattern = r"-\[.*?\]->"
+        replacement = "-[INTERACT_WITH]->"
+        tmp2 = re.sub(pattern, replacement, tmp1)
+
+        rel_index = self.schema_str.find("The relationships are the following:\n")
+        rel_string = self.schema_str[rel_index + len("The relationships are the following:\n") :]
+        relations = parse_relationships(rel_string)
+        query = swap(tmp2, relations)
+        logger.info(f"[ correct_query ] corrected query: {query}")
+        return query
+
+    def __call__(self, query: str) -> str:
+        """Correct the query to make it valid.
+
+        If
+        Args:
+            query: cypher query
+        """
+        return self.correct_query(query)
diff --git a/comps/text2cypher/src/integrations/gaudiutils.py b/comps/text2cypher/src/integrations/gaudiutils.py
new file mode 100644
index 0000000000..5e33646a37
--- /dev/null
+++ b/comps/text2cypher/src/integrations/gaudiutils.py
@@ -0,0 +1,680 @@
+# Copyright (C) 2025 Intel Corporation
+# SPDX-License-Identifier: Apache-2.0
+
+import argparse
+import copy
+import glob
+import os
+import shutil
+import tempfile
+import time
+from pathlib import Path
+
+import requests
+import torch
+from optimum.habana.checkpoint_utils import (
+    get_ds_injection_policy,
+    get_repo_root,
+    model_is_optimized,
+    model_on_meta,
+    write_checkpoints_json,
+)
+from optimum.habana.utils import (
+    check_habana_frameworks_version,
+    check_optimum_habana_min_version,
+    get_habana_frameworks_version,
+    set_seed,
+)
+from transformers import AutoConfig, AutoModelForCausalLM, AutoTokenizer
+from transformers.utils import check_min_version
+
+from comps import CustomLogger
+
+logger = CustomLogger("opea_text2cypher_utils")
+
+
+def setup_parser(parser):
+    # Arguments management
+    parser.add_argument("--device", "-d", type=str, choices=["hpu"], help="Device to run", default="hpu")
+    parser.add_argument(
+        "--model_name_or_path",
+        default=None,
+        type=str,
+        # required=True,
+        help="Path to pre-trained model (on the HF Hub or locally).",
+    )
+    parser.add_argument(
+        "--bf16",
+        default=True,
+        action="store_true",
+        help="Whether to perform generation in bf16 precision.",
+    )
+    parser.add_argument("--max_new_tokens", type=int, default=100, help="Number of tokens to generate.")
+    parser.add_argument(
+        "--max_input_tokens",
+        type=int,
+        default=0,
+        help="If > 0 then pad and truncate the input sequences to this specified length of tokens. \
+            if == 0, then truncate to 16 (original default) \
+            if < 0, then do not truncate, use full input prompt",
+    )
+    parser.add_argument("--tokenizer-config-path", type=str, default=None)
+
+    parser.add_argument("--batch_size", type=int, default=1, help="Input batch size.")
+    parser.add_argument("--warmup", type=int, default=3, help="Number of warmup iterations for benchmarking.")
+    parser.add_argument("--n_iterations", type=int, default=5, help="Number of inference iterations for benchmarking.")
+    parser.add_argument("--local_rank", type=int, default=0, metavar="N", help="Local process rank.")
+    parser.add_argument(
+        "--use_kv_cache",
+        default=True,
+        action="store_true",
+        help="Whether to use the key/value cache for decoding. It should speed up generation.",
+    )
+    parser.add_argument(
+        "--use_hpu_graphs",
+        default=True,
+        action="store_true",
+        help="Whether to use HPU graphs or not. Using HPU graphs should give better latencies.",
+    )
+    parser.add_argument(
+        "--dataset_name",
+        default=None,
+        type=str,
+        help="Optional argument if you want to assess your model on a given dataset of the HF Hub.",
+    )
+    parser.add_argument(
+        "--column_name",
+        default=None,
+        type=str,
+        help="If `--dataset_name` was given, this will be the name of the column to use as prompts for generation.",
+    )
+    parser.add_argument(
+        "--do_sample",
+        action="store_true",
+        help="Whether to use sampling for generation.",
+    )
+    parser.add_argument(
+        "--num_beams",
+        default=1,
+        type=int,
+        help="Number of beams used for beam search generation. 1 means greedy search will be performed.",
+    )
+    parser.add_argument(
+        "--trim_logits",
+        action="store_true",
+        help="Calculate logits only for the last token to save memory in the first step.",
+    )
+    parser.add_argument(
+        "--seed",
+        default=27,
+        type=int,
+        help="Seed to use for random generation. Useful to reproduce your runs with `--do_sample`.",
+    )
+    parser.add_argument(
+        "--profiling_warmup_steps",
+        default=0,
+        type=int,
+        help="Number of steps to ignore for profiling.",
+    )
+    parser.add_argument(
+        "--profiling_steps",
+        default=0,
+        type=int,
+        help="Number of steps to capture for profiling.",
+    )
+    parser.add_argument(
+        "--profiling_record_shapes",
+        default=False,
+        type=bool,
+        help="Record shapes when enabling profiling.",
+    )
+    parser.add_argument(
+        "--prompt",
+        default=None,
+        type=str,
+        nargs="*",
+        help='Optional argument to give a prompt of your choice as input. Can be a single string (eg: --prompt "Hello world"), or a list of space-separated strings (eg: --prompt "Hello world" "How are you?")',
+    )
+    parser.add_argument(
+        "--bad_words",
+        default=None,
+        type=str,
+        nargs="+",
+        help="Optional argument list of words that are not allowed to be generated.",
+    )
+    parser.add_argument(
+        "--force_words",
+        default=None,
+        type=str,
+        nargs="+",
+        help="Optional argument list of words that must be generated.",
+    )
+    parser.add_argument(
+        "--assistant_model",
+        default=None,
+        type=str,
+        help="Optional argument to give a path to a draft/assistant model for assisted decoding.",
+    )
+    parser.add_argument(
+        "--peft_model",
+        default=None,
+        type=str,
+        help="Optional argument to give a path to a PEFT model.",
+    )
+    parser.add_argument("--num_return_sequences", type=int, default=1)
+    parser.add_argument(
+        "--token",
+        default=None,
+        type=str,
+        help="The token to use as HTTP bearer authorization for remote files. If not specified, will use the token "
+        "generated when running `huggingface-cli login` (stored in `~/.huggingface`).",
+    )
+    parser.add_argument(
+        "--model_revision",
+        default="main",
+        type=str,
+        help="The specific model version to use (can be a branch name, tag name or commit id).",
+    )
+    parser.add_argument(
+        "--attn_softmax_bf16",
+        action="store_true",
+        help="Whether to run attention softmax layer in lower precision provided that the model supports it and "
+        "is also running in lower precision.",
+    )
+    parser.add_argument(
+        "--output_dir",
+        default=None,
+        type=str,
+        help="Output directory to store results in.",
+    )
+    parser.add_argument(
+        "--bucket_size",
+        default=-1,
+        type=int,
+        help="Bucket size to maintain static shapes. If this number is negative (default is -1) \
+            then we use `shape = prompt_length + max_new_tokens`. If a positive number is passed \
+            we increase the bucket in steps of `bucket_size` instead of allocating to max (`prompt_length + max_new_tokens`).",
+    )
+    parser.add_argument(
+        "--bucket_internal",
+        action="store_true",
+        help="Split kv sequence into buckets in decode phase. It improves throughput when max_new_tokens is large.",
+    )
+    parser.add_argument(
+        "--dataset_max_samples",
+        default=-1,
+        type=int,
+        help="If a negative number is passed (default = -1) perform inference on the whole dataset, else use only `dataset_max_samples` samples.",
+    )
+    parser.add_argument(
+        "--limit_hpu_graphs",
+        action="store_true",
+        help="Skip HPU Graph usage for first token to save memory",
+    )
+    parser.add_argument(
+        "--reuse_cache",
+        action="store_true",
+        help="Whether to reuse key/value cache for decoding. It should save memory.",
+    )
+    parser.add_argument("--verbose_workers", action="store_true", help="Enable output from non-master workers")
+    parser.add_argument(
+        "--simulate_dyn_prompt",
+        default=None,
+        type=int,
+        nargs="*",
+        help="If empty, static prompt is used. If a comma separated list of integers is passed, we warmup and use those shapes for prompt length.",
+    )
+    parser.add_argument(
+        "--reduce_recompile",
+        action="store_true",
+        help="Preprocess on cpu, and some other optimizations. Useful to prevent recompilations when using dynamic prompts (simulate_dyn_prompt)",
+    )
+
+    parser.add_argument(
+        "--use_flash_attention",
+        action="store_true",
+        help="Whether to enable Habana Flash Attention, provided that the model supports it.",
+    )
+    parser.add_argument(
+        "--flash_attention_recompute",
+        action="store_true",
+        help="Whether to enable Habana Flash Attention in recompute mode on first token generation. This gives an opportunity of splitting graph internally which helps reduce memory consumption.",
+    )
+    parser.add_argument(
+        "--flash_attention_causal_mask",
+        action="store_true",
+        help="Whether to enable Habana Flash Attention in causal mode on first token generation.",
+    )
+    parser.add_argument(
+        "--flash_attention_fast_softmax",
+        action="store_true",
+        help="Whether to enable Habana Flash Attention in fast softmax mode.",
+    )
+    parser.add_argument(
+        "--book_source",
+        action="store_true",
+        help="Whether to use project Guttenberg books data as input. Useful for testing large sequence lengths.",
+    )
+    parser.add_argument(
+        "--torch_compile",
+        action="store_true",
+        help="Whether to use torch compiled model or not.",
+    )
+    parser.add_argument(
+        "--ignore_eos",
+        default=True,
+        action=argparse.BooleanOptionalAction,
+        help="Whether to ignore eos, set False to disable it",
+    )
+    parser.add_argument("--temperature", default=1.0, type=float, help="Temperature value for text generation")
+    parser.add_argument("--top_p", default=1.0, type=float, help="Top_p value for generating text via sampling")
+    parser.add_argument(
+        "--const_serialization_path",
+        "--csp",
+        type=str,
+        help="Path to serialize const params. Const params will be held on disk memory instead of being allocated on host memory.",
+    )
+    parser.add_argument(
+        "--disk_offload",
+        action="store_true",
+        help="Whether to enable device map auto. In case no space left on cpu, weights will be offloaded to disk.",
+    )
+    parser.add_argument(
+        "--trust_remote_code",
+        action="store_true",
+        help="Whether or not to allow for custom models defined on the Hub in their own modeling files.",
+    )
+    args = parser.parse_args()
+
+    if args.torch_compile:
+        args.use_hpu_graphs = False
+
+    if not args.use_hpu_graphs:
+        args.limit_hpu_graphs = False
+
+    args.quant_config = os.getenv("QUANT_CONFIG", "")
+    if args.quant_config == "" and args.disk_offload:
+        logger.warning(
+            "`--disk_offload` was tested only with fp8, it may not work with full precision. If error raises try to remove the --disk_offload flag."
+        )
+    return args
+
+
+def adjust_batch(batch, size):
+    curr_size = batch["input_ids"].shape[1]
+    if curr_size >= size:
+        adjusted_batch = {
+            "input_ids": batch["input_ids"][:, :size],
+            "attention_mask": batch["attention_mask"][:, :size],
+        }
+    else:
+        adjusted_batch = {}
+        for k in batch.keys():
+            last_colm = batch[k][:, -1]
+            expanded = last_colm.tile((size - curr_size, 1)).T
+            adjusted_batch[k] = torch.concat([batch[k], expanded], 1)
+    assert adjusted_batch["input_ids"].shape[1] == size
+    assert adjusted_batch["attention_mask"].shape[1] == size
+    return adjusted_batch
+
+
+def override_print(enable):
+    import builtins as __builtin__
+
+    builtin_print = __builtin__.print
+
+    def print(*args, **kwargs):
+        force = kwargs.pop("force", False)
+        if force or enable:
+            builtin_print(*args, **kwargs)
+
+    __builtin__.print = print
+
+
+def override_logger(logger, enable):
+    logger_info = logger.info
+
+    def info(*args, **kwargs):
+        force = kwargs.pop("force", False)
+        if force or enable:
+            logger_info(*args, **kwargs)
+
+    logger.info = info
+
+
+def count_hpu_graphs():
+    return len(glob.glob(".graph_dumps/*PreGraph*"))
+
+
+def override_prints(enable, logger):
+    override_print(enable)
+    override_logger(logger, enable)
+
+
+def setup_inference(args, model):
+    import habana_frameworks.torch.core as htcore
+
+    habana_version = get_habana_frameworks_version()
+
+    print("Initializing inference mode")
+    # Keeping the if-else here for back compat. TODO remove later
+    if habana_version.major >= 1 and habana_version.minor >= 16:
+        htcore.hpu_initialize(model, mark_only_scales_as_const=True)
+    else:
+        const_marking = os.getenv("ENABLE_CONST_MARKING", "True")
+        if const_marking == "True":
+            htcore.hpu_initialize(model)
+    return model
+
+
+def setup_const_serialization(const_serialization_path):
+    import uuid
+
+    const_serialization_path = os.path.join(const_serialization_path + uuid.uuid4().hex)
+    os.makedirs(const_serialization_path)
+    from habana_frameworks.torch.hpu import enable_const_section_serialization
+
+    print("Serializing const params to {}".format(const_serialization_path))
+    enable_const_section_serialization(const_serialization_path, True)
+
+
+def setup_env(args):
+    # Will error if the minimal version of Transformers is not installed. Remove at your own risks.
+    check_min_version("4.34.0")
+    check_optimum_habana_min_version("1.9.0.dev0")
+    # TODO: SW-167588 - WA for memory issue in hqt prep_model
+    os.environ.setdefault("EXPERIMENTAL_WEIGHT_SHARING", "FALSE")
+
+    if args.use_hpu_graphs and args.limit_hpu_graphs and not args.reuse_cache and args.bucket_internal:
+        # Based upon above conditions and below env variable,
+        # we can call HPU graphs clear_inputs().
+        os.environ.setdefault("PT_HPUGRAPH_DISABLE_TENSOR_CACHE", "1")
+
+    # Tweak generation so that it runs faster on Gaudi
+    from optimum.habana.transformers.modeling_utils import adapt_transformers_to_gaudi
+
+    adapt_transformers_to_gaudi()
+
+
+def setup_device(args):
+    if args.device == "hpu":
+        import habana_frameworks.torch.core as htcore
+
+        if args.quant_config:
+            htcore.hpu_set_env()
+    return torch.device(args.device)
+
+
+# patching LinearAllreduce to use ScopedLinearAllReduce
+def patch_scoped_linear_all_reduce(model):
+    from deepspeed.module_inject.layers import LinearAllreduce
+    from optimum.habana.transformers.models.modeling_all_models import ScopedLinearAllReduce
+
+    for name, module in model.named_children():
+        if type(module) is LinearAllreduce:
+            SL = ScopedLinearAllReduce(mod=module)
+            setattr(model, name, SL)
+        patch_scoped_linear_all_reduce(module)
+
+
+def get_torch_compiled_model(model):
+    model.model = torch.compile(model.model, backend="hpu_backend", options={"keep_input_mutations": True})
+    return model
+
+
+def setup_model(args, model_dtype, model_kwargs, logger):
+    logger.info("Single-device run.")
+    os.environ["HUGGINGFACE_HUB_TIMEOUT"] = "300"
+    if args.assistant_model is None:
+        assistant_model = None
+    else:
+        logger.info(f"Using asssitant model {args.assistant_model}.")
+    if args.disk_offload:
+        from accelerate import infer_auto_device_map, init_empty_weights
+
+        config = AutoConfig.from_pretrained(args.model_name_or_path)
+        with init_empty_weights():
+            model = AutoModelForCausalLM.from_config(config)
+        max_memory = {"cpu": "10GiB"}
+        device_map = infer_auto_device_map(model, max_memory=max_memory, dtype=model_dtype)
+        model = AutoModelForCausalLM.from_pretrained(
+            args.model_name_or_path,
+            device_map=device_map,
+            offload_folder="/tmp/offload_folder/",
+            offload_state_dict=True,
+            torch_dtype=model_dtype,
+            **model_kwargs,
+        )
+    else:
+        if args.assistant_model is not None:
+            assistant_model = AutoModelForCausalLM.from_pretrained(
+                args.assistant_model, torch_dtype=model_dtype, **model_kwargs
+            )
+        if args.peft_model is not None:
+            model = peft_model(args, model_dtype, logger, **model_kwargs)
+        else:
+            if args.model_name_or_path == "neo4j/text2cypher-gemma-2-9b-it-finetuned-2024v1":
+                model = AutoModelForCausalLM.from_pretrained(
+                    "google/gemma-2-9b-it", torch_dtype=model_dtype, **model_kwargs
+                )
+                model.load_adapter(args.model_name_or_path)
+            else:
+                model = AutoModelForCausalLM.from_pretrained(
+                    args.model_name_or_path, torch_dtype=model_dtype, **model_kwargs
+                )
+    if args.quant_config:
+        import habana_quantization_toolkit
+
+        habana_quantization_toolkit.prep_model(model)
+        if args.assistant_model is not None:
+            habana_quantization_toolkit.quantize_model(assistant_model)
+
+    model = model.eval().to(args.device)
+    if args.assistant_model is not None:
+        assistant_model = assistant_model.eval().to(args.device)
+
+    if args.use_hpu_graphs:
+        from habana_frameworks.torch.hpu import wrap_in_hpu_graph
+        from optimum.habana.transformers.trainer import _is_peft_model
+
+        if check_habana_frameworks_version("1.13.0") and model.config.model_type == "falcon":
+            model = wrap_in_hpu_graph(model, hash_with_views=False)
+        else:
+            model = wrap_in_hpu_graph(model)
+        if args.assistant_model is not None:
+            assistant_model = wrap_in_hpu_graph(assistant_model)
+        if _is_peft_model(model):
+            model.base_model = wrap_in_hpu_graph(model.base_model)
+
+    if args.torch_compile and model.config.model_type == "llama":
+        model = get_torch_compiled_model(model)
+        # if args.assistant_model is not None:
+        #     assistant_model = get_torch_compiled_model(assistant_model)
+    return model, assistant_model
+
+
+def peft_model(args, model_dtype, logger, **model_kwargs):
+    import importlib.util
+
+    if importlib.util.find_spec("peft") is None:
+        raise ImportError("The `peft` package is not installed, please run: `pip install peft`.")
+    from peft import AutoPeftModelForCausalLM
+    from peft.config import PeftConfigMixin
+
+    base_model_name = PeftConfigMixin.from_pretrained(
+        args.peft_model,
+        token=model_kwargs["token"] if "token" in model_kwargs else None,
+    ).base_model_name_or_path
+
+    base_model_is_local = Path(base_model_name).is_dir()
+    if not base_model_is_local:
+        # Check if the base model path to a remote repository on the HF Hub exists
+        from huggingface_hub import list_repo_files
+
+        try:
+            list_repo_files(base_model_name)
+            base_model_is_remote = True
+        except Exception:
+            base_model_is_remote = False
+
+    if base_model_is_local or base_model_is_remote:
+        model = AutoPeftModelForCausalLM.from_pretrained(args.peft_model, torch_dtype=model_dtype, **model_kwargs)
+    else:
+        # Since the base model doesn't exist locally nor remotely, use `args.model_name_or_path` as the base model
+        logger.warning(
+            f"The base model `{base_model_name}` of the LoRA configuration associated"
+            f" to `{args.peft_model}` does not exist locally or remotely. Using "
+            f"`--model_name_or_path {args.model_name_or_path}` as a fall back for the base model."
+        )
+        from peft import PeftModel
+
+        model = AutoModelForCausalLM.from_pretrained(args.model_name_or_path, torch_dtype=model_dtype, **model_kwargs)
+        model = PeftModel.from_pretrained(model, args.peft_model, torch_dtype=model_dtype, **model_kwargs)
+    if hasattr(model, "merge_and_unload"):
+        model = model.merge_and_unload()
+        if model_dtype == torch.bfloat16:
+            model = model.to(torch.bfloat16)
+        return model
+    else:
+        from optimum.habana.peft.peft_model import gaudi_generate, gaudi_prepare_inputs_for_generation
+
+        model.__class__.generate = gaudi_generate
+        model.__class__.prepare_inputs_for_generation = gaudi_prepare_inputs_for_generation
+        return model
+
+
+def setup_tokenizer(args, model, assistant_model):
+    tokenizer_kwargs = {
+        "revision": args.model_revision,
+        "token": args.token,
+        "trust_remote_code": args.trust_remote_code,
+        "chat_template": "{{ bos_token }}{% if messages[0]['role'] == 'system' %}{{ raise_exception('System role not supported') }}{% endif %}{% for message in messages %}{% if (message['role'] == 'user') != (loop.index0 % 2 == 0) %}{{ raise_exception('Conversation roles must alternate user/assistant/user/assistant/...') }}{% endif %}{% if (message['role'] == 'assistant') %}{% set role = 'model' %}{% else %}{% set role = message['role'] %}{% endif %}{{ '<start_of_turn>' + role + '\n' + message['content'] | trim + '<end_of_turn>\n' }}{% endfor %}{% if add_generation_prompt %}{{'<start_of_turn>model\n'}}{% endif %}",
+    }
+    if args.bad_words is not None or args.force_words is not None:
+        tokenizer_kwargs["add_prefix_space"] = True
+    tokenizer = AutoTokenizer.from_pretrained(args.model_name_or_path, **tokenizer_kwargs)
+    if not model.config.is_encoder_decoder:
+        tokenizer.padding_side = "left"
+
+    if model.config.model_type == "llama":
+        # unwind broken decapoda-research config
+        model.generation_config.pad_token_id = 0
+        model.generation_config.bos_token_id = 1
+        model.generation_config.eos_token_id = 2
+        if assistant_model is not None:
+            assistant_model.generation_config.pad_token_id = 0
+            assistant_model.generation_config.bos_token_id = 1
+            assistant_model.generation_config.eos_token_id = 2
+        tokenizer.bos_token_id = model.generation_config.bos_token_id
+        tokenizer.eos_token_id = model.generation_config.eos_token_id
+        tokenizer.pad_token_id = model.generation_config.pad_token_id
+        tokenizer.pad_token = tokenizer.decode(tokenizer.pad_token_id)
+        tokenizer.eos_token = tokenizer.decode(tokenizer.eos_token_id)
+        tokenizer.bos_token = tokenizer.decode(tokenizer.bos_token_id)
+    if model.config.model_type == "persimmon":
+        model.generation_config.pad_token_id = model.generation_config.eos_token_id
+        if assistant_model is not None:
+            assistant_model.generation_config.pad_token_id = assistant_model.generation_config.eos_token_id
+        tokenizer.bos_token_id = model.generation_config.bos_token_id
+        tokenizer.eos_token_id = model.generation_config.eos_token_id
+        tokenizer.pad_token_id = model.generation_config.pad_token_id
+        tokenizer.pad_token = tokenizer.decode(tokenizer.pad_token_id)
+        tokenizer.eos_token = tokenizer.decode(tokenizer.eos_token_id)
+        tokenizer.bos_token = tokenizer.decode(tokenizer.bos_token_id)
+
+    # Some models like GPT2 do not have a PAD token so we have to set it if necessary
+    if tokenizer.pad_token is None:
+        tokenizer.pad_token = tokenizer.eos_token
+        model.generation_config.pad_token_id = model.generation_config.eos_token_id
+        if assistant_model is not None:
+            assistant_model.generation_config.pad_token_id = assistant_model.generation_config.eos_token_id
+
+    return tokenizer, model, assistant_model
+
+
+def setup_generation_config(args, model, assistant_model, tokenizer):
+    bad_words_ids = None
+    force_words_ids = None
+    if args.bad_words is not None:
+        bad_words_ids = [tokenizer.encode(bad_word, add_special_tokens=False) for bad_word in args.bad_words]
+    if args.force_words is not None:
+        force_words_ids = [tokenizer.encode(force_word, add_special_tokens=False) for force_word in args.force_words]
+
+    is_optimized = model_is_optimized(model.config)
+
+    # Generation configuration
+    generation_config = copy.deepcopy(model.generation_config)
+    generation_config.max_new_tokens = args.max_new_tokens
+    generation_config.use_cache = args.use_kv_cache
+    generation_config.static_shapes = is_optimized and assistant_model is None
+    generation_config.bucket_size = args.bucket_size if is_optimized else -1
+    generation_config.bucket_internal = args.bucket_internal
+    generation_config.do_sample = args.do_sample
+    generation_config.num_beams = args.num_beams
+    generation_config.bad_words_ids = bad_words_ids
+    generation_config.force_words_ids = force_words_ids
+    generation_config.num_return_sequences = args.num_return_sequences
+    generation_config.trim_logits = args.trim_logits
+    generation_config.attn_softmax_bf16 = args.attn_softmax_bf16
+    generation_config.limit_hpu_graphs = args.limit_hpu_graphs
+    generation_config.reuse_cache = args.reuse_cache
+    generation_config.reduce_recompile = args.reduce_recompile
+    if generation_config.reduce_recompile:
+        assert generation_config.bucket_size > 0
+    generation_config.use_flash_attention = args.use_flash_attention
+    generation_config.flash_attention_recompute = args.flash_attention_recompute
+    generation_config.flash_attention_causal_mask = args.flash_attention_causal_mask
+    generation_config.flash_attention_fast_softmax = args.flash_attention_fast_softmax
+    generation_config.trust_remote_code = args.trust_remote_code
+
+    return generation_config
+
+
+def exclude_hpu_graph_configs(args):
+    # Excluded configs for batch size 1 for hpu graph
+    if args.batch_size == 1 and args.limit_hpu_graphs:
+        if "falcon-180B" in args.model_name_or_path or "falcon-180b" in args.model_name_or_path:
+            return False
+        return True
+    else:
+        return False
+
+
+def initialize_model(args, logger):
+    init_start = time.perf_counter()
+    if exclude_hpu_graph_configs(args):
+        args.limit_hpu_graphs = False
+    override_prints(args.verbose_workers, logger)
+    setup_env(args)
+    setup_device(args)
+    set_seed(args.seed)
+    get_repo_root(args.model_name_or_path, local_rank=0, token=args.token)
+    if args.assistant_model is not None:
+        get_repo_root(args.assistant_model, local_rank=0, token=args.token)
+    use_deepspeed = False
+    if use_deepspeed or args.bf16:
+        model_dtype = torch.bfloat16
+    else:
+        model_dtype = torch.float
+        args.attn_softmax_bf16 = False
+    model_kwargs = {
+        "revision": args.model_revision,
+        "token": args.token,
+        "trust_remote_code": args.trust_remote_code,
+    }
+    if args.trust_remote_code:
+        logger.warning("`trust_remote_code` is set, there is no guarantee this model works properly and it may fail")
+
+    model, assistant_model = setup_model(args, model_dtype, model_kwargs, logger)
+    tokenizer, model, assistant_model = setup_tokenizer(args, model, assistant_model)
+    generation_config = setup_generation_config(args, model, assistant_model, tokenizer)
+
+    if args.const_serialization_path:
+        setup_const_serialization(args.const_serialization_path)
+    if args.quant_config:
+        model = setup_inference(args, model)
+    init_end = time.perf_counter()
+    logger.info(f"Args: {args}")
+    logger.info(f"device: {args.device}, n_hpu: 1, bf16: {model_dtype == torch.bfloat16}")
+    logger.info(f"Model initialization took {(init_end - init_start):.3f}s")
+    return model, assistant_model, tokenizer, generation_config
diff --git a/comps/text2cypher/src/integrations/native.py b/comps/text2cypher/src/integrations/native.py
new file mode 100644
index 0000000000..34c34f49ec
--- /dev/null
+++ b/comps/text2cypher/src/integrations/native.py
@@ -0,0 +1,170 @@
+# Copyright (C) 2025 Intel Corporation
+# SPDX-License-Identifier: Apache-2.0
+
+import argparse
+import asyncio
+import os
+import sys
+import threading
+import time
+import traceback
+from string import Template
+from typing import Annotated, Any, Dict, List, Optional, Union
+
+from langchain.chains.llm import LLMChain
+from langchain_community.chains.graph_qa.cypher import GraphCypherQAChain
+from langchain_community.chains.graph_qa.prompts import CYPHER_QA_PROMPT
+from langchain_community.graphs import Neo4jGraph
+from langchain_core.prompts import ChatPromptTemplate, PromptTemplate
+from langchain_huggingface import ChatHuggingFace, HuggingFaceEndpoint
+from langchain_huggingface.llms.huggingface_pipeline import HuggingFacePipeline
+from pydantic import BaseModel, Field
+from pyprojroot import here
+from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline
+
+from comps import CustomLogger, OpeaComponent, OpeaComponentRegistry, ServiceType
+from comps.text2cypher.src.integrations.cypher_utils import (
+    CypherQueryCorrectorExt,
+    construct_schema,
+    cypher_cleanup,
+    cypher_insert,
+    prepare_chat_template,
+)
+from comps.text2cypher.src.integrations.pipeline import GaudiTextGenerationPipeline
+
+logger = CustomLogger("opea_text2cypher_native")
+initialization_lock = threading.Lock()
+initialized = False
+query_chain = None
+
+
+class Neo4jConnection(BaseModel):
+    user: Annotated[str, Field(min_length=1)]
+    password: Annotated[str, Field(min_length=1)]
+    url: Annotated[str, Field(min_length=1)]
+
+
+class Neo4jSeeding(BaseModel):
+    cypher_insert: Annotated[str, Field(min_length=1)]
+    refresh_db: Annotated[bool, Field(default=True)]
+
+
+class Input(BaseModel):
+    input_text: str
+    conn_str: Optional[Neo4jConnection] = None
+    seeding: Optional[Neo4jSeeding] = None
+
+
+@OpeaComponentRegistry.register("OPEA_TEXT2CYPHER")
+class OpeaText2Cypher(OpeaComponent):
+    """A specialized text2cyher component derived from OpeaComponent for text2cypher services."""
+
+    def __init__(self, name: str, description: str, config: dict = None):
+        super().__init__(name, ServiceType.TEXT2CYPHER.name.lower(), description, config)
+        self.config = config
+
+    def _initialize_client(self, input: Input, config: dict = None):
+        """Initializes the chain client."""
+        global query_chain, initialized
+
+        logger.info("[ OpeaText2Cypher ] initialize_client started.")
+        model_name_or_path = config["model_name_or_path"]
+        device = config["device"]
+        chat_model = None
+        if device == "hpu":
+            # Convert config dict back to args-like object
+            args = argparse.Namespace(**config)
+            pipe = GaudiTextGenerationPipeline(args, logger, use_with_langchain=True)
+            hfpipe = HuggingFacePipeline(pipeline=pipe)
+
+            chat_model = ChatHuggingFace(temperature=0.1, llm=hfpipe, tokenizer=pipe.tokenizer)
+
+        else:
+            raise NotImplementedError(f"Only support hpu device now, device {device} not supported.")
+
+        prompt = input.input_text
+        user = os.getenv("NEO4J_USERNAME", "neo4j")
+        password = os.getenv("NEO4J_PASSWORD", "neo4jtest")
+        url = os.getenv("NEO4J_URL")
+
+        graph_store = Neo4jGraph(
+            username=user,
+            password=password,
+            url=url,
+        )
+
+        if input.seeding is None:
+            graph_store.query(cypher_cleanup)
+            graph_store.query(cypher_insert)
+        else:
+            if input.seeding.refresh_db:
+                graph_store.query(cypher_cleanup)
+            graph_store.query(input.seeding.cypher_insert)
+        graph_store.refresh_schema()
+
+        cypher_prompt = PromptTemplate(input_variables=["schema"], template=prepare_chat_template(prompt))
+
+        graph_schema = construct_schema(graph_store.get_structured_schema, [], [])
+
+        cypher_query_corrector = CypherQueryCorrectorExt(schemas=graph_store.schema, schema_str=graph_schema)
+
+        use_qa_llm_kwargs = {"prompt": CYPHER_QA_PROMPT}
+        use_cypher_llm_kwargs = {"prompt": cypher_prompt}
+
+        qa_chain = LLMChain(llm=chat_model, **use_qa_llm_kwargs)
+        cypher_generation_chain = LLMChain(
+            llm=chat_model,
+            **use_cypher_llm_kwargs,
+        )
+
+        query_chain = GraphCypherQAChain(
+            graph=graph_store,
+            graph_schema=graph_schema,
+            qa_chain=qa_chain,
+            cypher_generation_chain=cypher_generation_chain,
+            cypher_query_corrector=cypher_query_corrector,
+            cypher_llm_kwargs={"prompt": cypher_prompt},
+            verbose=True,
+            return_intermediate_steps=True,
+            return_direct=True,
+            allow_dangerous_requests=True,
+        )
+
+        return query_chain
+
+    async def check_health(self) -> bool:
+        """Checks the health of the Text2Cypher service.
+
+        Returns:
+            bool: True if the service is reachable and healthy, False otherwise.
+        """
+        global initialized
+        return initialized
+
+    async def invoke(self, input: Input):
+        """Invokes the text2cypher service.
+
+        Args:
+            input (Inputs): The input for text2cypher service, including input_text and optional connection info.
+
+        Returns:
+            str: the generated output.
+        """
+
+        global query_chain, initialized
+        with initialization_lock:
+            if not initialized:
+                try:
+                    query_chain = self._initialize_client(input, self.config)
+                    initialized = True
+                except Exception as e:
+                    logger.error(f"Error during _initialize_client: {e}")
+                    logger.error(traceback.format_exc())
+
+        try:
+            result = query_chain.run(input.input_text)
+        except Exception as e:
+            logger.error(f"Error during text2cypher invocation: {e}")
+            logger.error(traceback.format_exc())
+
+        return result
diff --git a/comps/text2cypher/src/integrations/pipeline.py b/comps/text2cypher/src/integrations/pipeline.py
new file mode 100644
index 0000000000..b23c8c41ef
--- /dev/null
+++ b/comps/text2cypher/src/integrations/pipeline.py
@@ -0,0 +1,84 @@
+# Copyright (C) 2025 Intel Corporation
+# SPDX-License-Identifier: Apache-2.0
+
+import os
+import sys
+
+import torch
+from transformers import TextGenerationPipeline
+
+SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
+sys.path.append(os.path.dirname(SCRIPT_DIR))
+
+
+class GaudiTextGenerationPipeline(TextGenerationPipeline):
+    def __init__(self, args, logger, use_with_langchain=False, warmup_on_init=True):
+        from comps.text2cypher.src.integrations.gaudiutils import initialize_model
+
+        self.model, _, self.tokenizer, self.generation_config = initialize_model(args, logger)
+
+        self.task = "text-generation"
+        self.device = args.device
+
+        if args.do_sample:
+            self.generation_config.temperature = args.temperature
+            self.generation_config.top_p = args.top_p
+
+        self.max_padding_length = args.max_input_tokens if args.max_input_tokens > 0 else 100
+        self.use_hpu_graphs = args.use_hpu_graphs
+        self.profiling_steps = args.profiling_steps
+        self.profiling_warmup_steps = args.profiling_warmup_steps
+        self.profiling_record_shapes = args.profiling_record_shapes
+
+        self.use_with_langchain = use_with_langchain
+        if self.use_with_langchain:
+            self.generation_config.ignore_eos = False
+
+        if warmup_on_init:
+            import habana_frameworks.torch.hpu as torch_hpu
+
+            logger.info("Graph compilation...")
+
+            warmup_promt = ["Here is my prompt"] * args.batch_size
+            for _ in range(args.warmup):
+                _ = self(warmup_promt)
+            torch_hpu.synchronize()
+
+    def __call__(self, prompt):
+        use_batch = isinstance(prompt, list)
+
+        if use_batch:
+            model_inputs = self.tokenizer.batch_encode_plus(
+                prompt, return_tensors="pt", max_length=self.max_padding_length, padding="max_length", truncation=True
+            )
+        else:
+            model_inputs = self.tokenizer.encode_plus(
+                prompt, return_tensors="pt", max_length=self.max_padding_length, padding="max_length", truncation=True
+            )
+
+        for t in model_inputs:
+            if torch.is_tensor(model_inputs[t]):
+                model_inputs[t] = model_inputs[t].to(self.device)
+
+        output = self.model.generate(
+            **model_inputs,
+            generation_config=self.generation_config,
+            lazy_mode=True,
+            hpu_graphs=self.use_hpu_graphs,
+            profiling_steps=self.profiling_steps,
+            profiling_warmup_steps=self.profiling_warmup_steps,
+            profiling_record_shapes=self.profiling_record_shapes,
+        ).cpu()
+
+        if use_batch:
+            output_text = self.tokenizer.batch_decode(output, skip_special_tokens=True)
+        else:
+            output_text = self.tokenizer.decode(output[0], skip_special_tokens=True)
+
+        if self.use_with_langchain:
+            if use_batch:
+                return [{"generated_text": unbatched_output_text} for unbatched_output_text in output_text]
+            else:
+                return [{"generated_text": output_text}]
+
+        return output_text
diff --git a/comps/text2cypher/src/opea_text2cypher_microservice.py b/comps/text2cypher/src/opea_text2cypher_microservice.py
new file mode 100644
index 0000000000..af2f3f15d0
--- /dev/null
+++ b/comps/text2cypher/src/opea_text2cypher_microservice.py
@@ -0,0 +1,84 @@
+# Copyright (C) 2025 Intel Corporation
+# SPDX-License-Identifier: Apache-2.0
+
+import argparse
+import os
+import time
+
+from comps import (
+    CustomLogger,
+    OpeaComponentLoader,
+    ServiceType,
+    opea_microservices,
+    register_microservice,
+    register_statistics,
+    statistics_dict,
+)
+from comps.text2cypher.src.integrations.gaudiutils import setup_parser
+from comps.text2cypher.src.integrations.native import Input, Neo4jConnection, OpeaText2Cypher
+
+logger = CustomLogger("opea_text2cypher_microservice")
+
+
+@register_microservice(
+    name="opea_service@text2cypher",
+    service_type=ServiceType.TEXT2CYPHER,
+    endpoint="/v1/text2cypher",
+    host="0.0.0.0",
+    port=9097,
+)
+@register_statistics(names=["opea_service@text2cypher"])
+async def text2cypher(input: Input):
+    start = time.time()
+    try:
+        # Use the loader to invoke the active component
+        results = await loader.invoke(input)
+        statistics_dict["opea_service@text2cypher"].append_latency(time.time() - start, None)
+        return results
+    except Exception as e:
+        logger.error(f"Error during text2cypher invocation: {e}")
+        logger.error("An error occurred:", exc_info=True)
+        raise
+
+
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser()
+    args = setup_parser(parser)
+
+    args.load_quantized_model = False
+    args.num_return_sequences = 1
+    args.model_name_or_path = "neo4j/text2cypher-gemma-2-9b-it-finetuned-2024v1"
+    args.max_new_tokens = 512
+    args.use_hpu_graphs = True
+    args.use_kv_cache = True
+    args.do_sample = True
+    args.show_graphs_count = False
+    args.parallel_strategy = "none"
+    args.fp8 = False
+    args.kv_cache_fp8 = False
+    args.temperature = 0.1
+    args.device = "hpu"
+    args.batch_size = 1
+    args.limit_hpu_graphs = False
+    args.reuse_cache = False
+    args.bucket_internal = False
+    args.disk_offload = False
+    args.seed = 27
+    args.token = None
+    args.assistant_model = None
+    args.torch_compile = False
+    args.peft_model = None
+    args.quant_config = os.getenv("QUANT_CONFIG", "")
+    args.bf16 = True
+    args.attn_softmax_bf16 = False
+
+    text2cypher_component_name = os.getenv("TEXT2CYPHER_COMPONENT_NAME", "OPEA_TEXT2CYPHER")
+    # Initialize OpeaComponentLoader
+    loader = OpeaComponentLoader(
+        text2cypher_component_name,
+        description=f"OPEA TEXT2CYPHER Component: {text2cypher_component_name}",
+        config=args.__dict__,
+    )
+
+    logger.info("Text2Cypher server started.")
+    opea_microservices["opea_service@text2cypher"].start()
diff --git a/comps/text2cypher/src/requirements.txt b/comps/text2cypher/src/requirements.txt
new file mode 100644
index 0000000000..96028c8887
--- /dev/null
+++ b/comps/text2cypher/src/requirements.txt
@@ -0,0 +1,30 @@
+accelerate
+docarray[full]
+fastapi
+huggingface_hub
+json_repair
+langchain
+langchain-community
+langchain-huggingface
+langchain_experimental
+llama-index
+llama-index-embeddings-huggingface
+llama-index-embeddings-langchain
+llama-index-graph-stores-neo4j
+llama-index-llms-huggingface
+llama-index-llms-huggingface-api
+neo4j
+opentelemetry-api
+opentelemetry-exporter-otlp
+opentelemetry-sdk
+peft
+prometheus-fastapi-instrumentator
+pydub
+pyprojroot
+sentence-transformers==3.2.1
+shortuuid
+transformers==4.45.2
+unstructured
+urllib3
+uvicorn
+
diff --git a/tests/text2cypher/test_text2cypher_on_intel_hpu.sh b/tests/text2cypher/test_text2cypher_on_intel_hpu.sh
new file mode 100755
index 0000000000..a707c6ae25
--- /dev/null
+++ b/tests/text2cypher/test_text2cypher_on_intel_hpu.sh
@@ -0,0 +1,119 @@
+#!/usr/bin/env bash
+# Copyright (C) 2025 Intel Corporation
+# SPDX-License-Identifier: Apache-2.0
+
+set -x
+
+WORKPATH=$(dirname "$PWD")
+LOG_PATH="$WORKPATH/tests"
+ip_address=$(hostname -I | awk '{print $1}')
+export TEXT2CYPHER_PORT=11801
+export TAG='comps'
+export HUGGINGFACEHUB_API_TOKEN=${HF_TOKEN}
+export HF_TOKEN=${HF_TOKEN}
+export host_ip=${ip_address}
+export NEO4J_PORT1=7474
+export NEO4J_PORT2=7687
+export NEO4J_URI="bolt://${host_ip}:${NEO4J_PORT2}"
+export NEO4J_URL="bolt://${host_ip}:${NEO4J_PORT2}"
+export NEO4J_USERNAME="neo4j"
+export NEO4J_PASSWORD="neo4jtest"
+export no_proxy="localhost,127.0.0.1,"${host_ip}
+export LOGFLAG=True
+
+function build_docker_images() {
+    cd $WORKPATH
+    echo $(pwd)
+    docker build --no-cache -t opea/text2cypher-gaudi:$TAG --build-arg no_proxy=$no_proxy --build-arg https_proxy=$https_proxy --build-arg http_proxy=$http_proxy -f comps/text2cypher/src/Dockerfile.intel_hpu .
+    if [ $? -ne 0 ]; then
+        echo "opea/text2cypher built fail"
+        exit 1
+    else
+        echo "opea/text2cypher built successful"
+    fi
+}
+
+function start_service() {
+    docker compose down
+    unset http_proxy
+    service_name="neo4j-apoc text2cypher-gaudi"
+    export NEO4J_AUTH="neo4j/neo4jtest"
+    export NEO4J_apoc_export_file_enabled=true
+    export NEO4J_apoc_import_file_use__neo4j__config=true
+    export NEO4J_PLUGINS=\[\"apoc\"\]
+
+    cd $WORKPATH/comps/text2cypher/deployment/docker_compose/
+    docker compose up ${service_name} -d
+    sleep 1m
+}
+
+function validate_neo4j_service() {
+    local URL="${ip_address}:7474"
+    local EXPECTED_RESULT="200 OK"
+    local SERVICE_NAME="neo4j-apoc"
+    local CONTAINER_NAME="neo4j-apoc"
+
+    HTTP_RESPONSE=$(curl --silent --write-out "HTTPSTATUS:%{http_code}" "$URL")
+    HTTP_STATUS=$(echo $HTTP_RESPONSE | tr -d '\n' | sed -e 's/.*HTTPSTATUS://')
+    RESPONSE_BODY=$(echo $HTTP_RESPONSE | sed -e 's/HTTPSTATUS\:.*//g')
+
+    docker logs ${CONTAINER_NAME}
+
+    # check response status
+    if [ "$HTTP_STATUS" -ne "200" ]; then
+        echo "[ $SERVICE_NAME ] HTTP status is not 200. Received status was $HTTP_STATUS"
+        exit 1
+    else
+        echo "[ $SERVICE_NAME ] HTTP status is 200."
+    fi
+
+    sleep 1m
+}
+
+function validate_text2cypher_service() {
+    local SERVICE_NAME="text2cypher-gaudi"
+    local CONTAINER_NAME="text2cypher-gaudi-container"
+
+    result=$(http_proxy="" curl http://${ip_address}:${TEXT2CYPHER_PORT}/v1/text2cypher\
+        -X POST \
+        -d '{"input_text": "what are the symptoms for Diabetes?","conn_str": {"user": "'${NEO4J_USERNAME}'","password": "neo4jtest","url": "'${NEO4J_URL}'" }}' \
+        -H 'Content-Type: application/json')
+
+    docker logs ${CONTAINER_NAME}
+
+    if [[ ${#result} -gt 0 ]]; then
+        echo $result
+        echo "Result correct."
+    else
+        echo "Result wrong. Received was $result"
+        exit 1
+    fi
+}
+
+function stop_docker() {
+    cid=$(docker ps -aq --filter "name=text2cypher-gaudi*")
+    if [[ ! -z "$cid" ]]; then
+        docker stop $cid && docker rm $cid && sleep 1s
+    fi
+    cid_db=$(docker ps -aq --filter "name=neo4j-apoc")
+    if [[ ! -z "$cid_db" ]]; then
+        docker stop $cid_db && docker rm $cid_db && sleep 1s
+    fi
+}
+
+function main() {
+
+    stop_docker
+
+    build_docker_images
+    start_service
+
+    #validate_neo4j_service
+    validate_text2cypher_service
+
+    stop_docker
+    echo y | docker system prune
+
+}
+
+main


Reviews:
user: login: Spycsh
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-02-25T01:42:47Z
user: login: Spycsh
body: Please remove/add some content to the empty readme under text2cypher/deployment/docker_compose. Please also change the title of this PR (e.g. add text2cypher component).
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-02-25T01:43:49Z
user: login: jeanyu-habana
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-02-27T18:01:43Z
user: login: jeanyu-habana
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-02-27T18:02:24Z
user: login: Spycsh
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-05T05:44:42Z
user: login: Spycsh
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-05T05:50:34Z
user: login: Spycsh
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-05T05:51:43Z
user: login: Spycsh
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-05T05:51:55Z
user: login: Spycsh
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-05T05:53:18Z
user: login: chensuyue
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-05T05:56:49Z
user: login: chensuyue
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-05T05:57:03Z
user: login: Spycsh
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-05T05:57:35Z
user: login: Spycsh
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-05T05:59:06Z
user: login: Spycsh
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-05T06:02:22Z
user: login: Spycsh
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-05T06:06:21Z
user: login: jeanyu-habana
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-06T18:45:20Z
user: login: jeanyu-habana
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-06T18:46:27Z
user: login: jeanyu-habana
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-06T18:51:39Z
user: login: jeanyu-habana
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-06T18:51:49Z
user: login: jeanyu-habana
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-06T20:10:45Z
user: login: jeanyu-habana
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-06T23:47:19Z
user: login: jeanyu-habana
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T00:04:25Z
user: login: jeanyu-habana
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T00:09:50Z
user: login: jeanyu-habana
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T00:10:45Z
user: login: Spycsh
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T05:04:30Z
user: login: chensuyue
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-07T05:39:14Z
user: login: Spycsh
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-10T02:38:39Z

Review comments:
diff_hunk: @@ -0,0 +1,69 @@
+# Copyright (C) 2024 Intel Corporation
+# SPDX-License-Identifier: Apache-2.0
+
+import argparse
+import os
+import time
+
+from comps import (
+    CustomLogger,
+    OpeaComponentLoader,
+    ServiceType,
+    opea_microservices,
+    register_microservice,
+    register_statistics,
+    statistics_dict,
+)
+from comps.text2cypher.src.integrations.gaudiutils import setup_parser
+from comps.text2cypher.src.integrations.native import OpeaText2Cypher
+
+logger = CustomLogger("opea_text2cypher_microservice")
+
+
+@register_microservice(
+    name="opea_service@text2cypher",
+    service_type=ServiceType.TEXT2CYPHER,
+    endpoint="/v1/text2cypher",
+    host="0.0.0.0",
+    port=9097,
+)
+@register_statistics(names=["opea_service@text2cypher"])
+async def text2cypher(input: SDInputs):
+    start = time.time()
+    try:
+        # Use the loader to invoke the active component
+        results = await loader.invoke(input)
+        statistics_dict["opea_service@text2cypher"].append_latency(time.time() - start, None)
+        return results
+    except Exception as e:
+        logger.error(f"Error during text2cypher invocation: {e}")
+        raise
+
+
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser()
+    args = setup_parser(parser)
+
+    args.load_quantized_model = False
+    args.num_return_sequences = 1
+    args.model_name_or_path = "neo4j/text2cypher-gemma-2-9b-it-finetuned-2024v1"
path: comps/text2cypher/src/opea_text2cypher_microservice.py
user: login: Spycsh
body: I see many of your code contains deepspeed and distributed model initializing steps, but here it seems this 9b model is the only option, that does not require a deepspeed setting. Have you tried the `neo4j/neo4j_llama318b_finetuned_merged_oct24` which use a 318b model? If not I guess you can remove the deepspeed related stuff for now.
created_at: 2025-02-25T01:42:47Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,69 @@
+# Copyright (C) 2024 Intel Corporation
+# SPDX-License-Identifier: Apache-2.0
+
+import argparse
+import os
+import time
+
+from comps import (
+    CustomLogger,
+    OpeaComponentLoader,
+    ServiceType,
+    opea_microservices,
+    register_microservice,
+    register_statistics,
+    statistics_dict,
+)
+from comps.text2cypher.src.integrations.gaudiutils import setup_parser
+from comps.text2cypher.src.integrations.native import OpeaText2Cypher
+
+logger = CustomLogger("opea_text2cypher_microservice")
+
+
+@register_microservice(
+    name="opea_service@text2cypher",
+    service_type=ServiceType.TEXT2CYPHER,
+    endpoint="/v1/text2cypher",
+    host="0.0.0.0",
+    port=9097,
+)
+@register_statistics(names=["opea_service@text2cypher"])
+async def text2cypher(input: SDInputs):
+    start = time.time()
+    try:
+        # Use the loader to invoke the active component
+        results = await loader.invoke(input)
+        statistics_dict["opea_service@text2cypher"].append_latency(time.time() - start, None)
+        return results
+    except Exception as e:
+        logger.error(f"Error during text2cypher invocation: {e}")
+        raise
+
+
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser()
+    args = setup_parser(parser)
+
+    args.load_quantized_model = False
+    args.num_return_sequences = 1
+    args.model_name_or_path = "neo4j/text2cypher-gemma-2-9b-it-finetuned-2024v1"
path: comps/text2cypher/src/opea_text2cypher_microservice.py
user: login: jeanyu-habana
body: done. 
created_at: 2025-02-27T18:01:43Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,69 @@
+# Copyright (C) 2024 Intel Corporation
+# SPDX-License-Identifier: Apache-2.0
+
+import argparse
+import os
+import time
+
+from comps import (
+    CustomLogger,
+    OpeaComponentLoader,
+    ServiceType,
+    opea_microservices,
+    register_microservice,
+    register_statistics,
+    statistics_dict,
+)
+from comps.text2cypher.src.integrations.gaudiutils import setup_parser
+from comps.text2cypher.src.integrations.native import OpeaText2Cypher
+
+logger = CustomLogger("opea_text2cypher_microservice")
+
+
+@register_microservice(
+    name="opea_service@text2cypher",
+    service_type=ServiceType.TEXT2CYPHER,
+    endpoint="/v1/text2cypher",
+    host="0.0.0.0",
+    port=9097,
+)
+@register_statistics(names=["opea_service@text2cypher"])
+async def text2cypher(input: SDInputs):
+    start = time.time()
+    try:
+        # Use the loader to invoke the active component
+        results = await loader.invoke(input)
+        statistics_dict["opea_service@text2cypher"].append_latency(time.time() - start, None)
+        return results
+    except Exception as e:
+        logger.error(f"Error during text2cypher invocation: {e}")
+        raise
+
+
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser()
+    args = setup_parser(parser)
+
+    args.load_quantized_model = False
+    args.num_return_sequences = 1
+    args.model_name_or_path = "neo4j/text2cypher-gemma-2-9b-it-finetuned-2024v1"
path: comps/text2cypher/src/opea_text2cypher_microservice.py
user: login: jeanyu-habana
body: > done. deepspeed/multi-device code was removed.


created_at: 2025-02-27T18:02:23Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,40 @@
+# Deploy text2cypher microservice using docker-compose
+
+## Deploy on Xeon
+
+```
+unset http_proxy
+service_name="neo4j-apoc text2cypher"
+export host_ip=${ip_address}
+export TAG="comps"
+export NEO4J_AUTH="neo4j/neo4jtest"
+export NEO4J_URL="bolt://${ip_address}:7687"
+export NEO4J_USERNAME="neo4j"
+export NEO4J_PASSWORD="neo4jtest"
+export NEO4J_apoc_export_file_enabled=true
+export NEO4J_apoc_import_file_use__neo4j__config=true
+export NEO4J_PLUGINS=\[\"apoc\"\]
+                                                                                                                                     cd $WORKPATH/comps/text2cypher/deployment/docker_compose/
path: comps/text2cypher/deployment/docker_compose/README.md
user: login: Spycsh
body: Please remove the indents.
created_at: 2025-03-05T05:44:42Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,101 @@
+# 🛢 Text-to-Cypher Microservice
+
+The microservice enables a wide range of use cases, making it a versatile tool for businesses, researchers, and individuals alike. Users can generate queries based on natural language questions, enabling them to quickly retrieve relevant data from graph databases.
+
+---
+
+## 🛠️ Features
+
+**Implement Cypher Query based on input text**: Transform user-provided natural language into Cypher queries, subsequently executing them to retrieve data from Graph databases.
+
+---
+
+## ⚙️ Implementation
+
+The text-to-cypher microservice able to implement with various framework and support various types of Graph databases.
+
+### 🔗 Utilizing Text-to-Cypher with Langchain framework
+
+The follow guide provides set-up instructions and comprehensive details regarding the Text-to-Cypher microservices via LangChain. In this configuration, we will employ Neo4J DB as our example database to showcase this microservice.
+
+---
+
+### Start Neo4J Service
+
+[Preparing Neo4J microservice](https://github.com/opea-project/GenAIComps/blob/main/comps/dataprep/src/README_neo4j_llamaindex.md)
+
+### 🚀 Start Text2Cypher Microservice with Python（Option 1）
+
+#### Install Requirements
+
+```bash
+pip install -r requirements.txt
+```
+
+#### Start Text-to-Cypher Microservice with Python Script
+
+Start Text-to-Cypher microservice with below command.
+
+```bash
+python3 opea_text2cypher_microservice.py
+```
+
+---
+
+### 🚀 Start Microservice with Docker (Option 2)
+
+#### Build Docker Image
+
+```bash
+cd GenAIComps/
+docker build -t opea/text2cypher:latest -f comps/text2cypher/src/Dockerfile .
+```
+
+#### Run Docker with CLI (Option A)
+
+```bash
+docker run  --runtime=runc --name="comps-langchain-text2cypher"  -p 9097:8080 --ipc=host opea/text2cypher:latest
path: comps/text2cypher/src/README.md
user: login: Spycsh
body: Is  `--runtime=runc` a must?
created_at: 2025-03-05T05:50:33Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,40 @@
+# Deploy text2cypher microservice using docker-compose
+
+## Deploy on Xeon
+
+```
+unset http_proxy
+service_name="neo4j-apoc text2cypher"
+export host_ip=${ip_address}
path: comps/text2cypher/deployment/docker_compose/README.md
user: login: Spycsh
body: please also add `export ip_address=xxx` 
created_at: 2025-03-05T05:51:43Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,40 @@
+# Deploy text2cypher microservice using docker-compose
+
+## Deploy on Xeon
+
+```
+unset http_proxy
+service_name="neo4j-apoc text2cypher"
+export host_ip=${ip_address}
+export TAG="comps"
+export NEO4J_AUTH="neo4j/neo4jtest"
+export NEO4J_URL="bolt://${ip_address}:7687"
+export NEO4J_USERNAME="neo4j"
+export NEO4J_PASSWORD="neo4jtest"
+export NEO4J_apoc_export_file_enabled=true
+export NEO4J_apoc_import_file_use__neo4j__config=true
+export NEO4J_PLUGINS=\[\"apoc\"\]
+                                                                                                                                     cd $WORKPATH/comps/text2cypher/deployment/docker_compose/
+docker compose up ${service_name} -d
+
+```
+
+## Deploy on Intel Gaudi
+
+```
+unset http_proxy
+service_name="neo4j-apoc text2cypher-gaudi"
+export host_ip=${ip_address}
path: comps/text2cypher/deployment/docker_compose/README.md
user: login: Spycsh
body: same here.
created_at: 2025-03-05T05:51:55Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,57 @@
+# Copyright (C) 2024 Intel Corporation
+# SPDX-License-Identifier: Apache-2.0
+
+ARG UBUNTU_VERSION=22.04
+ARG SYNAPSEAI_VERSION=1.17.0
+ARG INSTALLER_VERSION=2.3.1
+
+FROM vault.habana.ai/gaudi-docker/1.17.0/ubuntu22.04/habanalabs/pytorch-installer-2.3.1:latest
path: comps/text2cypher/src/Dockerfile
user: login: Spycsh
body: 1.17.0 seems a bit old. Have you tested it on 1.19.0+?
created_at: 2025-03-05T05:53:18Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,57 @@
+# Copyright (C) 2024 Intel Corporation
+# SPDX-License-Identifier: Apache-2.0
+
+ARG UBUNTU_VERSION=22.04
+ARG SYNAPSEAI_VERSION=1.17.0
+ARG INSTALLER_VERSION=2.3.1
+
+FROM vault.habana.ai/gaudi-docker/1.17.0/ubuntu22.04/habanalabs/pytorch-installer-2.3.1:latest
path: comps/text2cypher/src/Dockerfile
user: login: chensuyue
body: If this Dockerfile is for CPU, we should not require this container? 
created_at: 2025-03-05T05:56:49Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,48 @@
+# Copyright (C) 2024 Intel Corporation
path: comps/text2cypher/src/Dockerfile.intel_hpu
user: login: chensuyue
body: 2024->2025
created_at: 2025-03-05T05:57:03Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,57 @@
+# Copyright (C) 2024 Intel Corporation
+# SPDX-License-Identifier: Apache-2.0
+
+ARG UBUNTU_VERSION=22.04
+ARG SYNAPSEAI_VERSION=1.17.0
+ARG INSTALLER_VERSION=2.3.1
+
+FROM vault.habana.ai/gaudi-docker/1.17.0/ubuntu22.04/habanalabs/pytorch-installer-2.3.1:latest
path: comps/text2cypher/src/Dockerfile
user: login: Spycsh
body: Ah I see, this is the CPU dockerfile. If this is CPU only, why do you need to use this base image? You can use a very basic image without habana torch like https://github.com/jeanyu-habana/GenAIComps/blob/cypher/comps/llms/src/text-generation/Dockerfile#L4C1-L5C1
created_at: 2025-03-05T05:57:35Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,101 @@
+# 🛢 Text-to-Cypher Microservice
+
+The microservice enables a wide range of use cases, making it a versatile tool for businesses, researchers, and individuals alike. Users can generate queries based on natural language questions, enabling them to quickly retrieve relevant data from graph databases.
+
+---
+
+## 🛠️ Features
+
+**Implement Cypher Query based on input text**: Transform user-provided natural language into Cypher queries, subsequently executing them to retrieve data from Graph databases.
+
+---
+
+## ⚙️ Implementation
+
+The text-to-cypher microservice able to implement with various framework and support various types of Graph databases.
+
+### 🔗 Utilizing Text-to-Cypher with Langchain framework
+
+The follow guide provides set-up instructions and comprehensive details regarding the Text-to-Cypher microservices via LangChain. In this configuration, we will employ Neo4J DB as our example database to showcase this microservice.
+
+---
+
+### Start Neo4J Service
+
+[Preparing Neo4J microservice](https://github.com/opea-project/GenAIComps/blob/main/comps/dataprep/src/README_neo4j_llamaindex.md)
+
+### 🚀 Start Text2Cypher Microservice with Python（Option 1）
+
+#### Install Requirements
+
+```bash
+pip install -r requirements.txt
+```
+
+#### Start Text-to-Cypher Microservice with Python Script
+
+Start Text-to-Cypher microservice with below command.
+
+```bash
+python3 opea_text2cypher_microservice.py
+```
+
+---
+
+### 🚀 Start Microservice with Docker (Option 2)
+
+#### Build Docker Image
+
+```bash
+cd GenAIComps/
+docker build -t opea/text2cypher:latest -f comps/text2cypher/src/Dockerfile .
+```
+
+#### Run Docker with CLI (Option A)
+
+```bash
+docker run  --runtime=runc --name="comps-langchain-text2cypher"  -p 9097:8080 --ipc=host opea/text2cypher:latest
+```
+
+#### Run via docker compose (Option B)
+
+##### Setup Environment Variables.
+
+```bash
+ip_address=$(hostname -I | awk '{print $1}')
+export HF_TOKEN=${HUGGINGFACEHUB_API_TOKEN}
+export NEO4J_USER=neo4j
+export NEO4J_PASSWORD=neo4jtest
+export NEO4J_URL="bolt://${ip_address}:7687"
+```
+
+##### Start the services.
+
+- Xeon CPU
+
+```bash
+cd comps/text2cypher/deployment/docker_compose
+docker compose -f compose.yaml up text2cypher -d
+```
+
+- Gaudi2 HPU
+
+```bash
+cd comps/text2cypher/deployment/docker_compose
+docker compose -f compose.yaml up text2cypher-gaudi -d
+```
+
+---
+
+### ✅ Invoke the microservice.
+
+The Text-to-Cypher microservice exposes the following API endpoints:
+
+- Execute Cypher Query from input text
+
+  ```bash
+  curl http://${your_ip}:9097/v1/text2cypher\
path: comps/text2cypher/src/README.md
user: login: Spycsh
body: your_ip => ip_address?
created_at: 2025-03-05T05:59:05Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,227 @@
+# Copyright (C) 2025 Intel Corporation
+# SPDX-License-Identifier: Apache-2.0
+
+import re
+from collections import namedtuple
+from string import Template
+from typing import Any, Dict, List, Optional, Union
+
+from langchain_community.chains.graph_qa.cypher_utils import CypherQueryCorrector, Schema
+
+from comps import CustomLogger
+
+logger = CustomLogger("opea_text2cypher_cypher_utils")
+Relationship = namedtuple("Relationship", ["entity1", "relationship", "entity2"])
+
+cypher_cleanup = """
+ MATCH (n) DETACH DELETE n
+"""
+
+cypher_insert = """
+ LOAD CSV WITH HEADERS FROM 'https://docs.google.com/spreadsheets/d/e/2PACX-1vQCEUxVlMZwwI2sn2T1aulBrRzJYVpsM9no8AEsYOOklCDTljoUIBHItGnqmAez62wwLpbvKMr7YoHI/pub?gid=0&single=true&output=csv' AS rows
+ MERGE (d:disease {name:rows.Disease})
+ MERGE (dt:diet {name:rows.Diet})
+ //SET dt.Diet = rows.Diet
+ MERGE (d)-[:HOME_REMEDY]->(dt)
+
+ MERGE (m:medication {name:rows.Medication})
+ //SET m.Medication = rows.Medication
+ MERGE (d)-[:TREATMENT]->(m)
+
+ MERGE (s:symptoms {name:rows.Symptom})
+ //SET s.Symptoms = rows.Symptom
+ MERGE (d)-[:MANIFESTATION]->(s)
+
+ MERGE (p:precaution {name:rows.Precaution})
+ //SET p.precaution = rows.Precaution
+ MERGE (d)-[:PREVENTION]->(p)
+
+ //MATCH (n:Description) RETURN count(n) AS count
+"""
+
+
+def prepare_chat_template(question):
+    template = Template(
+        """
+Generate Cypher statement to query a graph database to answer "$question"
+Instructions:
+Use only the provided relationship types and properties in the schema.
+Do not use any other relationship types or properties that are not provided.
+Schema:
+{schema}
+Note: Do not include any explanations or apologies in your responses.
+Use only the user's question to construct a Cypher statement.
+Do not include any text except the generated Cypher statement.
+
+Cypher output:
+    """
+    )
+    temp_str = template.substitute(question=question)
+    return temp_str
+
+
+# in the original string, change the first character of the substring into lower case
+def replace_with_lowercase(s, sub):
+    index = s.lower().find(sub)
+    if index < 0 or index >= len(s):
+        raise ValueError("Index out of range")
+    # Replace the character at the index with its lowercase version
+    return s[:index] + s[index].lower() + s[index + 1 :]
+
+
+def parse_relationships(input_str):
+    # Split by comma to get each relationship
+    logger.info(f"[ parse_relationships ] input_str: {input_str}")
+    substrings = input_str.split(",")
+
+    logger.info(f"[ parse_relationships ] substrings: {substrings}")
+    relationships = []
+
+    for substring in substrings:
+        # Remove unnecessary characters and split by spaces
+        logger.info(f"[ parse_relationships ] substring: {substring}")
+        parts = (
+            substring.replace("(", "")
+            .replace(")", "")
+            .replace(">", "")
+            .replace("[", "")
+            .replace("]", "")
+            .replace(":", "")
+            .split("-")
+        )
+
+        logger.info(f"[ parse_relationships ] parts: {parts}")
+        entity1 = parts[0].strip()
+        relationship = parts[1].strip()
+        entity2 = parts[2].strip()
+
+        # Create a named tuple and add it to the list
+        relationships.append(Relationship(entity1, relationship, entity2))
+
+    return relationships
+
+
+def swap(cypher_string, relations):
+    # Regular expression pattern to match substrings
+    pattern = r"\(([^()]*)\)"
+
+    # Find all matches
+    matches = re.findall(pattern, cypher_string)
+    new_string = cypher_string
+    # Check if there are exactly two matches
+    if len(matches) == 2:
+        first = matches[0]
+        second = matches[1]
+        for rel in relations:
+            e1 = rel.entity1.lower()
+            r = rel.relationship.lower()
+            e2 = rel.entity2.lower()
+            if e1 in first.lower() and e2 in second.lower():
+                new_string = replace_with_lowercase(new_string, e1)
+                new_string = replace_with_lowercase(new_string, e2)
+                break
+            elif e1 in second.lower() and e2 in first.lower():
+                new_string = new_string.replace(f"({second})", "tmp", 1)
+                new_string = new_string.replace(f"({first})", f"({second})", 1)
+                new_string = new_string.replace("tmp", f"({first})", 1)
+                new_string = replace_with_lowercase(new_string, e1)
+                new_string = replace_with_lowercase(new_string, e2)
+                break
+            elif e1 in first.lower() and r in second.lower():
+                new_string = new_string.replace(f"{second[2:]}", f"{e2}", 1)
+                new_string = replace_with_lowercase(new_string, e1)
+                new_string = replace_with_lowercase(new_string, e2)
+                break
+            elif e1 in second.lower() and r in first.lower():
+                new_string = new_string.replace(f"({second})", "tmp", 1)
+                new_string = new_string.replace(f"({first})", f"({second})", 1)
+                new_string = new_string.replace("tmp", f"({first})", 1)
+                # now replace r with e2
+                new_string = new_string.replace(f"{first[2:]}", f"{e2}", 1)
+                new_string = replace_with_lowercase(new_string, e1)
+                new_string = replace_with_lowercase(new_string, e2)
+                break
+    return new_string
+
+
+def construct_schema(
+    structured_schema: Dict[str, Any],
+    include_types: List[str],
+    exclude_types: List[str],
+) -> str:
+    """Filter the schema based on included or excluded types."""
+
+    def filter_func(x: str) -> bool:
+        return x in include_types if include_types else x not in exclude_types
+
+    filtered_schema: Dict[str, Any] = {
+        "node_props": {k: v for k, v in structured_schema.get("node_props", {}).items() if filter_func(k)},
+        "rel_props": {k: v for k, v in structured_schema.get("rel_props", {}).items() if filter_func(k)},
+        "relationships": [
+            r
+            for r in structured_schema.get("relationships", [])
+            if all(filter_func(r[t]) for t in ["start", "end", "type"])
+        ],
+    }
+
+    # Format node properties
+    formatted_node_props = []
+    for label, properties in filtered_schema["node_props"].items():
+        props_str = ", ".join([f"{prop['property']}: {prop['type']}" for prop in properties])
+        formatted_node_props.append(f"{label} {{{props_str}}}")
+
+    # Format relationship properties
+    formatted_rel_props = []
+    for rel_type, properties in filtered_schema["rel_props"].items():
+        props_str = ", ".join([f"{prop['property']}: {prop['type']}" for prop in properties])
+        formatted_rel_props.append(f"{rel_type} {{{props_str}}}")
+
+    # Format relationships
+    formatted_rels = [f"(:{el['start']})-[:{el['type']}]->(:{el['end']})" for el in filtered_schema["relationships"]]
+
+    return "\n".join(
+        [
+            "Node properties are the following:",
+            ",".join(formatted_node_props),
+            "Relationship properties are the following:",
+            ",".join(formatted_rel_props),
+            "The relationships are the following:",
+            ",".join(formatted_rels),
+        ]
+    )
+
+
+class CypherQueryCorrector2(CypherQueryCorrector):
path: comps/text2cypher/src/integrations/cypher_utils.py
user: login: Spycsh
body: Could you change this class name to a more explainable one? Like after inheriting the `CypherQueryCorrector` what have you change. Also a few comments will be great!
created_at: 2025-03-05T06:02:22Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,136 @@
+#!/usr/bin/env bash
+# Copyright (C) 2024 Intel Corporation
+# SPDX-License-Identifier: Apache-2.0
+
+set -x
+
+WORKPATH=$(dirname "$PWD")
+LOG_PATH="$WORKPATH/tests"
+ip_address=$(hostname -I | awk '{print $1}')
+export TEXT2CYPHER_PORT=11801
+export TAG='comps'
+export HUGGINGFACEHUB_API_TOKEN=${HF_TOKEN}
+export HF_TOKEN=${HF_TOKEN}
+export host_ip=${ip_address}
+export NEO4J_PORT1=7474
+export NEO4J_PORT2=7687
+export NEO4J_URI="bolt://${host_ip}:${NEO4J_PORT2}"
+export NEO4J_URL="bolt://${host_ip}:${NEO4J_PORT2}"
+export NEO4J_USERNAME="neo4j"
+export NEO4J_PASSWORD="neo4jtest"
+export no_proxy="localhost,127.0.0.1,"${host_ip}
+export LOGFLAG=True
+
+function build_docker_images() {
+    cd $WORKPATH
+    echo $(pwd)
+    docker build --no-cache -t opea/text2cypher-gaudi:$TAG --build-arg no_proxy=$no_proxy --build-arg https_proxy=$https_proxy --build-arg http_proxy=$http_proxy -f comps/text2cypher/src/Dockerfile.intel_hpu .
+    if [ $? -ne 0 ]; then
+        echo "opea/text2cypher built fail"
+        exit 1
+    else
+        echo "opea/text2cypher built successful"
+    fi
+}
+
+function start_service() {
+    docker compose down
+    unset http_proxy
+    service_name="neo4j-apoc text2cypher-gaudi"
+    export NEO4J_AUTH="neo4j/neo4jtest"
+    export NEO4J_apoc_export_file_enabled=true
+    export NEO4J_apoc_import_file_use__neo4j__config=true
+    export NEO4J_PLUGINS=\[\"apoc\"\]
+
+    cd $WORKPATH/comps/text2cypher/deployment/docker_compose/
+    docker compose up ${service_name} -d
+    sleep 3m
+}
+
+function validate_neo4j_service() {
+    local URL="${ip_address}:7474"
+    local EXPECTED_RESULT="200 OK"
+    local SERVICE_NAME="neo4j-apoc"
+    local CONTAINER_NAME="neo4j-apoc"
+
+    HTTP_RESPONSE=$(curl --silent --write-out "HTTPSTATUS:%{http_code}" "$URL")
+    HTTP_STATUS=$(echo $HTTP_RESPONSE | tr -d '\n' | sed -e 's/.*HTTPSTATUS://')
+    RESPONSE_BODY=$(echo $HTTP_RESPONSE | sed -e 's/HTTPSTATUS\:.*//g')
+
+    #docker logs ${CONTAINER_NAME} >> ${LOG_PATH}/${SERVICE_NAME}.log
+    docker logs ${CONTAINER_NAME}
+
+    # check response status
+    if [ "$HTTP_STATUS" -ne "200" ]; then
+        echo "[ $SERVICE_NAME ] HTTP status is not 200. Received status was $HTTP_STATUS"
+        exit 1
+    else
+        echo "[ $SERVICE_NAME ] HTTP status is 200."
+    fi
+
+    sleep 1m
+}
+
+function validate_text2cypher_service() {
+    local SERVICE_NAME="text2cypher-gaudi"
+    local CONTAINER_NAME="text2cypher-gaudi-container"
+
+    result=$(http_proxy="" curl http://${ip_address}:${TEXT2CYPHER_PORT}/v1/text2cypher\
+        -X POST \
+        -d '{"input_text": "what are the symptoms for Diabetes?","conn_str": {"user": "'${NEO4J_USERNAME}'","password": "neo4jtest","url": "'${NEO4J_URL}'" }}' \
+        -H 'Content-Type: application/json')
+
+# connection refused if localhost and/or 9097
+#    result=$(http_proxy="" curl http://localhost:9097/v1/text2cypher\
+#        -X POST \
+#        -d '{"input_text": "what are the symptoms for Diabetes?","conn_str": {"user": "'${NEO4J_USERNAME}'","password": "neo4jtest","url": "'${NEO4J_URL}'" }}' \
+#        -H 'Content-Type: application/json')
+
+#    result=$(http_proxy="" curl http://${ip_address}:${TEXT2CYPHER_PORT}/v1/text2cypher\                                                                -X POST \                                                                                                                                       -d '{"input_text": "what are the symptoms for Diabetes?","conn_str": {"user": "'${NEO4J_USERNAME}'","password": "'${NEO4J_PASSWPORD}'","url": "'${NEO4J_URL}'" }}' \
path: tests/text2cypher/test_text2cypher_on_intel_hpu.sh
user: login: Spycsh
body: Remove these comments if you don't need them.
created_at: 2025-03-05T06:06:20Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,40 @@
+# Deploy text2cypher microservice using docker-compose
+
+## Deploy on Xeon
+
+```
+unset http_proxy
+service_name="neo4j-apoc text2cypher"
+export host_ip=${ip_address}
+export TAG="comps"
+export NEO4J_AUTH="neo4j/neo4jtest"
+export NEO4J_URL="bolt://${ip_address}:7687"
+export NEO4J_USERNAME="neo4j"
+export NEO4J_PASSWORD="neo4jtest"
+export NEO4J_apoc_export_file_enabled=true
+export NEO4J_apoc_import_file_use__neo4j__config=true
+export NEO4J_PLUGINS=\[\"apoc\"\]
+                                                                                                                                     cd $WORKPATH/comps/text2cypher/deployment/docker_compose/
path: comps/text2cypher/deployment/docker_compose/README.md
user: login: jeanyu-habana
body: done
created_at: 2025-03-06T18:45:20Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,101 @@
+# 🛢 Text-to-Cypher Microservice
+
+The microservice enables a wide range of use cases, making it a versatile tool for businesses, researchers, and individuals alike. Users can generate queries based on natural language questions, enabling them to quickly retrieve relevant data from graph databases.
+
+---
+
+## 🛠️ Features
+
+**Implement Cypher Query based on input text**: Transform user-provided natural language into Cypher queries, subsequently executing them to retrieve data from Graph databases.
+
+---
+
+## ⚙️ Implementation
+
+The text-to-cypher microservice able to implement with various framework and support various types of Graph databases.
+
+### 🔗 Utilizing Text-to-Cypher with Langchain framework
+
+The follow guide provides set-up instructions and comprehensive details regarding the Text-to-Cypher microservices via LangChain. In this configuration, we will employ Neo4J DB as our example database to showcase this microservice.
+
+---
+
+### Start Neo4J Service
+
+[Preparing Neo4J microservice](https://github.com/opea-project/GenAIComps/blob/main/comps/dataprep/src/README_neo4j_llamaindex.md)
+
+### 🚀 Start Text2Cypher Microservice with Python（Option 1）
+
+#### Install Requirements
+
+```bash
+pip install -r requirements.txt
+```
+
+#### Start Text-to-Cypher Microservice with Python Script
+
+Start Text-to-Cypher microservice with below command.
+
+```bash
+python3 opea_text2cypher_microservice.py
+```
+
+---
+
+### 🚀 Start Microservice with Docker (Option 2)
+
+#### Build Docker Image
+
+```bash
+cd GenAIComps/
+docker build -t opea/text2cypher:latest -f comps/text2cypher/src/Dockerfile .
+```
+
+#### Run Docker with CLI (Option A)
+
+```bash
+docker run  --runtime=runc --name="comps-langchain-text2cypher"  -p 9097:8080 --ipc=host opea/text2cypher:latest
path: comps/text2cypher/src/README.md
user: login: jeanyu-habana
body: removed
created_at: 2025-03-06T18:46:27Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,40 @@
+# Deploy text2cypher microservice using docker-compose
+
+## Deploy on Xeon
+
+```
+unset http_proxy
+service_name="neo4j-apoc text2cypher"
+export host_ip=${ip_address}
path: comps/text2cypher/deployment/docker_compose/README.md
user: login: jeanyu-habana
body: done
created_at: 2025-03-06T18:51:38Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,40 @@
+# Deploy text2cypher microservice using docker-compose
+
+## Deploy on Xeon
+
+```
+unset http_proxy
+service_name="neo4j-apoc text2cypher"
+export host_ip=${ip_address}
+export TAG="comps"
+export NEO4J_AUTH="neo4j/neo4jtest"
+export NEO4J_URL="bolt://${ip_address}:7687"
+export NEO4J_USERNAME="neo4j"
+export NEO4J_PASSWORD="neo4jtest"
+export NEO4J_apoc_export_file_enabled=true
+export NEO4J_apoc_import_file_use__neo4j__config=true
+export NEO4J_PLUGINS=\[\"apoc\"\]
+                                                                                                                                     cd $WORKPATH/comps/text2cypher/deployment/docker_compose/
+docker compose up ${service_name} -d
+
+```
+
+## Deploy on Intel Gaudi
+
+```
+unset http_proxy
+service_name="neo4j-apoc text2cypher-gaudi"
+export host_ip=${ip_address}
path: comps/text2cypher/deployment/docker_compose/README.md
user: login: jeanyu-habana
body: done
created_at: 2025-03-06T18:51:48Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,136 @@
+#!/usr/bin/env bash
+# Copyright (C) 2024 Intel Corporation
+# SPDX-License-Identifier: Apache-2.0
+
+set -x
+
+WORKPATH=$(dirname "$PWD")
+LOG_PATH="$WORKPATH/tests"
+ip_address=$(hostname -I | awk '{print $1}')
+export TEXT2CYPHER_PORT=11801
+export TAG='comps'
+export HUGGINGFACEHUB_API_TOKEN=${HF_TOKEN}
+export HF_TOKEN=${HF_TOKEN}
+export host_ip=${ip_address}
+export NEO4J_PORT1=7474
+export NEO4J_PORT2=7687
+export NEO4J_URI="bolt://${host_ip}:${NEO4J_PORT2}"
+export NEO4J_URL="bolt://${host_ip}:${NEO4J_PORT2}"
+export NEO4J_USERNAME="neo4j"
+export NEO4J_PASSWORD="neo4jtest"
+export no_proxy="localhost,127.0.0.1,"${host_ip}
+export LOGFLAG=True
+
+function build_docker_images() {
+    cd $WORKPATH
+    echo $(pwd)
+    docker build --no-cache -t opea/text2cypher-gaudi:$TAG --build-arg no_proxy=$no_proxy --build-arg https_proxy=$https_proxy --build-arg http_proxy=$http_proxy -f comps/text2cypher/src/Dockerfile.intel_hpu .
+    if [ $? -ne 0 ]; then
+        echo "opea/text2cypher built fail"
+        exit 1
+    else
+        echo "opea/text2cypher built successful"
+    fi
+}
+
+function start_service() {
+    docker compose down
+    unset http_proxy
+    service_name="neo4j-apoc text2cypher-gaudi"
+    export NEO4J_AUTH="neo4j/neo4jtest"
+    export NEO4J_apoc_export_file_enabled=true
+    export NEO4J_apoc_import_file_use__neo4j__config=true
+    export NEO4J_PLUGINS=\[\"apoc\"\]
+
+    cd $WORKPATH/comps/text2cypher/deployment/docker_compose/
+    docker compose up ${service_name} -d
+    sleep 3m
+}
+
+function validate_neo4j_service() {
+    local URL="${ip_address}:7474"
+    local EXPECTED_RESULT="200 OK"
+    local SERVICE_NAME="neo4j-apoc"
+    local CONTAINER_NAME="neo4j-apoc"
+
+    HTTP_RESPONSE=$(curl --silent --write-out "HTTPSTATUS:%{http_code}" "$URL")
+    HTTP_STATUS=$(echo $HTTP_RESPONSE | tr -d '\n' | sed -e 's/.*HTTPSTATUS://')
+    RESPONSE_BODY=$(echo $HTTP_RESPONSE | sed -e 's/HTTPSTATUS\:.*//g')
+
+    #docker logs ${CONTAINER_NAME} >> ${LOG_PATH}/${SERVICE_NAME}.log
+    docker logs ${CONTAINER_NAME}
+
+    # check response status
+    if [ "$HTTP_STATUS" -ne "200" ]; then
+        echo "[ $SERVICE_NAME ] HTTP status is not 200. Received status was $HTTP_STATUS"
+        exit 1
+    else
+        echo "[ $SERVICE_NAME ] HTTP status is 200."
+    fi
+
+    sleep 1m
+}
+
+function validate_text2cypher_service() {
+    local SERVICE_NAME="text2cypher-gaudi"
+    local CONTAINER_NAME="text2cypher-gaudi-container"
+
+    result=$(http_proxy="" curl http://${ip_address}:${TEXT2CYPHER_PORT}/v1/text2cypher\
+        -X POST \
+        -d '{"input_text": "what are the symptoms for Diabetes?","conn_str": {"user": "'${NEO4J_USERNAME}'","password": "neo4jtest","url": "'${NEO4J_URL}'" }}' \
+        -H 'Content-Type: application/json')
+
+# connection refused if localhost and/or 9097
+#    result=$(http_proxy="" curl http://localhost:9097/v1/text2cypher\
+#        -X POST \
+#        -d '{"input_text": "what are the symptoms for Diabetes?","conn_str": {"user": "'${NEO4J_USERNAME}'","password": "neo4jtest","url": "'${NEO4J_URL}'" }}' \
+#        -H 'Content-Type: application/json')
+
+#    result=$(http_proxy="" curl http://${ip_address}:${TEXT2CYPHER_PORT}/v1/text2cypher\                                                                -X POST \                                                                                                                                       -d '{"input_text": "what are the symptoms for Diabetes?","conn_str": {"user": "'${NEO4J_USERNAME}'","password": "'${NEO4J_PASSWPORD}'","url": "'${NEO4J_URL}'" }}' \
path: tests/text2cypher/test_text2cypher_on_intel_hpu.sh
user: login: jeanyu-habana
body: done
created_at: 2025-03-06T20:10:45Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,57 @@
+# Copyright (C) 2024 Intel Corporation
+# SPDX-License-Identifier: Apache-2.0
+
+ARG UBUNTU_VERSION=22.04
+ARG SYNAPSEAI_VERSION=1.17.0
+ARG INSTALLER_VERSION=2.3.1
+
+FROM vault.habana.ai/gaudi-docker/1.17.0/ubuntu22.04/habanalabs/pytorch-installer-2.3.1:latest
path: comps/text2cypher/src/Dockerfile
user: login: jeanyu-habana
body: This Dockerfile is for cpu, which is not supported at this time. Will remove any cpu only content (including this Dockerfile).
created_at: 2025-03-06T23:47:18Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,227 @@
+# Copyright (C) 2025 Intel Corporation
+# SPDX-License-Identifier: Apache-2.0
+
+import re
+from collections import namedtuple
+from string import Template
+from typing import Any, Dict, List, Optional, Union
+
+from langchain_community.chains.graph_qa.cypher_utils import CypherQueryCorrector, Schema
+
+from comps import CustomLogger
+
+logger = CustomLogger("opea_text2cypher_cypher_utils")
+Relationship = namedtuple("Relationship", ["entity1", "relationship", "entity2"])
+
+cypher_cleanup = """
+ MATCH (n) DETACH DELETE n
+"""
+
+cypher_insert = """
+ LOAD CSV WITH HEADERS FROM 'https://docs.google.com/spreadsheets/d/e/2PACX-1vQCEUxVlMZwwI2sn2T1aulBrRzJYVpsM9no8AEsYOOklCDTljoUIBHItGnqmAez62wwLpbvKMr7YoHI/pub?gid=0&single=true&output=csv' AS rows
+ MERGE (d:disease {name:rows.Disease})
+ MERGE (dt:diet {name:rows.Diet})
+ //SET dt.Diet = rows.Diet
+ MERGE (d)-[:HOME_REMEDY]->(dt)
+
+ MERGE (m:medication {name:rows.Medication})
+ //SET m.Medication = rows.Medication
+ MERGE (d)-[:TREATMENT]->(m)
+
+ MERGE (s:symptoms {name:rows.Symptom})
+ //SET s.Symptoms = rows.Symptom
+ MERGE (d)-[:MANIFESTATION]->(s)
+
+ MERGE (p:precaution {name:rows.Precaution})
+ //SET p.precaution = rows.Precaution
+ MERGE (d)-[:PREVENTION]->(p)
+
+ //MATCH (n:Description) RETURN count(n) AS count
+"""
+
+
+def prepare_chat_template(question):
+    template = Template(
+        """
+Generate Cypher statement to query a graph database to answer "$question"
+Instructions:
+Use only the provided relationship types and properties in the schema.
+Do not use any other relationship types or properties that are not provided.
+Schema:
+{schema}
+Note: Do not include any explanations or apologies in your responses.
+Use only the user's question to construct a Cypher statement.
+Do not include any text except the generated Cypher statement.
+
+Cypher output:
+    """
+    )
+    temp_str = template.substitute(question=question)
+    return temp_str
+
+
+# in the original string, change the first character of the substring into lower case
+def replace_with_lowercase(s, sub):
+    index = s.lower().find(sub)
+    if index < 0 or index >= len(s):
+        raise ValueError("Index out of range")
+    # Replace the character at the index with its lowercase version
+    return s[:index] + s[index].lower() + s[index + 1 :]
+
+
+def parse_relationships(input_str):
+    # Split by comma to get each relationship
+    logger.info(f"[ parse_relationships ] input_str: {input_str}")
+    substrings = input_str.split(",")
+
+    logger.info(f"[ parse_relationships ] substrings: {substrings}")
+    relationships = []
+
+    for substring in substrings:
+        # Remove unnecessary characters and split by spaces
+        logger.info(f"[ parse_relationships ] substring: {substring}")
+        parts = (
+            substring.replace("(", "")
+            .replace(")", "")
+            .replace(">", "")
+            .replace("[", "")
+            .replace("]", "")
+            .replace(":", "")
+            .split("-")
+        )
+
+        logger.info(f"[ parse_relationships ] parts: {parts}")
+        entity1 = parts[0].strip()
+        relationship = parts[1].strip()
+        entity2 = parts[2].strip()
+
+        # Create a named tuple and add it to the list
+        relationships.append(Relationship(entity1, relationship, entity2))
+
+    return relationships
+
+
+def swap(cypher_string, relations):
+    # Regular expression pattern to match substrings
+    pattern = r"\(([^()]*)\)"
+
+    # Find all matches
+    matches = re.findall(pattern, cypher_string)
+    new_string = cypher_string
+    # Check if there are exactly two matches
+    if len(matches) == 2:
+        first = matches[0]
+        second = matches[1]
+        for rel in relations:
+            e1 = rel.entity1.lower()
+            r = rel.relationship.lower()
+            e2 = rel.entity2.lower()
+            if e1 in first.lower() and e2 in second.lower():
+                new_string = replace_with_lowercase(new_string, e1)
+                new_string = replace_with_lowercase(new_string, e2)
+                break
+            elif e1 in second.lower() and e2 in first.lower():
+                new_string = new_string.replace(f"({second})", "tmp", 1)
+                new_string = new_string.replace(f"({first})", f"({second})", 1)
+                new_string = new_string.replace("tmp", f"({first})", 1)
+                new_string = replace_with_lowercase(new_string, e1)
+                new_string = replace_with_lowercase(new_string, e2)
+                break
+            elif e1 in first.lower() and r in second.lower():
+                new_string = new_string.replace(f"{second[2:]}", f"{e2}", 1)
+                new_string = replace_with_lowercase(new_string, e1)
+                new_string = replace_with_lowercase(new_string, e2)
+                break
+            elif e1 in second.lower() and r in first.lower():
+                new_string = new_string.replace(f"({second})", "tmp", 1)
+                new_string = new_string.replace(f"({first})", f"({second})", 1)
+                new_string = new_string.replace("tmp", f"({first})", 1)
+                # now replace r with e2
+                new_string = new_string.replace(f"{first[2:]}", f"{e2}", 1)
+                new_string = replace_with_lowercase(new_string, e1)
+                new_string = replace_with_lowercase(new_string, e2)
+                break
+    return new_string
+
+
+def construct_schema(
+    structured_schema: Dict[str, Any],
+    include_types: List[str],
+    exclude_types: List[str],
+) -> str:
+    """Filter the schema based on included or excluded types."""
+
+    def filter_func(x: str) -> bool:
+        return x in include_types if include_types else x not in exclude_types
+
+    filtered_schema: Dict[str, Any] = {
+        "node_props": {k: v for k, v in structured_schema.get("node_props", {}).items() if filter_func(k)},
+        "rel_props": {k: v for k, v in structured_schema.get("rel_props", {}).items() if filter_func(k)},
+        "relationships": [
+            r
+            for r in structured_schema.get("relationships", [])
+            if all(filter_func(r[t]) for t in ["start", "end", "type"])
+        ],
+    }
+
+    # Format node properties
+    formatted_node_props = []
+    for label, properties in filtered_schema["node_props"].items():
+        props_str = ", ".join([f"{prop['property']}: {prop['type']}" for prop in properties])
+        formatted_node_props.append(f"{label} {{{props_str}}}")
+
+    # Format relationship properties
+    formatted_rel_props = []
+    for rel_type, properties in filtered_schema["rel_props"].items():
+        props_str = ", ".join([f"{prop['property']}: {prop['type']}" for prop in properties])
+        formatted_rel_props.append(f"{rel_type} {{{props_str}}}")
+
+    # Format relationships
+    formatted_rels = [f"(:{el['start']})-[:{el['type']}]->(:{el['end']})" for el in filtered_schema["relationships"]]
+
+    return "\n".join(
+        [
+            "Node properties are the following:",
+            ",".join(formatted_node_props),
+            "Relationship properties are the following:",
+            ",".join(formatted_rel_props),
+            "The relationships are the following:",
+            ",".join(formatted_rels),
+        ]
+    )
+
+
+class CypherQueryCorrector2(CypherQueryCorrector):
path: comps/text2cypher/src/integrations/cypher_utils.py
user: login: jeanyu-habana
body: done
created_at: 2025-03-07T00:04:25Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,101 @@
+# 🛢 Text-to-Cypher Microservice
+
+The microservice enables a wide range of use cases, making it a versatile tool for businesses, researchers, and individuals alike. Users can generate queries based on natural language questions, enabling them to quickly retrieve relevant data from graph databases.
+
+---
+
+## 🛠️ Features
+
+**Implement Cypher Query based on input text**: Transform user-provided natural language into Cypher queries, subsequently executing them to retrieve data from Graph databases.
+
+---
+
+## ⚙️ Implementation
+
+The text-to-cypher microservice able to implement with various framework and support various types of Graph databases.
+
+### 🔗 Utilizing Text-to-Cypher with Langchain framework
+
+The follow guide provides set-up instructions and comprehensive details regarding the Text-to-Cypher microservices via LangChain. In this configuration, we will employ Neo4J DB as our example database to showcase this microservice.
+
+---
+
+### Start Neo4J Service
+
+[Preparing Neo4J microservice](https://github.com/opea-project/GenAIComps/blob/main/comps/dataprep/src/README_neo4j_llamaindex.md)
+
+### 🚀 Start Text2Cypher Microservice with Python（Option 1）
+
+#### Install Requirements
+
+```bash
+pip install -r requirements.txt
+```
+
+#### Start Text-to-Cypher Microservice with Python Script
+
+Start Text-to-Cypher microservice with below command.
+
+```bash
+python3 opea_text2cypher_microservice.py
+```
+
+---
+
+### 🚀 Start Microservice with Docker (Option 2)
+
+#### Build Docker Image
+
+```bash
+cd GenAIComps/
+docker build -t opea/text2cypher:latest -f comps/text2cypher/src/Dockerfile .
+```
+
+#### Run Docker with CLI (Option A)
+
+```bash
+docker run  --runtime=runc --name="comps-langchain-text2cypher"  -p 9097:8080 --ipc=host opea/text2cypher:latest
+```
+
+#### Run via docker compose (Option B)
+
+##### Setup Environment Variables.
+
+```bash
+ip_address=$(hostname -I | awk '{print $1}')
+export HF_TOKEN=${HUGGINGFACEHUB_API_TOKEN}
+export NEO4J_USER=neo4j
+export NEO4J_PASSWORD=neo4jtest
+export NEO4J_URL="bolt://${ip_address}:7687"
+```
+
+##### Start the services.
+
+- Xeon CPU
+
+```bash
+cd comps/text2cypher/deployment/docker_compose
+docker compose -f compose.yaml up text2cypher -d
+```
+
+- Gaudi2 HPU
+
+```bash
+cd comps/text2cypher/deployment/docker_compose
+docker compose -f compose.yaml up text2cypher-gaudi -d
+```
+
+---
+
+### ✅ Invoke the microservice.
+
+The Text-to-Cypher microservice exposes the following API endpoints:
+
+- Execute Cypher Query from input text
+
+  ```bash
+  curl http://${your_ip}:9097/v1/text2cypher\
path: comps/text2cypher/src/README.md
user: login: jeanyu-habana
body: fixed the curl command with correct ip_address
created_at: 2025-03-07T00:09:50Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,48 @@
+# Copyright (C) 2024 Intel Corporation
path: comps/text2cypher/src/Dockerfile.intel_hpu
user: login: jeanyu-habana
body: corrected
created_at: 2025-03-07T00:10:45Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,174 @@
+# Copyright (C) 2025 Intel Corporation
+# SPDX-License-Identifier: Apache-2.0
+
+import re
+from collections import namedtuple
+from string import Template
+from typing import Any, Dict, List, Optional, Union
+
+from langchain_community.chains.graph_qa.cypher_utils import CypherQueryCorrector, Schema
+
+from comps import CustomLogger
+
+logger = CustomLogger("opea_text2cypher_cypher_utils")
+Relationship = namedtuple("Relationship", ["entity1", "relationship", "entity2"])
+
+cypher_cleanup = """
+ MATCH (n) DETACH DELETE n
+"""
+
+cypher_insert = """
+ LOAD CSV WITH HEADERS FROM 'https://docs.google.com/spreadsheets/d/e/2PACX-1vQCEUxVlMZwwI2sn2T1aulBrRzJYVpsM9no8AEsYOOklCDTljoUIBHItGnqmAez62wwLpbvKMr7YoHI/pub?gid=0&single=true&output=csv' AS rows
path: comps/text2cypher/src/integrations/cypher_utils.py
user: login: Spycsh
body: Just summarize some key points from our discussion offline:

1) offer customizable cypher_insert statements (e.g. in the Input class in comps/text2cypher/src/integrations/native.py), passed by users.
2) provide the capability to cleanup previous database when users input new cypher_insert input (may be by setting a flag in the Input class).
3) add README about above changes.
created_at: 2025-03-07T05:04:30Z
author_association: COLLABORATOR

PR comments:
user: login: chensuyue
created_at: 2025-02-24T03:24:13Z
body: Please check and fix the CI issues. 
author_association: COLLABORATOR
user: login: jeanyu-habana
created_at: 2025-02-26T16:21:56Z
body: > Please remove/add some content to the empty readme under text2cypher/deployment/docker_compose. Please also change the title of this PR (e.g. add text2cypher component).

Thank you for your comment. README updated and PR title changed. 
author_association: COLLABORATOR
user: login: jeanyu-habana
created_at: 2025-02-28T04:28:54Z
body: > Please check and fix the CI issues.

The HF_TOKEN (set in the system) does not have access to a gates model: "google/gemma-2-9b-it". Would you be able to request access to this model using the HF_TOKEN? Without this, my PR cannot pass the microservice test. @chensuyue 
author_association: COLLABORATOR
user: login: chensuyue
created_at: 2025-02-28T07:44:02Z
body: > > Please check and fix the CI issues.
> 
> The HF_TOKEN (set in the system) does not have access to a gates model: "google/gemma-2-9b-it". Would you be able to request access to this model using the HF_TOKEN? Without this, my PR cannot pass the microservice test. @chensuyue

Current token has this permission. 
author_association: COLLABORATOR
user: login: codecov[bot]
created_at: 2025-02-28T07:46:42Z
body: ## [Codecov](https://app.codecov.io/gh/opea-project/GenAIComps/pull/1319?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opea-project) Report
All modified and coverable lines are covered by tests :white_check_mark:

| [Files with missing lines](https://app.codecov.io/gh/opea-project/GenAIComps/pull/1319?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opea-project) | Coverage Δ | |
|---|---|---|
| [comps/cores/mega/constants.py](https://app.codecov.io/gh/opea-project/GenAIComps/pull/1319?src=pr&el=tree&filepath=comps%2Fcores%2Fmega%2Fconstants.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opea-project#diff-Y29tcHMvY29yZXMvbWVnYS9jb25zdGFudHMucHk=) | `97.01% <100.00%> (+0.09%)` | :arrow_up: |

<details><summary>🚀 New features to boost your workflow: </summary>

- ❄ [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>
author_association: NONE
user: login: jeanyu-habana
created_at: 2025-03-02T00:19:51Z
body: @Spycsh Could you please review the changes made per you request?
author_association: COLLABORATOR
user: login: jeanyu-habana
created_at: 2025-03-04T18:01:29Z
body: @chensuyue @Spycsh Could you please review the changes according to your requests? Thank you! 
author_association: COLLABORATOR
user: login: rbrugaro
created_at: 2025-03-07T18:02:51Z
body: @jeanyu-habana Neo4j and also FalkorDB have published their GraphRAG python packages for graph extraction and natural language querying (text -> cypher -> exec query -> text) https://github.com/neo4j/neo4j-graphrag-python 
should we leverage that instead of reimplement?
author_association: COLLABORATOR
user: login: Spycsh
created_at: 2025-03-10T02:38:27Z
body: > @jeanyu-habana Neo4j and also FalkorDB have published their GraphRAG python packages for graph extraction and natural language querying (text -> cypher -> exec query -> text) https://github.com/neo4j/neo4j-graphrag-python should we leverage that instead of reimplement?

@rbrugaro I have a look on the neo4j-graphrag-python and I think it is more like an e2e solution rather than a component-level work. If you compared the code it seems that neo4j-graphrag-python uses the gpt-4o to do the text2cypher (retrieve the graph entities/relations -> talk with gpt-4o to do conversion to cypher query statements), but @jeanyu-habana 's PR seems to simply use the specialized model `neo4j/text2cypher-gemma-2-9b-it-finetuned-2024v1` to do the conversion from text to cypher. I believe they are different stories.

@jeanyu-habana has a conversation with me earlier and she claimed that `neo4j/text2cypher-gemma-2-9b-it-finetuned-2024v1` is better than SOTA LLMs like ChatGPT/DeepSeek in text2cypher tasks. Therefore from my perspective, I think this PR is eligible as a GenAI component here. Of course we can also keep an eye on neo4j-graphrag-python so we do not miss any official updates and bringing those features into GenAIComps is welcome!
author_association: COLLABORATOR
user: login: jeanyu-habana
created_at: 2025-03-10T03:33:21Z
body: > @jeanyu-habana Neo4j and also FalkorDB have published their GraphRAG python packages for graph extraction and natural language querying (text -> cypher -> exec query -> text) https://github.com/neo4j/neo4j-graphrag-python should we leverage that instead of reimplement?
Here is a comparison between our Text2Cypher component and neo4j-graphrag-python:
1. Our Text2Cypher component allows the user to choose from both open and close LLM models. In our example, we chose the best open model that is fined-tuned for cypher generation and host the model locally on Intel Gaudi. The user can also use any close models hosted online (for example, OpenAI). On the other hand, the neo4j-graphrag-python requires only close models that are hosted online. but local LLM is not supported. 
2. our Text2Cypher detects Neo4j Graph database schema automatically, but the neo4j-graphrag-python requires the user to enter the schema specifically.
3. our Text2Cypher took advantage of the langchain platform and extends the langchain's CypherQueryCorrector. The corrector fixed common errors in the generated cypher. For example, it corrects relationship direction in generated Cypher statements; and it removes any extra text around the cypher statement. Our implementation is more likely to generate correct cypher. On the contrary, the neo4j-graphrag-python makes no effort in choosing the right model or fix the cypher. There is only a warning in their documentation: "Warning The LLM-generated query is not guaranteed to be syntactically correct. In case it can’t be executed, a Text2CypherRetrievalError is raised."

author_association: COLLABORATOR