├── logs └── .gitkeep ├── tests ├── config │ ├── empty_cert.crt │ ├── postgres_cert.crt │ ├── empty_secret │ │ └── apitoken │ ├── key │ ├── secret │ │ └── apitoken │ ├── password │ ├── secret2 │ │ └── apitoken │ ├── postgres_password.txt │ ├── system_prompt.txt │ ├── secret_azure_no_client_id │ │ ├── client_secret │ │ └── tenant_id │ ├── secret_azure_no_tenant_id │ │ ├── client_secret │ │ └── client_id │ ├── secret_azure_no_client_secret │ │ ├── client_id │ │ └── tenant_id │ ├── secret_azure_tenant_id_client_id_client_secret │ │ ├── client_secret │ │ ├── client_id │ │ └── tenant_id │ ├── kubeconfig │ ├── operator_install │ │ ├── route.yaml │ │ ├── olsconfig.crd.openai.yaml │ │ ├── olsconfig.crd.bam.yaml │ │ ├── olsconfig.crd.rhoai_vllm.yaml │ │ ├── olsconfig.crd.rhelai_vllm.yaml │ │ ├── olsconfig.crd.openai_introspection.yaml │ │ ├── olsconfig.crd.watsonx.yaml │ │ ├── olsconfig.crd.watsonx_introspection.yaml │ │ ├── imagedigestmirrorset.yaml │ │ ├── olsconfig.crd.azure_openai_introspection.yaml │ │ └── olsconfig.crd.azure_openai.yaml │ ├── with_openai_api_key.yaml │ ├── empty_openai_api_key.yaml │ ├── config_without_logging.yaml │ ├── test_app_endpoints.yaml │ ├── auth_config.yaml │ ├── valid_config_with_bam_2.yaml │ ├── valid_config_with_bam.yaml │ ├── valid_config_postgres.yaml │ ├── valid_config_with_azure_openai.yaml │ ├── valid_config_with_azure_openai_2.yaml │ ├── valid_config_with_watsonx_2.yaml │ ├── valid_config_with_azure_openai_api_version.yaml │ ├── valid_config_with_watsonx.yaml │ ├── valid_config_without_query_filter.yaml │ ├── server.crt │ ├── invalid_config_no_models.yaml │ ├── invalid_config.yaml │ ├── valid_config_without_certificate_directory.yaml │ ├── config_with_disable_model_check.yaml │ ├── valid_config_with_query_filter.yaml │ ├── valid_config.yaml │ └── server.key ├── __init__.py ├── integration │ ├── __init__.py │ ├── pytest.ini │ ├── conftest.py │ ├── test_tls_enabled.py │ └── test_authorized_noop.py ├── unit │ ├── app │ │ ├── __init__.py │ │ ├── metrics │ │ │ └── __init__.py │ │ ├── models │ │ │ └── __init__.py │ │ ├── endpoints │ │ │ └── __init__.py │ │ └── test_routers.py │ ├── runners │ │ └── __init__.py │ ├── auth │ │ └── __init__.py │ ├── prompts │ │ └── __init__.py │ ├── quota │ │ ├── __init__.py │ │ └── test_quota_exceed_error.py │ ├── ui │ │ └── __init__.py │ ├── llms │ │ ├── __init__.py │ │ └── providers │ │ │ └── __init__.py │ ├── extra_certs │ │ ├── __init__.py │ │ ├── sample_cert_1.crt │ │ └── sample_cert_2.crt │ ├── query_helpers │ │ ├── __init__.py │ │ └── test_query_helper.py │ ├── rag_index │ │ └── __init__.py │ ├── cache │ │ └── __init__.py │ ├── user_data_collection │ │ └── __init__.py │ ├── utils │ │ ├── __init__.py │ │ ├── test_suid.py │ │ ├── test_logging.py │ │ └── test_connection_decorator.py │ ├── pytest.ini │ ├── conftest.py │ └── test_version.py ├── mock_classes │ ├── __init__.py │ ├── mock_tools.py │ ├── mock_summary.py │ ├── mock_watsonxllm.py │ ├── mock_langchain_interface.py │ ├── mock_retrieved_node.py │ ├── mock_llama_index.py │ ├── mock_query_engine.py │ ├── mock_llm_loader.py │ └── mock_llm_chain.py ├── e2e │ ├── utils │ │ ├── __init__.py │ │ ├── constants.py │ │ ├── retry.py │ │ ├── client.py │ │ ├── response.py │ │ ├── postgres.py │ │ └── wait_for_ols.py │ ├── evaluation │ │ ├── __init__.py │ │ └── test_evaluation.py │ ├── __init__.py │ └── pytest.ini ├── benchmarks │ └── pytest.ini └── constants.py ├── ols ├── src │ ├── __init__.py │ ├── quota │ │ ├── __init__.py │ │ ├── user_quota_limiter.py │ │ ├── cluster_quota_limiter.py │ │ └── quota_exceed_error.py │ ├── llms │ │ ├── providers │ │ │ ├── __init__.py │ │ │ ├── registry.py │ │ │ ├── openai.py │ │ │ └── rhoai_vllm.py │ │ └── __init__.py │ ├── tools │ │ └── __init__.py │ ├── cache │ │ ├── __init__.py │ │ ├── cache_error.py │ │ └── cache_factory.py │ ├── prompts │ │ └── __init__.py │ ├── rag_index │ │ └── __init__.py │ ├── ui │ │ └── __init__.py │ ├── auth │ │ ├── __init__.py │ │ ├── auth_dependency_interface.py │ │ └── auth.py │ └── query_helpers │ │ ├── __init__.py │ │ ├── query_helper.py │ │ └── attachment_appender.py ├── runners │ ├── __init__.py │ └── uvicorn.py ├── utils │ ├── __init__.py │ ├── suid.py │ ├── connection_decorator.py │ ├── logging_configurator.py │ ├── environments.py │ ├── pyroscope.py │ ├── redactor.py │ └── ssl.py ├── app │ ├── __init__.py │ ├── endpoints │ │ ├── __init__.py │ │ └── authorized.py │ ├── models │ │ └── __init__.py │ ├── routers.py │ └── metrics │ │ └── __init__.py ├── customize │ ├── noop │ │ ├── __init__.py │ │ ├── filenames.py │ │ ├── metadata.py │ │ ├── reranker.py │ │ └── keywords.py │ ├── ols │ │ ├── __init__.py │ │ ├── filenames.py │ │ ├── metadata.py │ │ ├── reranker.py │ │ └── keywords.py │ ├── rhdh │ │ ├── __init__.py │ │ ├── filenames.py │ │ ├── metadata.py │ │ ├── reranker.py │ │ └── keywords.py │ └── __init__.py ├── user_data_collection │ └── __init__.py ├── version.py ├── __init__.py └── plugins │ └── __init__.py ├── scripts ├── evaluation │ ├── __init__.py │ ├── utils │ │ ├── __init__.py │ │ ├── rag.py │ │ ├── plot.py │ │ └── similarity_score_llm.py │ ├── response_eval_flow.png │ └── eval_data │ │ ├── ocp_doc_qna-edited.parquet │ │ ├── interview_qna_30_per_title.parquet │ │ └── result │ │ ├── model_evaluation_result-cos_score.png │ │ ├── model_evaluation_result-rougeL_f1.png │ │ ├── model_evaluation_result-rougeL_recall.png │ │ ├── model_evaluation_result-answer_relevancy.png │ │ ├── model_evaluation_result-rougeL_precision.png │ │ └── model_evaluation_result-answer_similarity_llm.png ├── upload_artifact_s3.py ├── rcsconfig.yaml └── build-container.sh ├── docs ├── config.png ├── bam_api_key.png ├── llms_classes.png ├── llms_packages.png ├── requirements.png ├── hermetic_build.png ├── sequence_diagram.png ├── token_truncation.png ├── user_data_flow.png ├── architecture_diagram.png ├── component_integration.png ├── requirements_per_arch.png ├── examples │ ├── assets │ │ ├── beeping.wav │ │ ├── image1.png │ │ ├── image2.png │ │ └── beeping.txt │ ├── markdown.md │ └── barebones.html ├── lightspeed_core_architecture.png ├── dist │ ├── theme │ │ └── fonts │ │ │ ├── league-gothic │ │ │ ├── LICENSE │ │ │ ├── league-gothic.eot │ │ │ ├── league-gothic.ttf │ │ │ ├── league-gothic.woff │ │ │ └── league-gothic.css │ │ │ └── source-sans-pro │ │ │ ├── source-sans-pro-italic.eot │ │ │ ├── source-sans-pro-italic.ttf │ │ │ ├── source-sans-pro-italic.woff │ │ │ ├── source-sans-pro-regular.eot │ │ │ ├── source-sans-pro-regular.ttf │ │ │ ├── source-sans-pro-regular.woff │ │ │ ├── source-sans-pro-semibold.eot │ │ │ ├── source-sans-pro-semibold.ttf │ │ │ ├── source-sans-pro-semibold.woff │ │ │ ├── source-sans-pro-semibolditalic.eot │ │ │ ├── source-sans-pro-semibolditalic.ttf │ │ │ ├── source-sans-pro-semibolditalic.woff │ │ │ └── source-sans-pro.css │ └── reset.css ├── service_configuration_classes.md ├── js │ ├── utils │ │ ├── device.js │ │ ├── constants.js │ │ ├── loader.js │ │ └── color.js │ └── index.js ├── plugin │ ├── math │ │ └── plugin.js │ └── highlight │ │ ├── monokai.css │ │ └── zenburn.css ├── installation_linux.md ├── 404.html ├── _config.yml ├── installation_macos.md ├── css │ ├── theme │ │ ├── source │ │ │ ├── night.scss │ │ │ ├── serif.scss │ │ │ ├── league.scss │ │ │ ├── white.scss │ │ │ ├── black.scss │ │ │ ├── simple.scss │ │ │ ├── sky.scss │ │ │ ├── beige.scss │ │ │ ├── moon.scss │ │ │ └── solarized.scss │ │ ├── template │ │ │ ├── exposer.scss │ │ │ ├── settings.scss │ │ │ └── mixins.scss │ │ └── README.md │ └── layout.scss ├── index.md ├── Gemfile ├── user_data_flow.uml ├── presentation.html ├── requirements.uml ├── token_truncation.uml └── sequence_diagram.uml ├── .coveragerc ├── OWNERS ├── .github ├── dependabot.yml ├── workflows │ ├── shellcheck.yaml │ ├── bandit.yaml │ ├── ruff.yaml │ ├── radon.yaml │ ├── pyright.yaml │ ├── pydocstyle.yaml │ ├── outdated_dependencies.yaml │ ├── black.yaml │ ├── mypy.yaml │ ├── pylint.yaml │ ├── check_dependencies.yaml │ ├── unit_tests.yaml │ └── integration_tests.yaml ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── PULL_REQUEST_TEMPLATE.md ├── .snyk ├── hooks └── pre-commit ├── .codecov.yml ├── examples ├── minimal_rcsconfig.yaml ├── rcsconfig-pgvector.yaml └── rcsconfig-local-ollama.yaml ├── .gitignore └── Containerfile /logs/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/config/empty_cert.crt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/config/postgres_cert.crt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/config/empty_secret/apitoken: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/config/key: -------------------------------------------------------------------------------- 1 | * this is key * 2 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | """Tests package.""" 2 | -------------------------------------------------------------------------------- /tests/config/secret/apitoken: -------------------------------------------------------------------------------- 1 | secret_key 2 | -------------------------------------------------------------------------------- /ols/src/__init__.py: -------------------------------------------------------------------------------- 1 | """Business logic.""" 2 | -------------------------------------------------------------------------------- /tests/config/password: -------------------------------------------------------------------------------- 1 | * this is password * 2 | -------------------------------------------------------------------------------- /tests/config/secret2/apitoken: -------------------------------------------------------------------------------- 1 | secret_key_2 2 | -------------------------------------------------------------------------------- /ols/src/quota/__init__.py: -------------------------------------------------------------------------------- 1 | """Quota limiters.""" 2 | -------------------------------------------------------------------------------- /ols/runners/__init__.py: -------------------------------------------------------------------------------- 1 | """OLS service runners.""" 2 | -------------------------------------------------------------------------------- /tests/config/postgres_password.txt: -------------------------------------------------------------------------------- 1 | postgres_password 2 | -------------------------------------------------------------------------------- /tests/integration/__init__.py: -------------------------------------------------------------------------------- 1 | """Integration tests.""" 2 | -------------------------------------------------------------------------------- /ols/src/llms/providers/__init__.py: -------------------------------------------------------------------------------- 1 | """LLM providers.""" 2 | -------------------------------------------------------------------------------- /ols/src/tools/__init__.py: -------------------------------------------------------------------------------- 1 | """Tools/Functions Definition.""" 2 | -------------------------------------------------------------------------------- /ols/utils/__init__.py: -------------------------------------------------------------------------------- 1 | """Utility classes and functions.""" 2 | -------------------------------------------------------------------------------- /tests/config/system_prompt.txt: -------------------------------------------------------------------------------- 1 | This is test system prompt! 2 | -------------------------------------------------------------------------------- /tests/unit/app/__init__.py: -------------------------------------------------------------------------------- 1 | """Init of tests/unit/app.""" 2 | -------------------------------------------------------------------------------- /ols/app/__init__.py: -------------------------------------------------------------------------------- 1 | """REST API service based on FastAPI.""" 2 | -------------------------------------------------------------------------------- /ols/customize/noop/__init__.py: -------------------------------------------------------------------------------- 1 | """Default prompts/keywords.""" 2 | -------------------------------------------------------------------------------- /ols/src/cache/__init__.py: -------------------------------------------------------------------------------- 1 | """Various cache implementations.""" 2 | -------------------------------------------------------------------------------- /ols/src/prompts/__init__.py: -------------------------------------------------------------------------------- 1 | """Constants/methods for prompts.""" 2 | -------------------------------------------------------------------------------- /ols/src/rag_index/__init__.py: -------------------------------------------------------------------------------- 1 | """Modules related to index.""" 2 | -------------------------------------------------------------------------------- /ols/src/ui/__init__.py: -------------------------------------------------------------------------------- 1 | """Web-based user interface handler.""" 2 | -------------------------------------------------------------------------------- /scripts/evaluation/__init__.py: -------------------------------------------------------------------------------- 1 | """Modules for evaluation.""" 2 | -------------------------------------------------------------------------------- /tests/unit/runners/__init__.py: -------------------------------------------------------------------------------- 1 | """Unit tests for runners.""" 2 | -------------------------------------------------------------------------------- /tests/config/secret_azure_no_client_id/client_secret: -------------------------------------------------------------------------------- 1 | client secret 2 | -------------------------------------------------------------------------------- /tests/config/secret_azure_no_tenant_id/client_secret: -------------------------------------------------------------------------------- 1 | client secret 2 | -------------------------------------------------------------------------------- /tests/unit/auth/__init__.py: -------------------------------------------------------------------------------- 1 | """Unit tests for auth. dependency.""" 2 | -------------------------------------------------------------------------------- /tests/unit/prompts/__init__.py: -------------------------------------------------------------------------------- 1 | """Unit tests for prompt generator.""" 2 | -------------------------------------------------------------------------------- /tests/unit/quota/__init__.py: -------------------------------------------------------------------------------- 1 | """Unit tests for quota limiters.""" 2 | -------------------------------------------------------------------------------- /tests/unit/ui/__init__.py: -------------------------------------------------------------------------------- 1 | """Test cases GradioUI functionality.""" 2 | -------------------------------------------------------------------------------- /ols/src/auth/__init__.py: -------------------------------------------------------------------------------- 1 | """Various implementations of auth module.""" 2 | -------------------------------------------------------------------------------- /ols/user_data_collection/__init__.py: -------------------------------------------------------------------------------- 1 | """User data collection package.""" 2 | -------------------------------------------------------------------------------- /scripts/evaluation/utils/__init__.py: -------------------------------------------------------------------------------- 1 | """Utility modules for evaluation.""" 2 | -------------------------------------------------------------------------------- /tests/mock_classes/__init__.py: -------------------------------------------------------------------------------- 1 | """Mocks that are used in unit tests.""" 2 | -------------------------------------------------------------------------------- /tests/unit/app/metrics/__init__.py: -------------------------------------------------------------------------------- 1 | """Init of tests/unit/app/metrics.""" 2 | -------------------------------------------------------------------------------- /tests/unit/app/models/__init__.py: -------------------------------------------------------------------------------- 1 | """Init of tests/unit/app/models.""" 2 | -------------------------------------------------------------------------------- /tests/unit/llms/__init__.py: -------------------------------------------------------------------------------- 1 | """Test cases for LLMProvider registry.""" 2 | -------------------------------------------------------------------------------- /tests/unit/llms/providers/__init__.py: -------------------------------------------------------------------------------- 1 | """Test cases for LLM providers.""" 2 | -------------------------------------------------------------------------------- /docs/config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/road-core/service/HEAD/docs/config.png -------------------------------------------------------------------------------- /tests/e2e/utils/__init__.py: -------------------------------------------------------------------------------- 1 | """Utility/Helper modules for End to end tests.""" 2 | -------------------------------------------------------------------------------- /tests/unit/app/endpoints/__init__.py: -------------------------------------------------------------------------------- 1 | """Init of tests/unit/app/endpoints.""" 2 | -------------------------------------------------------------------------------- /tests/unit/extra_certs/__init__.py: -------------------------------------------------------------------------------- 1 | """Unit tests for extra certs handling.""" 2 | -------------------------------------------------------------------------------- /ols/app/endpoints/__init__.py: -------------------------------------------------------------------------------- 1 | """Handlers for all REST API service endpoints.""" 2 | -------------------------------------------------------------------------------- /tests/unit/query_helpers/__init__.py: -------------------------------------------------------------------------------- 1 | """Test cases for query helpers classes.""" 2 | -------------------------------------------------------------------------------- /tests/unit/rag_index/__init__.py: -------------------------------------------------------------------------------- 1 | """Test cases for rag index related modules.""" 2 | -------------------------------------------------------------------------------- /docs/bam_api_key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/road-core/service/HEAD/docs/bam_api_key.png -------------------------------------------------------------------------------- /tests/config/secret_azure_no_client_secret/client_id: -------------------------------------------------------------------------------- 1 | 00000000-0000-0000-0000-000000000002 2 | -------------------------------------------------------------------------------- /tests/config/secret_azure_no_tenant_id/client_id: -------------------------------------------------------------------------------- 1 | 00000000-0000-0000-0000-000000000002 2 | -------------------------------------------------------------------------------- /tests/config/secret_azure_tenant_id_client_id_client_secret/client_secret: -------------------------------------------------------------------------------- 1 | client secret 2 | -------------------------------------------------------------------------------- /docs/llms_classes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/road-core/service/HEAD/docs/llms_classes.png -------------------------------------------------------------------------------- /docs/llms_packages.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/road-core/service/HEAD/docs/llms_packages.png -------------------------------------------------------------------------------- /docs/requirements.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/road-core/service/HEAD/docs/requirements.png -------------------------------------------------------------------------------- /ols/app/models/__init__.py: -------------------------------------------------------------------------------- 1 | """Data schemas for payloads send and received via REST API.""" 2 | -------------------------------------------------------------------------------- /tests/config/secret_azure_no_client_id/tenant_id: -------------------------------------------------------------------------------- 1 | 00000000-0000-0000-0000-000000000001 2 | 3 | -------------------------------------------------------------------------------- /tests/config/secret_azure_no_client_secret/tenant_id: -------------------------------------------------------------------------------- 1 | 00000000-0000-0000-0000-000000000001 2 | 3 | -------------------------------------------------------------------------------- /tests/e2e/evaluation/__init__.py: -------------------------------------------------------------------------------- 1 | """Evaluation tests for models supported by OLS service.""" 2 | -------------------------------------------------------------------------------- /tests/unit/cache/__init__.py: -------------------------------------------------------------------------------- 1 | """Test cases for conversation history cache implementations.""" 2 | -------------------------------------------------------------------------------- /tests/unit/user_data_collection/__init__.py: -------------------------------------------------------------------------------- 1 | """Unit tests for the data_collector module.""" 2 | -------------------------------------------------------------------------------- /docs/hermetic_build.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/road-core/service/HEAD/docs/hermetic_build.png -------------------------------------------------------------------------------- /docs/sequence_diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/road-core/service/HEAD/docs/sequence_diagram.png -------------------------------------------------------------------------------- /docs/token_truncation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/road-core/service/HEAD/docs/token_truncation.png -------------------------------------------------------------------------------- /docs/user_data_flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/road-core/service/HEAD/docs/user_data_flow.png -------------------------------------------------------------------------------- /tests/unit/utils/__init__.py: -------------------------------------------------------------------------------- 1 | """Test cases for utility classes and functions from ols/utils/.""" 2 | -------------------------------------------------------------------------------- /ols/customize/ols/__init__.py: -------------------------------------------------------------------------------- 1 | """Customized prompts/keywords for OpenShift Lightspeed Service (ols).""" 2 | -------------------------------------------------------------------------------- /ols/customize/rhdh/__init__.py: -------------------------------------------------------------------------------- 1 | """Customized prompts/keywords for RHDH Lightspeed Service (rhdh).""" 2 | -------------------------------------------------------------------------------- /docs/architecture_diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/road-core/service/HEAD/docs/architecture_diagram.png -------------------------------------------------------------------------------- /docs/component_integration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/road-core/service/HEAD/docs/component_integration.png -------------------------------------------------------------------------------- /docs/requirements_per_arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/road-core/service/HEAD/docs/requirements_per_arch.png -------------------------------------------------------------------------------- /ols/src/query_helpers/__init__.py: -------------------------------------------------------------------------------- 1 | """Question validators, statament classifiers, and response generators.""" 2 | -------------------------------------------------------------------------------- /tests/config/secret_azure_tenant_id_client_id_client_secret/client_id: -------------------------------------------------------------------------------- 1 | 00000000-0000-0000-0000-000000000002 2 | -------------------------------------------------------------------------------- /docs/examples/assets/beeping.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/road-core/service/HEAD/docs/examples/assets/beeping.wav -------------------------------------------------------------------------------- /docs/examples/assets/image1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/road-core/service/HEAD/docs/examples/assets/image1.png -------------------------------------------------------------------------------- /docs/examples/assets/image2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/road-core/service/HEAD/docs/examples/assets/image2.png -------------------------------------------------------------------------------- /ols/customize/noop/filenames.py: -------------------------------------------------------------------------------- 1 | """Specific filenames.""" 2 | 3 | DATA_COLLECTION_MAGIC_FILE_NAME = "noop.json" 4 | -------------------------------------------------------------------------------- /ols/customize/rhdh/filenames.py: -------------------------------------------------------------------------------- 1 | """Specific filenames.""" 2 | 3 | DATA_COLLECTION_MAGIC_FILE_NAME = "rhds.json" 4 | -------------------------------------------------------------------------------- /tests/config/secret_azure_tenant_id_client_id_client_secret/tenant_id: -------------------------------------------------------------------------------- 1 | 00000000-0000-0000-0000-000000000001 2 | 3 | -------------------------------------------------------------------------------- /docs/examples/assets/beeping.txt: -------------------------------------------------------------------------------- 1 | Source: https://freesound.org/people/fennelliott/sounds/379419/ 2 | License: CC0 (public domain) -------------------------------------------------------------------------------- /docs/lightspeed_core_architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/road-core/service/HEAD/docs/lightspeed_core_architecture.png -------------------------------------------------------------------------------- /ols/customize/ols/filenames.py: -------------------------------------------------------------------------------- 1 | """Specific filenames.""" 2 | 3 | DATA_COLLECTION_MAGIC_FILE_NAME = "openshift_lightspeed.json" 4 | -------------------------------------------------------------------------------- /tests/unit/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | log_cli=false 3 | filterwarnings = 4 | ignore::DeprecationWarning 5 | junit_logging=system-out -------------------------------------------------------------------------------- /scripts/evaluation/response_eval_flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/road-core/service/HEAD/scripts/evaluation/response_eval_flow.png -------------------------------------------------------------------------------- /tests/benchmarks/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | log_cli=false 3 | filterwarnings = 4 | ignore::DeprecationWarning 5 | junit_logging=system-out -------------------------------------------------------------------------------- /docs/dist/theme/fonts/league-gothic/LICENSE: -------------------------------------------------------------------------------- 1 | SIL Open Font License (OFL) 2 | http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL 3 | -------------------------------------------------------------------------------- /tests/e2e/__init__.py: -------------------------------------------------------------------------------- 1 | """End to end tests for the OLS service.""" 2 | 3 | __all__ = ["test_api", "test_attachments", "test_query_endpoint"] 4 | -------------------------------------------------------------------------------- /ols/customize/rhdh/metadata.py: -------------------------------------------------------------------------------- 1 | """Metadata constants.""" 2 | 3 | # service name that is used in OpenAPI specification etc. 4 | SERVICE_NAME = "RHDH" 5 | -------------------------------------------------------------------------------- /ols/customize/noop/metadata.py: -------------------------------------------------------------------------------- 1 | """Metadata constants.""" 2 | 3 | # service name that is used in OpenAPI specification etc. 4 | SERVICE_NAME = "Road-core" 5 | -------------------------------------------------------------------------------- /ols/customize/ols/metadata.py: -------------------------------------------------------------------------------- 1 | """Metadata constants.""" 2 | 3 | # service name that is used in OpenAPI specification etc. 4 | SERVICE_NAME = "Road-core" 5 | -------------------------------------------------------------------------------- /docs/dist/theme/fonts/league-gothic/league-gothic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/road-core/service/HEAD/docs/dist/theme/fonts/league-gothic/league-gothic.eot -------------------------------------------------------------------------------- /docs/dist/theme/fonts/league-gothic/league-gothic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/road-core/service/HEAD/docs/dist/theme/fonts/league-gothic/league-gothic.ttf -------------------------------------------------------------------------------- /docs/dist/theme/fonts/league-gothic/league-gothic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/road-core/service/HEAD/docs/dist/theme/fonts/league-gothic/league-gothic.woff -------------------------------------------------------------------------------- /scripts/evaluation/eval_data/ocp_doc_qna-edited.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/road-core/service/HEAD/scripts/evaluation/eval_data/ocp_doc_qna-edited.parquet -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | omit = 3 | ols/user_data_collection/data_collector.py 4 | 5 | [report] 6 | exclude_lines = 7 | pragma: no cover 8 | if TYPE_CHECKING: 9 | -------------------------------------------------------------------------------- /docs/dist/theme/fonts/source-sans-pro/source-sans-pro-italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/road-core/service/HEAD/docs/dist/theme/fonts/source-sans-pro/source-sans-pro-italic.eot -------------------------------------------------------------------------------- /docs/dist/theme/fonts/source-sans-pro/source-sans-pro-italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/road-core/service/HEAD/docs/dist/theme/fonts/source-sans-pro/source-sans-pro-italic.ttf -------------------------------------------------------------------------------- /docs/dist/theme/fonts/source-sans-pro/source-sans-pro-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/road-core/service/HEAD/docs/dist/theme/fonts/source-sans-pro/source-sans-pro-italic.woff -------------------------------------------------------------------------------- /docs/dist/theme/fonts/source-sans-pro/source-sans-pro-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/road-core/service/HEAD/docs/dist/theme/fonts/source-sans-pro/source-sans-pro-regular.eot -------------------------------------------------------------------------------- /docs/dist/theme/fonts/source-sans-pro/source-sans-pro-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/road-core/service/HEAD/docs/dist/theme/fonts/source-sans-pro/source-sans-pro-regular.ttf -------------------------------------------------------------------------------- /docs/service_configuration_classes.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | nav_order: 5 4 | --- 5 | # Service configuration class hierarchy 6 | 7 | ![Class hierarchy](config.png) 8 | 9 | -------------------------------------------------------------------------------- /ols/version.py: -------------------------------------------------------------------------------- 1 | """Service version that is read by project manager tools.""" 2 | 3 | # this should be the only version value used in all source codes!!! 4 | __version__ = "0.3.4" 5 | -------------------------------------------------------------------------------- /scripts/evaluation/eval_data/interview_qna_30_per_title.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/road-core/service/HEAD/scripts/evaluation/eval_data/interview_qna_30_per_title.parquet -------------------------------------------------------------------------------- /docs/dist/theme/fonts/source-sans-pro/source-sans-pro-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/road-core/service/HEAD/docs/dist/theme/fonts/source-sans-pro/source-sans-pro-regular.woff -------------------------------------------------------------------------------- /docs/dist/theme/fonts/source-sans-pro/source-sans-pro-semibold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/road-core/service/HEAD/docs/dist/theme/fonts/source-sans-pro/source-sans-pro-semibold.eot -------------------------------------------------------------------------------- /docs/dist/theme/fonts/source-sans-pro/source-sans-pro-semibold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/road-core/service/HEAD/docs/dist/theme/fonts/source-sans-pro/source-sans-pro-semibold.ttf -------------------------------------------------------------------------------- /docs/dist/theme/fonts/source-sans-pro/source-sans-pro-semibold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/road-core/service/HEAD/docs/dist/theme/fonts/source-sans-pro/source-sans-pro-semibold.woff -------------------------------------------------------------------------------- /tests/constants.py: -------------------------------------------------------------------------------- 1 | """Constants used only in tests.""" 2 | 3 | # embeddings metadata 4 | OCP_DOCS_ROOT_URL = "https://docs.openshift.com/container-platform" 5 | OCP_DOCS_VERSION = "4.18" 6 | -------------------------------------------------------------------------------- /docs/dist/theme/fonts/source-sans-pro/source-sans-pro-semibolditalic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/road-core/service/HEAD/docs/dist/theme/fonts/source-sans-pro/source-sans-pro-semibolditalic.eot -------------------------------------------------------------------------------- /docs/dist/theme/fonts/source-sans-pro/source-sans-pro-semibolditalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/road-core/service/HEAD/docs/dist/theme/fonts/source-sans-pro/source-sans-pro-semibolditalic.ttf -------------------------------------------------------------------------------- /docs/dist/theme/fonts/source-sans-pro/source-sans-pro-semibolditalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/road-core/service/HEAD/docs/dist/theme/fonts/source-sans-pro/source-sans-pro-semibolditalic.woff -------------------------------------------------------------------------------- /ols/src/cache/cache_error.py: -------------------------------------------------------------------------------- 1 | """Any exception that can occur during cache operations.""" 2 | 3 | 4 | class CacheError(Exception): 5 | """Any exception that can occur during cache operations.""" 6 | -------------------------------------------------------------------------------- /scripts/evaluation/eval_data/result/model_evaluation_result-cos_score.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/road-core/service/HEAD/scripts/evaluation/eval_data/result/model_evaluation_result-cos_score.png -------------------------------------------------------------------------------- /scripts/evaluation/eval_data/result/model_evaluation_result-rougeL_f1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/road-core/service/HEAD/scripts/evaluation/eval_data/result/model_evaluation_result-rougeL_f1.png -------------------------------------------------------------------------------- /scripts/evaluation/eval_data/result/model_evaluation_result-rougeL_recall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/road-core/service/HEAD/scripts/evaluation/eval_data/result/model_evaluation_result-rougeL_recall.png -------------------------------------------------------------------------------- /scripts/evaluation/eval_data/result/model_evaluation_result-answer_relevancy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/road-core/service/HEAD/scripts/evaluation/eval_data/result/model_evaluation_result-answer_relevancy.png -------------------------------------------------------------------------------- /scripts/evaluation/eval_data/result/model_evaluation_result-rougeL_precision.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/road-core/service/HEAD/scripts/evaluation/eval_data/result/model_evaluation_result-rougeL_precision.png -------------------------------------------------------------------------------- /OWNERS: -------------------------------------------------------------------------------- 1 | approvers: 2 | - bparees 3 | - tisnik 4 | - xrajesh 5 | - amfred 6 | - joshuawilson 7 | - major 8 | - r0x0d 9 | - rohitkrai03 10 | - elsony 11 | - jameswnl 12 | - yangcao77 13 | -------------------------------------------------------------------------------- /ols/__init__.py: -------------------------------------------------------------------------------- 1 | """OpenShift Lightspeed service.""" 2 | 3 | from ols.utils.config import config 4 | 5 | # make config submodule easily importable by using 6 | # from ols import config 7 | __all__ = ["config"] 8 | -------------------------------------------------------------------------------- /scripts/evaluation/eval_data/result/model_evaluation_result-answer_similarity_llm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/road-core/service/HEAD/scripts/evaluation/eval_data/result/model_evaluation_result-answer_similarity_llm.png -------------------------------------------------------------------------------- /tests/integration/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | log_cli=false 3 | filterwarnings = 4 | ignore::DeprecationWarning 5 | junit_logging=system-out 6 | markers = 7 | attachment: Tests that sends attachments to OLS service 8 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "pip" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | commit-message: 8 | prefix: "dependency" 9 | include: "scope" 10 | -------------------------------------------------------------------------------- /tests/unit/utils/test_suid.py: -------------------------------------------------------------------------------- 1 | """Unit Test for the Utils class.""" 2 | 3 | import uuid 4 | 5 | from ols.utils import suid 6 | 7 | 8 | def test_get_suid(): 9 | """Test get_suid method.""" 10 | uid = suid.get_suid() 11 | assert isinstance(uid, str) 12 | assert uuid.UUID(uid) 13 | -------------------------------------------------------------------------------- /tests/e2e/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | addopts = --strict-markers 3 | log_cli=false 4 | filterwarnings = 5 | ignore::DeprecationWarning 6 | junit_logging=system-out 7 | markers = 8 | any 9 | cluster 10 | rag 11 | azure_entra_id 12 | smoketest 13 | not_konflux 14 | introspection 15 | certificates 16 | -------------------------------------------------------------------------------- /tests/unit/conftest.py: -------------------------------------------------------------------------------- 1 | """Configuration for unit tests.""" 2 | 3 | import pytest 4 | 5 | from ols import config 6 | 7 | 8 | @pytest.fixture(scope="function", autouse=True) 9 | def ensure_empty_config_for_each_unit_test_by_default(): 10 | """Set up fixture for all unit tests.""" 11 | config.reload_empty() 12 | -------------------------------------------------------------------------------- /.snyk: -------------------------------------------------------------------------------- 1 | exclude: 2 | global: 3 | # Ignore test code 4 | - 'tests/**' 5 | # Ignore utility scripts that don't ship 6 | - 'scripts/transform_coverage_report.py' 7 | - 'scripts/questions_gen.py' 8 | - 'scripts/generate_openapi_schema.py' 9 | - 'scripts/evaluation/**' 10 | - 'scripts/data_collection/**' 11 | -------------------------------------------------------------------------------- /docs/js/utils/device.js: -------------------------------------------------------------------------------- 1 | const UA = navigator.userAgent; 2 | 3 | export const isMobile = /(iphone|ipod|ipad|android)/gi.test( UA ) || 4 | ( navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1 ); // iPadOS 5 | 6 | export const isChrome = /chrome/i.test( UA ) && !/edge/i.test( UA ); 7 | 8 | export const isAndroid = /android/gi.test( UA ); -------------------------------------------------------------------------------- /tests/integration/conftest.py: -------------------------------------------------------------------------------- 1 | """Configuration for integration tests.""" 2 | 3 | import pytest 4 | 5 | from ols import config 6 | 7 | 8 | @pytest.fixture(scope="function", autouse=True) 9 | def ensure_empty_config_for_each_integration_test_by_default(): 10 | """Set up fixture for all integration tests.""" 11 | config.reload_empty() 12 | -------------------------------------------------------------------------------- /.github/workflows/shellcheck.yaml: -------------------------------------------------------------------------------- 1 | name: Shell check 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | shellcheck: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: read 12 | pull-requests: read 13 | steps: 14 | - uses: actions/checkout@v4 15 | - name: Shell check 16 | run: make shellcheck 17 | -------------------------------------------------------------------------------- /tests/config/kubeconfig: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Config 3 | clusters: 4 | - name: my-cluster 5 | cluster: 6 | server: https://localhost:6443 7 | 8 | users: 9 | - name: my-user 10 | user: 11 | token: my-access-token 12 | 13 | contexts: 14 | - name: my-context 15 | context: 16 | cluster: my-cluster 17 | user: my-user 18 | 19 | current-context: my-context 20 | -------------------------------------------------------------------------------- /.github/workflows/bandit.yaml: -------------------------------------------------------------------------------- 1 | name: Bandit security scanner 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | bandit: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: read 12 | pull-requests: read 13 | steps: 14 | - uses: mdegis/bandit-action@v1.0 15 | with: 16 | path: "ols" 17 | exit_zero: true 18 | -------------------------------------------------------------------------------- /hooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/sh 2 | # Script to verify what is about to be committed. 3 | 4 | # If any command fails, exit immediately with that command's exit status 5 | set -eo pipefail 6 | 7 | make format 8 | echo "Code formatting step passed" 9 | 10 | make verify 11 | echo "Code verification and linting passed" 12 | 13 | make check-types 14 | echo "Type hints verification passed" 15 | -------------------------------------------------------------------------------- /tests/config/operator_install/route.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: route.openshift.io/v1 2 | kind: Route 3 | metadata: 4 | labels: 5 | app: ols 6 | name: ols 7 | spec: 8 | port: 9 | targetPort: https 10 | tls: 11 | insecureEdgeTerminationPolicy: Redirect 12 | termination: reencrypt 13 | to: 14 | kind: Service 15 | name: lightspeed-app-server 16 | weight: 100 17 | wildcardPolicy: None 18 | -------------------------------------------------------------------------------- /docs/dist/theme/fonts/league-gothic/league-gothic.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'League Gothic'; 3 | src: url('./league-gothic.eot'); 4 | src: url('./league-gothic.eot?#iefix') format('embedded-opentype'), 5 | url('./league-gothic.woff') format('woff'), 6 | url('./league-gothic.ttf') format('truetype'); 7 | 8 | font-weight: normal; 9 | font-style: normal; 10 | } 11 | -------------------------------------------------------------------------------- /docs/plugin/math/plugin.js: -------------------------------------------------------------------------------- 1 | import {KaTeX} from "./katex"; 2 | import {MathJax2} from "./mathjax2"; 3 | import {MathJax3} from "./mathjax3"; 4 | 5 | const defaultTypesetter = MathJax2; 6 | 7 | /*! 8 | * This plugin is a wrapper for the MathJax2, 9 | * MathJax3 and KaTeX typesetter plugins. 10 | */ 11 | export default Plugin = Object.assign( defaultTypesetter(), { 12 | KaTeX, 13 | MathJax2, 14 | MathJax3 15 | } ); -------------------------------------------------------------------------------- /.github/workflows/ruff.yaml: -------------------------------------------------------------------------------- 1 | name: Ruff 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | ruff: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: read 12 | pull-requests: read 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: chartboost/ruff-action@v1 16 | with: 17 | args: 'check . --per-file-ignores=tests/*:S101 --per-file-ignores=scripts/*:S101' 18 | 19 | -------------------------------------------------------------------------------- /docs/installation_linux.md: -------------------------------------------------------------------------------- 1 | # Road core installation on Linux 2 | 3 | ## Prerequisities 4 | 5 | - git 6 | - Python 3.11 or 3.12 7 | - pip 8 | 9 | ## Installation steps 10 | 11 | 1. `pip install --user pdm` 12 | 1. `pdm --version` -- should return no error 13 | 1. Clone the repo to the current dir: 14 | `git clone https://github.com/road-core/service` 15 | 1. `cd service` 16 | 1. `pdm info` -- should return no error 17 | 1. `pdm install` 18 | -------------------------------------------------------------------------------- /.github/workflows/radon.yaml: -------------------------------------------------------------------------------- 1 | name: Radon 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | radon: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: read 12 | pull-requests: read 13 | name: "radon" 14 | steps: 15 | - uses: actions/checkout@v3 16 | - uses: davidslusser/actions_python_radon@v1.0.0 17 | with: 18 | src: "src" 19 | min: "A" 20 | grade: "B" 21 | -------------------------------------------------------------------------------- /.codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | notify: 3 | require_ci_to_pass: no 4 | 5 | coverage: 6 | precision: 2 7 | round: down 8 | range: "20...100" 9 | 10 | status: 11 | project: no 12 | patch: no 13 | changes: no 14 | 15 | parsers: 16 | gcov: 17 | branch_detection: 18 | conditional: yes 19 | loop: yes 20 | method: no 21 | macro: no 22 | 23 | comment: 24 | layout: "reach,diff,flags,tree" 25 | behavior: default 26 | require_changes: no 27 | 28 | -------------------------------------------------------------------------------- /tests/e2e/utils/constants.py: -------------------------------------------------------------------------------- 1 | """Constants for end-to-end tests.""" 2 | 3 | # timeout settings 4 | BASIC_ENDPOINTS_TIMEOUT = 5 5 | NON_LLM_REST_API_TIMEOUT = 20 6 | LLM_REST_API_TIMEOUT = 90 7 | CONVERSATION_ID = "12345678-abcd-0000-0123-456789abcdef" 8 | 9 | # constant from tests/config/cluster_install/ols_manifests.yaml 10 | OLS_USER_DATA_PATH = "/app-root/ols-user-data" 11 | OLS_USER_DATA_COLLECTION_INTERVAL = 10 12 | OLS_COLLECTOR_DISABLING_FILE = OLS_USER_DATA_PATH + "/disable_collector" 13 | -------------------------------------------------------------------------------- /ols/src/auth/auth_dependency_interface.py: -------------------------------------------------------------------------------- 1 | """An interface to be satisfied by all auth. implementations.""" 2 | 3 | from abc import ABC, abstractmethod 4 | 5 | from fastapi import Request 6 | 7 | 8 | class AuthDependencyInterface(ABC): 9 | """An interface to be satisfied by all auth. implementations.""" 10 | 11 | @abstractmethod 12 | async def __call__(self, request: Request) -> tuple[str, str, bool, str]: 13 | """Validate FastAPI Requests for authentication and authorization.""" 14 | -------------------------------------------------------------------------------- /tests/mock_classes/mock_tools.py: -------------------------------------------------------------------------------- 1 | """Mocked tools for tool calling.""" 2 | 3 | from langchain.tools import tool 4 | 5 | 6 | # Define Mock/Sample tools 7 | @tool 8 | def get_namespaces_mock() -> str: 9 | """Fetch the list of all namespaces in the cluster.""" 10 | return """ 11 | NAME STATUS AGE 12 | default Active 25m 13 | """ 14 | 15 | 16 | mock_tools_map = {"get_namespaces_mock": get_namespaces_mock} 17 | -------------------------------------------------------------------------------- /.github/workflows/pyright.yaml: -------------------------------------------------------------------------------- 1 | name: Pyright 2 | 3 | on: 4 | #- push 5 | #- pull_request 6 | 7 | jobs: 8 | pyright: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: read 12 | pull-requests: read 13 | name: "Pyright" 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: actions/setup-python@v5 17 | with: 18 | python-version: '3.11' 19 | - uses: jakebailey/pyright-action@v2 20 | with: 21 | version: 1.1.311 22 | -------------------------------------------------------------------------------- /ols/customize/noop/reranker.py: -------------------------------------------------------------------------------- 1 | """Reranker for post-processing the Vector DB search results.""" 2 | 3 | import logging 4 | 5 | from llama_index.core.schema import NodeWithScore 6 | 7 | logger = logging.getLogger(__name__) 8 | 9 | 10 | def rerank(retrieved_nodes: list[NodeWithScore]) -> list[NodeWithScore]: 11 | """Rerank Vector DB search results.""" 12 | message = f"reranker.rerank() is called with {len(retrieved_nodes)} result(s)." 13 | logger.debug(message) 14 | return retrieved_nodes 15 | -------------------------------------------------------------------------------- /ols/customize/ols/reranker.py: -------------------------------------------------------------------------------- 1 | """Reranker for post-processing the Vector DB search results.""" 2 | 3 | import logging 4 | 5 | from llama_index.core.schema import NodeWithScore 6 | 7 | logger = logging.getLogger(__name__) 8 | 9 | 10 | def rerank(retrieved_nodes: list[NodeWithScore]) -> list[NodeWithScore]: 11 | """Rerank Vector DB search results.""" 12 | message = f"reranker.rerank() is called with {len(retrieved_nodes)} result(s)." 13 | logger.debug(message) 14 | return retrieved_nodes 15 | -------------------------------------------------------------------------------- /ols/customize/rhdh/reranker.py: -------------------------------------------------------------------------------- 1 | """Reranker for post-processing the Vector DB search results.""" 2 | 3 | import logging 4 | 5 | from llama_index.core.schema import NodeWithScore 6 | 7 | logger = logging.getLogger(__name__) 8 | 9 | 10 | def rerank(retrieved_nodes: list[NodeWithScore]) -> list[NodeWithScore]: 11 | """Rerank Vector DB search results.""" 12 | message = f"reranker.rerank() is called with {len(retrieved_nodes)} result(s)." 13 | logger.debug(message) 14 | return retrieved_nodes 15 | -------------------------------------------------------------------------------- /docs/404.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 | 5 | 18 | 19 |
20 |

404

21 | 22 |

Page not found :(

23 |

The requested page could not be found.

24 |
25 | -------------------------------------------------------------------------------- /tests/config/with_openai_api_key.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | llm_providers: 3 | - name: openai 4 | url: "https://api.openai.com/v1" 5 | credentials_path: tests/config/secret/apitoken 6 | models: 7 | - name: model-name 8 | ols_config: 9 | conversation_cache: 10 | type: memory 11 | memory: 12 | max_entries: 1000 13 | logging_config: 14 | logging_level: INFO 15 | default_provider: openai 16 | default_model: model-name 17 | dev_config: 18 | disable_auth: true 19 | disable_tls: true 20 | -------------------------------------------------------------------------------- /tests/config/empty_openai_api_key.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | llm_providers: 3 | - name: openai 4 | url: "https://api.openai.com/v1" 5 | credentials_path: tests/config/empty_secret/apitoken 6 | models: 7 | - name: model-name 8 | ols_config: 9 | conversation_cache: 10 | type: memory 11 | memory: 12 | max_entries: 1000 13 | logging_config: 14 | logging_level: INFO 15 | default_provider: openai 16 | default_model: model-name 17 | dev_config: 18 | disable_auth: true 19 | disable_tls: true 20 | -------------------------------------------------------------------------------- /ols/utils/suid.py: -------------------------------------------------------------------------------- 1 | """Session ID utility functions.""" 2 | 3 | import uuid 4 | 5 | 6 | def get_suid() -> str: 7 | """Generate a unique session ID (SUID) using UUID4. 8 | 9 | Returns: 10 | A unique session ID. 11 | """ 12 | return str(uuid.uuid4()) 13 | 14 | 15 | def check_suid(suid: str) -> bool: 16 | """Check if given string is a proper session ID.""" 17 | try: 18 | # accepts strings and bytes only 19 | uuid.UUID(suid) 20 | return True 21 | except (ValueError, TypeError): 22 | return False 23 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | title: Road core service 2 | description: Road core service 3 | baseurl: "/service" 4 | url: "https://road-core.github.io" 5 | 6 | heading_anchors: true 7 | search_enabled: true 8 | 9 | aux_links: 10 | "Github repository": 11 | - "https://github.com/road-core/service/" 12 | 13 | footer_content: "Copyright © 2024, 2025 Red Hat and external authors" 14 | 15 | # Build settings 16 | markdown: kramdown 17 | 18 | exclude: 19 | - Gemfile 20 | - Gemfile.lock 21 | - vendor/ 22 | - sources.tmpl.md 23 | 24 | theme: jekyll-theme-leap-day 25 | -------------------------------------------------------------------------------- /tests/integration/test_tls_enabled.py: -------------------------------------------------------------------------------- 1 | """Integration tests for basic OLS REST API endpoints.""" 2 | 3 | from fastapi.testclient import TestClient 4 | 5 | from ols import config 6 | 7 | 8 | def test_setup_on_port_8443(): 9 | """Setups the test client.""" 10 | config.reload_from_yaml_file("tests/config/config_for_integration_tests_8443.yaml") 11 | 12 | # app.main need to be imported after the configuration is read 13 | from ols.app.main import app # pylint: disable=C0415 14 | 15 | client = TestClient(app) 16 | assert client is not None 17 | -------------------------------------------------------------------------------- /ols/customize/__init__.py: -------------------------------------------------------------------------------- 1 | """Contains customization packages for individual projects (for prompts/keywords).""" 2 | 3 | import importlib 4 | import os 5 | 6 | project = os.getenv("PROJECT", "noop") 7 | metadata = importlib.import_module(f"ols.customize.{project}.metadata") 8 | prompts = importlib.import_module(f"ols.customize.{project}.prompts") 9 | keywords = importlib.import_module(f"ols.customize.{project}.keywords") 10 | reranker = importlib.import_module(f"ols.customize.{project}.reranker") 11 | filenames = importlib.import_module(f"ols.customize.{project}.filenames") 12 | -------------------------------------------------------------------------------- /tests/mock_classes/mock_summary.py: -------------------------------------------------------------------------------- 1 | """Mocked summary returned from query engine.""" 2 | 3 | 4 | class MockSummary: 5 | """Mocked summary returned from query engine.""" 6 | 7 | def __init__(self, query, nodes=None): 8 | """Initialize all required object attributes.""" 9 | if nodes is None: 10 | nodes = [] 11 | 12 | self.query = query 13 | self.source_nodes = nodes 14 | 15 | def __str__(self): 16 | """Return string representation that is used by DocsSummarizer.""" 17 | return f"Summary for query '{self.query}'" 18 | -------------------------------------------------------------------------------- /tests/config/config_without_logging.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | llm_providers: 3 | - name: p1 4 | type: bam 5 | url: "https://url1" 6 | credentials_path: tests/config/secret/apitoken 7 | models: 8 | - name: m1 9 | url: "https://murl1" 10 | ols_config: 11 | reference_content: 12 | product_docs_index_path: "tests/config" 13 | product_docs_index_id: product 14 | conversation_cache: 15 | type: memory 16 | memory: 17 | max_entries: 1000 18 | default_provider: p1 19 | default_model: m1 20 | dev_config: 21 | disable_auth: true 22 | disable_tls: true 23 | -------------------------------------------------------------------------------- /scripts/upload_artifact_s3.py: -------------------------------------------------------------------------------- 1 | """Upload artifact containing the pytest results and configuration to an s3 bucket. 2 | 3 | This will in turn get picked by narberachka service, 4 | which will either send it to ibutsu or report portal, based on the 5 | prefix of the file name. 6 | A dictionary containing the credentials of the S3 bucket must be specified, containing the keys: 7 | AWS_BUCKET 8 | AWS_REGION 9 | AWS_ACCESS_KEY_ID 10 | AWS_SECRET_ACCESS_KEY 11 | 12 | """ 13 | 14 | 15 | def upload_artifact_s3(aws_env): 16 | """Upload artifact to the specified S3 bucket.""" 17 | return True 18 | -------------------------------------------------------------------------------- /.github/workflows/pydocstyle.yaml: -------------------------------------------------------------------------------- 1 | name: Pydocstyle 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | pydocstyle: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: read 12 | pull-requests: read 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: actions/setup-python@v5 16 | with: 17 | python-version: '3.11' 18 | - name: Python version 19 | run: python --version 20 | - name: Pydocstyle install 21 | run: pip install --user pydocstyle 22 | - name: Python docstring checks 23 | run: pydocstyle -v . 24 | -------------------------------------------------------------------------------- /tests/unit/test_version.py: -------------------------------------------------------------------------------- 1 | """Test that the project version is set.""" 2 | 3 | import semantic_version 4 | 5 | from ols import version 6 | 7 | 8 | def check_semantic_version(value): 9 | """Check that the value contains semantic version.""" 10 | # we just need to parse the value, that's all 11 | semantic_version.Version(value) 12 | 13 | 14 | def test_project_version_consistency(): 15 | """Test than the project version is set consistently.""" 16 | # read the "true" version defined in sources 17 | version_from_sources = version.__version__ 18 | check_semantic_version(version_from_sources) 19 | -------------------------------------------------------------------------------- /scripts/rcsconfig.yaml: -------------------------------------------------------------------------------- 1 | # Minimal service configuration 2 | --- 3 | llm_providers: 4 | - name: my_watsonx 5 | type: watsonx 6 | url: "http://wont-be-used" 7 | project_id: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX 8 | models: 9 | - name: model-name 10 | ols_config: 11 | conversation_cache: 12 | type: memory 13 | memory: 14 | max_entries: 1000 15 | logging_config: 16 | app_log_level: info 17 | lib_log_level: warning 18 | default_provider: my_watsonx 19 | default_model: model-name 20 | dev_config: 21 | enable_dev_ui: false 22 | disable_auth: true 23 | disable_tls: true 24 | 25 | -------------------------------------------------------------------------------- /docs/examples/markdown.md: -------------------------------------------------------------------------------- 1 | # Markdown Demo 2 | 3 | 4 | 5 | ## External 1.1 6 | 7 | Content 1.1 8 | 9 | Note: This will only appear in the speaker notes window. 10 | 11 | 12 | ## External 1.2 13 | 14 | Content 1.2 15 | 16 | 17 | 18 | ## External 2 19 | 20 | Content 2.1 21 | 22 | 23 | 24 | ## External 3.1 25 | 26 | Content 3.1 27 | 28 | 29 | ## External 3.2 30 | 31 | Content 3.2 32 | 33 | 34 | ## External 3.3 (Image) 35 | 36 | ![External Image](https://s3.amazonaws.com/static.slid.es/logo/v2/slides-symbol-512x512.png) 37 | 38 | 39 | ## External 3.4 (Math) 40 | 41 | `\[ J(\theta_0,\theta_1) = \sum_{i=0} \]` 42 | -------------------------------------------------------------------------------- /docs/js/utils/constants.js: -------------------------------------------------------------------------------- 1 | 2 | export const SLIDES_SELECTOR = '.slides section'; 3 | export const HORIZONTAL_SLIDES_SELECTOR = '.slides>section'; 4 | export const VERTICAL_SLIDES_SELECTOR = '.slides>section.present>section'; 5 | 6 | // Methods that may not be invoked via the postMessage API 7 | export const POST_MESSAGE_METHOD_BLACKLIST = /registerPlugin|registerKeyboardShortcut|addKeyBinding|addEventListener|showPreview/; 8 | 9 | // Regex for retrieving the fragment style from a class attribute 10 | export const FRAGMENT_STYLE_REGEX = /fade-(down|up|right|left|out|in-then-out|in-then-semi-out)|semi-fade-out|current-visible|shrink|grow/; -------------------------------------------------------------------------------- /tests/config/test_app_endpoints.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | llm_providers: 3 | - name: bam 4 | url: "https://bam-api.res.ibm.com" 5 | credentials_path: tests/config/secret/apitoken 6 | models: 7 | - name: model-name 8 | ols_config: 9 | reference_content: 10 | product_docs_index_path: "tests/config" 11 | product_docs_index_id: product 12 | conversation_cache: 13 | type: memory 14 | memory: 15 | max_entries: 1000 16 | logging_config: 17 | logging_level: INFO 18 | default_provider: bam 19 | default_model: model-name 20 | query_filters: 21 | [] 22 | dev_config: 23 | disable_auth: true 24 | disable_tls: true 25 | -------------------------------------------------------------------------------- /.github/workflows/outdated_dependencies.yaml: -------------------------------------------------------------------------------- 1 | name: List outdated dependencies 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | list_outdated_dependencies: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: read 12 | pull-requests: read 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: actions/setup-python@v5 16 | with: 17 | python-version: '3.11' 18 | - name: Check Python version 19 | run: python --version 20 | - name: PDM installation 21 | run: pip install --user pdm 22 | - name: List outdated dependencies 23 | run: pdm outdated 24 | -------------------------------------------------------------------------------- /scripts/build-container.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Version 4 | RCS_VERSION=v0.3.2 5 | 6 | # To build container for local use 7 | if [ -z "$RCS_NO_IMAGE_CACHE" ]; then 8 | podman build --no-cache --build-arg=VERSION="${RCS_VERSION}" -t "${RCS_API_IMAGE:-quay.io/openshift-lightspeed/lightspeed-service-api:latest}" -f Containerfile 9 | else 10 | podman build --build-arg=VERSION=${RCS_VERSION} -t "${RCS_API_IMAGE:-quay.io/openshift-lightspeed/lightspeed-service-api:latest}" -f Containerfile 11 | fi 12 | 13 | # To test-run for local development 14 | # podman run --rm -ti -p 8080:8080 ${RCS_API_IMAGE:-quay.io/openshift-lightspeed/lightspeed-service-api:latest} 15 | -------------------------------------------------------------------------------- /examples/minimal_rcsconfig.yaml: -------------------------------------------------------------------------------- 1 | # Minimal service configuration 2 | --- 3 | llm_providers: 4 | - name: my_openai 5 | type: openai 6 | url: "https://api.openai.com/v1" 7 | credentials_path: openai_api_key.txt 8 | models: 9 | - name: gpt-3.5-turbo 10 | ols_config: 11 | conversation_cache: 12 | type: memory 13 | memory: 14 | max_entries: 1000 15 | default_provider: my_openai 16 | default_model: gpt-3.5-turbo 17 | authentication_config: 18 | module: "noop" 19 | 20 | dev_config: 21 | # config options specific to dev environment - launching OLS in local 22 | enable_dev_ui: true 23 | disable_auth: true 24 | disable_tls: true 25 | 26 | -------------------------------------------------------------------------------- /docs/installation_macos.md: -------------------------------------------------------------------------------- 1 | # Road core installation on macOS 2 | 3 | ## Prerequisities 4 | 5 | - brew 6 | - git 7 | - Python 3.11 or 3.12 8 | - pip 9 | 10 | ## Installation steps 11 | 12 | 1. `brew install pdm` 13 | 1. `pdm --version` -- should return no error 14 | 1. Clone the repo to the current dir: 15 | `git clone https://github.com/road-core/service` 16 | 1. `cd service` 17 | 1. `pdm info` -- should return no error 18 | 1. change `torch==2.6.0+cpu` to `torch==2.6.0` in `pyproject.toml` (section `[project]/dependencies`) 19 | 1. `pdm install` -- if it fails (for example because you ran `pdm install` before changing `pyproject.toml`) run: 20 | ```sh 21 | pdm update 22 | pdm install 23 | ``` 24 | 25 | -------------------------------------------------------------------------------- /tests/e2e/utils/retry.py: -------------------------------------------------------------------------------- 1 | """Test retry code mechanisms implementations.""" 2 | 3 | import time 4 | 5 | 6 | def retry_until_timeout_or_success(attempts, interval, func, description=None): 7 | """Retry the function until timeout or success.""" 8 | for attempt in range(1, attempts + 1): 9 | if description is not None: 10 | print(f"{description}: attempt {attempt} of {attempts}") 11 | else: 12 | print(f"Attempt {attempt} of {attempts}") 13 | try: 14 | if func(): 15 | return True 16 | except Exception as e: 17 | print(f"Attempt {attempt} failed with exception {e}") 18 | time.sleep(interval) 19 | return False 20 | -------------------------------------------------------------------------------- /ols/utils/connection_decorator.py: -------------------------------------------------------------------------------- 1 | """Decocator that makes sure the object is 'connected' according to it's connected predicate.""" 2 | 3 | from typing import Any, Callable 4 | 5 | 6 | def connection(f: Callable) -> Callable: 7 | """Decocator that makes sure the object is 'connected' according to it's connected predicate. 8 | 9 | Example: 10 | ```python 11 | @connection 12 | def list_history(self) -> list[str]: 13 | pass 14 | ``` 15 | """ 16 | 17 | def wrapper(connectable: Any, *args: Any, **kwargs: Any) -> Callable: 18 | if not connectable.connected(): 19 | connectable.connect() 20 | return f(connectable, *args, **kwargs) 21 | 22 | return wrapper 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '[RFE]' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/black.yaml: -------------------------------------------------------------------------------- 1 | name: Black 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | black: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: read 12 | pull-requests: read 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: actions/setup-python@v5 16 | with: 17 | python-version: '3.11' 18 | - name: Python version 19 | run: python --version 20 | - name: PDM installation 21 | run: pip install --user pdm 22 | - name: Install devel dependencies 23 | run: pdm install --dev 24 | - name: Black version 25 | run: pdm run black --version 26 | - name: Black check 27 | run: pdm run black . --check 28 | -------------------------------------------------------------------------------- /.github/workflows/mypy.yaml: -------------------------------------------------------------------------------- 1 | name: Type checks 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | mypy: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: read 12 | pull-requests: read 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: actions/setup-python@v5 16 | with: 17 | python-version: '3.11' 18 | - name: Python version 19 | run: python --version 20 | - name: Mypy installation 21 | run: pip install --user mypy pydantic 22 | - name: Type checks 23 | run: mypy --explicit-package-bases --disallow-untyped-calls --disallow-untyped-defs --disallow-incomplete-defs --ignore-missing-imports --disable-error-code attr-defined ols/ 24 | -------------------------------------------------------------------------------- /tests/config/auth_config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | llm_providers: 3 | - name: bam 4 | url: "https://bam-api.res.ibm.com" 5 | credentials_path: tests/config/secret/apitoken 6 | models: 7 | - name: model-name 8 | ols_config: 9 | reference_content: 10 | product_docs_index_path: "tests/config" 11 | product_docs_index_id: product 12 | conversation_cache: 13 | type: memory 14 | memory: 15 | max_entries: 1000 16 | logging_config: 17 | logging_level: INFO 18 | default_provider: bam 19 | default_model: model-name 20 | authentication_config: 21 | k8s_cluster_api: "https://localhost:6443" 22 | # k8s_ca_cert_path: "/Users/home/ca.crt" 23 | skip_tls_verification: true 24 | dev_config: 25 | disable_auth: false 26 | disable_tls: true 27 | -------------------------------------------------------------------------------- /tests/config/valid_config_with_bam_2.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | llm_providers: 3 | - name: p1 4 | type: bam 5 | url: "https://url1" 6 | deployment_name: "test" 7 | bam_config: 8 | url: "http://localhost:1234" 9 | credentials_path: tests/config/secret/apitoken 10 | models: 11 | - name: m1 12 | url: "https://murl1" 13 | ols_config: 14 | conversation_cache: 15 | type: postgres 16 | postgres: 17 | host: "foobar.com" 18 | port: "1234" 19 | dbname: "test" 20 | user: "user" 21 | password_path: tests/config/postgres_password.txt 22 | ca_cert_path: tests/config/postgres_cert.crt 23 | ssl_mode: "require" 24 | default_provider: p1 25 | default_model: m1 26 | dev_config: 27 | disable_auth: true 28 | disable_tls: true 29 | -------------------------------------------------------------------------------- /.github/workflows/pylint.yaml: -------------------------------------------------------------------------------- 1 | name: Python linter 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | pylint: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: read 12 | pull-requests: read 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: actions/setup-python@v5 16 | with: 17 | python-version: '3.11' 18 | - name: Python version 19 | run: python --version 20 | - name: PDM installation 21 | run: pip install --user pdm 22 | - name: Install dependencies 23 | run: pdm install 24 | - name: Install devel dependencies 25 | run: pdm install --group default,dev,evaluation 26 | - name: Python linter 27 | run: pdm run pylint ols/ scripts/ tests/ runner.py 28 | -------------------------------------------------------------------------------- /docs/examples/barebones.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | reveal.js - Barebones 6 | 7 | 8 | 9 | 10 |
11 |
12 | 13 |
14 |

Barebones Presentation

15 |

This example contains the bare minimum includes and markup required to run a reveal.js presentation.

16 |
17 | 18 |
19 |

No Theme

20 |

There's no theme included, so it will fall back on browser defaults.

21 |
22 | 23 |
24 |
25 | 26 | 27 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG]" 5 | labels: bug 6 | assignees: '' 7 | --- 8 | 9 | # Bug Description 10 | 11 | ## **Output of `OLS` version** 12 | 13 | ## **Describe the bug** 14 | 15 | A clear and concise description of what the bug is. 16 | 17 | ## **To Reproduce** 18 | 19 | Steps to reproduce the behavior: 20 | 21 | 1. Go to '...' 22 | 1. Click on '....' 23 | 1. Scroll down to '....' 24 | 1. See error 25 | 26 | ## **Expected behavior** 27 | 28 | A clear and concise description of what you expected to happen. 29 | 30 | ## **Screenshots or output** 31 | 32 | If applicable, add screenshots or kube-burner output to help explain your problem. 33 | 34 | ## **Additional context** 35 | 36 | Add any other context about the problem here. 37 | -------------------------------------------------------------------------------- /ols/app/routers.py: -------------------------------------------------------------------------------- 1 | """REST API routers.""" 2 | 3 | from fastapi import FastAPI 4 | 5 | from ols.app.endpoints import ( 6 | authorized, 7 | conversations, 8 | feedback, 9 | health, 10 | ols, 11 | streaming_ols, 12 | ) 13 | from ols.app.metrics import metrics 14 | 15 | 16 | def include_routers(app: FastAPI) -> None: 17 | """Include FastAPI routers for different endpoints. 18 | 19 | Args: 20 | app: The `FastAPI` app instance. 21 | """ 22 | app.include_router(ols.router, prefix="/v1") 23 | app.include_router(streaming_ols.router, prefix="/v1") 24 | app.include_router(feedback.router, prefix="/v1") 25 | app.include_router(conversations.router) 26 | app.include_router(health.router) 27 | app.include_router(metrics.router) 28 | app.include_router(authorized.router) 29 | -------------------------------------------------------------------------------- /tests/mock_classes/mock_watsonxllm.py: -------------------------------------------------------------------------------- 1 | """Mocked ChatWatsonx class to avoid accessing real Watsonx API.""" 2 | 3 | from langchain.llms.base import LLM 4 | 5 | 6 | class ChatWatsonx(LLM): 7 | """Mocked ChatWatsonx class to avoid accessing real Watsonx API.""" 8 | 9 | def __init__(self): 10 | """Initialize mocked ChatWatsonx.""" 11 | 12 | def _call(self, prompt, stop=None, run_manager=None, **kwargs): 13 | """Override abstract method from LLM abstract class.""" 14 | return "foo" 15 | 16 | def __call__(self, prompt=None, stop=None, tags=None, **kwargs): 17 | """Override abstract method from LLM abstract class.""" 18 | return self 19 | 20 | @property 21 | def _llm_type(self): 22 | """Override abstract method from LLM abstract class.""" 23 | return "baz" 24 | -------------------------------------------------------------------------------- /tests/config/valid_config_with_bam.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | llm_providers: 3 | - name: p1 4 | type: bam 5 | url: "https://url1" 6 | deployment_name: "test" 7 | credentials_path: tests/config/secret/apitoken 8 | bam_config: 9 | url: "http://localhost:1234" 10 | credentials_path: tests/config/secret/apitoken 11 | models: 12 | - name: m1 13 | url: "https://murl1" 14 | ols_config: 15 | conversation_cache: 16 | type: postgres 17 | postgres: 18 | host: "foobar.com" 19 | port: "1234" 20 | dbname: "test" 21 | user: "user" 22 | password_path: tests/config/postgres_password.txt 23 | ca_cert_path: tests/config/postgres_cert.crt 24 | ssl_mode: "require" 25 | default_provider: p1 26 | default_model: m1 27 | dev_config: 28 | disable_auth: true 29 | disable_tls: true 30 | -------------------------------------------------------------------------------- /.github/workflows/check_dependencies.yaml: -------------------------------------------------------------------------------- 1 | name: Check dependencies 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | check_dependencies: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: read 12 | pull-requests: read 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: actions/setup-python@v5 16 | with: 17 | python-version: '3.11' 18 | - name: Check Python version 19 | run: python --version 20 | - name: PDM installation 21 | run: pip install --user pdm 22 | - name: PDM version 23 | run: pdm --version 24 | - name: Generate requirements.txt file 25 | run: pdm export --prod --format requirements --output requirements.txt --no-extras 26 | - name: List requirements.txt file 27 | run: cat requirements.txt 28 | -------------------------------------------------------------------------------- /tests/config/valid_config_postgres.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | llm_providers: 3 | - name: p1 4 | type: bam 5 | url: "https://url1" 6 | credentials_path: tests/config/secret/apitoken 7 | models: 8 | - name: m1 9 | url: "https://murl1" 10 | ols_config: 11 | reference_content: 12 | product_docs_index_path: "tests/config" 13 | product_docs_index_id: product 14 | conversation_cache: 15 | type: postgres 16 | postgres: 17 | host: "foobar.com" 18 | port: "1234" 19 | dbname: "test" 20 | user: "user" 21 | password_path: tests/config/postgres_password.txt 22 | ca_cert_path: tests/config/postgres_cert.crt 23 | ssl_mode: "require" 24 | logging_config: 25 | logging_level: INFO 26 | default_provider: p1 27 | default_model: m1 28 | dev_config: 29 | disable_auth: true 30 | disable_tls: true 31 | -------------------------------------------------------------------------------- /tests/config/valid_config_with_azure_openai.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | llm_providers: 3 | - name: p1 4 | type: azure_openai 5 | url: "https://url1" 6 | deployment_name: "test" 7 | credentials_path": tests/config/secret/apitoken 8 | azure_openai_config: 9 | url: "http://localhost:1234" 10 | deployment_name: "*deployment name*" 11 | models: 12 | - name: m1 13 | url: "https://murl1" 14 | ols_config: 15 | conversation_cache: 16 | type: postgres 17 | postgres: 18 | host: "foobar.com" 19 | port: "1234" 20 | dbname: "test" 21 | user: "user" 22 | password_path: tests/config/postgres_password.txt 23 | ca_cert_path: tests/config/postgres_cert.crt 24 | ssl_mode: "require" 25 | default_provider: p1 26 | default_model: m1 27 | dev_config: 28 | disable_auth: true 29 | disable_tls: true 30 | -------------------------------------------------------------------------------- /tests/config/valid_config_with_azure_openai_2.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | llm_providers: 3 | - name: p1 4 | type: azure_openai 5 | url: "https://url1" 6 | deployment_name: "test" 7 | azure_openai_config: 8 | url: "http://localhost:1234" 9 | deployment_name: "*deployment name*" 10 | credentials_path: tests/config/secret/apitoken 11 | models: 12 | - name: m1 13 | url: "https://murl1" 14 | ols_config: 15 | conversation_cache: 16 | type: postgres 17 | postgres: 18 | host: "foobar.com" 19 | port: "1234" 20 | dbname: "test" 21 | user: "user" 22 | password_path: tests/config/postgres_password.txt 23 | ca_cert_path: tests/config/postgres_cert.crt 24 | ssl_mode: "require" 25 | default_provider: p1 26 | default_model: m1 27 | dev_config: 28 | disable_auth: true 29 | disable_tls: true 30 | -------------------------------------------------------------------------------- /ols/src/llms/__init__.py: -------------------------------------------------------------------------------- 1 | """Interface to LLMs.""" 2 | 3 | # NOTE: In order to register all providers automatically, we are importing 4 | # everything from the providers package. 5 | 6 | import importlib.util 7 | import pathlib 8 | import sys 9 | 10 | 11 | def import_providers() -> None: 12 | """Import all providers from the providers directory.""" 13 | providers_dir = pathlib.Path(__file__).parent.resolve() / "providers" 14 | sys.path.append(providers_dir.as_posix()) 15 | modules = [f for f in providers_dir.iterdir() if not f.stem.startswith("__")] 16 | 17 | for module_name in modules: 18 | spec = importlib.util.spec_from_file_location( 19 | "module_name", module_name.as_posix() 20 | ) 21 | module = importlib.util.module_from_spec(spec) 22 | spec.loader.exec_module(module) 23 | 24 | 25 | import_providers() 26 | -------------------------------------------------------------------------------- /tests/config/valid_config_with_watsonx_2.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | llm_providers: 3 | - name: p1 4 | type: watsonx 5 | url: "https://url1" 6 | deployment_name: "test" 7 | project_id: "project ID" 8 | watsonx_config: 9 | url: "http://localhost:1234" 10 | credentials_path: tests/config/secret/apitoken 11 | project_id: "project ID" 12 | models: 13 | - name: m1 14 | url: "https://murl1" 15 | ols_config: 16 | conversation_cache: 17 | type: postgres 18 | postgres: 19 | host: "foobar.com" 20 | port: "1234" 21 | dbname: "test" 22 | user: "user" 23 | password_path: tests/config/postgres_password.txt 24 | ca_cert_path: tests/config/postgres_cert.crt 25 | ssl_mode: "require" 26 | default_provider: p1 27 | default_model: m1 28 | dev_config: 29 | disable_auth: true 30 | disable_tls: true 31 | -------------------------------------------------------------------------------- /ols/src/quota/user_quota_limiter.py: -------------------------------------------------------------------------------- 1 | """Simple user quota limiter where each user have fixed quota.""" 2 | 3 | import logging 4 | 5 | from ols.app.models.config import PostgresConfig 6 | from ols.src.quota.revokable_quota_limiter import RevokableQuotaLimiter 7 | 8 | logger = logging.getLogger(__name__) 9 | 10 | 11 | class UserQuotaLimiter(RevokableQuotaLimiter): 12 | """Simple user quota limiter where each user have fixed quota.""" 13 | 14 | def __init__( 15 | self, 16 | config: PostgresConfig, 17 | initial_quota: int = 0, 18 | increase_by: int = 0, 19 | ) -> None: 20 | """Initialize quota limiter storage.""" 21 | subject = "u" # user 22 | super().__init__(initial_quota, increase_by, subject, config) 23 | 24 | # initialize connection to DB 25 | # and initialize tables too 26 | self.connect() 27 | -------------------------------------------------------------------------------- /tests/mock_classes/mock_langchain_interface.py: -------------------------------------------------------------------------------- 1 | """Mock for LangChainInterface to be used in unit tests.""" 2 | 3 | import json 4 | from types import SimpleNamespace 5 | 6 | 7 | def mock_langchain_interface(retval): 8 | """Construct mock for LangChainInterface.""" 9 | 10 | class MockLangChainInterface: 11 | """Mock LangChainInterface class for testing. 12 | 13 | Example usage in a test: 14 | 15 | 16 | """ 17 | 18 | def __init__(self, *args, **kwargs): 19 | pass 20 | 21 | def __call__(self, *args, **kwargs): 22 | return retval 23 | 24 | def invoke(self, question): 25 | """Return query result.""" 26 | result = '{"content": "mock success for question: ' + question + '"}' 27 | return json.loads(result, object_hook=lambda d: SimpleNamespace(**d)) 28 | 29 | return MockLangChainInterface 30 | -------------------------------------------------------------------------------- /tests/config/valid_config_with_azure_openai_api_version.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | llm_providers: 3 | - name: p1 4 | type: azure_openai 5 | url: "https://url1" 6 | deployment_name: "test" 7 | credentials_path": tests/config/secret/apitoken 8 | api_version: "2024-12-31" 9 | azure_openai_config: 10 | url: "http://localhost:1234" 11 | deployment_name: "*deployment name*" 12 | models: 13 | - name: m1 14 | url: "https://murl1" 15 | ols_config: 16 | conversation_cache: 17 | type: postgres 18 | postgres: 19 | host: "foobar.com" 20 | port: "1234" 21 | dbname: "test" 22 | user: "user" 23 | password_path: tests/config/postgres_password.txt 24 | ca_cert_path: tests/config/postgres_cert.crt 25 | ssl_mode: "require" 26 | default_provider: p1 27 | default_model: m1 28 | dev_config: 29 | disable_auth: true 30 | disable_tls: true 31 | -------------------------------------------------------------------------------- /ols/src/quota/cluster_quota_limiter.py: -------------------------------------------------------------------------------- 1 | """Simple cluster quota limiter where quota is fixed for the whole cluster.""" 2 | 3 | import logging 4 | 5 | from ols.app.models.config import PostgresConfig 6 | from ols.src.quota.revokable_quota_limiter import RevokableQuotaLimiter 7 | 8 | logger = logging.getLogger(__name__) 9 | 10 | 11 | class ClusterQuotaLimiter(RevokableQuotaLimiter): 12 | """Simple cluster quota limiter where quota is fixed for the whole cluster.""" 13 | 14 | def __init__( 15 | self, 16 | config: PostgresConfig, 17 | initial_quota: int = 0, 18 | increase_by: int = 0, 19 | ) -> None: 20 | """Initialize quota limiter storage.""" 21 | subject = "c" # cluster 22 | super().__init__(initial_quota, increase_by, subject, config) 23 | 24 | # initialize connection to DB 25 | # and initialize tables too 26 | self.connect() 27 | -------------------------------------------------------------------------------- /ols/app/metrics/__init__.py: -------------------------------------------------------------------------------- 1 | """Metrics and metric collectors.""" 2 | 3 | from .metrics import ( 4 | llm_calls_failures_total, 5 | llm_calls_total, 6 | llm_calls_validation_errors_total, 7 | llm_token_received_total, 8 | llm_token_sent_total, 9 | provider_model_configuration, 10 | response_duration_seconds, 11 | rest_api_calls_total, 12 | setup_metrics, 13 | setup_model_metrics, 14 | ) 15 | from .token_counter import GenericTokenCounter, TokenMetricUpdater 16 | 17 | __all__ = [ 18 | "GenericTokenCounter", 19 | "TokenMetricUpdater", 20 | "llm_calls_failures_total", 21 | "llm_calls_total", 22 | "llm_calls_validation_errors_total", 23 | "llm_token_received_total", 24 | "llm_token_sent_total", 25 | "provider_model_configuration", 26 | "response_duration_seconds", 27 | "rest_api_calls_total", 28 | "setup_metrics", 29 | "setup_model_metrics", 30 | ] 31 | -------------------------------------------------------------------------------- /tests/config/valid_config_with_watsonx.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | llm_providers: 3 | - name: p1 4 | type: watsonx 5 | url: "https://url1" 6 | deployment_name: "test" 7 | credentials_path: tests/config/secret/apitoken 8 | project_id: "project ID" 9 | watsonx_config: 10 | url: "http://localhost:1234" 11 | credentials_path: tests/config/secret/apitoken 12 | project_id: "project ID" 13 | models: 14 | - name: m1 15 | url: "https://murl1" 16 | ols_config: 17 | conversation_cache: 18 | type: postgres 19 | postgres: 20 | host: "foobar.com" 21 | port: "1234" 22 | dbname: "test" 23 | user: "user" 24 | password_path: tests/config/postgres_password.txt 25 | ca_cert_path: tests/config/postgres_cert.crt 26 | ssl_mode: "require" 27 | default_provider: p1 28 | default_model: m1 29 | dev_config: 30 | disable_auth: true 31 | disable_tls: true 32 | -------------------------------------------------------------------------------- /tests/config/valid_config_without_query_filter.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | llm_providers: 3 | - name: p1 4 | type: bam 5 | url: "https://url1" 6 | credentials_path: tests/config/secret/apitoken 7 | models: 8 | - name: m1 9 | url: "https://murl1" 10 | credentials_path: tests/config/secret/apitoken 11 | - name: m2 12 | url: "https://murl2" 13 | - name: p2 14 | type: bam 15 | url: "https://url2" 16 | models: 17 | - name: m1 18 | url: "https://murl1" 19 | - name: m2 20 | url: "https://murl2" 21 | ols_config: 22 | reference_content: 23 | product_docs_index_path: "tests/config" 24 | product_docs_index_id: product 25 | conversation_cache: 26 | type: memory 27 | memory: 28 | max_entries: 1000 29 | logging_config: 30 | logging_level: INFO 31 | default_provider: p1 32 | default_model: m1 33 | dev_config: 34 | disable_tls: true 35 | disable_auth: true 36 | -------------------------------------------------------------------------------- /.github/workflows/unit_tests.yaml: -------------------------------------------------------------------------------- 1 | name: Unit tests 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | unit_tests: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: read 12 | pull-requests: read 13 | strategy: 14 | matrix: 15 | python-version: ["3.11", "3.12", "3.13"] 16 | steps: 17 | - uses: actions/checkout@v4 18 | - name: Set up Python ${{ matrix.python-version }} 19 | uses: actions/setup-python@v5 20 | with: 21 | python-version: ${{ matrix.python-version }} 22 | - name: Python version 23 | run: python --version 24 | - name: PDM installation 25 | run: pip install --user pdm 26 | - name: Install dependencies 27 | run: pdm install 28 | - name: Install devel dependencies 29 | run: pdm install --dev 30 | - name: Run unit tests 31 | run: pdm run python -m pytest tests/unit --cov=ols --cov=runner --cov-report term-missing 32 | -------------------------------------------------------------------------------- /tests/unit/query_helpers/test_query_helper.py: -------------------------------------------------------------------------------- 1 | """Unit tests for query helper class.""" 2 | 3 | from ols import config 4 | from ols.src.llms.llm_loader import load_llm 5 | from ols.src.query_helpers.query_helper import QueryHelper 6 | 7 | 8 | def test_defaults_used(): 9 | """Test that the defaults are used when no inputs are provided.""" 10 | config.reload_from_yaml_file("tests/config/valid_config.yaml") 11 | 12 | qh = QueryHelper() 13 | 14 | assert qh.provider == config.ols_config.default_provider 15 | assert qh.model == config.ols_config.default_model 16 | assert qh.llm_loader is load_llm 17 | assert qh.generic_llm_params == {} 18 | 19 | 20 | def test_inputs_are_used(): 21 | """Test that the inputs are used when provided.""" 22 | test_provider = "test_provider" 23 | test_model = "test_model" 24 | qh = QueryHelper(provider=test_provider, model=test_model) 25 | 26 | assert qh.provider == test_provider 27 | assert qh.model == test_model 28 | -------------------------------------------------------------------------------- /docs/dist/reset.css: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ 2 | v4.0 | 20180602 3 | License: none (public domain) 4 | */ 5 | 6 | html, body, div, span, applet, object, iframe, 7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 8 | a, abbr, acronym, address, big, cite, code, 9 | del, dfn, em, img, ins, kbd, q, s, samp, 10 | small, strike, strong, sub, sup, tt, var, 11 | b, u, i, center, 12 | dl, dt, dd, ol, ul, li, 13 | fieldset, form, label, legend, 14 | table, caption, tbody, tfoot, thead, tr, th, td, 15 | article, aside, canvas, details, embed, 16 | figure, figcaption, footer, header, hgroup, 17 | main, menu, nav, output, ruby, section, summary, 18 | time, mark, audio, video { 19 | margin: 0; 20 | padding: 0; 21 | border: 0; 22 | font-size: 100%; 23 | font: inherit; 24 | vertical-align: baseline; 25 | } 26 | /* HTML5 display-role reset for older browsers */ 27 | article, aside, details, figcaption, figure, 28 | footer, header, hgroup, main, menu, nav, section { 29 | display: block; 30 | } -------------------------------------------------------------------------------- /tests/e2e/utils/client.py: -------------------------------------------------------------------------------- 1 | """General utilities for end-to-end tests.""" 2 | 3 | from typing import Optional 4 | 5 | from httpx import Client 6 | 7 | from tests.e2e.utils.constants import LLM_REST_API_TIMEOUT 8 | from tests.e2e.utils.response import check_content_type 9 | 10 | 11 | def get_http_client(url: str, user_token: Optional[str] = None) -> Client: 12 | """Get HTTP client.""" 13 | client = Client(base_url=url, verify=False) # noqa: S501 14 | if user_token: 15 | client.headers.update({"Authorization": f"Bearer {user_token}"}) 16 | return client 17 | 18 | 19 | def perform_query(client, conversation_id, query): 20 | """Call service REST API using /query endpoint.""" 21 | endpoint = "/v1/query" 22 | 23 | response = client.post( 24 | endpoint, 25 | json={"conversation_id": conversation_id, "query": query}, 26 | timeout=LLM_REST_API_TIMEOUT, 27 | ) 28 | check_content_type(response, "application/json") 29 | print(vars(response)) 30 | -------------------------------------------------------------------------------- /tests/config/operator_install/olsconfig.crd.openai.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ols.openshift.io/v1alpha1 2 | kind: OLSConfig 3 | metadata: 4 | name: cluster 5 | labels: 6 | app.kubernetes.io/created-by: lightspeed-operator 7 | app.kubernetes.io/instance: olsconfig-sample 8 | app.kubernetes.io/managed-by: kustomize 9 | app.kubernetes.io/name: olsconfig 10 | app.kubernetes.io/part-of: lightspeed-operator 11 | spec: 12 | llm: 13 | providers: 14 | - credentialsSecretRef: 15 | name: llmcreds 16 | models: 17 | - name: gpt-4o-mini 18 | name: openai 19 | type: openai 20 | ols: 21 | defaultModel: gpt-4o-mini 22 | defaultProvider: openai 23 | deployment: 24 | replicas: 1 25 | disableAuth: false 26 | logLevel: DEBUG 27 | queryFilters: 28 | - name: foo_filter 29 | pattern: '\b(?:foo)\b' 30 | replaceWith: "deployment" 31 | - name: bar_filter 32 | pattern: '\b(?:bar)\b' 33 | replaceWith: "openshift" 34 | -------------------------------------------------------------------------------- /tests/config/server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIIChzCCAW8CAQAwQjELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0 3 | eTEcMBoGA1UECgwTRGVmYXVsdCBDb21wYW55IEx0ZDCCASIwDQYJKoZIhvcNAQEB 4 | BQADggEPADCCAQoCggEBAK1IyHGMqcajSDXUHppWjEKkgKARorUqailS7nAdqymS 5 | 9jt3My7Z6K9LZhELJkeerPzWPwFotyRpy1iegSQwoeaw4bh/7xL/k9QLYbNkyjft 6 | 8dfYLkyAuk7CTsdGrryOjeQ0L7JnJaWgjNAsWseNemVdhvLbyUQHS2c6IX6y3G9t 7 | 0TGek+y3sKgp56s/y5Wk0YVx2nX6inGE5rAg6yRwuenZo9Yup/0nmlPnMRclW0JL 8 | K87nX3wcPeqk/vB888yjmiALeKnyVdFX3NeqHRTn2JV0wYKn2ZKBPkMRVKtwzCck 9 | 2BN+NxO3COidzV3zgNnL0KApxgFGPZVRAu5s9jflcpcCAwEAAaAAMA0GCSqGSIb3 10 | DQEBCwUAA4IBAQBFuniyOPskbxlQPrd2Yz2SKHSXo+B2We1oRhPjjafyI8S+FVjI 11 | yXrP86Px4sNBO9f6YdZ8WlqxN0c68SJVZq1CB6PpdpRxSovN0qQPnKQ8i3Tv6SZx 12 | cpYqbwxqI5HYAroix49gAJvUUsfeYOuONUAm/WfidEtLBhzt5wtDvE/VmwdFvm6g 13 | vnYr9IM6CBtQhj9n0AaOccQqmxtMSQjqRhAzlfWZvSc2x7t7/ZIo1xgh/h+qGyXi 14 | PwwLvb/zcDF6hEOVJqdFcjKw2DrmHksHEk/tw1BQ7pN4Oli0nfOhTZIdcGpjhGlI 15 | 93jS5tOZBNjc4Xv43nlWItbu7z3E2YlKWGSi 16 | -----END CERTIFICATE REQUEST----- 17 | -------------------------------------------------------------------------------- /.github/workflows/integration_tests.yaml: -------------------------------------------------------------------------------- 1 | name: Integration tests 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | integration_tests: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: read 12 | pull-requests: read 13 | strategy: 14 | matrix: 15 | python-version: ["3.11", "3.12", "3.13"] 16 | steps: 17 | - uses: actions/checkout@v4 18 | - name: Set up Python ${{ matrix.python-version }} 19 | uses: actions/setup-python@v5 20 | with: 21 | python-version: ${{ matrix.python-version }} 22 | - name: Python version 23 | run: python --version 24 | - name: PDM installation 25 | run: pip install --user pdm 26 | - name: Install dependencies 27 | run: pdm install 28 | - name: Install devel dependencies 29 | run: pdm install --dev 30 | - name: Run integration tests 31 | run: pdm run python -m pytest -m 'not redis' tests/integration --cov=ols --cov=runner --cov-report term-missing 32 | -------------------------------------------------------------------------------- /tests/config/operator_install/olsconfig.crd.bam.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ols.openshift.io/v1alpha1 2 | kind: OLSConfig 3 | metadata: 4 | name: cluster 5 | labels: 6 | app.kubernetes.io/created-by: lightspeed-operator 7 | app.kubernetes.io/instance: olsconfig-sample 8 | app.kubernetes.io/managed-by: kustomize 9 | app.kubernetes.io/name: olsconfig 10 | app.kubernetes.io/part-of: lightspeed-operator 11 | spec: 12 | llm: 13 | providers: 14 | - credentialsSecretRef: 15 | name: llmcreds 16 | models: 17 | - name: ibm/granite-3-8b-instruct 18 | name: bam 19 | type: bam 20 | ols: 21 | defaultModel: ibm/granite-3-8b-instruct 22 | defaultProvider: bam 23 | deployment: 24 | replicas: 1 25 | disableAuth: false 26 | logLevel: DEBUG 27 | queryFilters: 28 | - name: foo_filter 29 | pattern: '\b(?:foo)\b' 30 | replaceWith: "deployment" 31 | - name: bar_filter 32 | pattern: '\b(?:bar)\b' 33 | replaceWith: "openshift" 34 | -------------------------------------------------------------------------------- /tests/config/operator_install/olsconfig.crd.rhoai_vllm.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ols.openshift.io/v1alpha1 2 | kind: OLSConfig 3 | metadata: 4 | name: cluster 5 | labels: 6 | app.kubernetes.io/created-by: lightspeed-operator 7 | app.kubernetes.io/instance: olsconfig-sample 8 | app.kubernetes.io/managed-by: kustomize 9 | app.kubernetes.io/name: olsconfig 10 | app.kubernetes.io/part-of: lightspeed-operator 11 | spec: 12 | llm: 13 | providers: 14 | - credentialsSecretRef: 15 | name: llmcreds 16 | models: 17 | - name: gpt-3.5-turbo 18 | name: rhoai_vllm 19 | type: rhoai_vllm 20 | ols: 21 | defaultModel: gpt-3.5-turbo 22 | defaultProvider: rhoai_vllm 23 | deployment: 24 | replicas: 1 25 | disableAuth: false 26 | logLevel: DEBUG 27 | queryFilters: 28 | - name: foo_filter 29 | pattern: '\b(?:foo)\b' 30 | replaceWith: "deployment" 31 | - name: bar_filter 32 | pattern: '\b(?:bar)\b' 33 | replaceWith: "openshift" 34 | -------------------------------------------------------------------------------- /tests/config/operator_install/olsconfig.crd.rhelai_vllm.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ols.openshift.io/v1alpha1 2 | kind: OLSConfig 3 | metadata: 4 | name: cluster 5 | labels: 6 | app.kubernetes.io/created-by: lightspeed-operator 7 | app.kubernetes.io/instance: olsconfig-sample 8 | app.kubernetes.io/managed-by: kustomize 9 | app.kubernetes.io/name: olsconfig 10 | app.kubernetes.io/part-of: lightspeed-operator 11 | spec: 12 | llm: 13 | providers: 14 | - credentialsSecretRef: 15 | name: llmcreds 16 | models: 17 | - name: gpt-3.5-turbo 18 | name: rhelai_vllm 19 | type: rhelai_vllm 20 | ols: 21 | defaultModel: gpt-3.5-turbo 22 | defaultProvider: rhelai_vllm 23 | deployment: 24 | replicas: 1 25 | disableAuth: false 26 | logLevel: DEBUG 27 | queryFilters: 28 | - name: foo_filter 29 | pattern: '\b(?:foo)\b' 30 | replaceWith: "deployment" 31 | - name: bar_filter 32 | pattern: '\b(?:bar)\b' 33 | replaceWith: "openshift" 34 | -------------------------------------------------------------------------------- /tests/config/operator_install/olsconfig.crd.openai_introspection.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ols.openshift.io/v1alpha1 2 | kind: OLSConfig 3 | metadata: 4 | name: cluster 5 | labels: 6 | app.kubernetes.io/created-by: lightspeed-operator 7 | app.kubernetes.io/instance: olsconfig-sample 8 | app.kubernetes.io/managed-by: kustomize 9 | app.kubernetes.io/name: olsconfig 10 | app.kubernetes.io/part-of: lightspeed-operator 11 | spec: 12 | llm: 13 | providers: 14 | - credentialsSecretRef: 15 | name: llmcreds 16 | models: 17 | - name: gpt-4o-mini 18 | name: openai 19 | type: openai 20 | ols: 21 | defaultModel: gpt-4o-mini 22 | defaultProvider: openai 23 | introspectionEnabled: true 24 | deployment: 25 | replicas: 1 26 | disableAuth: false 27 | logLevel: DEBUG 28 | queryFilters: 29 | - name: foo_filter 30 | pattern: '\b(?:foo)\b' 31 | replaceWith: "deployment" 32 | - name: bar_filter 33 | pattern: '\b(?:bar)\b' 34 | replaceWith: "openshift" 35 | -------------------------------------------------------------------------------- /tests/mock_classes/mock_retrieved_node.py: -------------------------------------------------------------------------------- 1 | """Class to create mock data for retrieved node.""" 2 | 3 | from typing import Any, Optional 4 | 5 | 6 | class MockRetrievedNode: 7 | """Class to create mock data for retrieved node.""" 8 | 9 | score: Optional[float] = None 10 | 11 | def __init__(self, node_detail: dict): 12 | """Initialize the class instance.""" 13 | super() 14 | self._text = node_detail["text"] 15 | self.score = node_detail["score"] 16 | self._metadata = node_detail["metadata"] 17 | 18 | def get_text(self) -> str: 19 | """Mock get_text.""" 20 | return self._text 21 | 22 | def get_score(self, raise_error: bool = False) -> float: 23 | """Mock method to retrieve score.""" 24 | if self.score is None: 25 | if raise_error: 26 | raise ValueError("Score not set.") 27 | return 0.0 28 | return self.score 29 | 30 | @property 31 | def metadata(self) -> dict[str, Any]: 32 | """Mock metadata.""" 33 | return self._metadata 34 | -------------------------------------------------------------------------------- /ols/customize/ols/keywords.py: -------------------------------------------------------------------------------- 1 | """Constant for set of keywords.""" 2 | 3 | # Add keyword string to below set, preferably in alphabetical order. 4 | # We are adding this manually for now. Add to a txt file, If/when we automate this. 5 | # Important: Please use lower case. 6 | 7 | KEYWORDS = { 8 | "alert", 9 | "autoscale", 10 | "cluster", 11 | "config", 12 | "configmap", 13 | "console", 14 | "container", 15 | "crd", 16 | "deploy", 17 | "deployment", 18 | "image", 19 | "imagepullpolicy", 20 | "imagepullsecret", 21 | "infra", 22 | "ingress", 23 | "k8s", 24 | "kubernetes", 25 | "log", 26 | "master", 27 | "namespace", 28 | "network", 29 | "node", 30 | "oc", 31 | "ocp", 32 | "openshift", 33 | "operator", 34 | "pod", 35 | "podconfig", 36 | "poddisruptionbudgets", 37 | "podsecurity", 38 | "policy", 39 | "project", 40 | "quay", 41 | "replica", 42 | "replicaset", 43 | "secret", 44 | "service", 45 | "virtualization", 46 | "worker", 47 | "yaml", 48 | } 49 | -------------------------------------------------------------------------------- /tests/e2e/utils/response.py: -------------------------------------------------------------------------------- 1 | """Checks for responses from the service.""" 2 | 3 | import requests 4 | 5 | 6 | def check_content_type(response, content_type, message=""): 7 | """Check if response content-type is set to defined value.""" 8 | assert response.headers["content-type"].startswith(content_type), message 9 | 10 | 11 | def check_missing_field_response(response, field_name): 12 | """Check if 'Field required' error is returned by the service.""" 13 | # error should be detected on Pydantic level 14 | assert response.status_code == requests.codes.unprocessable 15 | 16 | # the resonse payload should be valid JSON 17 | check_content_type(response, "application/json") 18 | json_response = response.json() 19 | 20 | # check payload details 21 | assert ( 22 | "detail" in json_response 23 | ), "Improper response format: 'detail' node is missing" 24 | assert json_response["detail"][0]["msg"] == "Field required" 25 | assert json_response["detail"][0]["loc"][0] == "body" 26 | assert json_response["detail"][0]["loc"][1] == field_name 27 | -------------------------------------------------------------------------------- /ols/customize/noop/keywords.py: -------------------------------------------------------------------------------- 1 | """Constant for set of keywords.""" 2 | 3 | # Add keyword string to below set, preferably in alphabetical order. 4 | # We are adding this manually for now. Add to a txt file, If/when we automate this. 5 | # Important: Please use lower case. 6 | 7 | KEYWORDS = { 8 | "alert", 9 | "autoscale", 10 | "cluster", 11 | "config", 12 | "configmap", 13 | "console", 14 | "container", 15 | "crd", 16 | "deploy", 17 | "deployment", 18 | "image", 19 | "imagepullpolicy", 20 | "imagepullsecret", 21 | "infra", 22 | "ingress", 23 | "k8s", 24 | "kubernetes", 25 | "log", 26 | "master", 27 | "namespace", 28 | "network", 29 | "node", 30 | "oc", 31 | "ocp", 32 | "openshift", 33 | "operator", 34 | "pod", 35 | "podconfig", 36 | "poddisruptionbudgets", 37 | "podsecurity", 38 | "policy", 39 | "project", 40 | "quay", 41 | "replica", 42 | "replicaset", 43 | "secret", 44 | "service", 45 | "virtualization", 46 | "worker", 47 | "yaml", 48 | } 49 | -------------------------------------------------------------------------------- /tests/config/invalid_config_no_models.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | llm_providers: 3 | - name: p1 4 | type: bam 5 | url: "https://url1" 6 | credentials_path: tests/config/secret/apitoken 7 | bam_config: 8 | url: "http://localhost:1234" 9 | credentials_path: tests/config/secret/apitoken 10 | models: 11 | - name: m1 12 | url: "https://murl1" 13 | credentials_path: tests/config/secret/apitoken 14 | context_window_size: 450 15 | parameters: 16 | max_tokens_for_response: 100 17 | - name: m2 18 | url: "https://murl2" 19 | - name: p2 20 | type: openai 21 | url: "https://url2" 22 | ols_config: 23 | reference_content: 24 | product_docs_index_path: "tests/config" 25 | product_docs_index_id: product 26 | conversation_cache: 27 | type: memory 28 | memory: 29 | max_entries: 1000 30 | logging_config: 31 | logging_level: INFO 32 | default_provider: p1 33 | default_model: m1 34 | user_data_collection: 35 | transcripts_disabled: true 36 | dev_config: 37 | disable_auth: true 38 | disable_tls: true 39 | -------------------------------------------------------------------------------- /tests/config/operator_install/olsconfig.crd.watsonx.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ols.openshift.io/v1alpha1 2 | kind: OLSConfig 3 | metadata: 4 | name: cluster 5 | labels: 6 | app.kubernetes.io/created-by: lightspeed-operator 7 | app.kubernetes.io/instance: olsconfig-sample 8 | app.kubernetes.io/managed-by: kustomize 9 | app.kubernetes.io/name: olsconfig 10 | app.kubernetes.io/part-of: lightspeed-operator 11 | spec: 12 | llm: 13 | providers: 14 | - credentialsSecretRef: 15 | name: llmcreds 16 | projectID: ad629765-c373-4731-9d69-dc701724c081 17 | models: 18 | - name: ibm/granite-3-8b-instruct 19 | name: watsonx 20 | type: watsonx 21 | ols: 22 | defaultModel: ibm/granite-3-8b-instruct 23 | defaultProvider: watsonx 24 | deployment: 25 | replicas: 1 26 | disableAuth: false 27 | logLevel: DEBUG 28 | queryFilters: 29 | - name: foo_filter 30 | pattern: '\b(?:foo)\b' 31 | replaceWith: "deployment" 32 | - name: bar_filter 33 | pattern: '\b(?:bar)\b' 34 | replaceWith: "openshift" 35 | -------------------------------------------------------------------------------- /scripts/evaluation/utils/rag.py: -------------------------------------------------------------------------------- 1 | """Utility for evaluation.""" 2 | 3 | from langchain_core.messages import AIMessage 4 | 5 | from ols import config 6 | from ols.constants import RAG_CONTENT_LIMIT 7 | from ols.src.prompts.prompt_generator import GeneratePrompt 8 | from ols.utils.token_handler import TokenHandler 9 | 10 | 11 | def retrieve_rag_chunks(query, model, model_config): 12 | """Retrieve rag chunks.""" 13 | token_handler = TokenHandler() 14 | temp_prompt, temp_prompt_input = GeneratePrompt( 15 | query, ["sample"], [AIMessage(content="sample")] 16 | ).generate_prompt(model) 17 | available_tokens = token_handler.calculate_and_check_available_tokens( 18 | temp_prompt.format(**temp_prompt_input), 19 | model_config.context_window_size, 20 | model_config.parameters.max_tokens_for_response, 21 | ) 22 | 23 | retriever = config.rag_index.as_retriever(similarity_top_k=RAG_CONTENT_LIMIT) 24 | rag_chunks, _ = token_handler.truncate_rag_context( 25 | retriever.retrieve(query), available_tokens 26 | ) 27 | return [rag_chunk.text for rag_chunk in rag_chunks] 28 | -------------------------------------------------------------------------------- /tests/config/invalid_config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | llm_providers: 3 | - name: p1 4 | type: bam_invalid # this is invalid 5 | url: "https://url1" 6 | credentials_path: tests/config/secret/apitoken 7 | models: 8 | - name: m1 9 | url: "https://murl1" 10 | credentials_path: tests/config/secret/apitoken 11 | context_window_size: 450 12 | parameters: 13 | max_tokens_for_response: 100 14 | - name: m2 15 | url: "https://murl2" 16 | - name: p2 17 | type: openai 18 | url: "https://url2" 19 | models: 20 | - name: m1 21 | url: "https://murl1" 22 | - name: m2 23 | url: "https://murl2" 24 | ols_config: 25 | reference_content: 26 | product_docs_index_path: "tests/config" 27 | product_docs_index_id: product 28 | conversation_cache: 29 | type: memory 30 | memory: 31 | max_entries: 1000 32 | logging_config: 33 | logging_level: INFO 34 | default_provider: p1 35 | default_model: m1 36 | user_data_collection: 37 | transcripts_disabled: true 38 | dev_config: 39 | disable_auth: true 40 | disable_tls: true 41 | -------------------------------------------------------------------------------- /tests/config/valid_config_without_certificate_directory.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | llm_providers: 3 | - name: p1 4 | type: bam 5 | url: "https://url1" 6 | credentials_path: tests/config/secret/apitoken 7 | models: 8 | - name: m1 9 | url: "https://murl1" 10 | credentials_path: tests/config/secret/apitoken 11 | context_window_size: 450 12 | parameters: 13 | max_tokens_for_response: 100 14 | - name: m2 15 | url: "https://murl2" 16 | - name: p2 17 | type: openai 18 | url: "https://url2" 19 | models: 20 | - name: m1 21 | url: "https://murl1" 22 | - name: m2 23 | url: "https://murl2" 24 | ols_config: 25 | reference_content: 26 | product_docs_index_path: "tests/config" 27 | product_docs_index_id: product 28 | conversation_cache: 29 | type: memory 30 | memory: 31 | max_entries: 1000 32 | logging_config: 33 | logging_level: INFO 34 | default_provider: p1 35 | default_model: m1 36 | user_data_collection: 37 | transcripts_disabled: true 38 | dev_config: 39 | disable_auth: true 40 | disable_tls: true 41 | -------------------------------------------------------------------------------- /tests/config/config_with_disable_model_check.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | llm_providers: 3 | - name: p1 4 | type: bam 5 | url: "https://url1" 6 | credentials_path: tests/config/secret/apitoken 7 | bam_config: 8 | url: "http://localhost:1234" 9 | credentials_path: tests/config/secret/apitoken 10 | models: 11 | - name: m1 12 | url: "https://murl1" 13 | credentials_path: tests/config/secret/apitoken 14 | context_window_size: 450 15 | parameters: 16 | max_tokens_for_response: 100 17 | - name: m2 18 | url: "https://murl2" 19 | - name: p2 20 | type: openai 21 | url: "https://url2" 22 | disable_model_check: true 23 | ols_config: 24 | reference_content: 25 | product_docs_index_path: "tests/config" 26 | product_docs_index_id: product 27 | conversation_cache: 28 | type: memory 29 | memory: 30 | max_entries: 1000 31 | logging_config: 32 | logging_level: INFO 33 | default_provider: p1 34 | default_model: m1 35 | user_data_collection: 36 | transcripts_disabled: true 37 | dev_config: 38 | disable_auth: true 39 | disable_tls: true 40 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | 4 | 5 | ## Type of change 6 | 7 | - [ ] Refactor 8 | - [ ] New feature 9 | - [ ] Bug fix 10 | - [ ] CVE fix 11 | - [ ] Optimization 12 | - [ ] Documentation Update 13 | - [ ] Configuration Update 14 | - [ ] Bump-up service version 15 | - [ ] Bump-up dependent library 16 | - [ ] Bump-up library or tool used for development (does not change the final image) 17 | - [ ] CI configuration change 18 | - [ ] Konflux configuration change 19 | - [ ] Unit tests improvement 20 | - [ ] Integration tests improvement 21 | - [ ] End to end tests improvement 22 | 23 | 24 | ## Related Tickets & Documents 25 | 26 | - Related Issue # 27 | - Closes # 28 | 29 | ## Checklist before requesting a review 30 | 31 | - [ ] I have performed a self-review of my code. 32 | - [ ] PR has passed all pre-merge test jobs. 33 | - [ ] If it is a core feature, I have added thorough tests. 34 | 35 | ## Testing 36 | - Please provide detailed steps to perform tests related to this code change. 37 | - How were the fix/results from this change verified? Please provide relevant screenshots or results. 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ignore python and editors cache 2 | __pycache__ 3 | __pypackages__ 4 | .env* 5 | .vscode 6 | .DS_Store 7 | 8 | # ignore venv 9 | venv 10 | .venv 11 | .pdm-python 12 | 13 | # ignore local configuration 14 | .env 15 | /rcsconfig.yaml 16 | scratch 17 | 18 | # ignore locally significant directories 19 | tmp 20 | data 21 | vector_db 22 | vendor 23 | embeddings_model 24 | .output 25 | 26 | # ignore credential files 27 | *_api_key.txt 28 | 29 | # ignore files that begin with tmptest_ 30 | tmptest_* 31 | 32 | # don't keep log files 33 | logs/* 34 | 35 | # ignore eval results 36 | */*/eval_result/* 37 | 38 | # ignore test results 39 | tests/test_results/* 40 | htmlcov/ 41 | .coverage 42 | 43 | # ignore cache files 44 | .dccache 45 | 46 | # ignore cache directories 47 | .mypy_cache 48 | .pytest_cache 49 | .ruff_cache 50 | 51 | # ignore faiss index directory 52 | index/ 53 | 54 | # ignore .idea for pycharm 55 | .idea 56 | 57 | dev/ 58 | 59 | # ignore woke cli to be pushed to the repo 60 | woke 61 | 62 | # files generated by benchmark 63 | benchmark_*.svg 64 | 65 | # distribution archives 66 | dist/ 67 | 68 | # requirements file 69 | requirements* 70 | -------------------------------------------------------------------------------- /tests/config/operator_install/olsconfig.crd.watsonx_introspection.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ols.openshift.io/v1alpha1 2 | kind: OLSConfig 3 | metadata: 4 | name: cluster 5 | labels: 6 | app.kubernetes.io/created-by: lightspeed-operator 7 | app.kubernetes.io/instance: olsconfig-sample 8 | app.kubernetes.io/managed-by: kustomize 9 | app.kubernetes.io/name: olsconfig 10 | app.kubernetes.io/part-of: lightspeed-operator 11 | spec: 12 | llm: 13 | providers: 14 | - credentialsSecretRef: 15 | name: llmcreds 16 | projectID: ad629765-c373-4731-9d69-dc701724c081 17 | models: 18 | - name: ibm/granite-3-2-8b-instruct 19 | name: watsonx 20 | type: watsonx 21 | ols: 22 | defaultModel: ibm/granite-3-2-8b-instruct 23 | defaultProvider: watsonx 24 | introspectionEnabled: true 25 | deployment: 26 | replicas: 1 27 | disableAuth: false 28 | logLevel: DEBUG 29 | queryFilters: 30 | - name: foo_filter 31 | pattern: '\b(?:foo)\b' 32 | replaceWith: "deployment" 33 | - name: bar_filter 34 | pattern: '\b(?:bar)\b' 35 | replaceWith: "openshift" 36 | -------------------------------------------------------------------------------- /tests/config/valid_config_with_query_filter.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | llm_providers: 3 | - name: p1 4 | type: bam 5 | url: "https://url1" 6 | credentials_path: tests/config/secret/apitoken 7 | models: 8 | - name: m1 9 | url: "https://murl1" 10 | credentials_path: tests/config/secret/apitoken 11 | - name: m2 12 | url: "https://murl2" 13 | - name: p2 14 | type: bam 15 | url: "https://url2" 16 | models: 17 | - name: m1 18 | url: "https://murl1" 19 | - name: m2 20 | url: "https://murl2" 21 | ols_config: 22 | reference_content: 23 | product_docs_index_path: "tests/config" 24 | product_docs_index_id: product 25 | conversation_cache: 26 | type: memory 27 | memory: 28 | max_entries: 1000 29 | logging_config: 30 | logging_level: INFO 31 | query_filters: 32 | - name: foo_filter 33 | pattern: '\b(?:foo)\b' 34 | replace_with: "openshift" 35 | - name: bar_filter 36 | pattern: '\b(?:bar)\b' 37 | replace_with: "kubernetes" 38 | 39 | default_provider: p1 40 | default_model: m1 41 | dev_config: 42 | disable_tls: true 43 | disable_auth: true 44 | -------------------------------------------------------------------------------- /tests/config/operator_install/imagedigestmirrorset.yaml: -------------------------------------------------------------------------------- 1 | # The images are pulled from our redhat-user-workloads registry or staging registry while the production image is not yet available. 2 | kind: ImageDigestMirrorSet 3 | apiVersion: config.openshift.io/v1 4 | metadata: 5 | name: openshift-lightspeed-prod-to-ci 6 | spec: 7 | imageDigestMirrors: 8 | - source: registry.redhat.io/openshift-lightspeed-tech-preview/lightspeed-rhel9-operator 9 | mirrors: 10 | - quay.io/redhat-user-workloads/crt-nshift-lightspeed-tenant/ols/lightspeed-operator 11 | - source: registry.redhat.io/openshift-lightspeed-tech-preview/lightspeed-operator-bundle 12 | mirrors: 13 | - quay.io/redhat-user-workloads/crt-nshift-lightspeed-tenant/ols/bundle 14 | - source: registry.redhat.io/openshift-lightspeed-tech-preview/lightspeed-service-api-rhel9 15 | mirrors: 16 | - quay.io/redhat-user-workloads/crt-nshift-lightspeed-tenant/ols/lightspeed-service 17 | - source: registry.redhat.io/openshift-lightspeed-tech-preview/lightspeed-console-plugin-rhel9 18 | mirrors: 19 | - quay.io/redhat-user-workloads/crt-nshift-lightspeed-tenant/ols/lightspeed-console 20 | -------------------------------------------------------------------------------- /tests/config/operator_install/olsconfig.crd.azure_openai_introspection.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ols.openshift.io/v1alpha1 2 | kind: OLSConfig 3 | metadata: 4 | name: cluster 5 | labels: 6 | app.kubernetes.io/created-by: lightspeed-operator 7 | app.kubernetes.io/instance: olsconfig-sample 8 | app.kubernetes.io/managed-by: kustomize 9 | app.kubernetes.io/name: olsconfig 10 | app.kubernetes.io/part-of: lightspeed-operator 11 | spec: 12 | llm: 13 | providers: 14 | - name: azure_openai 15 | type: azure_openai 16 | credentialsSecretRef: 17 | name: llmcreds 18 | deploymentName: gpt-4o-mini 19 | models: 20 | - name: gpt-4o-mini 21 | url: 'https://ols-test.openai.azure.com/' 22 | ols: 23 | defaultModel: gpt-4o-mini 24 | defaultProvider: azure_openai 25 | introspectionEnabled: true 26 | deployment: 27 | replicas: 1 28 | disableAuth: false 29 | logLevel: DEBUG 30 | queryFilters: 31 | - name: foo_filter 32 | pattern: '\b(?:foo)\b' 33 | replaceWith: "deployment" 34 | - name: bar_filter 35 | pattern: '\b(?:bar)\b' 36 | replaceWith: "openshift" 37 | -------------------------------------------------------------------------------- /tests/mock_classes/mock_llama_index.py: -------------------------------------------------------------------------------- 1 | """Mocked (llama) index.""" 2 | 3 | from .mock_query_engine import MockQueryEngine 4 | from .mock_retrievers import MockRetriever 5 | 6 | 7 | class MockLlamaIndex: 8 | """Mocked (llama) index. 9 | 10 | Example usage in a test: 11 | 12 | @patch("ols.src.query_helpers.docs_summarizer.load_index_from_storage", new=MockLlamaIndex) 13 | def test_xyz(): 14 | 15 | or within test function or test method: 16 | with patch("ols.src.query_helpers.docs_summarizer.load_index_from_storage", new=MockLlamaIndex): 17 | some test steps 18 | 19 | Note: it is better to use context manager to patch llama index, compared to `patch` decorator 20 | """ # noqa: E501 21 | 22 | def __init__(self, *args, **kwargs): 23 | """Store all provided arguments for later usage.""" 24 | self.args = args 25 | self.kwargs = kwargs 26 | 27 | def as_query_engine(self, **kwargs): 28 | """Return mocked query engine.""" 29 | return MockQueryEngine(kwargs) 30 | 31 | def as_retriever(self, **kwargs): 32 | """Return mocked query engine.""" 33 | return MockRetriever(kwargs) 34 | -------------------------------------------------------------------------------- /ols/plugins/__init__.py: -------------------------------------------------------------------------------- 1 | """Plugins.""" 2 | 3 | import importlib.util 4 | import pathlib 5 | import sys 6 | 7 | 8 | def _import_modules_from_dir(dir_name: str) -> None: 9 | """Import all modules from the provided directory. 10 | 11 | The path is either relative to the file where this function is 12 | called or absolute. 13 | """ 14 | plugins_dir = pathlib.Path(__file__).parent.resolve() / dir_name 15 | sys.path.append(plugins_dir.as_posix()) 16 | 17 | # gather .py files except files starting with "__" 18 | modules = [ 19 | f 20 | for f in plugins_dir.iterdir() 21 | if f.suffix == ".py" and not f.stem.startswith("__") 22 | ] 23 | 24 | for module_name in modules: 25 | spec = importlib.util.spec_from_file_location( 26 | "module_name", module_name.as_posix() 27 | ) 28 | if spec is not None and spec.loader is not None: 29 | module = importlib.util.module_from_spec(spec) 30 | spec.loader.exec_module(module) 31 | else: 32 | raise Exception(f"Can not import module {module_name}") 33 | 34 | 35 | # TODO: move providers to plugins 36 | # TODO: move tools to plugins 37 | -------------------------------------------------------------------------------- /docs/css/theme/source/night.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Black theme for reveal.js. 3 | * 4 | * Copyright (C) 2011-2012 Hakim El Hattab, http://hakim.se 5 | */ 6 | 7 | 8 | // Default mixins and settings ----------------- 9 | @import "../template/mixins"; 10 | @import "../template/settings"; 11 | // --------------------------------------------- 12 | 13 | 14 | // Include theme-specific fonts 15 | @import url(https://fonts.googleapis.com/css?family=Montserrat:700); 16 | @import url(https://fonts.googleapis.com/css?family=Open+Sans:400,700,400italic,700italic); 17 | 18 | 19 | // Override theme settings (see ../template/settings.scss) 20 | $backgroundColor: #111; 21 | 22 | $mainFont: 'Open Sans', sans-serif; 23 | $linkColor: #e7ad52; 24 | $linkColorHover: lighten( $linkColor, 20% ); 25 | $headingFont: 'Montserrat', Impact, sans-serif; 26 | $headingTextShadow: none; 27 | $headingLetterSpacing: -0.03em; 28 | $headingTextTransform: none; 29 | $selectionBackgroundColor: #e7ad52; 30 | 31 | // Change text colors against light slide backgrounds 32 | @include light-bg-text-color(#222); 33 | 34 | 35 | // Theme template ------------------------------ 36 | @import "../template/theme"; 37 | // --------------------------------------------- -------------------------------------------------------------------------------- /ols/src/cache/cache_factory.py: -------------------------------------------------------------------------------- 1 | """Cache factory class.""" 2 | 3 | from ols import constants 4 | from ols.app.models.config import ConversationCacheConfig 5 | from ols.src.cache.cache import Cache 6 | from ols.src.cache.in_memory_cache import InMemoryCache 7 | from ols.src.cache.postgres_cache import PostgresCache 8 | 9 | 10 | class CacheFactory: 11 | """Cache factory class.""" 12 | 13 | @staticmethod 14 | def conversation_cache(config: ConversationCacheConfig) -> Cache: 15 | """Create an instance of Cache based on loaded configuration. 16 | 17 | Returns: 18 | An instance of `Cache` (either `PostgresCache` or `InMemoryCache`). 19 | """ 20 | match config.type: 21 | case constants.CACHE_TYPE_MEMORY: 22 | return InMemoryCache(config.memory) 23 | case constants.CACHE_TYPE_POSTGRES: 24 | return PostgresCache(config.postgres) 25 | case _: 26 | raise ValueError( 27 | f"Invalid cache type: {config.type}. " 28 | f"Use '{constants.CACHE_TYPE_POSTGRES}' or " 29 | f"'{constants.CACHE_TYPE_MEMORY}' options." 30 | ) 31 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | nav_order: 1 4 | --- 5 | # Road Core service 6 | 7 | ## Description 8 | 9 | Road Core Service (RCS) is an AI powered assistant that can run in any environment (including OpenShift) 10 | and provides answers to product questions using backend LLM services. Currently 11 | [OpenAI](https://openai.com/), [Azure 12 | OpenAI](https://azure.microsoft.com/en-us/products/ai-services/openai-service), 13 | [OpenShift 14 | AI](https://www.redhat.com/en/technologies/cloud-computing/openshift/openshift-ai), 15 | [RHEL 16 | AI](https://www.redhat.com/en/technologies/linux-platforms/enterprise-linux/ai), 17 | and [Watsonx](https://www.ibm.com/watsonx) are officially supported as 18 | backends. Other providers, even ones that are not fully supported, can be used 19 | as well. For example, it is possible to use BAM (IBM's research environment). 20 | It is also possible to run [InstructLab](https://instructlab.ai/) locally, 21 | configure model, and connect to it. 22 | 23 | 24 | 25 | ## Architecture 26 | 27 | ![architecture diagram](architecture_diagram.png) 28 | 29 | ## Quick howto 30 | 31 | ## Developer info 32 | 33 | 1. [Service configuration class hierarchy](service_configuration_classes.md) 34 | -------------------------------------------------------------------------------- /examples/rcsconfig-pgvector.yaml: -------------------------------------------------------------------------------- 1 | llm_providers: 2 | - name: ollama 3 | type: openai 4 | url: "http://localhost:11434/v1/" 5 | models: 6 | - name: 'llama3.2' 7 | ols_config: 8 | reference_content: 9 | vector_store_type: postgres 10 | product_docs_index_id: product_index 11 | embeddings_model_path: "./embeddings_model" 12 | postgres: 13 | host: localhost 14 | port: 15432 15 | dbname: postgres 16 | user: postgres 17 | password_path: /var/tmp/secrets/postgres.txt 18 | conversation_cache: 19 | type: memory 20 | memory: 21 | max_entries: 1000 22 | logging_config: 23 | app_log_level: info 24 | lib_log_level: warning 25 | uvicorn_log_level: info 26 | suppress_metrics_in_log: false 27 | suppress_auth_checks_warning_in_log: false 28 | default_provider: ollama 29 | default_model: 'llama3.2' 30 | expire_llm_is_ready_persistent_state: -1 31 | enable_event_stream_format: true 32 | dev_config: 33 | # config options specific to dev environment - launching OLS in local 34 | enable_dev_ui: true 35 | disable_auth: true 36 | disable_tls: true 37 | pyroscope_url: "https://pyroscope.pyroscope.svc.cluster.local:4040" 38 | enable_system_prompt_override: true 39 | -------------------------------------------------------------------------------- /tests/mock_classes/mock_query_engine.py: -------------------------------------------------------------------------------- 1 | """Mocked query engine.""" 2 | 3 | from .mock_summary import MockSummary 4 | 5 | 6 | class Node: 7 | """Node containing source node metadata.""" 8 | 9 | def __init__(self, doc_url, doc_title): 10 | """Initialize doc_url metadata.""" 11 | self.metadata = {"doc_url": doc_url, "doc_title": doc_title} 12 | 13 | 14 | class SourceNode: 15 | """Node containing one reference to document.""" 16 | 17 | def __init__(self, doc_url, doc_title): 18 | """Initialize sub-node with metadata.""" 19 | self.node = Node(doc_url, doc_title) 20 | 21 | 22 | class MockQueryEngine: 23 | """Mocked query engine.""" 24 | 25 | def __init__(self, *args, **kwargs): 26 | """Store all provided arguments for later usage.""" 27 | self.args = args 28 | self.kwargs = kwargs 29 | 30 | def query(self, query): 31 | """Return summary for given query.""" 32 | # fill-in some documents for more realistic tests 33 | nodes = [ 34 | SourceNode("/docs/test.", "Docs Test"), 35 | SourceNode("/known-bugs.", "Known Bugs"), 36 | SourceNode("/errata.", "Errata"), 37 | ] 38 | return MockSummary(query, nodes) 39 | -------------------------------------------------------------------------------- /tests/unit/utils/test_logging.py: -------------------------------------------------------------------------------- 1 | """Unit tests for configure_logging function.""" 2 | 3 | import logging 4 | 5 | from ols.utils.logging_configurator import LoggingConfig, configure_logging 6 | 7 | 8 | def test_configure_app_logging(caplog): 9 | """Test configure_logging function.""" 10 | logging_config = LoggingConfig(app_log_level="info") 11 | 12 | configure_logging(logging_config) 13 | logger = logging.getLogger("ols") 14 | logger.handlers = [caplog.handler] # add caplog handler to logger 15 | logger.debug("debug msg") 16 | logger.info("info msg") 17 | 18 | captured_out = caplog.text 19 | assert "debug msg" not in captured_out 20 | assert "info msg" in captured_out 21 | 22 | 23 | def test_configure_libs_logging(caplog): 24 | """Test configure_logging function for root/libs logger.""" 25 | logging_config = LoggingConfig(lib_log_level="info") 26 | 27 | configure_logging(logging_config) 28 | logger = logging.getLogger() 29 | logger.handlers = [caplog.handler] # add caplog handler to logger 30 | logger.debug("debug msg") 31 | logger.info("info msg") 32 | 33 | captured_out = caplog.text 34 | assert "debug msg" not in captured_out 35 | assert "info msg" in captured_out 36 | -------------------------------------------------------------------------------- /docs/css/theme/source/serif.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * A simple theme for reveal.js presentations, similar 3 | * to the default theme. The accent color is brown. 4 | * 5 | * This theme is Copyright (C) 2012-2013 Owen Versteeg, http://owenversteeg.com - it is MIT licensed. 6 | */ 7 | 8 | 9 | // Default mixins and settings ----------------- 10 | @import "../template/mixins"; 11 | @import "../template/settings"; 12 | // --------------------------------------------- 13 | 14 | 15 | 16 | // Override theme settings (see ../template/settings.scss) 17 | $mainFont: 'Palatino Linotype', 'Book Antiqua', Palatino, FreeSerif, serif; 18 | $mainColor: #000; 19 | $headingFont: 'Palatino Linotype', 'Book Antiqua', Palatino, FreeSerif, serif; 20 | $headingColor: #383D3D; 21 | $headingTextShadow: none; 22 | $headingTextTransform: none; 23 | $backgroundColor: #F0F1EB; 24 | $linkColor: #51483D; 25 | $linkColorHover: lighten( $linkColor, 20% ); 26 | $selectionBackgroundColor: #26351C; 27 | 28 | .reveal a { 29 | line-height: 1.3em; 30 | } 31 | 32 | // Change text colors against dark slide backgrounds 33 | @include dark-bg-text-color(#fff); 34 | 35 | 36 | // Theme template ------------------------------ 37 | @import "../template/theme"; 38 | // --------------------------------------------- 39 | -------------------------------------------------------------------------------- /docs/css/theme/template/exposer.scss: -------------------------------------------------------------------------------- 1 | // Exposes theme's variables for easy re-use in CSS for plugin authors 2 | 3 | :root { 4 | --r-background-color: #{$backgroundColor}; 5 | --r-main-font: #{$mainFont}; 6 | --r-main-font-size: #{$mainFontSize}; 7 | --r-main-color: #{$mainColor}; 8 | --r-block-margin: #{$blockMargin}; 9 | --r-heading-margin: #{$headingMargin}; 10 | --r-heading-font: #{$headingFont}; 11 | --r-heading-color: #{$headingColor}; 12 | --r-heading-line-height: #{$headingLineHeight}; 13 | --r-heading-letter-spacing: #{$headingLetterSpacing}; 14 | --r-heading-text-transform: #{$headingTextTransform}; 15 | --r-heading-text-shadow: #{$headingTextShadow}; 16 | --r-heading-font-weight: #{$headingFontWeight}; 17 | --r-heading1-text-shadow: #{$heading1TextShadow}; 18 | --r-heading1-size: #{$heading1Size}; 19 | --r-heading2-size: #{$heading2Size}; 20 | --r-heading3-size: #{$heading3Size}; 21 | --r-heading4-size: #{$heading4Size}; 22 | --r-code-font: #{$codeFont}; 23 | --r-link-color: #{$linkColor}; 24 | --r-link-color-dark: #{darken($linkColor , 15% )}; 25 | --r-link-color-hover: #{$linkColorHover}; 26 | --r-selection-background-color: #{$selectionBackgroundColor}; 27 | --r-selection-color: #{$selectionColor}; 28 | } 29 | -------------------------------------------------------------------------------- /docs/css/theme/template/settings.scss: -------------------------------------------------------------------------------- 1 | // Base settings for all themes that can optionally be 2 | // overridden by the super-theme 3 | 4 | // Background of the presentation 5 | $backgroundColor: #2b2b2b; 6 | 7 | // Primary/body text 8 | $mainFont: 'Lato', sans-serif; 9 | $mainFontSize: 40px; 10 | $mainColor: #eee; 11 | 12 | // Vertical spacing between blocks of text 13 | $blockMargin: 20px; 14 | 15 | // Headings 16 | $headingMargin: 0 0 $blockMargin 0; 17 | $headingFont: 'League Gothic', Impact, sans-serif; 18 | $headingColor: #eee; 19 | $headingLineHeight: 1.2; 20 | $headingLetterSpacing: normal; 21 | $headingTextTransform: uppercase; 22 | $headingTextShadow: none; 23 | $headingFontWeight: normal; 24 | $heading1TextShadow: $headingTextShadow; 25 | 26 | $heading1Size: 3.77em; 27 | $heading2Size: 2.11em; 28 | $heading3Size: 1.55em; 29 | $heading4Size: 1.00em; 30 | 31 | $codeFont: monospace; 32 | 33 | // Links and actions 34 | $linkColor: #13DAEC; 35 | $linkColorHover: lighten( $linkColor, 20% ); 36 | 37 | // Text selection 38 | $selectionBackgroundColor: #FF5E99; 39 | $selectionColor: #fff; 40 | 41 | // Generates the presentation background, can be overridden 42 | // to return a background image or gradient 43 | @mixin bodyBackground() { 44 | background: $backgroundColor; 45 | } 46 | -------------------------------------------------------------------------------- /docs/plugin/highlight/monokai.css: -------------------------------------------------------------------------------- 1 | /* 2 | Monokai style - ported by Luigi Maselli - http://grigio.org 3 | */ 4 | 5 | .hljs { 6 | display: block; 7 | overflow-x: auto; 8 | padding: 0.5em; 9 | background: #272822; 10 | color: #ddd; 11 | } 12 | 13 | .hljs-tag, 14 | .hljs-keyword, 15 | .hljs-selector-tag, 16 | .hljs-literal, 17 | .hljs-strong, 18 | .hljs-name { 19 | color: #f92672; 20 | } 21 | 22 | .hljs-code { 23 | color: #66d9ef; 24 | } 25 | 26 | .hljs-class .hljs-title { 27 | color: white; 28 | } 29 | 30 | .hljs-attribute, 31 | .hljs-symbol, 32 | .hljs-regexp, 33 | .hljs-link { 34 | color: #bf79db; 35 | } 36 | 37 | .hljs-string, 38 | .hljs-bullet, 39 | .hljs-subst, 40 | .hljs-title, 41 | .hljs-section, 42 | .hljs-emphasis, 43 | .hljs-type, 44 | .hljs-built_in, 45 | .hljs-builtin-name, 46 | .hljs-selector-attr, 47 | .hljs-selector-pseudo, 48 | .hljs-addition, 49 | .hljs-variable, 50 | .hljs-template-tag, 51 | .hljs-template-variable { 52 | color: #a6e22e; 53 | } 54 | 55 | .hljs-comment, 56 | .hljs-quote, 57 | .hljs-deletion, 58 | .hljs-meta { 59 | color: #75715e; 60 | } 61 | 62 | .hljs-keyword, 63 | .hljs-selector-tag, 64 | .hljs-literal, 65 | .hljs-doctag, 66 | .hljs-title, 67 | .hljs-section, 68 | .hljs-type, 69 | .hljs-selector-id { 70 | font-weight: bold; 71 | } 72 | -------------------------------------------------------------------------------- /scripts/evaluation/utils/plot.py: -------------------------------------------------------------------------------- 1 | """Plot score for evaluation.""" 2 | 3 | import matplotlib.pyplot as plt 4 | from matplotlib.colors import BASE_COLORS 5 | 6 | 7 | def plot_score(results_df, score_name, out_file_path=None): 8 | """Plot score.""" 9 | _, ax = plt.subplots(figsize=(14, 8)) 10 | ax.set_xlabel(score_name) 11 | ax.set_xlim(0, 1) 12 | 13 | ax.axvline(x=0.25, linewidth=2, color="red") 14 | ax.axvline(x=0.5, linewidth=2, color="orange") 15 | ax.axvline(x=0.75, linewidth=2, color="green") 16 | 17 | ax.axvspan(0, 0.25, facecolor="gainsboro") 18 | ax.axvspan(0.25, 0.5, facecolor="mistyrose") 19 | ax.axvspan(0.5, 0.75, facecolor="lightyellow") 20 | ax.axvspan(0.75, 1.0, facecolor="lightgreen") 21 | 22 | ax.grid(True) 23 | 24 | labels = results_df.columns 25 | bplot = ax.boxplot( 26 | results_df.fillna(0), 27 | patch_artist=True, 28 | sym=".", 29 | widths=0.5, 30 | # tick_labels=labels, 31 | labels=labels, 32 | vert=False, 33 | ) 34 | colors = list(BASE_COLORS.keys())[: len(labels)] 35 | for patch, color in zip(bplot["boxes"], colors): 36 | patch.set_facecolor(color) 37 | 38 | plt.yticks(rotation=45) 39 | 40 | if out_file_path: 41 | plt.savefig(out_file_path) 42 | -------------------------------------------------------------------------------- /docs/js/utils/loader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Loads a JavaScript file from the given URL and executes it. 3 | * 4 | * @param {string} url Address of the .js file to load 5 | * @param {function} callback Method to invoke when the script 6 | * has loaded and executed 7 | */ 8 | export const loadScript = ( url, callback ) => { 9 | 10 | const script = document.createElement( 'script' ); 11 | script.type = 'text/javascript'; 12 | script.async = false; 13 | script.defer = false; 14 | script.src = url; 15 | 16 | if( typeof callback === 'function' ) { 17 | 18 | // Success callback 19 | script.onload = script.onreadystatechange = event => { 20 | if( event.type === 'load' || /loaded|complete/.test( script.readyState ) ) { 21 | 22 | // Kill event listeners 23 | script.onload = script.onreadystatechange = script.onerror = null; 24 | 25 | callback(); 26 | 27 | } 28 | }; 29 | 30 | // Error callback 31 | script.onerror = err => { 32 | 33 | // Kill event listeners 34 | script.onload = script.onreadystatechange = script.onerror = null; 35 | 36 | callback( new Error( 'Failed loading script: ' + script.src + '\n' + err ) ); 37 | 38 | }; 39 | 40 | } 41 | 42 | // Append the script at the end of 43 | const head = document.querySelector( 'head' ); 44 | head.insertBefore( script, head.lastChild ); 45 | 46 | } -------------------------------------------------------------------------------- /docs/plugin/highlight/zenburn.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Zenburn style from voldmar.ru (c) Vladimir Epifanov 4 | based on dark.css by Ivan Sagalaev 5 | 6 | */ 7 | 8 | .hljs { 9 | display: block; 10 | overflow-x: auto; 11 | padding: 0.5em; 12 | background: #3f3f3f; 13 | color: #dcdcdc; 14 | } 15 | 16 | .hljs-keyword, 17 | .hljs-selector-tag, 18 | .hljs-tag { 19 | color: #e3ceab; 20 | } 21 | 22 | .hljs-template-tag { 23 | color: #dcdcdc; 24 | } 25 | 26 | .hljs-number { 27 | color: #8cd0d3; 28 | } 29 | 30 | .hljs-variable, 31 | .hljs-template-variable, 32 | .hljs-attribute { 33 | color: #efdcbc; 34 | } 35 | 36 | .hljs-literal { 37 | color: #efefaf; 38 | } 39 | 40 | .hljs-subst { 41 | color: #8f8f8f; 42 | } 43 | 44 | .hljs-title, 45 | .hljs-name, 46 | .hljs-selector-id, 47 | .hljs-selector-class, 48 | .hljs-section, 49 | .hljs-type { 50 | color: #efef8f; 51 | } 52 | 53 | .hljs-symbol, 54 | .hljs-bullet, 55 | .hljs-link { 56 | color: #dca3a3; 57 | } 58 | 59 | .hljs-deletion, 60 | .hljs-string, 61 | .hljs-built_in, 62 | .hljs-builtin-name { 63 | color: #cc9393; 64 | } 65 | 66 | .hljs-addition, 67 | .hljs-comment, 68 | .hljs-quote, 69 | .hljs-meta { 70 | color: #7f9f7f; 71 | } 72 | 73 | 74 | .hljs-emphasis { 75 | font-style: italic; 76 | } 77 | 78 | .hljs-strong { 79 | font-weight: bold; 80 | } 81 | -------------------------------------------------------------------------------- /ols/src/llms/providers/registry.py: -------------------------------------------------------------------------------- 1 | """LLM providers registry.""" 2 | 3 | import logging 4 | from collections.abc import Callable 5 | from typing import ClassVar 6 | 7 | from ols.src.llms.providers.provider import LLMProvider 8 | 9 | logger = logging.getLogger(__name__) 10 | 11 | 12 | class LLMProvidersRegistry: 13 | """Registry for LLM providers.""" 14 | 15 | llm_providers: ClassVar = {} 16 | 17 | @classmethod 18 | def register(cls, provider_type: str, llm_provider: Callable) -> None: 19 | """Register LLM provider.""" 20 | if not issubclass(llm_provider, LLMProvider): 21 | raise TypeError( 22 | f"LLMProvider subclass required, got '{type(llm_provider)}'" 23 | ) 24 | cls.llm_providers[provider_type] = llm_provider 25 | logger.debug("LLM provider '%s' registered", provider_type) 26 | 27 | 28 | def register_llm_provider_as(provider_type: str) -> Callable: 29 | """Register LLM provider in the `LLMProvidersRegistry`. 30 | 31 | Example: 32 | ```python 33 | @register_llm_provider_as("openai") 34 | class OpenAI(LLMProvider): 35 | pass 36 | ``` 37 | """ 38 | 39 | def decorator(cls: LLMProvider) -> LLMProvider: 40 | LLMProvidersRegistry.register(provider_type, cls) 41 | return cls 42 | 43 | return decorator 44 | -------------------------------------------------------------------------------- /docs/css/theme/source/league.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * League theme for reveal.js. 3 | * 4 | * This was the default theme pre-3.0.0. 5 | * 6 | * Copyright (C) 2011-2012 Hakim El Hattab, http://hakim.se 7 | */ 8 | 9 | 10 | // Default mixins and settings ----------------- 11 | @import "../template/mixins"; 12 | @import "../template/settings"; 13 | // --------------------------------------------- 14 | 15 | 16 | 17 | // Include theme-specific fonts 18 | @import url(./fonts/league-gothic/league-gothic.css); 19 | @import url(https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic); 20 | 21 | // Override theme settings (see ../template/settings.scss) 22 | $headingTextShadow: 0px 0px 6px rgba(0,0,0,0.2); 23 | $heading1TextShadow: 0 1px 0 #ccc, 0 2px 0 #c9c9c9, 0 3px 0 #bbb, 0 4px 0 #b9b9b9, 0 5px 0 #aaa, 0 6px 1px rgba(0,0,0,.1), 0 0 5px rgba(0,0,0,.1), 0 1px 3px rgba(0,0,0,.3), 0 3px 5px rgba(0,0,0,.2), 0 5px 10px rgba(0,0,0,.25), 0 20px 20px rgba(0,0,0,.15); 24 | 25 | // Background generator 26 | @mixin bodyBackground() { 27 | @include radial-gradient( rgba(28,30,32,1), rgba(85,90,95,1) ); 28 | } 29 | 30 | // Change text colors against light slide backgrounds 31 | @include light-bg-text-color(#222); 32 | 33 | 34 | // Theme template ------------------------------ 35 | @import "../template/theme"; 36 | // --------------------------------------------- 37 | -------------------------------------------------------------------------------- /tests/unit/app/test_routers.py: -------------------------------------------------------------------------------- 1 | """Unit tests for routers.py.""" 2 | 3 | from ols import config 4 | 5 | # needs to be setup there before is_user_authorized is imported 6 | config.ols_config.authentication_config.module = "k8s" 7 | 8 | from ols.app.endpoints import ( # noqa:E402 9 | authorized, 10 | conversations, 11 | feedback, 12 | health, 13 | ols, 14 | streaming_ols, 15 | ) 16 | from ols.app.metrics import metrics # noqa:E402 17 | from ols.app.routers import include_routers # noqa:E402 18 | 19 | 20 | class MockFastAPI: 21 | """Mock class for FastAPI.""" 22 | 23 | def __init__(self): 24 | """Initialize mock class.""" 25 | self.routers = [] 26 | 27 | def include_router(self, router, prefix=None): 28 | """Register new router.""" 29 | self.routers.append(router) 30 | 31 | 32 | def test_include_routers(): 33 | """Test the function include_routers.""" 34 | app = MockFastAPI() 35 | include_routers(app) 36 | 37 | # are all routers added? 38 | assert len(app.routers) == 7 39 | assert authorized.router in app.routers 40 | assert conversations.router in app.routers 41 | assert feedback.router in app.routers 42 | assert health.router in app.routers 43 | assert metrics.router in app.routers 44 | assert ols.router in app.routers 45 | assert streaming_ols.router in app.routers 46 | -------------------------------------------------------------------------------- /ols/utils/logging_configurator.py: -------------------------------------------------------------------------------- 1 | """Logging utilities.""" 2 | 3 | import logging.config 4 | 5 | from ols.app.models.config import LoggingConfig 6 | 7 | 8 | def configure_logging(logging_config: LoggingConfig) -> None: 9 | """Configure application logging according to the configuration.""" 10 | log_msg_fmt = ( 11 | "%(asctime)s [%(name)s:%(filename)s:%(lineno)d] %(levelname)s: %(message)s" 12 | ) 13 | log_config_dict = { 14 | "version": 1, 15 | "disable_existing_loggers": False, 16 | "loggers": { 17 | # needs to be root (and not ""), otherwise external libs 18 | # won't be affected 19 | "root": { 20 | "level": logging_config.lib_log_level, 21 | "handlers": ["console"], 22 | }, 23 | "ols": { 24 | "level": logging_config.app_log_level, 25 | "handlers": ["console"], 26 | "propagate": False, # don't propagate to root logger 27 | }, 28 | }, 29 | "handlers": { 30 | "console": { 31 | "class": "logging.StreamHandler", 32 | "formatter": "standard", 33 | }, 34 | }, 35 | "formatters": { 36 | "standard": {"format": log_msg_fmt}, 37 | }, 38 | } 39 | 40 | logging.config.dictConfig(log_config_dict) 41 | -------------------------------------------------------------------------------- /docs/css/theme/source/white.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * White theme for reveal.js. This is the opposite of the 'black' theme. 3 | * 4 | * By Hakim El Hattab, http://hakim.se 5 | */ 6 | 7 | 8 | // Default mixins and settings ----------------- 9 | @import "../template/mixins"; 10 | @import "../template/settings"; 11 | // --------------------------------------------- 12 | 13 | 14 | // Include theme-specific fonts 15 | @import url(./fonts/source-sans-pro/source-sans-pro.css); 16 | 17 | 18 | // Override theme settings (see ../template/settings.scss) 19 | $backgroundColor: #fff; 20 | 21 | $mainColor: #222; 22 | $headingColor: #222; 23 | 24 | $mainFontSize: 42px; 25 | $mainFont: 'Source Sans Pro', Helvetica, sans-serif; 26 | $headingFont: 'Source Sans Pro', Helvetica, sans-serif; 27 | $headingTextShadow: none; 28 | $headingLetterSpacing: normal; 29 | $headingTextTransform: uppercase; 30 | $headingFontWeight: 600; 31 | $linkColor: #2a76dd; 32 | $linkColorHover: lighten( $linkColor, 15% ); 33 | $selectionBackgroundColor: lighten( $linkColor, 25% ); 34 | 35 | $heading1Size: 2.5em; 36 | $heading2Size: 1.6em; 37 | $heading3Size: 1.3em; 38 | $heading4Size: 1.0em; 39 | 40 | // Change text colors against dark slide backgrounds 41 | @include dark-bg-text-color(#fff); 42 | 43 | 44 | // Theme template ------------------------------ 45 | @import "../template/theme"; 46 | // --------------------------------------------- 47 | -------------------------------------------------------------------------------- /docs/css/theme/source/black.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Black theme for reveal.js. This is the opposite of the 'white' theme. 3 | * 4 | * By Hakim El Hattab, http://hakim.se 5 | */ 6 | 7 | 8 | // Default mixins and settings ----------------- 9 | @import "../template/mixins"; 10 | @import "../template/settings"; 11 | // --------------------------------------------- 12 | 13 | 14 | // Include theme-specific fonts 15 | @import url(./fonts/source-sans-pro/source-sans-pro.css); 16 | 17 | 18 | // Override theme settings (see ../template/settings.scss) 19 | $backgroundColor: #191919; 20 | 21 | $mainColor: #fff; 22 | $headingColor: #fff; 23 | 24 | $mainFontSize: 42px; 25 | $mainFont: 'Source Sans Pro', Helvetica, sans-serif; 26 | $headingFont: 'Source Sans Pro', Helvetica, sans-serif; 27 | $headingTextShadow: none; 28 | $headingLetterSpacing: normal; 29 | $headingTextTransform: uppercase; 30 | $headingFontWeight: 600; 31 | $linkColor: #42affa; 32 | $linkColorHover: lighten( $linkColor, 15% ); 33 | $selectionBackgroundColor: lighten( $linkColor, 25% ); 34 | 35 | $heading1Size: 2.5em; 36 | $heading2Size: 1.6em; 37 | $heading3Size: 1.3em; 38 | $heading4Size: 1.0em; 39 | 40 | // Change text colors against light slide backgrounds 41 | @include light-bg-text-color(#222); 42 | 43 | 44 | // Theme template ------------------------------ 45 | @import "../template/theme"; 46 | // --------------------------------------------- 47 | -------------------------------------------------------------------------------- /ols/utils/environments.py: -------------------------------------------------------------------------------- 1 | """Environment variables handling.""" 2 | 3 | import os 4 | import tempfile 5 | 6 | import ols.app.models.config as config_model 7 | 8 | 9 | def configure_gradio_ui_envs() -> None: 10 | """Configure GradioUI framework environment variables.""" 11 | # disable Gradio analytics, which calls home to https://api.gradio.app 12 | os.environ["GRADIO_ANALYTICS_ENABLED"] = "false" 13 | 14 | # Setup config directory for Matplotlib. It will be used to store info 15 | # about fonts (usually one JSON file) and it really is just temporary 16 | # storage that can be deleted at any time and recreated later. 17 | # Fixes: https://issues.redhat.com/browse/OLS-301 18 | tempdir = os.path.join(tempfile.gettempdir(), "matplotlib") 19 | os.environ["MPLCONFIGDIR"] = tempdir 20 | 21 | 22 | def configure_hugging_face_envs(ols_config: config_model.OLSConfig) -> None: 23 | """Configure HuggingFace library environment variables.""" 24 | if ( 25 | ols_config 26 | and hasattr(ols_config, "reference_content") 27 | and hasattr(ols_config.reference_content, "embeddings_model_path") 28 | and ols_config.reference_content.embeddings_model_path 29 | ): 30 | os.environ["TRANSFORMERS_CACHE"] = str( 31 | ols_config.reference_content.embeddings_model_path 32 | ) 33 | os.environ["TRANSFORMERS_OFFLINE"] = "1" 34 | -------------------------------------------------------------------------------- /docs/Gemfile: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); 2 | # you may not use this file except in compliance with the License. 3 | # You may obtain a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, 9 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | # See the License for the specific language governing permissions and 11 | # limitations under the License. 12 | 13 | source "https://rubygems.org" 14 | 15 | # Hello! This is where you manage which Jekyll version is used to run. 16 | # When you want to use a different version, change it below, save the 17 | # file and run `bundle install`. Run Jekyll with `bundle exec`, like so: 18 | # 19 | # bundle exec jekyll serve 20 | # 21 | # This will help ensure the proper Jekyll version is running. 22 | # Happy Jekylling! 23 | # gem "jekyll", "~> 3.8.5" 24 | 25 | # If you want to use GitHub Pages, remove the "gem "jekyll"" above and 26 | # uncomment the line below. To upgrade, run `bundle update github-pages`. 27 | gem "github-pages", "~> 204", group: :jekyll_plugins 28 | 29 | # If you have any plugins, put them here! 30 | group :jekyll_plugins do 31 | gem "jekyll-feed", "~> 0.6" 32 | gem "jekyll-remote-theme", "~> 0.4.1" 33 | end 34 | -------------------------------------------------------------------------------- /tests/unit/quota/test_quota_exceed_error.py: -------------------------------------------------------------------------------- 1 | """Unit tests for QuotaExceedError class.""" 2 | 3 | import pytest 4 | 5 | from ols.src.quota.quota_exceed_error import QuotaExceedError 6 | 7 | 8 | def test_quota_exceed_error_constructor(): 9 | """Test the QuotaExceedError constructor.""" 10 | expected = "User 1234 has 100 tokens, but 1000 tokens are needed" 11 | with pytest.raises(QuotaExceedError, match=expected): 12 | raise QuotaExceedError("1234", "u", 100, 1000) 13 | 14 | expected = "Cluster has 100 tokens, but 1000 tokens are needed" 15 | with pytest.raises(QuotaExceedError, match=expected): 16 | raise QuotaExceedError("", "c", 100, 1000) 17 | 18 | expected = "Unknown subject 1234 has 100 tokens, but 1000 tokens are needed" 19 | with pytest.raises(QuotaExceedError, match=expected): 20 | raise QuotaExceedError("1234", "?", 100, 1000) 21 | 22 | expected = "User 1234 has no available tokens" 23 | with pytest.raises(QuotaExceedError, match=expected): 24 | raise QuotaExceedError("1234", "u", 0, 0) 25 | 26 | expected = "Cluster has no available tokens" 27 | with pytest.raises(QuotaExceedError, match=expected): 28 | raise QuotaExceedError("", "c", 0, 0) 29 | 30 | expected = "Unknown subject 1234 has no available tokens" 31 | with pytest.raises(QuotaExceedError, match=expected): 32 | raise QuotaExceedError("1234", "?", 0, 0) 33 | -------------------------------------------------------------------------------- /docs/css/theme/source/simple.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * A simple theme for reveal.js presentations, similar 3 | * to the default theme. The accent color is darkblue. 4 | * 5 | * This theme is Copyright (C) 2012 Owen Versteeg, https://github.com/StereotypicalApps. It is MIT licensed. 6 | * reveal.js is Copyright (C) 2011-2012 Hakim El Hattab, http://hakim.se 7 | */ 8 | 9 | 10 | // Default mixins and settings ----------------- 11 | @import "../template/mixins"; 12 | @import "../template/settings"; 13 | // --------------------------------------------- 14 | 15 | 16 | 17 | // Include theme-specific fonts 18 | @import url(https://fonts.googleapis.com/css?family=News+Cycle:400,700); 19 | @import url(https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic); 20 | 21 | 22 | // Override theme settings (see ../template/settings.scss) 23 | $mainFont: 'Lato', sans-serif; 24 | $mainColor: #000; 25 | $headingFont: 'News Cycle', Impact, sans-serif; 26 | $headingColor: #000; 27 | $headingTextShadow: none; 28 | $headingTextTransform: none; 29 | $backgroundColor: #fff; 30 | $linkColor: #00008B; 31 | $linkColorHover: lighten( $linkColor, 20% ); 32 | $selectionBackgroundColor: rgba(0, 0, 0, 0.99); 33 | 34 | // Change text colors against dark slide backgrounds 35 | @include dark-bg-text-color(#fff); 36 | 37 | 38 | // Theme template ------------------------------ 39 | @import "../template/theme"; 40 | // --------------------------------------------- -------------------------------------------------------------------------------- /tests/config/operator_install/olsconfig.crd.azure_openai.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ols.openshift.io/v1alpha1 2 | kind: OLSConfig 3 | metadata: 4 | name: cluster 5 | labels: 6 | app.kubernetes.io/created-by: lightspeed-operator 7 | app.kubernetes.io/instance: olsconfig-sample 8 | app.kubernetes.io/managed-by: kustomize 9 | app.kubernetes.io/name: olsconfig 10 | app.kubernetes.io/part-of: lightspeed-operator 11 | spec: 12 | llm: 13 | providers: 14 | - name: azure_openai 15 | type: azure_openai 16 | credentialsSecretRef: 17 | name: llmcreds 18 | deploymentName: gpt-4o-mini 19 | models: 20 | - name: gpt-4o-mini 21 | url: 'https://ols-test.openai.azure.com/' 22 | - name: azure_openai_with_entra_id 23 | type: azure_openai 24 | credentialsSecretRef: 25 | name: azure-entra-id 26 | deploymentName: gpt-4o-mini 27 | models: 28 | - name: gpt-4o-mini 29 | url: 'https://ols-test.openai.azure.com/' 30 | ols: 31 | defaultModel: gpt-4o-mini 32 | defaultProvider: azure_openai 33 | deployment: 34 | replicas: 1 35 | disableAuth: false 36 | logLevel: DEBUG 37 | queryFilters: 38 | - name: foo_filter 39 | pattern: '\b(?:foo)\b' 40 | replaceWith: "deployment" 41 | - name: bar_filter 42 | pattern: '\b(?:bar)\b' 43 | replaceWith: "openshift" 44 | -------------------------------------------------------------------------------- /tests/e2e/evaluation/test_evaluation.py: -------------------------------------------------------------------------------- 1 | """Model response and model evaluation tests.""" 2 | 3 | # we add new attributes into pytest instance, which is not recognized 4 | # properly by linters 5 | # pyright: reportAttributeAccessIssue=false 6 | 7 | import os 8 | from argparse import Namespace 9 | 10 | import pytest 11 | 12 | from scripts.evaluation.response_evaluation import ResponseEvaluation 13 | 14 | 15 | def test_model_response(request) -> None: 16 | """Evaluate model response.""" 17 | args = Namespace(**vars(request.config.option)) 18 | args.eval_provider_model_id = [] 19 | providers = os.getenv("PROVIDER", "").split() 20 | models = os.getenv("MODEL", "").split() 21 | for i, provider in enumerate(providers): 22 | args.eval_provider_model_id.append(f"{provider}+{models[i]}") 23 | args.eval_type = "consistency" 24 | 25 | val_success_flag = ResponseEvaluation(args, pytest.client).validate_response() 26 | # If flag is False, then response(s) is not consistent, 27 | # And score is more than cut-off score. 28 | # Please check eval_result/response_evaluation_* csv file in artifact folder or 29 | # Check the log to find out exact file path. 30 | assert val_success_flag 31 | 32 | 33 | def test_model_evaluation(request) -> None: 34 | """Evaluate model.""" 35 | # TODO: Use this to assert. 36 | ResponseEvaluation(request.config.option, pytest.client).evaluate_models() 37 | -------------------------------------------------------------------------------- /tests/mock_classes/mock_llm_loader.py: -------------------------------------------------------------------------------- 1 | """Mock for LLMLoader to be used in unit tests.""" 2 | 3 | from types import SimpleNamespace 4 | 5 | from langchain_core.runnables import Runnable 6 | 7 | 8 | class MockLLMLoader(Runnable): 9 | """Mock for LLMLoader.""" 10 | 11 | def __init__(self, llm=None): 12 | """Store the selected LLM into object's attribute.""" 13 | if llm is None: 14 | llm = SimpleNamespace() 15 | llm.provider = "mock_provider" 16 | llm.model = "mock_model" 17 | self.llm = llm 18 | 19 | def invoke(self, *args, **kwargs): 20 | """Mock model invoke.""" 21 | return args[0].messages[1] 22 | 23 | @classmethod 24 | def bind_tools(cls, *args, **kwargs): 25 | """Mock bind tools.""" 26 | return cls() 27 | 28 | async def astream(self, *args, **kwargs): 29 | """Return query result.""" 30 | # yield input prompt/user query 31 | yield args[0][1].content 32 | 33 | 34 | def mock_llm_loader(llm=None, expected_params=None): 35 | """Construct mock for load_llm.""" 36 | 37 | def loader(*args, **kwargs): 38 | # if expected params are provided, check if (mocked) LLM loader 39 | # was called with expected parameters 40 | if expected_params is not None: 41 | assert expected_params == args, expected_params 42 | return MockLLMLoader(llm) 43 | 44 | return loader 45 | -------------------------------------------------------------------------------- /tests/mock_classes/mock_llm_chain.py: -------------------------------------------------------------------------------- 1 | """Mock for LLMChain to be used in unit tests.""" 2 | 3 | 4 | def mock_llm_chain(retval): 5 | """Construct mock for LLMChain.""" 6 | 7 | class MockLLMChain: 8 | """Mock LLMChain class for testing. 9 | 10 | Example usage in a test: 11 | 12 | from tests.mock_classes.llm_chain import mock_llm_chain 13 | ml = mock_llm_chain({"text": "default"}) 14 | 15 | @patch("ols.src.query_helpers.yes_no_classifier.LLMChain", new=ml) 16 | def test_xyz(): 17 | 18 | or within test function or test method: 19 | with patch("ols.src.query_helpers.question_validator.LLMChain", new=ml): 20 | some test steps 21 | 22 | None: it is better to use context manager to patch LLMChain, compared to `patch` decorator 23 | """ 24 | 25 | def __init__(self, *args, **kwargs): 26 | pass 27 | 28 | def __call__(self, *args, **kwargs): 29 | return retval 30 | 31 | def invoke( 32 | self, 33 | input, # noqa: A002 34 | config=None, 35 | **kwargs, # pylint: disable=W0622 36 | ): 37 | """Perform invocation of the LLM chain.""" 38 | if retval is not None: 39 | return retval 40 | input["text"] = input["query"] 41 | return input 42 | 43 | return MockLLMChain 44 | -------------------------------------------------------------------------------- /docs/css/theme/source/sky.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Sky theme for reveal.js. 3 | * 4 | * Copyright (C) 2011-2012 Hakim El Hattab, http://hakim.se 5 | */ 6 | 7 | 8 | // Default mixins and settings ----------------- 9 | @import "../template/mixins"; 10 | @import "../template/settings"; 11 | // --------------------------------------------- 12 | 13 | 14 | 15 | // Include theme-specific fonts 16 | @import url(https://fonts.googleapis.com/css?family=Quicksand:400,700,400italic,700italic); 17 | @import url(https://fonts.googleapis.com/css?family=Open+Sans:400italic,700italic,400,700); 18 | 19 | 20 | // Override theme settings (see ../template/settings.scss) 21 | $mainFont: 'Open Sans', sans-serif; 22 | $mainColor: #333; 23 | $headingFont: 'Quicksand', sans-serif; 24 | $headingColor: #333; 25 | $headingLetterSpacing: -0.08em; 26 | $headingTextShadow: none; 27 | $backgroundColor: #f7fbfc; 28 | $linkColor: #3b759e; 29 | $linkColorHover: lighten( $linkColor, 20% ); 30 | $selectionBackgroundColor: #134674; 31 | 32 | // Fix links so they are not cut off 33 | .reveal a { 34 | line-height: 1.3em; 35 | } 36 | 37 | // Background generator 38 | @mixin bodyBackground() { 39 | @include radial-gradient( #add9e4, #f7fbfc ); 40 | } 41 | 42 | // Change text colors against dark slide backgrounds 43 | @include dark-bg-text-color(#fff); 44 | 45 | 46 | 47 | // Theme template ------------------------------ 48 | @import "../template/theme"; 49 | // --------------------------------------------- 50 | -------------------------------------------------------------------------------- /ols/customize/rhdh/keywords.py: -------------------------------------------------------------------------------- 1 | """Constant for set of keywords.""" 2 | 3 | # Add keyword string to below set, preferably in alphabetical order. 4 | # We are adding this manually for now. Add to a txt file, If/when we automate this. 5 | # Important: Please use lower case. 6 | 7 | KEYWORDS = { 8 | # Kubernetes & Openshift 9 | "alert", 10 | "autoscale", 11 | "cluster", 12 | "config", 13 | "configmap", 14 | "console", 15 | "container", 16 | "crd", 17 | "deploy", 18 | "deployment", 19 | "image", 20 | "imagepullpolicy", 21 | "imagepullsecret", 22 | "infra", 23 | "ingress", 24 | "k8s", 25 | "kubernetes", 26 | "log", 27 | "master", 28 | "namespace", 29 | "network", 30 | "node", 31 | "oc", 32 | "ocp", 33 | "openshift", 34 | "operator", 35 | "pod", 36 | "podconfig", 37 | "poddisruptionbudgets", 38 | "podsecurity", 39 | "policy", 40 | "project", 41 | "quay", 42 | "replica", 43 | "replicaset", 44 | "secret", 45 | "service", 46 | "virtualization", 47 | "worker", 48 | "yaml", 49 | # RHDH & Backstge 50 | "argocd", 51 | "backstage", 52 | "catalog", 53 | "ci/cd", 54 | "component", 55 | "developer hub", 56 | "gitops", 57 | "integration", 58 | "jenkins", 59 | "lightspeed", 60 | "plugin", 61 | "resource", 62 | "rhdh", 63 | "software template", 64 | "tekton", 65 | } 66 | -------------------------------------------------------------------------------- /tests/unit/extra_certs/sample_cert_1.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDvTCCAqWgAwIBAgIUFPgi6jc7VxcsZLN/85pBUh3z4j0wDQYJKoZIhvcNAQEL 3 | BQAwbjELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE5ldyBZb3JrMRYwFAYDVQQHDA1O 4 | ZXcgWW9yayBDaXR5MRgwFgYDVQQKDA9FeGFtcGxlIENvbXBhbnkxGjAYBgNVBAMM 5 | EWZpcnN0LmV4YW1wbGUuY29tMB4XDTI0MDcwOTA2NTgxM1oXDTI1MDcwOTA2NTgx 6 | M1owbjELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE5ldyBZb3JrMRYwFAYDVQQHDA1O 7 | ZXcgWW9yayBDaXR5MRgwFgYDVQQKDA9FeGFtcGxlIENvbXBhbnkxGjAYBgNVBAMM 8 | EWZpcnN0LmV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC 9 | AQEA2ObpJE6yIilV68TVmIZksFoqmF9pqs4vZMxO05tFRwKsm0mhTZ7/j6bNZVlm 10 | FJenwN/iUIiw0+aJmiMSf6VCd/N82oNQDNcaUb39kyZ/MgH3EuUx/rX+Jeud9t4Q 11 | QjtRtVlyh3Qb18ic+in9lqkHpK6q7XjKFOG2FfITAcwIL4qymjLvB5UZoII0kmDZ 12 | r0AsGnsdzDc01XhOMLqffea8rv1y1Kw1mgDT7iZZL5h8DkCpVjzod8hWVVz/kyZb 13 | mZx6bHuS2cKkTmdeCMrmOuqoFiVyNqh5a1qmBPm2OJrFJB3alUmX8cwjlaZPIr8Q 14 | 8voLCP7tfCkInuCNfJ7bWv2EswIDAQABo1MwUTAdBgNVHQ4EFgQU5P85eEIQuO99 15 | 2yzAdH+x8mBut6owHwYDVR0jBBgwFoAU5P85eEIQuO992yzAdH+x8mBut6owDwYD 16 | VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEARFINl8bYoMt7XDrGtGty 17 | PYtroU0rYrfPUkjASE3jx9NFb4sA91AaML/uUc3Vqzwg39I6LNtT+lHR/dmrY1ZS 18 | ZECkc7Q3YZYnIRZokoqipdmEZNJZ7LfUleFIN/XHq4A0BA0258NwQ3mJDOEi3juM 19 | gkc6Id0diB7JkEfOyvVtvALR8cmtrizkqT7a+RWyRDzVbCVoepY2Fq3oE7IxdLEP 20 | lUG9d2VfXjSJ4ttg50Yu8E1klWA6YAh5VuZhC/c43F1eJ/MIBb6AInPEnuWPtoIp 21 | Z5bbGRAkgKeSTycbx/k3bZEEXvR9SvE/xkD80C+x2bszeQ/WAZYaAKftHBH6i+yH 22 | 4Q== 23 | -----END CERTIFICATE----- 24 | -------------------------------------------------------------------------------- /tests/unit/extra_certs/sample_cert_2.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDzjCCAragAwIBAgITPvIiXjsJAURVdT0lg3GeV7qDwDANBgkqhkiG9w0BAQsF 3 | ADB3MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEUMBIGA1UEBwwL 4 | TG9zIEFuZ2VsZXMxIDAeBgNVBAoMF0Fub3RoZXIgRXhhbXBsZSBDb21wYW55MRsw 5 | GQYDVQQDDBJzZWNvbmQuZXhhbXBsZS5jb20wHhcNMjQwNzA5MDY1ODIxWhcNMjUw 6 | NzA5MDY1ODIxWjB3MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEU 7 | MBIGA1UEBwwLTG9zIEFuZ2VsZXMxIDAeBgNVBAoMF0Fub3RoZXIgRXhhbXBsZSBD 8 | b21wYW55MRswGQYDVQQDDBJzZWNvbmQuZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3 9 | DQEBAQUAA4IBDwAwggEKAoIBAQCrqqgEEYDC74H1AnuWacRqVtdU/mpk09OC0fRh 10 | XRURzQo6YxpKfSL6wJMt6Q3eOtPPaOLD0OrzqQnQEInUL+GzBLODl3LRwSRP96mM 11 | 46pxJ4ynZ/mgDKxlHYXXgDZJjP4ZEhapmJb0gwfJ0HtKZFGeoHtoA01svUQiZcpZ 12 | v9hZ64XgGus5YTenGCyWMoX7snhtX12vK8m42p5OsqGc9Zt9JHs847JGDpwjkuUy 13 | xl9NyboB6ybq/WFI+KO/fLzShGpVOJNYlVAvWMjFcwfWzrwp9t12dkY3uyIH+bL+ 14 | xRYwwLRm9JB+VdbtfLmYHJkbbs3LCnYKX3rzKYiVYi0H/Bg3AgMBAAGjUzBRMB0G 15 | A1UdDgQWBBRSoTqJnTkt8srxULTWVWQLTb/xbzAfBgNVHSMEGDAWgBRSoTqJnTkt 16 | 8srxULTWVWQLTb/xbzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IB 17 | AQBmUM8H43wVfHCWqXFEtYYCwIa+ftA5tXds/HzDXSfXiK3V1WMUJAiIHk3ZWAG1 18 | ZT7Jtgmm+Hb8gucqu8JUIY3blMRPnkb3uHlc2lfPqdh9n0Kdj1Piaq22U33wcd5K 19 | SR/MVk+K0DuL0j+qpxsFpz0uzHMKbPgNI4wGd0GDFSFLz1w7AjtAAAqUx28Lm0w+ 20 | +4Xvs28NZSPgcJAIAd0ee4gI38o8QqU61vitkJaxHBfcOBstStDbY0NhZ/D5IQEc 21 | cBWgz4zSBEOvnguq3PAxjWKaFTJQ8nU23EJeuESvTi8diIO0FePjfwOTMF7xOQNv 22 | qcYMpEBfo4j/snJoaOwdD+sw 23 | -----END CERTIFICATE----- 24 | -------------------------------------------------------------------------------- /docs/css/theme/source/beige.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Beige theme for reveal.js. 3 | * 4 | * Copyright (C) 2011-2012 Hakim El Hattab, http://hakim.se 5 | */ 6 | 7 | 8 | // Default mixins and settings ----------------- 9 | @import "../template/mixins"; 10 | @import "../template/settings"; 11 | // --------------------------------------------- 12 | 13 | 14 | 15 | // Include theme-specific fonts 16 | @import url(./fonts/league-gothic/league-gothic.css); 17 | @import url(https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic); 18 | 19 | 20 | // Override theme settings (see ../template/settings.scss) 21 | $mainColor: #333; 22 | $headingColor: #333; 23 | $headingTextShadow: none; 24 | $backgroundColor: #f7f3de; 25 | $linkColor: #8b743d; 26 | $linkColorHover: lighten( $linkColor, 20% ); 27 | $selectionBackgroundColor: rgba(79, 64, 28, 0.99); 28 | $heading1TextShadow: 0 1px 0 #ccc, 0 2px 0 #c9c9c9, 0 3px 0 #bbb, 0 4px 0 #b9b9b9, 0 5px 0 #aaa, 0 6px 1px rgba(0,0,0,.1), 0 0 5px rgba(0,0,0,.1), 0 1px 3px rgba(0,0,0,.3), 0 3px 5px rgba(0,0,0,.2), 0 5px 10px rgba(0,0,0,.25), 0 20px 20px rgba(0,0,0,.15); 29 | 30 | // Background generator 31 | @mixin bodyBackground() { 32 | @include radial-gradient( rgba(247,242,211,1), rgba(255,255,255,1) ); 33 | } 34 | 35 | // Change text colors against dark slide backgrounds 36 | @include dark-bg-text-color(#fff); 37 | 38 | 39 | // Theme template ------------------------------ 40 | @import "../template/theme"; 41 | // --------------------------------------------- 42 | -------------------------------------------------------------------------------- /docs/user_data_flow.uml: -------------------------------------------------------------------------------- 1 | @startuml 2 | skin rose 3 | 4 | header Sequence diagram for User Data Collection System 5 | footer Copyright © 2024 Red Hat Inc. Author: Ondrej Metelka 6 | 7 | box "OpenShift Cluster" 8 | participant "User" as user 9 | participant "OLS Service" as ols 10 | participant "Local Disk" as disk 11 | participant "Sidecar Container" as sidecar 12 | end box 13 | box "cloud.redhat.com" 14 | database "Ingress Endpoint" as ingress 15 | participant "Customer Services" as cs 16 | database "AWS S3 Bucket" as aws 17 | end box 18 | box "RH VPN" 19 | participant "Internal Data Pipeline" as ccx 20 | database "Ceph S3 Bucket" as ceph 21 | participant "OLS pipeline" as olspipe 22 | database "Redshift" as rs 23 | participant "Dashboard" as dash 24 | end box 25 | 26 | == User Interaction == 27 | user -> ols: Interaction 28 | ols -> disk: Store transcript as JSON 29 | 30 | == User Feedback == 31 | user -> ols: Feedback 32 | ols -> disk: Store feedback as JSON 33 | 34 | == Data Packaging and Upload == 35 | sidecar -> disk: Scan for new JSON files 36 | disk -> sidecar: New JSON files 37 | sidecar -> sidecar: Package data into tar.gz 38 | sidecar -> ingress: Upload rcs.tgz 39 | 40 | == Data Forwarding == 41 | ingress -> cs: Forward data 42 | cs -> aws: Validate and store into 43 | aws -> ccx: Notify 44 | ccx -> ceph: Process and store 45 | 46 | == Data Processing == 47 | ceph -> olspipe: Process archives 48 | olspipe -> rs: Publish data 49 | rs -> dash: Consume 50 | 51 | @enduml 52 | -------------------------------------------------------------------------------- /docs/presentation.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Road-core service 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |
16 |
17 |
18 |
19 | 20 | 21 | 22 | 23 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /ols/src/query_helpers/query_helper.py: -------------------------------------------------------------------------------- 1 | """Base class for query helpers.""" 2 | 3 | import logging 4 | from collections.abc import Callable 5 | from typing import Optional 6 | 7 | from langchain.llms.base import LLM 8 | 9 | from ols import config 10 | from ols.customize import prompts 11 | from ols.src.llms.llm_loader import load_llm 12 | 13 | logger = logging.getLogger(__name__) 14 | 15 | 16 | class QueryHelper: 17 | """Base class for query helpers.""" 18 | 19 | def __init__( 20 | self, 21 | provider: Optional[str] = None, 22 | model: Optional[str] = None, 23 | generic_llm_params: Optional[dict] = None, 24 | llm_loader: Optional[Callable[[str, str, dict], LLM]] = None, 25 | system_prompt: Optional[str] = None, 26 | ) -> None: 27 | """Initialize query helper.""" 28 | # NOTE: As signature of this method is evaluated before the config, 29 | # is loaded, we cannot use the config directly as defaults and we 30 | # need to use those values in the init evaluation. 31 | self.provider = provider or config.ols_config.default_provider 32 | self.model = model or config.ols_config.default_model 33 | self.generic_llm_params = generic_llm_params or {} 34 | self.llm_loader = llm_loader or load_llm 35 | 36 | self._system_prompt = ( 37 | (config.dev_config.enable_system_prompt_override and system_prompt) 38 | or config.ols_config.system_prompt 39 | or prompts.QUERY_SYSTEM_INSTRUCTION 40 | ) 41 | logger.debug("System prompt: %s", self._system_prompt) 42 | -------------------------------------------------------------------------------- /docs/css/theme/source/moon.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Solarized Dark theme for reveal.js. 3 | * Author: Achim Staebler 4 | */ 5 | 6 | 7 | // Default mixins and settings ----------------- 8 | @import "../template/mixins"; 9 | @import "../template/settings"; 10 | // --------------------------------------------- 11 | 12 | 13 | 14 | // Include theme-specific fonts 15 | @import url(./fonts/league-gothic/league-gothic.css); 16 | @import url(https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic); 17 | 18 | /** 19 | * Solarized colors by Ethan Schoonover 20 | */ 21 | html * { 22 | color-profile: sRGB; 23 | rendering-intent: auto; 24 | } 25 | 26 | // Solarized colors 27 | $base03: #002b36; 28 | $base02: #073642; 29 | $base01: #586e75; 30 | $base00: #657b83; 31 | $base0: #839496; 32 | $base1: #93a1a1; 33 | $base2: #eee8d5; 34 | $base3: #fdf6e3; 35 | $yellow: #b58900; 36 | $orange: #cb4b16; 37 | $red: #dc322f; 38 | $magenta: #d33682; 39 | $violet: #6c71c4; 40 | $blue: #268bd2; 41 | $cyan: #2aa198; 42 | $green: #859900; 43 | 44 | // Override theme settings (see ../template/settings.scss) 45 | $mainColor: $base1; 46 | $headingColor: $base2; 47 | $headingTextShadow: none; 48 | $backgroundColor: $base03; 49 | $linkColor: $blue; 50 | $linkColorHover: lighten( $linkColor, 20% ); 51 | $selectionBackgroundColor: $magenta; 52 | 53 | // Change text colors against light slide backgrounds 54 | @include light-bg-text-color(#222); 55 | 56 | // Theme template ------------------------------ 57 | @import "../template/theme"; 58 | // --------------------------------------------- 59 | -------------------------------------------------------------------------------- /scripts/evaluation/utils/similarity_score_llm.py: -------------------------------------------------------------------------------- 1 | """Similarity check by LLM.""" 2 | 3 | from time import sleep 4 | 5 | from langchain_core.prompts.prompt import PromptTemplate 6 | 7 | from .constants import MAX_RETRY_ATTEMPTS, TIME_TO_BREATH 8 | from .prompts import ANSWER_SIMILARITY_PROMPT 9 | 10 | 11 | class AnswerSimilarityScore: 12 | """Get similarity score generated by LLM.""" 13 | 14 | def __init__(self, judge_llm): 15 | """Initialize.""" 16 | prompt = PromptTemplate.from_template(ANSWER_SIMILARITY_PROMPT) 17 | self._judge_llm = prompt | judge_llm 18 | 19 | def get_score( 20 | self, 21 | question, 22 | answer, 23 | response, 24 | retry_attempts=MAX_RETRY_ATTEMPTS, 25 | time_to_breath=TIME_TO_BREATH, 26 | ): 27 | """Generate similarity score.""" 28 | score = None 29 | for retry_counter in range(retry_attempts): 30 | try: 31 | result = self._judge_llm.invoke( 32 | { 33 | "question": question, 34 | "answer": answer, 35 | "response": response, 36 | } 37 | ) 38 | score = float(result.content) / 10 39 | break 40 | except Exception as e: 41 | if retry_counter == retry_attempts - 1: 42 | print(f"error_answer_relevancy: {e}") 43 | # Continue with score as None 44 | score = None 45 | 46 | sleep(time_to_breath) 47 | 48 | return score 49 | -------------------------------------------------------------------------------- /ols/src/quota/quota_exceed_error.py: -------------------------------------------------------------------------------- 1 | """Any exception that can occur when user does not have enough tokens available.""" 2 | 3 | 4 | class QuotaExceedError(Exception): 5 | """Any exception that can occur when user does not have enough tokens available.""" 6 | 7 | def __init__( 8 | self, subject_id: str, subject_type: str, available: int, needed: int = 0 9 | ) -> None: 10 | """Construct exception object.""" 11 | message: str = "" 12 | 13 | if needed == 0 and available <= 0: 14 | match subject_type: 15 | case "u": 16 | message = f"User {subject_id} has no available tokens" 17 | case "c": 18 | message = "Cluster has no available tokens" 19 | case _: 20 | message = f"Unknown subject {subject_id} has no available tokens" 21 | else: 22 | match subject_type: 23 | case "u": 24 | message = f"User {subject_id} has {available} tokens, but {needed} tokens are needed" # noqa: E501 25 | case "c": 26 | message = f"Cluster has {available} tokens, but {needed} tokens are needed" 27 | case _: 28 | message = f"Unknown subject {subject_id} has {available} tokens, but {needed} tokens are needed" # noqa: E501 29 | 30 | # call the base class constructor with the parameters it needs 31 | super().__init__(message) 32 | 33 | # custom attributes 34 | self.subject_id = subject_id 35 | self.available = available 36 | self.needed = needed 37 | -------------------------------------------------------------------------------- /tests/config/valid_config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | llm_providers: 3 | - name: p1 4 | type: bam 5 | url: "https://url1" 6 | credentials_path: tests/config/secret/apitoken 7 | models: 8 | - name: m1 9 | url: "https://murl1" 10 | credentials_path: tests/config/secret/apitoken 11 | context_window_size: 450 12 | parameters: 13 | max_tokens_for_response: 100 14 | - name: m2 15 | url: "https://murl2" 16 | - name: p2 17 | type: openai 18 | url: "https://url2" 19 | models: 20 | - name: m1 21 | url: "https://murl1" 22 | - name: m2 23 | url: "https://murl2" 24 | ols_config: 25 | max_workers: 1 26 | reference_content: 27 | product_docs_index_path: "tests/config" 28 | product_docs_index_id: product 29 | conversation_cache: 30 | type: memory 31 | memory: 32 | max_entries: 1000 33 | logging_config: 34 | logging_level: INFO 35 | default_provider: p1 36 | default_model: m1 37 | user_data_collection: 38 | transcripts_disabled: true 39 | certificate_directory: "/foo/bar/baz/xyzzy" 40 | system_prompt_path: "tests/config/system_prompt.txt" 41 | mcp_servers: 42 | - name: foo 43 | transport: stdio 44 | stdio: 45 | command: python 46 | args: 47 | - mcp_server_1.py 48 | - name: bar 49 | transport: sse 50 | sse: 51 | url: 127.0.0.1:8080 52 | dev_config: 53 | disable_auth: true 54 | disable_tls: true 55 | user_data_collector_config: 56 | user_agent: "lightspeed-operator/user-data-collection cluster/{cluster_id}" 57 | ingress_url: "https://example.ingress.com/upload" 58 | -------------------------------------------------------------------------------- /docs/css/theme/README.md: -------------------------------------------------------------------------------- 1 | ## Dependencies 2 | 3 | Themes are written using Sass to keep things modular and reduce the need for repeated selectors across files. Make sure that you have the reveal.js development environment installed before proceeding: https://revealjs.com/installation/#full-setup 4 | 5 | ## Creating a Theme 6 | 7 | To create your own theme, start by duplicating a ```.scss``` file in [/css/theme/source](https://github.com/hakimel/reveal.js/blob/master/css/theme/source). It will be automatically compiled from Sass to CSS (see the [gulpfile](https://github.com/hakimel/reveal.js/blob/master/gulpfile.js)) when you run `npm run build -- css-themes`. 8 | 9 | Each theme file does four things in the following order: 10 | 11 | 1. **Include [/css/theme/template/mixins.scss](https://github.com/hakimel/reveal.js/blob/master/css/theme/template/mixins.scss)** 12 | Shared utility functions. 13 | 14 | 2. **Include [/css/theme/template/settings.scss](https://github.com/hakimel/reveal.js/blob/master/css/theme/template/settings.scss)** 15 | Declares a set of custom variables that the template file (step 4) expects. Can be overridden in step 3. 16 | 17 | 3. **Override** 18 | This is where you override the default theme. Either by specifying variables (see [settings.scss](https://github.com/hakimel/reveal.js/blob/master/css/theme/template/settings.scss) for reference) or by adding any selectors and styles you please. 19 | 20 | 4. **Include [/css/theme/template/theme.scss](https://github.com/hakimel/reveal.js/blob/master/css/theme/template/theme.scss)** 21 | The template theme file which will generate final CSS output based on the currently defined variables. 22 | -------------------------------------------------------------------------------- /docs/dist/theme/fonts/source-sans-pro/source-sans-pro.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Source Sans Pro'; 3 | src: url('./source-sans-pro-regular.eot'); 4 | src: url('./source-sans-pro-regular.eot?#iefix') format('embedded-opentype'), 5 | url('./source-sans-pro-regular.woff') format('woff'), 6 | url('./source-sans-pro-regular.ttf') format('truetype'); 7 | font-weight: normal; 8 | font-style: normal; 9 | } 10 | 11 | @font-face { 12 | font-family: 'Source Sans Pro'; 13 | src: url('./source-sans-pro-italic.eot'); 14 | src: url('./source-sans-pro-italic.eot?#iefix') format('embedded-opentype'), 15 | url('./source-sans-pro-italic.woff') format('woff'), 16 | url('./source-sans-pro-italic.ttf') format('truetype'); 17 | font-weight: normal; 18 | font-style: italic; 19 | } 20 | 21 | @font-face { 22 | font-family: 'Source Sans Pro'; 23 | src: url('./source-sans-pro-semibold.eot'); 24 | src: url('./source-sans-pro-semibold.eot?#iefix') format('embedded-opentype'), 25 | url('./source-sans-pro-semibold.woff') format('woff'), 26 | url('./source-sans-pro-semibold.ttf') format('truetype'); 27 | font-weight: 600; 28 | font-style: normal; 29 | } 30 | 31 | @font-face { 32 | font-family: 'Source Sans Pro'; 33 | src: url('./source-sans-pro-semibolditalic.eot'); 34 | src: url('./source-sans-pro-semibolditalic.eot?#iefix') format('embedded-opentype'), 35 | url('./source-sans-pro-semibolditalic.woff') format('woff'), 36 | url('./source-sans-pro-semibolditalic.ttf') format('truetype'); 37 | font-weight: 600; 38 | font-style: italic; 39 | } 40 | -------------------------------------------------------------------------------- /ols/src/auth/auth.py: -------------------------------------------------------------------------------- 1 | """Authentication related utilities.""" 2 | 3 | import logging 4 | 5 | from ols.app.models.config import OLSConfig 6 | 7 | from . import k8s, noop, noop_with_token 8 | from .auth_dependency_interface import AuthDependencyInterface 9 | 10 | logger = logging.getLogger(__name__) 11 | 12 | 13 | def use_k8s_auth(ols_config: OLSConfig) -> bool: 14 | """Return True if k8s authentication should be used in the service.""" 15 | auth_module = ols_config.authentication_config.module 16 | return auth_module is not None and auth_module == "k8s" 17 | 18 | 19 | def get_auth_dependency( 20 | ols_config: OLSConfig, virtual_path: str 21 | ) -> AuthDependencyInterface: 22 | """Select the configured authentication dependency interface.""" 23 | module = ols_config.authentication_config.module 24 | if module is None: 25 | raise Exception("Authentication module is not specified") 26 | 27 | # module is specified -> time to construct AuthDependency instance 28 | logger.info( 29 | "Authentication retrieval for module %s and virtual path %s", 30 | module, 31 | virtual_path, 32 | ) 33 | 34 | match module: 35 | case "k8s": 36 | return k8s.AuthDependency(virtual_path=virtual_path) 37 | case "noop": 38 | return noop.AuthDependency(virtual_path=virtual_path) 39 | case "noop-with-token": 40 | return noop_with_token.AuthDependency(virtual_path=virtual_path) 41 | case _: 42 | # this is internal error and should not happen in reality 43 | raise Exception(f"Invalid/unknown auth. module was configured: {module}") 44 | -------------------------------------------------------------------------------- /tests/unit/utils/test_connection_decorator.py: -------------------------------------------------------------------------------- 1 | """Unit tests for the connection decorator.""" 2 | 3 | import pytest 4 | 5 | from ols.utils.connection_decorator import connection 6 | 7 | 8 | class Connectable: 9 | """Class used to test connection decorator.""" 10 | 11 | def __init__(self, raise_exception_from_foo: bool): 12 | """Initialize class used to test connection decorator.""" 13 | self._raise_exception_from_foo = raise_exception_from_foo 14 | 15 | def connected(self) -> bool: 16 | """Predicate if connection is alive.""" 17 | return self._connected 18 | 19 | def connect(self) -> None: 20 | """Connect.""" 21 | self._connected = True 22 | 23 | def disconnect(self) -> None: 24 | """Disconnect.""" 25 | self._connected = False 26 | 27 | @connection 28 | def foo(self) -> None: 29 | """Perform any action, but with active connection.""" 30 | if self._raise_exception_from_foo: 31 | raise Exception("foo error!") 32 | 33 | 34 | def test_connection_decorator(): 35 | """Test the connection decorator.""" 36 | c = Connectable(raise_exception_from_foo=False) 37 | c.disconnect() 38 | assert c.connected() is False 39 | 40 | # this method should autoconnect 41 | c.foo() 42 | assert c.connected() is True 43 | 44 | 45 | def test_connection_decorator_on_connection_exception(): 46 | """Test the connection decorator.""" 47 | c = Connectable(raise_exception_from_foo=True) 48 | c.disconnect() 49 | assert c.connected() is False 50 | 51 | with pytest.raises(Exception, match="foo error!"): 52 | # this method should autoconnect 53 | c.foo() 54 | -------------------------------------------------------------------------------- /docs/css/theme/source/solarized.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Solarized Light theme for reveal.js. 3 | * Author: Achim Staebler 4 | */ 5 | 6 | 7 | // Default mixins and settings ----------------- 8 | @import "../template/mixins"; 9 | @import "../template/settings"; 10 | // --------------------------------------------- 11 | 12 | 13 | 14 | // Include theme-specific fonts 15 | @import url(./fonts/league-gothic/league-gothic.css); 16 | @import url(https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic); 17 | 18 | 19 | /** 20 | * Solarized colors by Ethan Schoonover 21 | */ 22 | html * { 23 | color-profile: sRGB; 24 | rendering-intent: auto; 25 | } 26 | 27 | // Solarized colors 28 | $base03: #002b36; 29 | $base02: #073642; 30 | $base01: #586e75; 31 | $base00: #657b83; 32 | $base0: #839496; 33 | $base1: #93a1a1; 34 | $base2: #eee8d5; 35 | $base3: #fdf6e3; 36 | $yellow: #b58900; 37 | $orange: #cb4b16; 38 | $red: #dc322f; 39 | $magenta: #d33682; 40 | $violet: #6c71c4; 41 | $blue: #268bd2; 42 | $cyan: #2aa198; 43 | $green: #859900; 44 | 45 | // Override theme settings (see ../template/settings.scss) 46 | $mainColor: $base00; 47 | $headingColor: $base01; 48 | $headingTextShadow: none; 49 | $backgroundColor: $base3; 50 | $linkColor: $blue; 51 | $linkColorHover: lighten( $linkColor, 20% ); 52 | $selectionBackgroundColor: $magenta; 53 | 54 | // Background generator 55 | // @mixin bodyBackground() { 56 | // @include radial-gradient( rgba($base3,1), rgba(lighten($base3, 20%),1) ); 57 | // } 58 | 59 | 60 | 61 | // Theme template ------------------------------ 62 | @import "../template/theme"; 63 | // --------------------------------------------- 64 | -------------------------------------------------------------------------------- /ols/utils/pyroscope.py: -------------------------------------------------------------------------------- 1 | """Pyroscope handling utility functions.""" 2 | 3 | import logging 4 | import threading 5 | from typing import Any 6 | 7 | import requests 8 | 9 | from ols.runners.uvicorn import start_uvicorn 10 | 11 | 12 | def start_with_pyroscope_enabled( 13 | config: Any, 14 | logger: logging.Logger, 15 | ) -> None: 16 | """Start the application using pyroscope.""" 17 | try: 18 | response = requests.get(config.dev_config.pyroscope_url, timeout=60) 19 | if response.status_code == requests.codes.ok: 20 | logger.info( 21 | "Pyroscope server is reachable at %s", config.dev_config.pyroscope_url 22 | ) 23 | # pylint: disable=C0415 24 | import pyroscope 25 | 26 | pyroscope.configure( 27 | application_name="lightspeed-service", 28 | server_address=config.dev_config.pyroscope_url, 29 | oncpu=True, 30 | gil_only=True, 31 | enable_logging=True, 32 | ) 33 | with pyroscope.tag_wrapper({"main": "main_method"}): 34 | # create and start the rag_index_thread 35 | rag_index_thread = threading.Thread(target=config.rag_index) 36 | rag_index_thread.start() 37 | 38 | # start the Uvicorn server 39 | start_uvicorn(config) 40 | else: 41 | logger.info( 42 | "Failed to reach Pyroscope server. Status code: %d", 43 | response.status_code, 44 | ) 45 | except requests.exceptions.RequestException as e: 46 | logger.info("Error connecting to Pyroscope server: %s", str(e)) 47 | -------------------------------------------------------------------------------- /tests/e2e/utils/postgres.py: -------------------------------------------------------------------------------- 1 | """Postgres-related utilities.""" 2 | 3 | import os 4 | 5 | import psycopg2 6 | 7 | 8 | def _get_env(env_name): 9 | if env_name not in os.environ: 10 | raise ValueError(f"Environment variable {env_name} not set.") 11 | return os.getenv(env_name) 12 | 13 | 14 | def retrieve_connection(): 15 | """Perform setup e2e tests for conversation cache based on PostgreSQL.""" 16 | try: 17 | pg_host = _get_env("PG_HOST") 18 | pg_port = _get_env("PG_PORT") 19 | pg_user = _get_env("PG_USER") 20 | pg_password = _get_env("PG_PASSWORD") 21 | pg_dbname = _get_env("PG_DBNAME") 22 | connection = psycopg2.connect( 23 | host=pg_host, 24 | port=pg_port, 25 | user=pg_user, 26 | password=pg_password, 27 | dbname=pg_dbname, 28 | ) 29 | assert connection is not None 30 | return connection 31 | except Exception as e: 32 | print("Skipping PostgreSQL tests because of", e) 33 | return None 34 | 35 | 36 | def read_conversation_history_count(postgres_connection): 37 | """Read number of items in conversation history.""" 38 | query = "SELECT count(*) FROM cache;" 39 | with postgres_connection.cursor() as cursor: 40 | cursor.execute(query) 41 | return cursor.fetchone() 42 | 43 | 44 | def read_conversation_history(postgres_connection, conversation_id): 45 | """Read number of items in conversation history.""" 46 | query = "SELECT value, updated_at FROM cache WHERE conversation_id = %s" 47 | with postgres_connection.cursor() as cursor: 48 | cursor.execute(query, (conversation_id,)) 49 | return cursor.fetchone() 50 | -------------------------------------------------------------------------------- /docs/js/index.js: -------------------------------------------------------------------------------- 1 | import Deck, { VERSION } from './reveal.js' 2 | 3 | /** 4 | * Expose the Reveal class to the window. To create a 5 | * new instance: 6 | * let deck = new Reveal( document.querySelector( '.reveal' ), { 7 | * controls: false 8 | * } ); 9 | * deck.initialize().then(() => { 10 | * // reveal.js is ready 11 | * }); 12 | */ 13 | let Reveal = Deck; 14 | 15 | 16 | /** 17 | * The below is a thin shell that mimics the pre 4.0 18 | * reveal.js API and ensures backwards compatibility. 19 | * This API only allows for one Reveal instance per 20 | * page, whereas the new API above lets you run many 21 | * presentations on the same page. 22 | * 23 | * Reveal.initialize( { controls: false } ).then(() => { 24 | * // reveal.js is ready 25 | * }); 26 | */ 27 | 28 | let enqueuedAPICalls = []; 29 | 30 | Reveal.initialize = options => { 31 | 32 | // Create our singleton reveal.js instance 33 | Object.assign( Reveal, new Deck( document.querySelector( '.reveal' ), options ) ); 34 | 35 | // Invoke any enqueued API calls 36 | enqueuedAPICalls.map( method => method( Reveal ) ); 37 | 38 | return Reveal.initialize(); 39 | 40 | } 41 | 42 | /** 43 | * The pre 4.0 API let you add event listener before 44 | * initializing. We maintain the same behavior by 45 | * queuing up premature API calls and invoking all 46 | * of them when Reveal.initialize is called. 47 | */ 48 | [ 'configure', 'on', 'off', 'addEventListener', 'removeEventListener', 'registerPlugin' ].forEach( method => { 49 | Reveal[method] = ( ...args ) => { 50 | enqueuedAPICalls.push( deck => deck[method].call( null, ...args ) ); 51 | } 52 | } ); 53 | 54 | Reveal.isReady = () => false; 55 | 56 | Reveal.VERSION = VERSION; 57 | 58 | export default Reveal; -------------------------------------------------------------------------------- /docs/requirements.uml: -------------------------------------------------------------------------------- 1 | skin rose 2 | 3 | header requirements.txt file generation 4 | 5 | footer Copyright © 2025 Red Hat, Inc. Author: Pavel Tisnovsky 6 | 7 | @startditaa 8 | 9 | 10 | +------------------+ 11 | | | 12 | | pyproject.toml | 13 | | cFF9{d} | 14 | +--------+---------+ 15 | | 16 | | dependencies 17 | | with versions 18 | | specified 19 | v 20 | +------------------+ 21 | | pdm lock | 22 | | cGRE | 23 | +--------+---------+ 24 | | 25 | | generates lock file 26 | | all packages+versions 27 | | + names and hashes 28 | v 29 | +------------------+ 30 | | | 31 | | pdm.lock | 32 | | cF99{d} | 33 | +--------+---------+ 34 | | 35 | | dependencies 36 | | with versions 37 | | specified 38 | v 39 | +------------------+ 40 | | pip compile | 41 | | cGRE | 42 | +--------+---------+ 43 | | 44 | | generates 45 | | requirements.txt 46 | | with hashes 47 | v 48 | +------------------+ 49 | | | 50 | | requirements.txt | 51 | | cF99{d} | 52 | +--------+---------+ 53 | | 54 | | filtering 55 | | 56 | v 57 | +------------------+ 58 | | generate package | 59 | | to prefetch | 60 | | cGRE | 61 | +--------+---------+ 62 | | 63 | | filter packages 64 | | for given platform 65 | | and/or architecture 66 | v 67 | +------------------+ 68 | | | 69 | | requirements.txt | 70 | | cFF9{d} | 71 | +------------------+ 72 | 73 | @endditaa 74 | 75 | 76 | -------------------------------------------------------------------------------- /docs/css/layout.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Layout helpers. 3 | */ 4 | 5 | // Stretch an element vertically based on available space 6 | .reveal .stretch, 7 | .reveal .r-stretch { 8 | max-width: none; 9 | max-height: none; 10 | } 11 | 12 | .reveal pre.stretch code, 13 | .reveal pre.r-stretch code { 14 | height: 100%; 15 | max-height: 100%; 16 | box-sizing: border-box; 17 | } 18 | 19 | // Text that auto-fits its container 20 | .reveal .r-fit-text { 21 | display: inline-block; // https://github.com/rikschennink/fitty#performance 22 | white-space: nowrap; 23 | } 24 | 25 | // Stack multiple elements on top of each other 26 | .reveal .r-stack { 27 | display: grid; 28 | } 29 | 30 | .reveal .r-stack > * { 31 | grid-area: 1/1; 32 | margin: auto; 33 | } 34 | 35 | // Horizontal and vertical stacks 36 | .reveal .r-vstack, 37 | .reveal .r-hstack { 38 | display: flex; 39 | 40 | img, video { 41 | min-width: 0; 42 | min-height: 0; 43 | object-fit: contain; 44 | } 45 | } 46 | 47 | .reveal .r-vstack { 48 | flex-direction: column; 49 | align-items: center; 50 | justify-content: center; 51 | } 52 | 53 | .reveal .r-hstack { 54 | flex-direction: row; 55 | align-items: center; 56 | justify-content: center; 57 | } 58 | 59 | // Naming based on tailwindcss 60 | .reveal .items-stretch { align-items: stretch; } 61 | .reveal .items-start { align-items: flex-start; } 62 | .reveal .items-center { align-items: center; } 63 | .reveal .items-end { align-items: flex-end; } 64 | 65 | .reveal .justify-between { justify-content: space-between; } 66 | .reveal .justify-around { justify-content: space-around; } 67 | .reveal .justify-start { justify-content: flex-start; } 68 | .reveal .justify-center { justify-content: center; } 69 | .reveal .justify-end { justify-content: flex-end; } 70 | -------------------------------------------------------------------------------- /examples/rcsconfig-local-ollama.yaml: -------------------------------------------------------------------------------- 1 | # rcsconfig.yaml sample for local ollama server 2 | # 3 | # 1. install local ollama server from https://ollama.com/ 4 | # 2. install llama3.1:latest model with: 5 | # ollama pull llama3.1:latest 6 | # 3. Copy this file to the project root of cloned lightspeed-service repo 7 | # 4. Install dependencies with: 8 | # make install-deps 9 | # 5. Start lightspeed-service with: 10 | # OPENAI_API_KEY=IGNORED make run 11 | # 6. Open https://localhost:8080/ui in your web browser 12 | # 13 | llm_providers: 14 | - name: ollama 15 | type: openai 16 | url: "http://localhost:11434/v1/" 17 | models: 18 | - name: 'llama3.1:latest' 19 | ols_config: 20 | # max_workers: 1 21 | reference_content: 22 | # product_docs_index_path: "./vector_db/ocp_product_docs/4.15" 23 | # product_docs_index_id: ocp-product-docs-4_15 24 | # embeddings_model_path: "./embeddings_model" 25 | conversation_cache: 26 | type: memory 27 | memory: 28 | max_entries: 1000 29 | logging_config: 30 | app_log_level: info 31 | lib_log_level: warning 32 | uvicorn_log_level: info 33 | default_provider: ollama 34 | default_model: 'llama3.1:latest' 35 | user_data_collection: 36 | feedback_disabled: false 37 | feedback_storage: "/tmp/data/feedback" 38 | transcripts_disabled: false 39 | transcripts_storage: "/tmp/data/transcripts" 40 | dev_config: 41 | # config options specific to dev environment - launching OLS in local 42 | enable_dev_ui: true 43 | disable_auth: true 44 | disable_tls: true 45 | pyroscope_url: "https://pyroscope.pyroscope.svc.cluster.local:4040" 46 | # llm_params: 47 | # temperature_override: 0 48 | # k8s_auth_token: optional_token_when_no_available_kube_config 49 | -------------------------------------------------------------------------------- /docs/token_truncation.uml: -------------------------------------------------------------------------------- 1 | // 2 | // vim:syntax=plantuml 3 | // 4 | 5 | // Generate PNG image with this diagram by using the following command: 6 | // java -jar plantuml.jar token_truncation.uml 7 | // 8 | // Generate SVG drawing with this diagram by using the following command: 9 | // java -jar plantuml.jar -tsvg token_truncation.uml 10 | 11 | 12 | // Current truncation logic/context window token check: 13 | // 14 | // - Tokens for current prompt system instruction + user query + attachment (if any) + tokens reserved for response (default 512) shouldn’t be greater than model context window size, otherwise OLS will raise an error. 15 | // - Let’s say above tokens count as default tokens that will be used all the time. If any token is left after default usage then RAG context will be used completely or truncated depending upon how much tokens are left. 16 | // - Finally if we have further available tokens after using complete RAG context, then history will be used (or will be truncated) 17 | // - There is a flag set to True by the service, if history is truncated due to tokens limitation. 18 | 19 | @startuml 20 | skin rose 21 | 22 | :tokens count = prompt + user query + attachments + response tokens buffer; 23 | if (tokens count >= context window size) then 24 | :raise an error; 25 | else 26 | :available tokens = context window size - tokens count; 27 | if (available tokens <= 0) then 28 | :do not add RAG content; 29 | else 30 | :add RAG content up to remained_tokens; 31 | :available tokens = context window size - tokens count; 32 | if (available tokens <= 0) then 33 | :do not add history; 34 | else 35 | :add history; 36 | :set history_truncated flag; 37 | endif 38 | endif 39 | endif 40 | 41 | @enduml 42 | -------------------------------------------------------------------------------- /tests/config/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCtSMhxjKnGo0g1 3 | 1B6aVoxCpICgEaK1KmopUu5wHaspkvY7dzMu2eivS2YRCyZHnqz81j8BaLckactY 4 | noEkMKHmsOG4f+8S/5PUC2GzZMo37fHX2C5MgLpOwk7HRq68jo3kNC+yZyWloIzQ 5 | LFrHjXplXYby28lEB0tnOiF+stxvbdExnpPst7CoKeerP8uVpNGFcdp1+opxhOaw 6 | IOskcLnp2aPWLqf9J5pT5zEXJVtCSyvO5198HD3qpP7wfPPMo5ogC3ip8lXRV9zX 7 | qh0U59iVdMGCp9mSgT5DEVSrcMwnJNgTfjcTtwjonc1d84DZy9CgKcYBRj2VUQLu 8 | bPY35XKXAgMBAAECggEADzjxMthsI0dwyBnF3nUMuUYBbqf3jS0YSmA/Esy1OCyK 9 | 3DDdtDEixoaiUfKSy4Cq90MZVQkn6ldieUewIvrxHhAbCoqVxZHiIKL0ID6roDvG 10 | hQzR2sw4+A/Y784yLd182Lek52oCqqKIYp+i3rWumbA2DKhpU93E+y2y7fhnpcbw 11 | riDUsoHNuIhk1S/ZiXH5hRdAXQkF9PCvTxvBnraBLQ2J9Xb8GvGYNQmNV6RlP+PD 12 | aECcUnlWAfn3oY9Rcv4VauvSGgiRrKz/E8U8VyZ8ru3rAkRtgaMbAYenx8otwy2R 13 | RGCnn3J5i4byYhEqouCMpWHA5VaFlq9hb6/KlPaFEQKBgQDrB41536Z757J38x1Z 14 | csaJmYOlQRrw0D+vUnirJIgH0In8NoH/hS5nMryO6Hvuga8BWc3iRrTq9SUFntzu 15 | 7rW++rJlbflr8PIqebFIL2qnxQjulF5Z84SQqzaKdU4M4lIVrC3yyDmdUxh4Qzxe 16 | Lx3gZnbwePbQnleGwQIFEaB8ywKBgQC8vt86PQPSDsr4zNNMtZepWAUWcOl/pUeE 17 | dh6aotzWS8+DEEYe5NJo4zok6amCeYaoYnVmzYc/PyMFjyRFAjv0rZcasXmlnDpF 18 | 9dYbi3Py+poEniqhoDRyDTKmYPMQWaU5YSCZYW8chfiub6FHu0911w4aCGX9sUwv 19 | wUr3xKBT5QKBgEtGSfuks++vT6u3Q8koR4o+jrlY1sYWDK+cXGWD98V49xi+ZHhw 20 | MWKAD4A92aOUCXcHSjeUqdEu3sKkFOK5taALLyPpSx9VNlqAS1VxSlmdc7Tl9Cof 21 | InArRfamh2789YlXcgf2NO28dXPfJPCNaqY6axGtzG8Yp7PTEVCKSrptAoGBAIhH 22 | 6TZLx+5r7/IlMow4sgD8JPBHJtGbrT/facmNvpactoCTx42W+rXxG5J7hIFI7gm2 23 | zb4kuHraQ5dH+5A4RMIudOQ5MPR7EThigO3yiw3uFDcxXObGnhebn+6yIc60a7A6 24 | iSc2Stv1f9GzmGgKvxz+4pe5aKr80V3J2y97MahhAoGBAKan+9Np5bBRI+Jqjcpf 25 | nWPKam7IWqil19Mz+q/MBmAerFNpAj6ABeN3zh0X+GwfBhaXQK1E2Kq1AVaMRcBQ 26 | 9eLGqFDhEign2iiYoniGO4N0DD1c66hxfaac0SH0XPURrwEDwRJzfe1u4nZrgnF4 27 | drJm1nw6O2ev1JLChg31hZA5 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /ols/utils/redactor.py: -------------------------------------------------------------------------------- 1 | """A class helps redact the question based on the regex filters provided in the config file.""" 2 | 3 | import logging 4 | import re 5 | from collections import namedtuple 6 | 7 | from ols.app.models.config import QueryFilter 8 | 9 | logger = logging.getLogger(__name__) 10 | 11 | RegexFilter = namedtuple("RegexFilter", "pattern, name, replace_with") 12 | 13 | 14 | # TODO: OLS-380 Config object mirrors configuration 15 | 16 | 17 | class Redactor: 18 | """Redact the input based on the regex filters provided in the config file.""" 19 | 20 | def __init__(self, filters: list[QueryFilter]) -> None: 21 | """Initialize the class instance.""" 22 | regex_filters: list[RegexFilter] = [] 23 | self.regex_filters = regex_filters 24 | logger.debug("Filters : %s", filters) 25 | if not filters: 26 | return 27 | for regex_filter in filters: 28 | pattern = re.compile(str(regex_filter.pattern)) 29 | regex_filters.append( 30 | RegexFilter( 31 | pattern=pattern, 32 | name=regex_filter.name, 33 | replace_with=regex_filter.replace_with, 34 | ) 35 | ) 36 | self.regex_filters = regex_filters 37 | 38 | def redact(self, conversation_id: str, text_input: str) -> str: 39 | """Redact the input using regex built.""" 40 | logger.debug("Redacting conversation %s", conversation_id) 41 | for regex_filter in self.regex_filters: 42 | text_input, count = regex_filter.pattern.subn( 43 | regex_filter.replace_with, text_input 44 | ) 45 | logger.debug( 46 | "Replaced: %d matched with filter: %s", count, regex_filter.name 47 | ) 48 | logger.debug("Redacted conversation %s input: %s", conversation_id, text_input) 49 | return text_input 50 | -------------------------------------------------------------------------------- /ols/src/llms/providers/openai.py: -------------------------------------------------------------------------------- 1 | """OpenAI provider implementation.""" 2 | 3 | import logging 4 | from typing import Any, Optional 5 | 6 | from langchain_core.language_models.chat_models import BaseChatModel 7 | from langchain_openai import ChatOpenAI 8 | 9 | from ols import constants 10 | from ols.src.llms.providers.provider import LLMProvider 11 | from ols.src.llms.providers.registry import register_llm_provider_as 12 | 13 | logger = logging.getLogger(__name__) 14 | 15 | 16 | @register_llm_provider_as(constants.PROVIDER_OPENAI) 17 | class OpenAI(LLMProvider): 18 | """OpenAI provider.""" 19 | 20 | url: str = "https://api.openai.com/v1" 21 | credentials: Optional[str] = None 22 | 23 | @property 24 | def default_params(self) -> dict[str, Any]: 25 | """Construct and return structure with default LLM params.""" 26 | self.url = str(self.provider_config.url or self.url) 27 | self.credentials = self.provider_config.credentials 28 | # provider-specific configuration has precendence over regular configuration 29 | if self.provider_config.openai_config is not None: 30 | openai_config = self.provider_config.openai_config 31 | self.url = str(openai_config.url) 32 | if openai_config.api_key is not None: 33 | self.credentials = openai_config.api_key 34 | 35 | return { 36 | "base_url": self.url, 37 | "openai_api_key": self.credentials, 38 | "model": self.model, 39 | "top_p": 0.95, 40 | "frequency_penalty": 1.03, 41 | "organization": None, 42 | "cache": None, 43 | "temperature": 0.01, 44 | "max_tokens": 512, 45 | "verbose": False, 46 | "http_client": self._construct_httpx_client(True, False), 47 | "http_async_client": self._construct_httpx_client(True, True), 48 | } 49 | 50 | def load(self) -> BaseChatModel: 51 | """Load LLM.""" 52 | return ChatOpenAI(**self.params) 53 | -------------------------------------------------------------------------------- /ols/utils/ssl.py: -------------------------------------------------------------------------------- 1 | """Utility function for retrieving SSL version and list of ciphers for TLS secutiry profile.""" 2 | 3 | import logging 4 | import ssl 5 | from typing import Optional 6 | 7 | from ols import constants 8 | from ols.app.models.config import TLSSecurityProfile 9 | from ols.utils import tls 10 | 11 | logger = logging.getLogger(__name__) 12 | 13 | 14 | def get_ssl_version( 15 | sec_profile: Optional[TLSSecurityProfile], 16 | ) -> Optional[ssl.TLSVersion]: 17 | """Get SSL version to be used. It can be configured in tls_security_profile section.""" 18 | # if security profile is not set, use default SSL version 19 | # as specified in SSL library 20 | if sec_profile is None or sec_profile.profile_type is None: 21 | logger.info("Using default SSL version: %s", constants.DEFAULT_SSL_VERSION) 22 | return constants.DEFAULT_SSL_VERSION 23 | 24 | # security profile is set -> we need to retrieve SSL version and list of allowed ciphers 25 | min_tls_version = tls.min_tls_version( 26 | sec_profile.min_tls_version, sec_profile.profile_type 27 | ) 28 | logger.info("min TLS version: %s", min_tls_version) 29 | 30 | ssl_version = tls.ssl_tls_version(min_tls_version) 31 | logger.info("Using SSL version: %s", ssl_version) 32 | return ssl_version 33 | 34 | 35 | def get_ciphers(sec_profile: Optional[TLSSecurityProfile]) -> str: 36 | """Get allowed ciphers to be used. It can be configured in tls_security_profile section.""" 37 | # if security profile is not set, use default ciphers 38 | # as specified in SSL library 39 | if sec_profile is None or sec_profile.profile_type is None: 40 | logger.info("Allowing default ciphers: %s", constants.DEFAULT_SSL_CIPHERS) 41 | return constants.DEFAULT_SSL_CIPHERS 42 | 43 | # security profile is set -> we need to retrieve ciphers to be allowed 44 | ciphers = tls.ciphers_as_string(sec_profile.ciphers, sec_profile.profile_type) 45 | logger.info("Allowing following ciphers: %s", ciphers) 46 | return ciphers 47 | -------------------------------------------------------------------------------- /ols/runners/uvicorn.py: -------------------------------------------------------------------------------- 1 | """Uvicorn runner.""" 2 | 3 | import logging 4 | 5 | import uvicorn 6 | 7 | from ols.utils import ssl 8 | from ols.utils.config import AppConfig 9 | 10 | logger: logging.Logger = logging.getLogger(__name__) 11 | 12 | 13 | def start_uvicorn(config: AppConfig) -> None: 14 | """Start Uvicorn-based REST API service.""" 15 | logger.info("Starting Uvicorn") 16 | 17 | # use workers=1 so config loaded can be accessed from other modules 18 | host = ( 19 | "localhost" 20 | if config.dev_config.run_on_localhost 21 | else "0.0.0.0" # noqa: S104 # nosec: B104 22 | ) 23 | port = ( 24 | config.dev_config.uvicorn_port_number 25 | if config.dev_config.uvicorn_port_number 26 | else 8080 if config.dev_config.disable_tls else 8443 27 | ) 28 | log_level = config.ols_config.logging_config.uvicorn_log_level 29 | 30 | # The tls fields can be None, which means we will pass those values as None to uvicorn.run 31 | ssl_keyfile = config.ols_config.tls_config.tls_key_path 32 | ssl_certfile = config.ols_config.tls_config.tls_certificate_path 33 | ssl_keyfile_password = config.ols_config.tls_config.tls_key_password 34 | 35 | # setup SSL version and allowed SSL ciphers based on service configuration 36 | # when TLS security profile is not specified, default values will be used 37 | # that default values are based on default SSL package settings 38 | sec_profile = config.ols_config.tls_security_profile 39 | ssl_version = ssl.get_ssl_version(sec_profile) 40 | ssl_ciphers = ssl.get_ciphers(sec_profile) 41 | 42 | uvicorn.run( 43 | "ols.app.main:app", 44 | host=host, 45 | port=port, 46 | workers=config.ols_config.max_workers, 47 | log_level=log_level, 48 | ssl_keyfile=ssl_keyfile, 49 | ssl_certfile=ssl_certfile, 50 | ssl_keyfile_password=ssl_keyfile_password, 51 | ssl_version=ssl_version, 52 | ssl_ciphers=ssl_ciphers, 53 | access_log=log_level < logging.INFO, 54 | ) 55 | -------------------------------------------------------------------------------- /docs/css/theme/template/mixins.scss: -------------------------------------------------------------------------------- 1 | @mixin vertical-gradient( $top, $bottom ) { 2 | background: $top; 3 | background: -moz-linear-gradient( top, $top 0%, $bottom 100% ); 4 | background: -webkit-gradient( linear, left top, left bottom, color-stop(0%,$top), color-stop(100%,$bottom) ); 5 | background: -webkit-linear-gradient( top, $top 0%, $bottom 100% ); 6 | background: -o-linear-gradient( top, $top 0%, $bottom 100% ); 7 | background: -ms-linear-gradient( top, $top 0%, $bottom 100% ); 8 | background: linear-gradient( top, $top 0%, $bottom 100% ); 9 | } 10 | 11 | @mixin horizontal-gradient( $top, $bottom ) { 12 | background: $top; 13 | background: -moz-linear-gradient( left, $top 0%, $bottom 100% ); 14 | background: -webkit-gradient( linear, left top, right top, color-stop(0%,$top), color-stop(100%,$bottom) ); 15 | background: -webkit-linear-gradient( left, $top 0%, $bottom 100% ); 16 | background: -o-linear-gradient( left, $top 0%, $bottom 100% ); 17 | background: -ms-linear-gradient( left, $top 0%, $bottom 100% ); 18 | background: linear-gradient( left, $top 0%, $bottom 100% ); 19 | } 20 | 21 | @mixin radial-gradient( $outer, $inner, $type: circle ) { 22 | background: $outer; 23 | background: -moz-radial-gradient( center, $type cover, $inner 0%, $outer 100% ); 24 | background: -webkit-gradient( radial, center center, 0px, center center, 100%, color-stop(0%,$inner), color-stop(100%,$outer) ); 25 | background: -webkit-radial-gradient( center, $type cover, $inner 0%, $outer 100% ); 26 | background: -o-radial-gradient( center, $type cover, $inner 0%, $outer 100% ); 27 | background: -ms-radial-gradient( center, $type cover, $inner 0%, $outer 100% ); 28 | background: radial-gradient( center, $type cover, $inner 0%, $outer 100% ); 29 | } 30 | 31 | @mixin light-bg-text-color( $color ) { 32 | section.has-light-background { 33 | &, h1, h2, h3, h4, h5, h6 { 34 | color: $color; 35 | } 36 | } 37 | } 38 | 39 | @mixin dark-bg-text-color( $color ) { 40 | section.has-dark-background { 41 | &, h1, h2, h3, h4, h5, h6 { 42 | color: $color; 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /tests/e2e/utils/wait_for_ols.py: -------------------------------------------------------------------------------- 1 | """This module contains the wait_for_ols function and generate_junit_report function. 2 | 3 | to check the readiness of the OLS and generate a JUnit report. 4 | """ 5 | 6 | import time 7 | import warnings 8 | 9 | import requests 10 | from requests.exceptions import SSLError 11 | from urllib3.exceptions import InsecureRequestWarning 12 | 13 | warnings.filterwarnings("ignore", category=InsecureRequestWarning) 14 | 15 | 16 | # ruff: noqa: S501 17 | def wait_for_ols(url, timeout=300, interval=10): 18 | """Wait for the OLS to become ready by checking its readiness endpoint. 19 | 20 | Args: 21 | url (str): The base URL of the OLS service. 22 | timeout (int, optional): The maximum time to wait in seconds. Default is 600. 23 | interval (int, optional): The interval between readiness checks in seconds. Default is 10. 24 | 25 | Returns: 26 | bool: True if OLS becomes ready within the timeout, False otherwise. 27 | """ 28 | print(f"Starting wait_for_ols at url {url}") 29 | attempts = int(timeout / interval) 30 | for attempt in range(1, attempts + 1): 31 | print(f"Checking OLS readiness, attempt {attempt} of {attempts}") 32 | try: 33 | response = requests.get(f"{url}/readiness", verify=True, timeout=5) 34 | if response.status_code == requests.codes.ok: 35 | print("OLS is ready") 36 | return True 37 | except SSLError: 38 | print("SSL error detected, retrying without SSL verification") 39 | try: 40 | response = requests.get(f"{url}/readiness", verify=False, timeout=5) 41 | if response.status_code == requests.codes.ok: 42 | print("OLS is ready") 43 | return True 44 | except requests.RequestException: 45 | pass 46 | except requests.RequestException: 47 | pass 48 | time.sleep(interval) 49 | print("Timed out waiting for OLS to become available") 50 | return False 51 | -------------------------------------------------------------------------------- /tests/integration/test_authorized_noop.py: -------------------------------------------------------------------------------- 1 | """Integration tests for basic OLS REST API endpoints with no-op auth.""" 2 | 3 | from fastapi import Request 4 | 5 | from ols import config, constants 6 | from ols.src.auth.auth import get_auth_dependency 7 | 8 | 9 | def test_authorized(): 10 | """Check authorization based on no-op module.""" 11 | config.reload_from_yaml_file("tests/config/config_for_integration_tests.yaml") 12 | config.ols_config.authentication_config.module = "noop" 13 | config.dev_config.disable_auth = True 14 | from ols.app.endpoints import authorized # pylint: disable=C0415 15 | 16 | # auth is disabled 17 | request = Request(scope={"type": "http", "headers": [], "query_string": ""}) 18 | response = authorized.is_user_authorized(request) 19 | assert response is not None 20 | assert response.user_id == constants.DEFAULT_USER_UID 21 | assert response.username == constants.DEFAULT_USER_NAME 22 | 23 | config.dev_config.disable_auth = False 24 | authorized.auth_dependency = get_auth_dependency( 25 | config.ols_config, virtual_path="/ols-access" 26 | ) 27 | 28 | # auth is enabled 29 | request = Request(scope={"type": "http", "headers": [], "query_string": ""}) 30 | response = authorized.is_user_authorized(request) 31 | assert response is not None 32 | assert response.user_id == constants.DEFAULT_USER_UID 33 | assert response.username == constants.DEFAULT_USER_NAME 34 | 35 | # Simulate a request with user_id specified as optional parameter 36 | user_id_in_request = "00000000-1234-1234-1234-000000000000" 37 | request = Request( 38 | scope={ 39 | "type": "http", 40 | "headers": [], 41 | "query_string": f"user_id={user_id_in_request}", 42 | } 43 | ) 44 | 45 | response = authorized.is_user_authorized(request) 46 | assert response is not None 47 | assert response.user_id == user_id_in_request 48 | assert response.username == constants.DEFAULT_USER_NAME 49 | assert response.skip_user_id_check is True 50 | -------------------------------------------------------------------------------- /docs/js/utils/color.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Converts various color input formats to an {r:0,g:0,b:0} object. 3 | * 4 | * @param {string} color The string representation of a color 5 | * @example 6 | * colorToRgb('#000'); 7 | * @example 8 | * colorToRgb('#000000'); 9 | * @example 10 | * colorToRgb('rgb(0,0,0)'); 11 | * @example 12 | * colorToRgb('rgba(0,0,0)'); 13 | * 14 | * @return {{r: number, g: number, b: number, [a]: number}|null} 15 | */ 16 | export const colorToRgb = ( color ) => { 17 | 18 | let hex3 = color.match( /^#([0-9a-f]{3})$/i ); 19 | if( hex3 && hex3[1] ) { 20 | hex3 = hex3[1]; 21 | return { 22 | r: parseInt( hex3.charAt( 0 ), 16 ) * 0x11, 23 | g: parseInt( hex3.charAt( 1 ), 16 ) * 0x11, 24 | b: parseInt( hex3.charAt( 2 ), 16 ) * 0x11 25 | }; 26 | } 27 | 28 | let hex6 = color.match( /^#([0-9a-f]{6})$/i ); 29 | if( hex6 && hex6[1] ) { 30 | hex6 = hex6[1]; 31 | return { 32 | r: parseInt( hex6.slice( 0, 2 ), 16 ), 33 | g: parseInt( hex6.slice( 2, 4 ), 16 ), 34 | b: parseInt( hex6.slice( 4, 6 ), 16 ) 35 | }; 36 | } 37 | 38 | let rgb = color.match( /^rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i ); 39 | if( rgb ) { 40 | return { 41 | r: parseInt( rgb[1], 10 ), 42 | g: parseInt( rgb[2], 10 ), 43 | b: parseInt( rgb[3], 10 ) 44 | }; 45 | } 46 | 47 | let rgba = color.match( /^rgba\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\,\s*([\d]+|[\d]*.[\d]+)\s*\)$/i ); 48 | if( rgba ) { 49 | return { 50 | r: parseInt( rgba[1], 10 ), 51 | g: parseInt( rgba[2], 10 ), 52 | b: parseInt( rgba[3], 10 ), 53 | a: parseFloat( rgba[4] ) 54 | }; 55 | } 56 | 57 | return null; 58 | 59 | } 60 | 61 | /** 62 | * Calculates brightness on a scale of 0-255. 63 | * 64 | * @param {string} color See colorToRgb for supported formats. 65 | * @see {@link colorToRgb} 66 | */ 67 | export const colorBrightness = ( color ) => { 68 | 69 | if( typeof color === 'string' ) color = colorToRgb( color ); 70 | 71 | if( color ) { 72 | return ( color.r * 299 + color.g * 587 + color.b * 114 ) / 1000; 73 | } 74 | 75 | return null; 76 | 77 | } -------------------------------------------------------------------------------- /docs/sequence_diagram.uml: -------------------------------------------------------------------------------- 1 | // 2 | // vim:syntax=plantuml 3 | // 4 | 5 | // Generate PNG image with sequence diagram by using the following command: 6 | // java -jar plantuml.jar sequence_diagram.uml 7 | // 8 | // Generate SVG drawing with sequence diagram by using the following command: 9 | // java -jar plantuml.jar -tsvg sequence_diagram.uml 10 | 11 | @startuml 12 | skin rose 13 | 14 | header Sequence diagram for Road-core service 15 | footer Copyright © 2024 Red Hat, Inc. Author: Pavel Tisnovsky 16 | 17 | actor "User" as user 18 | participant "Road-core\nservice" as rcs 19 | control "Redactor" as redactor 20 | control "Question\nValidator" as question_validator 21 | control "Document\nSummarizer" as document_summarizer 22 | database "Conversation\ncache" as cache 23 | database "Vector\ndatabase" as vector_db 24 | collections "LLM" as llm 25 | 26 | == Question == 27 | 28 | user -> rcs: Question 29 | 30 | == Redaction == 31 | 32 | rcs -> redactor: Redact query 33 | redactor -> rcs: Redacted query 34 | 35 | == Conversation history == 36 | 37 | rcs -> cache: Retrieve conversation history 38 | cache -> rcs: Conversation history 39 | 40 | == Attachments == 41 | 42 | rcs -> rcs: Retrieve attachments from request 43 | rcs -> rcs: Attachments 44 | rcs -> redactor: Redact attachments 45 | redactor -> rcs: Redacted attachments 46 | 47 | == Validation == 48 | 49 | rcs -> question_validator: Validate question 50 | question_validator -> llm: Query 51 | llm -> question_validator: Valid or invalid 52 | 53 | == Answer generation == 54 | 55 | alt "Invalid question case" 56 | question_validator -> rcs: Invalid question 57 | rcs -> user: Your question\nis not valid 58 | end 59 | 60 | alt "Valid question case" 61 | question_validator -> rcs: Valid question 62 | rcs -> document_summarizer: Summarize document 63 | document_summarizer -> vector_db: Read vector DB (RAG) 64 | vector_db -> document_summarizer: RAG DB content 65 | document_summarizer -> llm: Query 66 | llm -> document_summarizer: Answer 67 | document_summarizer -> rcs: Summary 68 | rcs -> user: Answer to\nyour question 69 | end 70 | 71 | 72 | 73 | @enduml 74 | -------------------------------------------------------------------------------- /Containerfile: -------------------------------------------------------------------------------- 1 | # vim: set filetype=dockerfile 2 | ARG LIGHTSPEED_RAG_CONTENT_IMAGE=quay.io/redhat-user-workloads/crt-nshift-lightspeed-tenant/own-app-lightspeed-rag-content@sha256:997f46b714ec08d2e476647ec4c8415575a3123840efd4b70311f4a25eee21f6 3 | ARG LIGHTSPEED_RAG_EMBEDDINGS_IMAGE=quay.io/redhat-user-workloads/crt-nshift-lightspeed-tenant/own-app-lightspeed-rag-content@sha256:997f46b714ec08d2e476647ec4c8415575a3123840efd4b70311f4a25eee21f6 4 | ARG RAG_CONTENTS_SUB_FOLDER=vector_db/ocp_product_docs 5 | 6 | FROM ${LIGHTSPEED_RAG_CONTENT_IMAGE} as lightspeed-rag-content 7 | 8 | FROM ${LIGHTSPEED_RAG_EMBEDDINGS_IMAGE} as lightspeed-rag-embeddings 9 | 10 | FROM registry.access.redhat.com/ubi9/ubi-minimal 11 | 12 | ARG APP_ROOT=/app-root 13 | 14 | RUN microdnf install -y --nodocs --setopt=keepcache=0 --setopt=tsflags=nodocs \ 15 | python3.11 python3.11-devel python3.11-pip 16 | 17 | # PYTHONDONTWRITEBYTECODE 1 : disable the generation of .pyc 18 | # PYTHONUNBUFFERED 1 : force the stdout and stderr streams to be unbuffered 19 | # PYTHONCOERCECLOCALE 0, PYTHONUTF8 1 : skip legacy locales and use UTF-8 mode 20 | ENV PYTHONDONTWRITEBYTECODE=1 \ 21 | PYTHONUNBUFFERED=1 \ 22 | PYTHONCOERCECLOCALE=0 \ 23 | PYTHONUTF8=1 \ 24 | PYTHONIOENCODING=UTF-8 \ 25 | LANG=en_US.UTF-8 \ 26 | PIP_NO_CACHE_DIR=off 27 | 28 | WORKDIR /app-root 29 | 30 | COPY --from=lightspeed-rag-content /rag/${RAG_CONTENTS_SUB_FOLDER} ${APP_ROOT}/${RAG_CONTENTS_SUB_FOLDER} 31 | COPY --from=lightspeed-rag-embeddings /rag/embeddings_model ./embeddings_model 32 | 33 | # Add explicit files and directories 34 | # (avoid accidental inclusion of local directories or env files or credentials) 35 | COPY runner.py pyproject.toml LICENSE README.md ./ 36 | 37 | COPY ols ./ols 38 | 39 | RUN pip3.11 install --no-cache-dir . 40 | 41 | # this directory is checked by ecosystem-cert-preflight-checks task in Konflux 42 | COPY LICENSE /licenses/ 43 | 44 | # Run the application 45 | EXPOSE 8080 46 | EXPOSE 8443 47 | CMD ["python3.11", "runner.py"] 48 | 49 | LABEL vendor="Red Hat, Inc." 50 | 51 | 52 | # no-root user is checked in Konflux 53 | USER 1001 54 | -------------------------------------------------------------------------------- /ols/src/llms/providers/rhoai_vllm.py: -------------------------------------------------------------------------------- 1 | """Red Hat OpenShift VLLM provider implementation.""" 2 | 3 | import logging 4 | from typing import Any, Optional 5 | 6 | from langchain_core.language_models.chat_models import BaseChatModel 7 | from langchain_openai import ChatOpenAI 8 | 9 | from ols import constants 10 | from ols.src.llms.providers.provider import LLMProvider 11 | from ols.src.llms.providers.registry import register_llm_provider_as 12 | 13 | logger = logging.getLogger(__name__) 14 | 15 | 16 | @register_llm_provider_as(constants.PROVIDER_RHOAI_VLLM) 17 | class RHOAIVLLM(LLMProvider): 18 | """RHOAI VLLM provider.""" 19 | 20 | # note: there's no default URL for RHOAI VLLM 21 | url: str = "https://api.openai.com/v1" 22 | credentials: Optional[str] = None 23 | 24 | @property 25 | def default_params(self) -> dict[str, Any]: 26 | """Construct and return structure with default LLM params.""" 27 | self.url = str(self.provider_config.url or self.url) 28 | self.credentials = self.provider_config.credentials 29 | # provider-specific configuration has precendence over regular configuration 30 | if self.provider_config.rhoai_vllm_config is not None: 31 | rhoai_vllm_config = self.provider_config.rhoai_vllm_config 32 | self.url = str(rhoai_vllm_config.url) 33 | if rhoai_vllm_config.api_key is not None: 34 | self.credentials = rhoai_vllm_config.api_key 35 | 36 | return { 37 | "base_url": self.url, 38 | "openai_api_key": self.credentials, 39 | "model": self.model, 40 | "top_p": 0.95, 41 | "frequency_penalty": 1.03, 42 | "organization": None, 43 | "cache": None, 44 | "temperature": 0.01, 45 | "max_tokens": 512, 46 | "verbose": False, 47 | "http_client": self._construct_httpx_client(True, False), 48 | "http_async_client": self._construct_httpx_client(True, True), 49 | } 50 | 51 | def load(self) -> BaseChatModel: 52 | """Load LLM.""" 53 | return ChatOpenAI(**self.params) 54 | -------------------------------------------------------------------------------- /ols/app/endpoints/authorized.py: -------------------------------------------------------------------------------- 1 | """Handler for REST API call to provide user feedback.""" 2 | 3 | import asyncio 4 | import logging 5 | from typing import Any, Optional 6 | 7 | from fastapi import APIRouter, Request 8 | 9 | from ols import config 10 | from ols.app.models.models import ( 11 | AuthorizationResponse, 12 | ErrorResponse, 13 | ForbiddenResponse, 14 | UnauthorizedResponse, 15 | ) 16 | from ols.src.auth.auth import get_auth_dependency 17 | 18 | logger = logging.getLogger(__name__) 19 | router = APIRouter(tags=["authorized"]) 20 | auth_dependency = get_auth_dependency(config.ols_config, virtual_path="/ols-access") 21 | 22 | authorized_responses: dict[int | str, dict[str, Any]] = { 23 | 200: { 24 | "description": "The user is logged-in and authorized to access OLS", 25 | "model": AuthorizationResponse, 26 | }, 27 | 401: { 28 | "description": "Missing or invalid credentials provided by client", 29 | "model": UnauthorizedResponse, 30 | }, 31 | 403: { 32 | "description": "User is not authorized", 33 | "model": ForbiddenResponse, 34 | }, 35 | 500: { 36 | "description": "Unexpected error during token review", 37 | "model": ErrorResponse, 38 | }, 39 | } 40 | 41 | 42 | @router.post("/authorized", responses=authorized_responses) 43 | def is_user_authorized( 44 | request: Request, user_id: Optional[str] = None 45 | ) -> AuthorizationResponse: 46 | """Validate if the logged-in user is authorized to access OLS. 47 | 48 | Parameters: 49 | request (Request): The FastAPI request object. 50 | 51 | Returns: 52 | The user's UID and username if authentication and authorization succeed. 53 | 54 | Raises: 55 | HTTPException: If authentication fails or the user does not have access. 56 | 57 | """ 58 | # we don't care about the user-token here - hence the _ 59 | user_id, username, skip_user_id_check, _ = asyncio.run(auth_dependency(request)) 60 | return AuthorizationResponse( 61 | user_id=user_id, 62 | username=username, 63 | skip_user_id_check=skip_user_id_check, 64 | ) 65 | -------------------------------------------------------------------------------- /ols/src/query_helpers/attachment_appender.py: -------------------------------------------------------------------------------- 1 | """Function to append attachments to query.""" 2 | 3 | from typing import Optional 4 | 5 | import yaml 6 | 7 | from ols.app.models.models import Attachment 8 | 9 | # mapping between content-type and language specification in Markdown 10 | content_type_to_markdown = { 11 | "text/plain": "", 12 | "application/json": "json", 13 | "application/yaml": "yaml", 14 | "application/xml": "xml", 15 | } 16 | 17 | 18 | def append_attachments_to_query(query: str, attachments: list[Attachment]) -> str: 19 | """Append all attachments to query.""" 20 | output = query 21 | for attachment in attachments: 22 | output += format_attachment(attachment) 23 | return output 24 | 25 | 26 | def format_attachment(attachment: Attachment) -> str: 27 | """Format attachment to be included into query.""" 28 | intro_message = "" 29 | 30 | if attachment.content_type == "application/yaml": 31 | intro_message = construct_intro_message(attachment.content) 32 | 33 | # attachments were tested for proper content types already, so 34 | # no KeyError should be thrown there 35 | header = "```" + content_type_to_markdown[attachment.content_type] 36 | footer = "```" 37 | 38 | return f""" 39 | 40 | {intro_message} 41 | {header} 42 | {attachment.content} 43 | {footer} 44 | """ 45 | 46 | 47 | def construct_intro_message(content: str) -> str: 48 | """Construct intro message for given attachment.""" 49 | kind, name = retrieve_kind_name_from_yaml(content) 50 | if kind is not None and name is not None: 51 | return f"For reference, here is the full resource YAML for {kind} '{name}':" 52 | return "For reference, here is the full resource YAML:" 53 | 54 | 55 | def retrieve_kind_name_from_yaml(content: str) -> tuple[Optional[str], Optional[str]]: 56 | """Try to parse YAML file and retrieve kind and name attributes from it.""" 57 | try: 58 | d = yaml.safe_load(content) 59 | kind = d.get("kind") 60 | name = d.get("metadata", {}).get("name") 61 | return kind, name 62 | except Exception: 63 | return None, None 64 | --------------------------------------------------------------------------------