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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ In addition to the `AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_API_KEY`, and `azure_d
}
}
}
```
```

* AWS Bedrock

Expand Down Expand Up @@ -276,7 +276,7 @@ In addition to the `AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_API_KEY`, and `azure_d

* Hugging Face

Example configuration for a model on Hugging Face with a dedicated endpoint is shown below. Please specify your configuration details:\
Example configuration for a model on Hugging Face with a dedicated endpoint is shown below. Please specify your configuration details:

```json
{
Expand Down
40 changes: 40 additions & 0 deletions common/db/connections.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,3 +160,43 @@ def elevate_db_connection_to_token(host, username, password, graphname, async_co
conn.restppUrl = conn.restppUrl+"/restpp"

return conn

def get_schema_ver(conn: TigerGraphConnectionProxy) -> int:
"""Retrieves the schema version of the graph by running an interpreted query.

Returns:
The schema version as an integer.
"""
logger.info("entry: _get_schema_ver")

# Create the interpreted query to get schema version
query_text = f'INTERPRET QUERY () FOR GRAPH {conn.graphname} {{ PRINT "OK"; }}'

try:
# Run the interpreted query
#result = self.conn.runInterpretedQuery(query_text)
if conn._version_greater_than_4_0():
ret = conn._post(conn.gsUrl + "/gsql/v1/queries/interpret",
params={}, data=query_text, authMode="pwd", resKey="version",
headers={'Content-Type': 'text/plain'})
else:
ret = conn._post(conn.gsUrl + "/gsqlserver/interpreted_query", data=query_text,
params={}, authMode="pwd", resKey="version")

schema_version_int = None
if ret and "version" in ret:
version_info = ret["version"]
if isinstance(version_info, dict) and "schema" in version_info:
schema_version = version_info["schema"]
try:
schema_version_int = int(schema_version)
except (ValueError, TypeError):
logger.warning(f"Schema version '{schema_version}' could not be converted to integer")
if schema_version_int is None:
logger.warning("Schema version not found in query result")
logger.info("exit: _get_schema_ver")
return schema_version_int

except Exception as e:
logger.error(f"Error getting schema version: {str(e)}")
raise Exception(f"Failed to get schema version: {str(e)}")
43 changes: 42 additions & 1 deletion common/llm_services/base_llm.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,19 @@ def generate_function_prompt(self):
@property
def generate_cypher_prompt(self):
"""Property to get the prompt for the GenerateCypher tool."""
prompt = """You're an expert in OpenCypher programming. Given the following schema: {schema}, what is the OpenCypher query that retrieves the {question}
prompt = """You're an expert in OpenCypher programming. Given the following schema, what is the OpenCypher query that retrieves the {question}
Only include attributes that are found in the schema. Never include any attributes that are not found in the schema.
Use attributes instead of primary id if attribute name is closer to the keyword type in the question.
Use as less vertex type, edge type and attributes as possible. If an attribute is not found in the schema, please exclude it from the query.
Do not return attributes that are not explicitly mentioned in the question. If a vertex type is mentioned in the question, only return the vertex.
Never use directed edge pattern in the OpenCypher query. Always use and create query using undirected pattern.
Always use double quotes for strings instead of single quotes.

Avoid generating invalid OpenCypher queries based on the errors from history below.

Schema: {schema}
History: {history}

You cannot use the following clauses:
OPTIONAL MATCH
CREATE
Expand All @@ -48,6 +53,42 @@ def generate_cypher_prompt(self):
ONLY write the OpenCypher query in the response. Do not include any other information in the response."""
return prompt

@property
def generate_gsql_prompt(self):
"""Property to get the prompt for the GenerateGSQL tool."""
prompt = """You're an expert in GSQL (Graph SQL) programming for TigerGraph. Given the following schema: {schema}, what is the GSQL query that retrieves the answer for question: {question}
Only include attributes that are found in the schema. Never include any attributes that are not found in the schema.
Use attributes instead of primary id if attribute name is more similar to the keyword type in the question.
Use as few vertex types, edge types and attributes as possible. If an attribute is not found in the schema, please exclude it from the query.
Do not return attributes that are not explicitly mentioned in the question. If a vertex type is mentioned in the question, only return the vertex.
Always use double quotes for strings instead of single quotes.
Use alias for ORDER BY if any, and make sure the alias or attributes used in ORDER BY is also in PRINT. Always add ASC or DESC for ORDER BY based on data type.

Avoid generating invalid GSQL queries based on the errors from history below.

Schema: {schema}
History: {history}

Additionally, you cannot use the following clauses:
CREATE
DELETE
INSERT
UPDATE
UPSERT

Here's some commonly used abbreviations:
dt -> date
pct -> percentage
qty -> quantity
lng -> longitude
cm -> Contract Manufacturer

Always make the GSQL query returns the entity in the original question together with the data to be queried.
Make sure to have correct attribute names in the GSQL query and not to name result aliases that are vertex or edge types, operator or function names, and other reserved keywords, always construct alias with multiple words connected with underscore.

ONLY write the GSQL query in the response. Do not include any other information in the response."""
return prompt

@property
def route_response_prompt(self):
"""Property to get the prompt for the RouteResponse tool."""
Expand Down
8 changes: 8 additions & 0 deletions common/llm_services/google_genai_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@ def generate_cypher_prompt(self):
else:
return super().generate_cypher_prompt

@property
def generate_gsql_prompt(self):
filepath = self.prompt_path + "generate_gsql.txt"
if os.path.exists(filepath):
return self._read_prompt_file(filepath)
else:
return super().generate_gsql_prompt

@property
def entity_relationship_extraction_prompt(self):
return self._read_prompt_file(
Expand Down
8 changes: 8 additions & 0 deletions common/llm_services/openai_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ def generate_cypher_prompt(self):
else:
return super().generate_cypher_prompt

@property
def generate_gsql_prompt(self):
filepath = self.prompt_path + "generate_gsql.txt"
if os.path.exists(filepath):
return self._read_prompt_file(filepath)
else:
return super().generate_gsql_prompt

@property
def entity_relationship_extraction_prompt(self):
return self._read_prompt_file(
Expand Down
5 changes: 3 additions & 2 deletions common/prompts/openai_gpt4/chatbot_response.txt
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
You are a highly efficient and empathetic AI-powered customer support agent for TigerGraph. Your goal is to provide accurate, helpful, and friendly support while maintaining professionalism. Follow these guidelines:

1. **Understand the User's Needs**: Carefully interpret the user's question or issue. Ask clarifying questions if necessary.
2. **Provide Accurate Answers**: Use the given context in JSON format to deliver reliable and precise responses.
2. **Provide Accurate Answers**: Use the given context in JSON format as well as the data structure of the original query used to fecth the context to deliver reliable and precise responses.
3. **Be Polite and Friendly**: Maintain a tone that is warm, respectful, and supportive.
4. **Stay Within Scope**: Respond only with information relevant to the user's question. If the information is unavailable, apologize and suggest alternative actions.
5. **Encourage Next Steps**: Proactively suggest solutions or next steps, such as contacting a human agent if the issue cannot be resolved by the AI.
6. **Escalate When Needed**: Recognize complex or sensitive queries that require human attention and politely guide the user to contact human support.
7. **Reference Relevant Knowledge**: Ground your responses based on the context and resources. Include the IDs of the knowledge (always in UUID format) you refer to in your response in the format [UUID].
7. **Reference Relevant Knowledge**: Ground your responses based on the context and resources, and referencing the data struction from the query providedy. Include the IDs of the knowledge (always in UUID format) you refer to in your response in the format [UUID].
8. **Here are the channels to reach the TigerGraph team**:
- [Contact Sales](https://www.tigergraph.com/contact/)
- [Support Portal](https://www.tigergraph.com/support/)
Expand All @@ -29,4 +29,5 @@ Make sure all the fields required by the format instructions are included, set a

Question: {question}
Context: {context}
Query: {query}
Format: {format_instructions}
23 changes: 20 additions & 3 deletions common/prompts/openai_gpt4/generate_cypher.txt
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
You're an expert in OpenCypher programming. Given the following schema: {schema}, what is the OpenCypher query that retrieves the answer for question: {question}.
You're an expert in OpenCypher programming. Given the following schema, find the best OpenCypher query that retrieves the answer for question {question}.
If there're multiple words in the question having same meaning then remove the duplication.
Only include attributes that are found in the schema. Never include any attributes that are not found in the schema.
Use attributes instead of primary id if attribute name is more similar to the keyword type in the question.
Use as less vertex type, edge type and attributes as possible. If an attribute is not found in the schema, please exclude it from the query.
Always make sure the attributes used exist in the vertex type or edge type referenced, DO NOT use an attribute that does not exist in the vertex or edge from the schema.
Do not return attributes that are not explicitly mentioned in the question. If a vertex type is mentioned in the question, only return the vertex.
Never use directed edge pattern in the OpenCypher query. Always use and create query using undirected pattern. Always ensure the edge used starts from and ends with correct vertex types matching the schema.
Always use double quotes for strings instead of single quotes.
Use alias for ORDER BY if any, and make sure the same alias is used in both RETURN and ORDER BY. Always add ASC or DESC for ORDER BY based on data type.
Use alias for ORDER BY if any, and make sure the alias or attributes used in ORDER BY is also in RETURN. Always add ASC or DESC for ORDER BY based on data type.

Avoid to generate invalid OpenCypher queries based on the errors from history below.

Schema: {schema}
History: {history}

Only use the Supported Clauses, Operators, Functions and Expressions below but do not use any of the Unsupported Features, Functions or Syntax Limitations below:

Expand All @@ -33,6 +39,9 @@ Others: id(), elementId(), labels(), properties(), timestamp()
Supported Expressions:
CASE: Conditional logic.

Supported Operators:
Comparison: IS NULL, IS NOT NULL

Unsupported Features:
Clauses Not Yet Supported
CALL, CREATE, MERGE, REMOVE, SET, UNION, UNION ALL, UNWIND
Expand All @@ -55,6 +64,14 @@ UNION ALL
UNWIND
SET

Make sure to have correct attribute names in the OpenCypher query and not to name result aliases that are vertex or edge types, operator or function names, and other reserved keywords.
Here's some commonly used abbreviations:
dt -> date
pct -> percentage
qty -> quantity
lng -> longitude
cm -> Contract Manufacturer

Always make the cypher query returns the entity in the original question together with the data to be queried.
Make sure to have correct attribute names in the OpenCypher query and not to name result aliases that are vertex or edge types, operator or function names, and other reserved keywords, always construct alias with multiple words connected with underscore.

ONLY write the OpenCypher query in the response. Do not include any other information in the response.
8 changes: 5 additions & 3 deletions graphrag/app/agent/agent_generation.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@ class TigerGraphAgentGenerator:
def __init__(self, llm_model):
self.llm = llm_model

def generate_answer(self, question: str, context: str) -> dict:
def generate_answer(self, question: str, context: str, query: str = "") -> dict:
"""Generate an answer based on the question and context.
Args:
question: str: The question to generate an answer for.
context: str: The context to generate an answer from.
query: str: The original query used to fetch the conext.
Returns:
str: The answer to the question.
"""
Expand All @@ -31,7 +32,7 @@ def generate_answer(self, question: str, context: str) -> dict:

prompt = PromptTemplate(
template=self.llm.chatbot_response_prompt,
input_variables=["question", "context"],
input_variables=["question", "context", "query"],
partial_variables={
"format_instructions": answer_parser.get_format_instructions()
}
Expand All @@ -40,6 +41,7 @@ def generate_answer(self, question: str, context: str) -> dict:
full_prompt = prompt.format(
question=question,
context=context,
query=query,
format_instructions=answer_parser.get_format_instructions()
)

Expand All @@ -48,7 +50,7 @@ def generate_answer(self, question: str, context: str) -> dict:

usage_data = {}
with get_openai_callback() as cb:
generation = rag_chain.invoke({"question": question, "context": context})
generation = rag_chain.invoke({"question": question, "context": context, "query": query})

usage_data["input_tokens"] = cb.prompt_tokens
usage_data["output_tokens"] = cb.completion_tokens
Expand Down
25 changes: 17 additions & 8 deletions graphrag/app/agent/agent_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,25 +163,34 @@ def generate_cypher(self, state):
Run the agent cypher generator.
"""
self.emit_progress("Generating the Cypher to answer your question")
cypher = self.cypher_gen._run(state["question"])
logger.info(f"cypher: {cypher}")
gen_history = []
response_json = None

response = self.db_connection.gsql(cypher)
response_lines = response.split("\n")
try:
for i in range(3):
cypher = self.cypher_gen._run(state["question"], gen_history)
logger.info(f"cypher: {cypher}")

response = self.db_connection.gsql(cypher)
response_lines = response.split("\n")
json_str = "\n".join(response_lines[1:])
response_json = json.loads(json_str)
try:
response_json = json.loads(json_str)
break
except Exception as e:
gen_history.append(f"{i}: {cypher}\n\tError: {json_str}\n")
if response_json:
state["context"] = {
"answer": response_json["results"][0],
"cypher": cypher,
"reasoning": "The following OpenCypher query was executed to answer the question. {}".format(
cypher
),
}
except Exception as e:
else:
state["context"] = {
"error": True,
"cypher": cypher,
"answer": json_str
}
if state["error_history"] is None:
state["error_history"] = []
Expand Down Expand Up @@ -355,7 +364,7 @@ def generate_answer(self, state):
logger.debug_pii(
f"""request_id={req_id_cv.get()} Got result: {state["context"]["answer"]}"""
)
answer = step.generate_answer(state["question"], state["context"]["answer"])
answer = step.generate_answer(state["question"], state["context"]["answer"], state["context"]["cypher"])
logger.debug_pii(
f"request_id={req_id_cv.get()} Generated answer: {answer.generated_answer}"
)
Expand Down
1 change: 1 addition & 0 deletions graphrag/app/tools/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from .generate_function import GenerateFunction
from .map_question_to_schema import MapQuestionToSchema
from .generate_cypher import GenerateCypher
from .generate_gsql import GenerateGSQL
from .validation_utils import MapQuestionToSchemaException, InvalidFunctionCallException
from .validation_utils import validate_schema, validate_function_call
Loading
Loading