├── 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 | 
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 | 
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 | 
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 |
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 |
--------------------------------------------------------------------------------