This commit is contained in:
2025-11-14 14:47:19 +00:00
parent 2a85845835
commit 3758c7572a
137 changed files with 365654 additions and 11147 deletions
+27 -31
View File
@@ -1,3 +1,4 @@
from weakref import ref
from dotenv import load_dotenv, find_dotenv
load_dotenv(find_dotenv())
import os
@@ -18,36 +19,21 @@ from langchain_community.graphs import Neo4jGraph
from langchain_community.chat_models import ChatOpenAI
from langchain.prompts.prompt import PromptTemplate
from routers.llm.private.ollama.ollama_wrapper import OllamaWrapper
from modules.database.tools.neontology.utils import get_node_types, get_rels_by_type
from modules.database.tools.neontology.basenode import BaseNode
from modules.database.tools.neontology.baserelationship import BaseRelationship
router = APIRouter()
# Define the schema for nodes and relationships
node_types = {
"KeyStage": ["merged", "key_stage_name", "unique_id", "created"],
"KeyStageSyllabus": ["ks_syllabus_name", "unique_id", "created", "merged", "ks_syllabus_key_stage", "ks_syllabus_subject"],
"YearGroup": ["created", "merged", "unique_id", "year_group_name"],
"YearGroupSyllabus": ["created", "merged", "yr_syllabus_name", "yr_syllabus_year_group", "yr_syllabus_id", "yr_syllabus_subject"],
"Topic": ["topic_type", "topic_assessment_type", "created", "merged", "unique_id", "topic_id", "total_number_of_lessons_for_topic", "topic_title"],
"Lesson": ["topic_lesson_id", "topic_lesson_type", "created", "merged", "topic_lesson_title", "topic_lesson_length", "topic_lesson_suggested_activities", "topic_lesson_weblinks", "topic_lesson_skills_learned"],
"LearningStatement": ["created", "merged", "lesson_learning_statement", "lesson_learning_statement_id", "lesson_learning_statement_type"]
}
relationship_types = {
"KEY_STAGE_INCLUDES_KEY_STAGE_SYLLABUS": ["created", "merged"],
"KEY_STAGE_SYLLABUS_INCLUDES_YEAR_GROUP_SYLLABUS": ["created", "merged"],
"YEAR_GROUP_FOLLOWS_YEAR_GROUP": ["created", "merged"],
"KEY_STAGE_FOLLOWS_KEY_STAGE": ["created", "merged"],
"YEAR_SYLLABUS_INCLUDES_TOPIC": ["created", "merged"],
"TOPIC_INCLUDES_LESSON": ["created", "merged"],
"LESSON_INCLUDES_LEARNING_STATEMENT": ["created", "merged"],
"LESSON_FOLLOWS_LESSON": ["created", "merged"]
}
node_types = get_node_types(BaseNode)
relationship_types = get_rels_by_type(BaseRelationship)
@router.get("/prompt")
async def query_graph(
database: str, prompt: str, top_k: int = 30, model: str = "gpt-4o", temperature: float = 0,
database: str, prompt: str, top_k: int = 30, model: str = "qwen2.5-coder:3b", temperature: float = 0,
verbose: bool = False, return_intermediate_steps: bool = False, exclude_types: list = None, include_types: list = None,
return_direct: bool = False, validate_cypher: bool = False, model_type: str = "openai"
return_direct: bool = False, validate_cypher: bool = False, model_type: str = "ollama"
):
logging.info(f"Received request with prompt: {prompt}")
if exclude_types is None:
@@ -70,7 +56,9 @@ async def query_graph(
url=os.environ['APP_BOLT_URL'],
username=os.environ['USER_NEO4J'],
password=os.environ['PASSWORD_NEO4J'],
database=database
database=database,
enhanced_schema=True,
sanitize=True,
)
logging.info("Refreshing schema...")
@@ -79,14 +67,18 @@ async def query_graph(
schema = graph.schema
logging.info(f"Schema: {schema}")
CYPHER_GENERATION_TEMPLATE = """Task: Generate a Cypher statement to query a graph database for timetable information.
CYPHER_GENERATION_TEMPLATE = """Task: Generate a Cypher statement to query a graph database.
Role:
You are an assistant in a school for teachers, specializing in querying graph databases to find answers to questions.
The teacher will ask you questions about their timetable.
You are an assistant specializing in querying graph databases to find answers to questions about establishments, schools, and related data.
The user will ask you questions about the graph database
Instructions:
1. Use only the provided relationship types and properties in the schema.
2. Do not use any other relationship types or properties that are not provided.
3. When querying for geographic entities like counties, towns, or countries, use the 'name' property, not 'code'.
4. To find relationship types, use: MATCH (n:NodeType)-[r]->(m) RETURN DISTINCT type(r)
5. Relationship labels are in uppercase, e.g. LOCATED_IN_COUNTRY
6. For broad queries use OPTIONAL MATCH to allow for null results, e.g. OPTIONAL MATCH (n:NodeType) RETURN n.name
Schema:
{schema}
@@ -95,6 +87,7 @@ async def query_graph(
1. Do not include any explanations or apologies in your responses.
2. Do not respond to any questions that might ask anything else than for you to construct a Cypher statement.
3. Do not include any text except the generated Cypher statement.
4. Do not include line break characters n other formatting keys.
The question is:
{question}"""
@@ -105,11 +98,11 @@ async def query_graph(
)
if model_type == "ollama":
ollama_host = os.getenv("OLLAMA_URL")
ollama_port = os.getenv("OLLAMA_PORT")
ollama_host = os.getenv("HOST_OLLAMA")
ollama_port = os.getenv("PORT_OLLAMA")
if not ollama_host or not ollama_port:
raise HTTPException(status_code=500, detail="Ollama host or port not set")
client = OllamaWrapper(host=f'http://{ollama_host}:{ollama_port}')
client = OllamaWrapper(host=f'{ollama_host}:{ollama_port}', model=model)
cypher_llm = client
qa_llm = client
else:
@@ -127,7 +120,8 @@ async def query_graph(
exclude_types=exclude_types,
include_types=include_types,
return_direct=return_direct,
validate_cypher=validate_cypher
validate_cypher=validate_cypher,
allow_dangerous_requests=True
)
formatted_prompt = CYPHER_GENERATION_PROMPT.format(schema=schema, question=prompt)
@@ -150,4 +144,6 @@ async def query_graph(
logging.info(f"Cypher chain: \n{chain}\n")
logging.info("==================================================")
return chain(prompt)
return chain(prompt)