Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions byokg-rag/src/graphrag_toolkit/byokg_rag/byokg_query_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,15 +168,23 @@ def query(self, query: str, iterations: int = 2, cypher_iterations: int = 2, use
context, linked_entities_cypher = self.graph_query_executor.retrieve(linking_query, return_answers=True)
cypher_context_with_feedback += context
if len(linked_entities_cypher) == 0:
cypher_context_with_feedback.append("No executable results for the above cypher query for entity linking. Please improve cypher generation in the future for linking.")

# Check if the context contains an actual execution error
has_error = any("Error" in c and "Error executing query" in c for c in context)
if has_error:
cypher_context_with_feedback.append("The above cypher query for entity linking failed with an error. Please review the error message and fix the query syntax or schema references.")
else:
cypher_context_with_feedback.append("No executable results for the above cypher query for entity linking. Please improve cypher generation in the future for linking.")

if "opencypher" in artifacts:
graph_query = " ".join(artifacts["opencypher"])
context, answers = self.graph_query_executor.retrieve(graph_query, return_answers=True)
cypher_context_with_feedback += context
if len(answers) == 0:
cypher_context_with_feedback.append("No executable results for the above. Please improve cypher generation in the future by focusing more on the given schema and the relations between node types.")
has_error = any("Error" in c and "Error executing query" in c for c in context)
if has_error:
cypher_context_with_feedback.append("The above cypher query failed with an error. Please review the error message and fix the query syntax or schema references.")
else:
cypher_context_with_feedback.append("No executable results for the above. Please improve cypher generation in the future by focusing more on the given schema and the relations between node types.")

if self.kg_linker is None:
return cypher_context_with_feedback
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -441,9 +441,9 @@ def retrieve(self, graph_query: str, return_answers=False, **kwargs):
return [context]

except Exception as e:
# Return error context based on return_answers flag
error_context = f"Error executing query: {graph_query}\nError: {str(e)}"
error_msg = f"Error executing query: {graph_query}\nError: {type(e).__name__}: {e}"
if return_answers:
return [error_context], []
return [error_msg], []
else:
return [error_context]
return [error_msg]

85 changes: 85 additions & 0 deletions byokg-rag/tests/test_cypher_error_feedback.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
from unittest.mock import MagicMock

import pytest
from graphrag_toolkit.byokg_rag.graph_retrievers.graph_retrievers import (
GraphQueryRetriever,
)


class TestGraphQueryRetrieverErrorFeedback:
@pytest.mark.parametrize(
"exc",
[
SyntaxError("Variable `x` not defined"),
RuntimeError("Connection timeout"),
ValueError("Unknown label"),
],
)
def test_error_surfaces_type_and_message(self, exc):
store = MagicMock()
store.execute_query.side_effect = exc
context, answers = GraphQueryRetriever(store).retrieve(
"MATCH (n) RETURN n", return_answers=True
)
assert answers == []
assert type(exc).__name__ in context[0]
assert str(exc) in context[0]

def test_successful_query_returns_results(self):
store = MagicMock()
store.execute_query.return_value = [{"name": "Alice"}]
context, answers = GraphQueryRetriever(store).retrieve(
"MATCH (n) RETURN n.name", return_answers=True
)
assert answers == [{"name": "Alice"}]
assert "Execution Result" in context[0]
assert "Error" not in context[0]


class TestCypherRetryFeedback:
def _make_engine(self, retrieve_fn):
from graphrag_toolkit.byokg_rag.byokg_query_engine import ByoKGQueryEngine

linker = MagicMock()
linker.task_prompts = ""
linker.is_cypher_linker.return_value = True

executor = MagicMock()
executor.retrieve.side_effect = retrieve_fn

engine = object.__new__(ByoKGQueryEngine)
engine.cypher_kg_linker = linker
engine.kg_linker = None
engine.graph_query_executor = executor
engine.schema = "(:Person)-[:KNOWS]->(:Person)"
engine.direct_query_linking = False
engine.entity_linker = None
engine.triplet_retriever = None
engine.path_retriever = None
return engine, linker

@pytest.mark.parametrize("tag", ["opencypher", "opencypher-linking"])
def test_error_gets_error_specific_feedback(self, tag):
def fake(query, return_answers=False):
return [f"Error executing query: {query}\nError: SyntaxError: bad"], []

engine, linker = self._make_engine(fake)
linker.generate_response.return_value = f"<{tag}>MATCH (n) RETURN m</{tag}>"
linker.parse_response.return_value = {tag: ["MATCH (n) RETURN m"]}

feedback = "\n".join(engine.query("test?", cypher_iterations=1))
assert "review the error message" in feedback

def test_empty_results_gets_generic_feedback(self):
def fake(query, return_answers=False):
return [f"Graph Query: {query}\nExecution Result: []"], []

engine, linker = self._make_engine(fake)
linker.generate_response.return_value = (
"<opencypher>MATCH (n) RETURN n</opencypher>"
)
linker.parse_response.return_value = {"opencypher": ["MATCH (n) RETURN n"]}

feedback = "\n".join(engine.query("test?", cypher_iterations=1))
assert "focusing more on the given schema" in feedback
assert "review the error message" not in feedback