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

AAP customization #3

Closed
wants to merge 13 commits into from
60 changes: 31 additions & 29 deletions Containerfile
Original file line number Diff line number Diff line change
@@ -1,40 +1,42 @@
# vim: set filetype=dockerfile
ARG LIGHTSPEED_RAG_CONTENT_IMAGE=quay.io/openshift-lightspeed/lightspeed-rag-content@sha256:a91aca8224b1405e7c91576374c7bbc766b2009b2ef852895c27069fffc5b06f
# # vim: set filetype=dockerfile
# ARG LIGHTSPEED_RAG_CONTENT_IMAGE=quay.io/ttakamiy/aap-rag-content:latest

FROM ${LIGHTSPEED_RAG_CONTENT_IMAGE} as lightspeed-rag-content
# FROM ${LIGHTSPEED_RAG_CONTENT_IMAGE} as lightspeed-rag-content

FROM registry.redhat.io/ubi9/ubi-minimal:latest
# FROM registry.access.redhat.com/ubi9/ubi-minimal

ARG VERSION
# todo: this is overriden by the image ubi9/python-311, we hard coded WORKDIR below to /app-root
# makesure the default value of rag content is set according to APP_ROOT and then update the operator.
ARG APP_ROOT=/app-root

RUN microdnf install -y --nodocs --setopt=keepcache=0 --setopt=tsflags=nodocs \
python3.11 python3.11-devel python3.11-pip
# ARG APP_ROOT=/app-root

# PYTHONDONTWRITEBYTECODE 1 : disable the generation of .pyc
# PYTHONUNBUFFERED 1 : force the stdout and stderr streams to be unbuffered
# PYTHONCOERCECLOCALE 0, PYTHONUTF8 1 : skip legacy locales and use UTF-8 mode
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PYTHONCOERCECLOCALE=0 \
PYTHONUTF8=1 \
PYTHONIOENCODING=UTF-8 \
LANG=en_US.UTF-8 \
PIP_NO_CACHE_DIR=off
# RUN microdnf install -y --nodocs --setopt=keepcache=0 --setopt=tsflags=nodocs \
# python3.11 python3.11-devel python3.11-pip shadow-utils \
# && microdnf clean all --enablerepo='*'

WORKDIR /app-root
# # PYTHONDONTWRITEBYTECODE 1 : disable the generation of .pyc
# # PYTHONUNBUFFERED 1 : force the stdout and stderr streams to be unbuffered
# # PYTHONCOERCECLOCALE 0, PYTHONUTF8 1 : skip legacy locales and use UTF-8 mode
# ENV PYTHONDONTWRITEBYTECODE=1 \
# PYTHONUNBUFFERED=1 \
# PYTHONCOERCECLOCALE=0 \
# PYTHONUTF8=1 \
# PYTHONIOENCODING=UTF-8 \
# LANG=en_US.UTF-8 \
# PIP_NO_CACHE_DIR=off

COPY --from=lightspeed-rag-content /rag/vector_db/ocp_product_docs ./vector_db/ocp_product_docs
COPY --from=lightspeed-rag-content /rag/embeddings_model ./embeddings_model
# WORKDIR ${APP_ROOT}

# Add explicit files and directories
# (avoid accidental inclusion of local directories or env files or credentials)
COPY runner.py requirements.txt ./
# COPY --from=lightspeed-rag-content /rag/vector_db/aap_product_docs ./vector_db/aap_product_docs
# COPY --from=lightspeed-rag-content /rag/embeddings_model ./embeddings_model

RUN pip3.11 install --no-cache-dir -r requirements.txt
# # Add explicit files and directories
# # (avoid accidental inclusion of local directories or env files or credentials)
# COPY pyproject.toml pdm.lock runner.py ./
# RUN pip3.11 install --no-cache-dir --upgrade pip pdm==2.18.1 \
# && pdm config python.use_venv false \
# && pdm sync --global --prod -p ${APP_ROOT}

FROM quay.io/ansible/ansible-chatbot-service:base
ARG APP_ROOT=/app-root
WORKDIR ${APP_ROOT}
COPY ols ./ols

# this directory is checked by ecosystem-cert-preflight-checks task in Konflux
Expand All @@ -55,5 +57,5 @@ LABEL io.k8s.display-name="OpenShift LightSpeed Service" \
vendor="Red Hat, Inc."


# no-root user is checked in Konflux
# no-root user is checked in Konflux
USER 1001
16 changes: 13 additions & 3 deletions ols/app/endpoints/ols.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,19 @@
from ols.src.query_helpers.question_validator import QuestionValidator
from ols.utils import errors_parsing, suid
from ols.utils.auth_dependency import AuthDependency
from ols.utils.keywords import KEYWORDS
from ols.utils.token_handler import PromptTooLongError

import importlib
customize_package = 'ols.utils.keywords'
if config.ols_config.customize:
keywords = importlib.import_module(f"{config.ols_config.customize}.keywords")
prompts = importlib.import_module(f"{config.ols_config.customize}.prompts")
print(f'customized: {prompts.INVALID_QUERY_RESP}')
else:
keywords = importlib.import_module('ols.utils.keywords')
prompts = importlib.import_module('ols.src.prompts.prompts')
print(f'NOT-customized: {prompts.INVALID_QUERY_RESP}')

logger = logging.getLogger(__name__)

router = APIRouter(tags=["query"])
Expand Down Expand Up @@ -130,7 +140,7 @@ def conversation_request(

if not valid:
summarizer_response = SummarizerResponse(
constants.INVALID_QUERY_RESP,
prompts.INVALID_QUERY_RESP,
[],
False,
)
Expand Down Expand Up @@ -496,7 +506,7 @@ def _validate_question_keyword(query: str) -> bool:
# Current implementation is without any tokenizer method, lemmatization/n-grams.
# Add valid keywords to keywords.py file.
query_temp = query.lower()
for kw in KEYWORDS:
for kw in keywords.KEYWORDS:
if kw in query_temp:
return True
# query_temp = {q_word.lower().strip(".?,") for q_word in query.split()}
Expand Down
6 changes: 4 additions & 2 deletions ols/app/models/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -892,6 +892,7 @@ class OLSConfig(BaseModel):

extra_ca: list[FilePath] = []
certificate_directory: Optional[str] = None
customize: Optional[str] = None

def __init__(
self, data: Optional[dict] = None, ignore_missing_certs: bool = False
Expand All @@ -902,8 +903,8 @@ def __init__(
return

self.conversation_cache = ConversationCacheConfig(
data.get("conversation_cache", None)
)
data.get("conversation_cache")
) if data.get("conversation_cache") else None
self.logging_config = LoggingConfig(**data.get("logging_config", {}))
if data.get("reference_content") is not None:
self.reference_content = ReferenceContent(data.get("reference_content"))
Expand Down Expand Up @@ -932,6 +933,7 @@ def __init__(
self.certificate_directory = data.get(
"certificate_directory", constants.DEFAULT_CERTIFICATE_DIRECTORY
)
self.customize = data.get('customize')

def __eq__(self, other: object) -> bool:
"""Compare two objects for equality."""
Expand Down
7 changes: 0 additions & 7 deletions ols/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,6 @@ class QueryValidationMethod(StrEnum):
SUBJECT_ALLOWED = "ALLOWED"


# Default responses
INVALID_QUERY_RESP = (
"Hi, I'm the OpenShift Lightspeed assistant, I can help you with questions about OpenShift, "
"please ask me a question related to OpenShift."
)


# providers
PROVIDER_BAM = "bam"
PROVIDER_OPENAI = "openai"
Expand Down
7 changes: 7 additions & 0 deletions ols/customize/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"""OpenShift Lightspeed service."""

from ols.utils.config import config

# make config submodule easily importable by using
# from ols import config
__all__ = ["config"]
92 changes: 92 additions & 0 deletions ols/customize/keywords.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
"""Constant for set of keywords."""

# Add keyword string to below set, preferably in alphabetical order.
# We are adding this manually for now. Add to a txt file, If/when we automate this.
# Important: Please use lower case.

KEYWORDS = {
"aap",
"access",
"account",
"administrator",
"ansible",
"application",
"associated",
"authentication",
"authenticator",
"automatically",
"automation",
"backup",
"capacity",
"certificate",
"client",
"cluster",
"collection",
"command",
"configuration",
"connection",
"container",
"content",
"controller",
"credential",
"deployment",
"directory",
"documentation",
"enterprise",
"environment",
"event-driven",
"execution",
"group",
"hosts",
"information",
"install",
"instance",
"inventory",
"jobs",
"kubernetes",
"ldap",
"license",
"linux",
"log",
"management",
"mesh",
"namespace",
"navigation",
"navigator",
"node",
"nodes",
"number",
"oauth2",
"openshift",
"operator",
"option",
"organization",
"password",
"permission",
"platform",
"playbook",
"playbooks",
"pod",
"podman",
"postgresql",
"project",
"repository",
"resource",
"roles",
"rulebook",
"secret",
"security",
"server",
"service",
"ssh",
"subscription",
"system",
"template",
"token",
"username",
"variable",
"vault",
"version",
"workflow",
"yaml",
}
80 changes: 80 additions & 0 deletions ols/customize/prompts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# There is no need for enforcing line length in this file,
# as these are mostly special purpose constants.
# ruff: noqa: E501
"""Prompt templates/constants."""

from ols.constants import SUBJECT_ALLOWED, SUBJECT_REJECTED

# TODO: OLS-503 Fine tune system prompt

# Note::
# Right now templates are somewhat alligned to make granite work better.
# GPT still works well with this. Ideally we should have model specific tags.
# For history we can laverage ChatPromptTemplate from langchain,
# but that is not done as granite was adding role tags like `Human:` in the response.
# With PromptTemplate, we have more control how we want to structure the prompt.

# Default responses
INVALID_QUERY_RESP = (
"Hi, I'm the Ansible Lightspeed assistant, I can help you with questions about Ansible, "
"please ask me a question related to Ansible."
)

QUERY_SYSTEM_INSTRUCTION = """
You are Ansible Lightspeed - an intelligent assistant for question-answering tasks \
related to the Ansible container orchestration platform.

Here are your instructions:
You are Ansible Lightspeed, an intelligent assistant and expert on all things Ansible. \
Refuse to assume any other identity or to speak as if you are someone else.
If the context of the question is not clear, consider it to be Ansible.
Never include URLs in your replies.
Refuse to answer questions or execute commands not about Ansible.
Do not mention your last update. You have the most recent information on Ansible.

Here are some basic facts about Ansible:
- The latest version of Ansible is 2.12.3.
- Ansible is an open source IT automation engine that automates provisioning, \
configuration management, application deployment, orchestration, and many other \
IT processes. It is free to use, and the project benefits from the experience and \
intelligence of its thousands of contributors.
"""

USE_CONTEXT_INSTRUCTION = """
Use the retrieved document to answer the question.
"""

USE_HISTORY_INSTRUCTION = """
Use the previous chat history to interact and help the user.
"""

# {{query}} is escaped because it will be replaced as a parameter at time of use
QUESTION_VALIDATOR_PROMPT_TEMPLATE = f"""
Instructions:
- You are a question classifying tool
- You are an expert in ansible
- Your job is to determine where or a user's question is related to ansible technologies and to provide a one-word response
- If a question appears to be related to ansible technologies, answer with the word {SUBJECT_ALLOWED}, otherwise answer with the word {SUBJECT_REJECTED}
- Do not explain your answer, just provide the one-word response


Example Question:
Why is the sky blue?
Example Response:
{SUBJECT_REJECTED}

Example Question:
Can you help generate an ansible playbook to install an ansible collection?
Example Response:
{SUBJECT_ALLOWED}


Example Question:
Can you help write an ansible role to install an ansible collection?
Example Response:
{SUBJECT_ALLOWED}

Question:
{{query}}
Response:
"""
Loading
Loading