-
Notifications
You must be signed in to change notification settings - Fork 285
feat(llm): add OCI Generative AI as a documented provider #316
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
fede-kamel
wants to merge
2
commits into
NVIDIA-NeMo:main
Choose a base branch
from
fede-kamel:feat/oci-generative-ai-example
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 1 commit
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,175 @@ | ||
| # OCI Generative AI | ||
|
|
||
| NOOA uses LiteLLM model strings through `get_llm_client()`, and LiteLLM routes | ||
| `oci/<model>` to [Oracle Cloud Infrastructure (OCI) Generative AI](https://docs.oracle.com/en-us/iaas/Content/generative-ai/home.htm). | ||
| Any model in the OCI Generative AI catalog, and any model you import into a | ||
| dedicated endpoint, can drive a NOOA agent without changing agent code. | ||
|
|
||
| ```python | ||
| from nooa.unifiedllm.registry import get_llm_client | ||
|
|
||
| llm = get_llm_client( | ||
| "oci/meta.llama-3.3-70b-instruct", | ||
| oci_region="us-chicago-1", | ||
| oci_compartment_id="ocid1.compartment.oc1..example", | ||
| ) | ||
| ``` | ||
|
|
||
| Requests go to `https://inference.generativeai.<region>.oci.oraclecloud.com`. | ||
| The region defaults to `us-ashburn-1`; set it to a region where your tenancy is | ||
| subscribed to the service. `oci_compartment_id` is required. | ||
|
|
||
| ## Authentication | ||
|
|
||
| LiteLLM signs OCI requests itself. Two options: | ||
|
|
||
| **API-key credentials.** Set the values from your OCI user's API key as | ||
| parameters or as environment variables, which LiteLLM reads automatically: | ||
|
|
||
| ```bash | ||
| export OCI_REGION=us-chicago-1 | ||
| export OCI_COMPARTMENT_ID=ocid1.compartment.oc1..example | ||
| export OCI_USER=ocid1.user.oc1..example | ||
| export OCI_TENANCY=ocid1.tenancy.oc1..example | ||
| export OCI_FINGERPRINT=aa:bb:cc:... | ||
| export OCI_KEY_FILE=~/.oci/oci_api_key.pem # or OCI_KEY with the PEM contents | ||
| ``` | ||
|
|
||
| **An OCI SDK signer.** If you already use the `oci` CLI, reuse a profile from | ||
| `~/.oci/config`, including session-token profiles created by | ||
| `oci session authenticate`, by passing a signer object: | ||
|
|
||
| ```python | ||
| import os | ||
|
|
||
| import oci | ||
|
|
||
| from nooa.unifiedllm.registry import get_llm_client | ||
|
|
||
| config = oci.config.from_file(profile_name="DEFAULT") | ||
| if "security_token_file" in config: | ||
| with open(os.path.expanduser(config["security_token_file"])) as f: | ||
| token = f.read().strip() | ||
| signer = oci.auth.signers.SecurityTokenSigner( | ||
| token, oci.signer.load_private_key_from_file(config["key_file"]) | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
| ) | ||
| else: | ||
| signer = oci.signer.Signer( | ||
| tenancy=config["tenancy"], | ||
| user=config["user"], | ||
| fingerprint=config["fingerprint"], | ||
| private_key_file_location=config["key_file"], | ||
| pass_phrase=config.get("pass_phrase"), | ||
| ) | ||
|
|
||
| llm = get_llm_client( | ||
| "oci/meta.llama-3.3-70b-instruct", | ||
| oci_signer=signer, | ||
| oci_region=config.get("region", "us-ashburn-1"), | ||
| oci_compartment_id=os.environ["OCI_COMPARTMENT_ID"], | ||
| ) | ||
| ``` | ||
|
|
||
| The `oci` SDK is not a NOOA dependency; install it with `uv pip install oci`. | ||
| The quickstart selector implements both options behind `OCI_CLI_PROFILE` and the | ||
| `OCI_*` variables; see [Quickstart selector](#quickstart-selector). | ||
|
|
||
| ## Choose a model | ||
|
|
||
| Use the catalog model id after `oci/`. The default below is the one exercised | ||
| with NOOA's Predict and CodeAct strategies; the others are catalog examples that | ||
| LiteLLM routes the same way: | ||
|
|
||
| | Model string | Notes | | ||
| | --- | --- | | ||
| | `oci/meta.llama-3.3-70b-instruct` | Default; tested with NOOA quickstarts 01, 02, 03, and 16 | | ||
| | `oci/meta.llama-4-maverick-17b-128e-instruct-fp8` | Multimodal | | ||
| | `oci/xai.grok-4` | Reasoning model | | ||
| | `oci/google.gemini-2.5-pro` | Reasoning model, multimodal | | ||
| | `oci/openai.gpt-oss-120b` | Open-weights MoE | | ||
| | `oci/cohere.command-a-03-2025` | Cohere family | | ||
|
|
||
| Availability differs by region. List the catalog for your compartment with | ||
| `oci generative-ai model-collection list-models --compartment-id <ocid> --region <region>`. | ||
|
|
||
| ## NVIDIA Nemotron on OCI | ||
|
|
||
| The Generative AI catalog does not include Nemotron models by default. Two ways to run them: | ||
|
|
||
| **Imported model on a dedicated endpoint.** OCI Generative AI can | ||
| [import open-weights models](https://docs.oracle.com/en-us/iaas/Content/generative-ai/imported-models.htm), | ||
| including NVIDIA Nemotron 3 and Nemotron 3.5 Lightning, onto a dedicated AI | ||
| cluster behind an endpoint. Point LiteLLM at the endpoint: | ||
|
|
||
| ```python | ||
| llm = get_llm_client( | ||
| "oci/my-imported-nemotron", # any name; the endpoint decides the model | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
| oci_serving_mode="DEDICATED", | ||
| oci_endpoint_id="ocid1.generativeaiendpoint.oc1..example", | ||
| oci_region="us-chicago-1", | ||
| oci_compartment_id="ocid1.compartment.oc1..example", | ||
| ) | ||
| ``` | ||
|
|
||
| **Self-hosted on OKE.** Serve Nemotron with vLLM on Oracle Container Engine for | ||
| Kubernetes and use LiteLLM's `hosted_vllm/` route, exactly as in | ||
| [Local models](local-models.md): | ||
|
|
||
| ```python | ||
| llm = get_llm_client( | ||
| "hosted_vllm/nvidia/NVIDIA-Nemotron-3.5-Lightning-30B-A3B-NVFP4", | ||
| api_base="http://127.0.0.1:8000/v1", # e.g. a kubectl port-forward to the vLLM router | ||
| ) | ||
| ``` | ||
|
|
||
| A reference deployment of Nemotron 3.5 Lightning on an OKE A10 node pool is in | ||
| [NVIDIA/nvidia-oci-samples](https://github.com/NVIDIA/nvidia-oci-samples). | ||
|
|
||
| ## Quickstart selector | ||
|
|
||
| The quickstart examples pick a provider from your environment. When | ||
| `OCI_COMPARTMENT_ID` is set and `NVIDIA_API_KEY` is not, they use OCI Generative AI: | ||
|
|
||
| | Variable | Effect | | ||
| | --- | --- | | ||
| | `OCI_COMPARTMENT_ID` | Selects OCI; passed as `oci_compartment_id` | | ||
| | `OCI_REGION` | Passed as `oci_region` (default `us-ashburn-1`) | | ||
| | `OCI_MODEL` | Model string, default `oci/meta.llama-3.3-70b-instruct` | | ||
| | `OCI_ENDPOINT_ID` | Adds `oci_serving_mode="DEDICATED"` and `oci_endpoint_id` | | ||
| | `OCI_CLI_PROFILE` | Signs requests with that `~/.oci/config` profile (API key or session token); needs `uv pip install oci` | | ||
| | `OCI_USER`, `OCI_TENANCY`, `OCI_FINGERPRINT`, `OCI_KEY_FILE` | API-key credentials read by LiteLLM when no profile is given | | ||
|
|
||
| With these variables set, every quickstart in `examples/quickstart/` runs on OCI | ||
| Generative AI unchanged. | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
|
|
||
| ## Aliases | ||
|
|
||
| Put repeated configuration in `.nooa/llm_config.yaml`: | ||
|
|
||
| ```yaml | ||
| models: | ||
| oci-llama: | ||
| model_name: oci/meta.llama-3.3-70b-instruct | ||
| temperature: 0.0 | ||
| ``` | ||
|
|
||
| Registry aliases forward `model_name`, `api_base`, `api_key_env`, and a fixed set | ||
| of generation parameters to LiteLLM. Provider settings such as the region and | ||
| compartment are not part of that set, so supply them through the `OCI_REGION` and | ||
| `OCI_COMPARTMENT_ID` environment variables or as call-site keyword arguments: | ||
|
|
||
| ```python | ||
| llm = get_llm_client("oci-llama") # OCI_* variables set | ||
| llm = get_llm_client("oci-llama", oci_compartment_id="ocid1....") # or override here | ||
| ``` | ||
|
|
||
| ## Troubleshooting | ||
|
|
||
| - `404` or `NotAuthorizedOrNotFound`: the model is not available in `oci_region`, | ||
| or the compartment lacks a policy allowing `generative-ai-family` access. | ||
| - `oci_compartment_id is required`: set `OCI_COMPARTMENT_ID` or pass the parameter. | ||
| - Signature errors with a session-token profile: the token expires after about an | ||
| hour; run `oci session authenticate` again and rebuild the signer. | ||
|
|
||
| References: [LiteLLM OCI provider](https://docs.litellm.ai/docs/providers/oci), | ||
| [OCI Generative AI pretrained models](https://docs.oracle.com/en-us/iaas/Content/generative-ai/pretrained-models.htm). | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| # SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
| # ruff: noqa: F403,F405 | ||
| """Quickstart 16: Oracle Cloud Infrastructure (OCI) Generative AI as the model provider. | ||
|
|
||
| NOOA is model-agnostic through litellm, which routes ``oci/<model>`` to OCI | ||
| Generative AI. The quickstart model selector picks OCI when OCI_COMPARTMENT_ID is | ||
| set, so every quickstart in this directory runs on OCI with the same variables: | ||
|
|
||
| export OCI_COMPARTMENT_ID=ocid1.compartment.oc1..example | ||
| export OCI_REGION=us-chicago-1 # a region with the service | ||
| export OCI_CLI_PROFILE=DEFAULT # an ~/.oci/config profile (needs `uv pip install oci`) | ||
| # or, instead of a profile: OCI_USER, OCI_TENANCY, OCI_FINGERPRINT, OCI_KEY_FILE | ||
| uv run python examples/quickstart/16_oci_generative_ai.py | ||
|
|
||
| Optional: OCI_MODEL (default oci/meta.llama-3.3-70b-instruct) and OCI_ENDPOINT_ID to | ||
| target a dedicated endpoint such as an imported NVIDIA Nemotron model. | ||
|
|
||
| The agent is a capacity planner whose deterministic helpers are its only source | ||
| of facts. See docs/oci-generative-ai.md for explicit client construction, the | ||
| model catalog, aliases, and Nemotron options. | ||
| """ | ||
|
|
||
| import sys | ||
|
|
||
| from nooa.util.quickstart import * | ||
|
|
||
| if not MODEL.startswith("oci/"): | ||
| print( | ||
| f"SKIP: the quickstart selector chose {MODEL!r}. Set OCI_COMPARTMENT_ID (and OCI credentials) to run on OCI." | ||
| ) | ||
| sys.exit(0) | ||
|
|
||
| # GPU count and total GPU memory per OCI shape. The agent reads these through the | ||
| # helper methods instead of recalling specifications from training data. | ||
| GPU_SHAPES: dict[str, dict[str, float]] = { | ||
| "VM.GPU.A10.1": {"gpus": 1, "gpu_memory_gb": 24}, | ||
| "VM.GPU.A10.2": {"gpus": 2, "gpu_memory_gb": 48}, | ||
| "BM.GPU.A10.4": {"gpus": 4, "gpu_memory_gb": 96}, | ||
| "BM.GPU.A100-v2.8": {"gpus": 8, "gpu_memory_gb": 640}, | ||
| "BM.GPU.H100.8": {"gpus": 8, "gpu_memory_gb": 640}, | ||
| } | ||
|
|
||
|
|
||
| class Recommendation(BaseModel): | ||
| shape: str = Field(description="The recommended OCI GPU shape name.") | ||
| total_gpu_memory_gb: float = Field(description="Total GPU memory of that shape in GB.") | ||
| rationale: str = Field(description="One sentence explaining why this is the smallest fit.") | ||
|
|
||
|
|
||
| class CapacityPlanner(Agent, llm=llm): | ||
| """You plan GPU capacity on Oracle Cloud for serving open-weights models.""" | ||
|
|
||
| def gpu_shapes(self) -> dict[str, dict[str, float]]: | ||
| """Return the available OCI GPU shapes with their GPU count and total GPU memory in GB.""" | ||
| return GPU_SHAPES | ||
|
|
||
| def fits(self, weights_gb: float, shape: str, headroom: float = 1.25) -> bool: | ||
| """Whether model weights, with headroom for the KV cache, fit a shape's total GPU memory.""" | ||
| return weights_gb * headroom <= GPU_SHAPES[shape]["gpu_memory_gb"] | ||
|
|
||
| async def recommend(self, model_name: str, weights_gb: float) -> Recommendation: | ||
| """Recommend the smallest shape whose GPU memory fits the model weights with headroom. | ||
|
|
||
| Use self.gpu_shapes() and self.fits() rather than recalling shape specifications. | ||
| """ | ||
| ... | ||
|
|
||
|
|
||
| @autorun | ||
| async def main(): | ||
| planner = CapacityPlanner() | ||
| result = await planner.recommend( | ||
| "NVIDIA Nemotron 3.5 Lightning 30B-A3B (NVFP4)", weights_gb=21.6 | ||
| ) | ||
| print(f"model: {MODEL}") | ||
| print(result) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.