Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Katieupdates #41

Merged
merged 19 commits into from
Sep 2, 2024
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
151 changes: 151 additions & 0 deletions .github/scripts/aura.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import argparse
import os
import json
import time
import logging

import requests

logger = logging.getLogger(__name__)
logging.basicConfig(level='INFO')


class AuraAPI:
def __init__(self, url, tenant_id, token=None, **kwargs):
self.url = url
self.token = token
self.tenant_id = tenant_id
self.config = kwargs

def status(self, instance_id):
headers = {"Content-Type": "application/json", "Authorization": self.token}
_url = os.path.join(self.url, instance_id)
response = requests.get(_url, headers=headers)
res = json.loads(response.content)
if not res.get('data'):
logger.info("Unable to retrieve instance Status : {}".format(instance_id))
return 'Unknown'
status = res.get('data').get('status')
return status

def create(self, params):
headers = {"Content-Type": "application/json", "Authorization": self.token}
params.update({
'tenant_id': self.tenant_id
})
response = requests.post(self.url, headers=headers, json=params)
res = json.loads(response.content)
instance_details = res.get('data', {})
errors = res.get('errors', {})
if not instance_details:
logger.info("Instance creation not successful: {}".format(errors))
return instance_details

def delete(self, instance_id):
_url = os.path.join(self.url, instance_id)
headers = {"Content-Type": "application/json", "Authorization": self.token}
response = requests.delete(_url, headers=headers)
res = json.loads(response.content)
instance_details = res.get('data', {})
errors = res.get('errors', {})
if not instance_details:
logger.info("Instance not found or unable to delete: {}".format(errors))
return dict()
return instance_details

def generate_token(self, url, client_id, client_secret):
body = {
"grant_type": "client_credentials"
}
headers = {"Content-Type": "application/x-www-form-urlencoded"}
response = requests.post(url, auth=(client_id, client_secret), headers=headers, data=body)
data = json.loads(response.content)
token = data['access_token']
return token

def generate_token_if_expired(self):
auth_config = self.config['auth']
auth_url = auth_config.get('endpoint')
client_id = auth_config.get('client_id')
client_secret = auth_config.get('client_secret')
if time.time() - auth_config.get('token_ttl') >= 3599:
self.token = self.generate_token(auth_url, client_id, client_secret)
self.config['auth']['access_token'] = self.token
self.config['auth']['token_ttl'] = time.time()
logger.info("Token Generation Successful: {}".format(time.ctime()))
return True
logger.info("Token is Valid")
return False

def wait_for_status(self, instance_id, status=None, time_out=300):
start = time.time()
current_status = self.status(instance_id)
while current_status != status and time.time() - start <= time_out:
time.sleep(20)
current_status = self.status(instance_id)
logger.info("Waiting: {} {}".format(instance_id, status))
return current_status


def cli():
parser = argparse.ArgumentParser()
parser.add_argument('task', type=str, help='setup task', choices=['configure', 'delete'])
parser.add_argument('--tenant-id', type=str, help="Aura Tenant ID")
parser.add_argument('--client-id', type=str, help="Aura API Client ID")
parser.add_argument('--client-secret', type=str, help="Aura API Client Secret")
parser.add_argument('--region', type=str, help="Aura Region")
parser.add_argument('--cloud-provider', type=str, help="Aura Cloud Provider")
parser.add_argument('--instance-id', type=str, help="Aura Instance Id")

return parser.parse_args()


def configure_instance(api, region, cloud_provider):
logger.info("Creating Aura instance")
data = api.create(params={
"name": "gh-action-genai-workshop",
"version": "5",
"region": region,
"memory": "8GB",
"type": "enterprise-ds",
"cloud_provider": cloud_provider,
})
instance_details = {k: v for k, v in data.items() if
k in ['id', 'connection_url', 'name', 'username', 'password']}
logger.info(f"Waiting for Aura instance {instance_details['id']} to come online")
api.wait_for_status(instance_details['id'], status="running", time_out=300)

print(f"""
AURA_INSTANCEID={instance_details['id']}
NEO4J_URI={instance_details['connection_url']}
NEO4J_USERNAME={instance_details['username']}
NEO4J_PASSWORD={instance_details['password']}
AURA_DS=true
""")


def delete_instance(api, instance_id):
logger.info(f"Deleting Aura instance {instance_id}")
api.delete(instance_id)


if __name__ == '__main__':
args = cli()

config = {
"auth": {
"endpoint": "https://api.neo4j.io/oauth/token",
"client_id": args.client_id,
"client_secret": args.client_secret,
"token_ttl": 0.0
}
}
api = AuraAPI("https://api.neo4j.io/v1/instances", args.tenant_id, **config)
_ = api.generate_token_if_expired()

task = args.task
if task == 'configure':
configure_instance(api, args.region, args.cloud_provider)

if task == 'delete':
delete_instance(api, args.instance_id)
84 changes: 84 additions & 0 deletions .github/workflows/run-notebooks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
name: Run Notebook and Commit Version With Output

on:
pull_request:
types: [opened, synchronize, reopened]
branches:
- main

jobs:
run-notebooks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.ref }}

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install jupyter nbconvert

- name: Create env file
run: |
echo "${{ secrets.WORKSHOP_ENV }}" > ws.env

- name: Create Aura instance
run: |
source ws.env
python .github/scripts/aura.py configure \
--tenant-id $AURA_TENANT_ID \
--client-id $AURA_CLIENT_ID \
--client-secret $AURA_CLIENT_SECRET \
--region $AURA_REGION \
--cloud-provider $AURA_CLOUD_PROVIDER \
>> ws.env
env:
ENV_FILE: ws.env

- name: Run data loading notebook
run: |
jupyter nbconvert --to notebook --ExecutePreprocessor.timeout=1200 --execute data-load.ipynb
rm data-load.nbconvert.ipynb
env:
ENV_FILE: ws.env

- name: Run and save workshop notebook
run: |
export AUTOMATED_RUN=true
jupyter nbconvert --to notebook --ExecutePreprocessor.timeout=1200 --execute genai-workshop.ipynb
mv genai-workshop.nbconvert.ipynb genai-workshop-w-outputs.ipynb
env:
ENV_FILE: ws.env

- name: Run example-app-only notebook
run: |
export AUTOMATED_RUN=true
jupyter nbconvert --to notebook --ExecutePreprocessor.timeout=1200 --execute genai-example-app-only.ipynb
rm genai-example-app-only.nbconvert.ipynb
env:
ENV_FILE: ws.env

- name: Delete Aura instance
run: |
source ws.env
python .github/scripts/aura.py delete \
--tenant-id $AURA_TENANT_ID \
--client-id $AURA_CLIENT_ID \
--client-secret $AURA_CLIENT_SECRET \
--instance-id $AURA_INSTANCEID
env:
ENV_FILE: ws.env

- name: Commit and push notebook with outputs
run: |
git config --global user.name 'GitHub Action'
git config --global user.email 'action@github.com'
git add genai-workshop-w-outputs.ipynb
git commit -m "Auto-commit: Run notebook and update notebook with output file"
git push
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# GenAI Workshop
# Neo4j GenAI Workshop

Please see [`genai-workshop.ipynb`](genai-workshop.ipynb) which serves as the self-contained workshop.

The other notebook, [`data-prep.ipynb`](data-prep.ipynb), contains the logic used for staging the workshop data, originally sourced from the [H&M Personalized Fashion Recommendations Dataset](https://www.kaggle.com/competitions/h-and-m-personalized-fashion-recommendations/data).
The other companion notebooks contain code for staging data, building the Neo4j Graph, and providing easy access to demos:
1. [`data-prep.ipynb`](data-prep.ipynb) stages the workshop data, sampling and formatting data sourced from the [H&M Personalized Fashion Recommendations Dataset](https://www.kaggle.com/competitions/h-and-m-personalized-fashion-recommendations/data).
2. [`data-load.ipynb`](data-load.ipynb) loads the staged data into Neo4j, performs text embedding, and creates a vector index.
3. [`genai-example-app-only.ipynb`](genai-example-app-only.ipynb) is a copy of [`genai-workshop.ipynb`](genai-workshop.ipynb) that contains only the final section: the demo LLM GraphRAG app for content generation. It assumes you have already run [`genai-workshop.ipynb`](genai-workshop.ipynb), and exists only for instructor demo purposes.
Loading