Spaces:
Build error
Build error
hari-huynh
commited on
Commit
•
b0747e4
0
Parent(s):
First Commit
Browse files- .github/workflows/hfspace_cicd.yml +20 -0
- .gitignore +2 -0
- Dockerfile +33 -0
- README.md +7 -0
- main.py +24 -0
- prompts/cypher_prompt.yaml +53 -0
- prompts/qa_prompt.yaml +20 -0
- requirements.txt +5 -0
- utils.py +76 -0
.github/workflows/hfspace_cicd.yml
ADDED
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
name: Sync to Hugging Face hub
|
2 |
+
on:
|
3 |
+
push:
|
4 |
+
branches: [main]
|
5 |
+
|
6 |
+
# to run this workflow manually from the Actions tab
|
7 |
+
workflow_dispatch:
|
8 |
+
|
9 |
+
jobs:
|
10 |
+
sync-to-hub:
|
11 |
+
runs-on: ubuntu-latest
|
12 |
+
steps:
|
13 |
+
- uses: actions/checkout@v3
|
14 |
+
with:
|
15 |
+
fetch-depth: 0
|
16 |
+
lfs: true
|
17 |
+
- name: Push to hub
|
18 |
+
env:
|
19 |
+
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
20 |
+
run: git push https://haihuynh:[email protected]/spaces/haihuynh/jobs-qa-kg main
|
.gitignore
ADDED
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
1 |
+
.venv
|
2 |
+
.idea
|
Dockerfile
ADDED
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# read the doc: https://huggingface.co/docs/hub/spaces-sdks-docker
|
2 |
+
# you will also find guides on how best to write your Dockerfile
|
3 |
+
|
4 |
+
FROM python:latest
|
5 |
+
|
6 |
+
RUN apt-get update
|
7 |
+
|
8 |
+
WORKDIR /code
|
9 |
+
|
10 |
+
COPY ./requirements.txt /code/requirements.txt
|
11 |
+
|
12 |
+
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
13 |
+
|
14 |
+
# Set up a new user named "user" with user ID 1000
|
15 |
+
RUN useradd -m -u 1000 user
|
16 |
+
|
17 |
+
# Switch to the "user" user
|
18 |
+
USER user
|
19 |
+
|
20 |
+
# Set home to the user's home directory
|
21 |
+
ENV HOME=/home/user \
|
22 |
+
PATH=/home/user/.local/bin:$PATH
|
23 |
+
|
24 |
+
# Set the working directory to the user's home directory
|
25 |
+
WORKDIR $HOME/app
|
26 |
+
|
27 |
+
# Try and run pip command after setting the user with `USER user` to avoid permission issues with Python
|
28 |
+
RUN pip install --no-cache-dir --upgrade pip
|
29 |
+
|
30 |
+
# Copy the current directory contents into the container at $HOME/app setting the owner to the user
|
31 |
+
COPY --chown=user . $HOME/app
|
32 |
+
|
33 |
+
CMD ["python", "main.py"]
|
README.md
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
---
|
2 |
+
title: JobsQA KnowledgeGraph
|
3 |
+
sdk: docker
|
4 |
+
emoji: 👀
|
5 |
+
colorFrom: yellow
|
6 |
+
colorTo: indigo
|
7 |
+
---
|
main.py
ADDED
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
from utils import llm_answer
|
3 |
+
|
4 |
+
# demo = gr.ChatInterface(
|
5 |
+
# fn = echo,
|
6 |
+
# multimodal= True, # Allow upload CV
|
7 |
+
# examples = ["hello", "hola", "merhaba"],
|
8 |
+
# title= "Echo Bot",
|
9 |
+
# description= "Chatbot to answer about job postings & recommend jobs based on your CV.",
|
10 |
+
# theme= None,
|
11 |
+
# autofocus= True,
|
12 |
+
# fill_height= False,
|
13 |
+
# )
|
14 |
+
|
15 |
+
|
16 |
+
demo = gr.ChatInterface(
|
17 |
+
fn= llm_answer,
|
18 |
+
examples=[],
|
19 |
+
title="Jobs QA & Recommendations Chatbot",
|
20 |
+
multimodal=True,
|
21 |
+
)
|
22 |
+
|
23 |
+
if __name__ == "__main__":
|
24 |
+
demo.launch(server_name = "0.0.0.0", server_port = 7860)
|
prompts/cypher_prompt.yaml
ADDED
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
input_variables: [schema, question]
|
2 |
+
output_parser: null
|
3 |
+
template: |
|
4 |
+
Task:Generate Cypher statement to query a graph database.
|
5 |
+
Instructions:
|
6 |
+
Use only the provided relationship types and properties in the schema.
|
7 |
+
Do not use any other relationship types or properties that are not provided.
|
8 |
+
Schema:
|
9 |
+
{schema}
|
10 |
+
Note: Do not include any explanations or apologies in your responses.
|
11 |
+
Do not respond to any questions that might ask anything else than for you to construct a Cypher statement.
|
12 |
+
Do not include any text except the generated Cypher statement.
|
13 |
+
|
14 |
+
Examples:
|
15 |
+
Find all jobs in the 'Software Engineering' industry that offer remote work options and require 'Python' skills?
|
16 |
+
MATCH (j:Job)
|
17 |
+
WHERE j.name CONTAINS 'Software Engineer'
|
18 |
+
AND j.work_mode = 'Remote'
|
19 |
+
AND (j)-[:REQUIRES]->(:Skill {{name: "Python"}})
|
20 |
+
RETURN j AS job
|
21 |
+
|
22 |
+
Which companies located in 'San Francisco' are hiring for 'Data Scientist' roles with a 'Master's Degree' requirement?
|
23 |
+
MATCH (c:Company)-[:LOCATES_IN]->(l:Location {{name: "San Francisco"}})
|
24 |
+
WHERE (c)-[:RECRUITES]->(j:Job {{name: "Data Scientist"}})
|
25 |
+
AND (j)-[:REQUIRES]->(e:Education {{name: "Master's Degree"}})
|
26 |
+
RETURN DISTINCT c.name AS company
|
27 |
+
|
28 |
+
What are the most common skills required for 'Product Manager' jobs across different industries?
|
29 |
+
MATCH (j:Job {{name: "Product Manager"}})-[:REQUIRES]->(s:Skill)
|
30 |
+
RETURN s.name, count(*) AS skill_count
|
31 |
+
ORDER BY skill_count DESC
|
32 |
+
LIMIT 10
|
33 |
+
|
34 |
+
Find all jobs that require at least 5 years of experience and a 'Bachelor's Degree' in 'Computer Science':
|
35 |
+
MATCH (j:Job)-[:REQUIRES]->(e:Education {{name: "Bachelor's Degree", fields: "Computer Science"}})
|
36 |
+
WHERE (j)-[:REQUIRES]->(we:Work_Exper {{duration: "5 years"}})
|
37 |
+
RETURN j AS job
|
38 |
+
|
39 |
+
Identify companies that are subsidiaries of 'Google' and are recruiting for 'Software Engineer' roles with 'Senior' level.
|
40 |
+
MATCH (g:Company {{name: "Google"}})<-[:SUBDIARY]-(c:Company)
|
41 |
+
WHERE (c)-[:RECRUITES]->(j:Job {{name: "Software Engineer"}})
|
42 |
+
AND (j)-[:AT_LEVEL]->(wl:Work_LV {{name: "Senior"}})
|
43 |
+
RETURN DISTINCT c.name AS company
|
44 |
+
|
45 |
+
Find companies recruiting "Machine Learning" jobs and their corresponding job titles.
|
46 |
+
MATCH (company: Company)-[:RECRUITES]->(job: Job)
|
47 |
+
WHERE job.name CONTAINS "Machine Learning"
|
48 |
+
RETURN company.name as company_name, job.name as job_title
|
49 |
+
|
50 |
+
The question is:
|
51 |
+
{question}
|
52 |
+
|
53 |
+
template_format: f-string
|
prompts/qa_prompt.yaml
ADDED
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
input_variables: [context, question]
|
2 |
+
output_parser: null
|
3 |
+
template: |
|
4 |
+
Task: answer the question you are given based on the context provided.
|
5 |
+
Instructions:
|
6 |
+
You are an assistant that helps to form nice and human understandable answers.
|
7 |
+
Use the context information provided to generate a well organized and comprehensive answer to the user's question.
|
8 |
+
When the provided information contains multiple elements, structure your answer as a bulleted or numbered list to enhance clarity and readability.
|
9 |
+
You must use the information to construct your answer.
|
10 |
+
The provided information is authoritative; do not doubt it or try to use your internal knowledge to correct it.
|
11 |
+
Make the answer sound like a response to the question without mentioning that you based the result on the given information.
|
12 |
+
If there is no information provided, say that the knowledge base returned empty results.
|
13 |
+
You should answer result in Vietnamese.
|
14 |
+
|
15 |
+
Here's the information:
|
16 |
+
{context}
|
17 |
+
|
18 |
+
Question: {question}
|
19 |
+
Answer:
|
20 |
+
template_format: f-string
|
requirements.txt
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
gradio
|
2 |
+
langchain
|
3 |
+
langchain-community
|
4 |
+
langchain-google-genai
|
5 |
+
neo4j
|
utils.py
ADDED
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import yaml
|
3 |
+
from dotenv import load_dotenv
|
4 |
+
from langchain_google_genai import ChatGoogleGenerativeAI
|
5 |
+
from langchain_community.graphs import Neo4jGraph
|
6 |
+
from langchain_core.prompts.prompt import PromptTemplate
|
7 |
+
from langchain.chains import GraphCypherQAChain
|
8 |
+
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
|
9 |
+
|
10 |
+
def config():
|
11 |
+
# load_dotenv()
|
12 |
+
|
13 |
+
# Set up Neo4J & Gemini API
|
14 |
+
os.environ["NEO4J_URI"] = os.getenv("NEO4J_URI")
|
15 |
+
os.environ["NEO4J_USERNAME"] = os.getenv("NEO4J_USERNAME")
|
16 |
+
os.environ["NEO4J_PASSWORD"] = os.getenv("NEO4J_PASSWORD")
|
17 |
+
os.environ["GOOGLE_API_KEY"] = os.getenv("GEMINI_API_KEY")
|
18 |
+
|
19 |
+
def load_prompt(filepath):
|
20 |
+
with open(filepath, "r") as file:
|
21 |
+
prompt = yaml.safe_load(file)
|
22 |
+
|
23 |
+
return prompt
|
24 |
+
|
25 |
+
def init_():
|
26 |
+
config()
|
27 |
+
knowledge_graph = Neo4jGraph()
|
28 |
+
llm_chat = ChatGoogleGenerativeAI(
|
29 |
+
model= "gemini-1.5-flash-latest"
|
30 |
+
)
|
31 |
+
|
32 |
+
# Connect to Neo4J Knowledge Graph
|
33 |
+
cypher_prompt = load_prompt("hf_space/prompts/cypher_prompt.yaml")
|
34 |
+
qa_prompt = load_prompt("hf_space/prompts/qa_prompt.yaml")
|
35 |
+
|
36 |
+
CYPHER_GENERATION_PROMPT = PromptTemplate(**cypher_prompt)
|
37 |
+
QA_GENERATION_PROMPT = PromptTemplate(**qa_prompt)
|
38 |
+
|
39 |
+
chain = GraphCypherQAChain.from_llm(
|
40 |
+
llm_chat, graph=knowledge_graph, verbose=True,
|
41 |
+
cypher_prompt= CYPHER_GENERATION_PROMPT,
|
42 |
+
qa_prompt= QA_GENERATION_PROMPT
|
43 |
+
)
|
44 |
+
|
45 |
+
return chain
|
46 |
+
|
47 |
+
# Init GraphQA Chain
|
48 |
+
chain = init_()
|
49 |
+
|
50 |
+
def get_llm_response(query):
|
51 |
+
return chain.invoke({"query": query})["result"]
|
52 |
+
|
53 |
+
|
54 |
+
def llm_answer(message, history):
|
55 |
+
# history_langchain_format = []
|
56 |
+
#
|
57 |
+
# for human, ai in history:
|
58 |
+
# history_langchain_format.append(HumanMessage(content= human))
|
59 |
+
# history_langchain_format.append(AIMessage(content= ai))
|
60 |
+
#
|
61 |
+
# history_langchain_format.append(HumanMessage(content= message["text"]))
|
62 |
+
|
63 |
+
try:
|
64 |
+
response = get_llm_response(message["text"])
|
65 |
+
except Exception:
|
66 |
+
response = "Exception"
|
67 |
+
except Error:
|
68 |
+
response = "Error"
|
69 |
+
return response
|
70 |
+
|
71 |
+
# if __name__ == "__main__":
|
72 |
+
# message = "Have any company recruiting jobs about Machine Learning and coresponding job titles?"
|
73 |
+
# history = [("What's your name?", "My name is Gemini")]
|
74 |
+
# resp = llm_answer(message, history)
|
75 |
+
# print(resp)
|
76 |
+
|