forked from phoenix-oss/llama-stack-mirror
# What does this PR do? This stubs in some OpenAI server-side compatibility with three new endpoints: /v1/openai/v1/models /v1/openai/v1/completions /v1/openai/v1/chat/completions This gives common inference apps using OpenAI clients the ability to talk to Llama Stack using an endpoint like http://localhost:8321/v1/openai/v1 . The two "v1" instances in there isn't awesome, but the thinking is that Llama Stack's API is v1 and then our OpenAI compatibility layer is compatible with OpenAI V1. And, some OpenAI clients implicitly assume the URL ends with "v1", so this gives maximum compatibility. The openai models endpoint is implemented in the routing layer, and just returns all the models Llama Stack knows about. The following providers should be working with the new OpenAI completions and chat/completions API: * remote::anthropic (untested) * remote::cerebras-openai-compat (untested) * remote::fireworks (tested) * remote::fireworks-openai-compat (untested) * remote::gemini (untested) * remote::groq-openai-compat (untested) * remote::nvidia (tested) * remote::ollama (tested) * remote::openai (untested) * remote::passthrough (untested) * remote::sambanova-openai-compat (untested) * remote::together (tested) * remote::together-openai-compat (untested) * remote::vllm (tested) The goal to support this for every inference provider - proxying directly to the provider's OpenAI endpoint for OpenAI-compatible providers. For providers that don't have an OpenAI-compatible API, we'll add a mixin to translate incoming OpenAI requests to Llama Stack inference requests and translate the Llama Stack inference responses to OpenAI responses. This is related to #1817 but is a bit larger in scope than just chat completions, as I have real use-cases that need the older completions API as well. ## Test Plan ### vLLM ``` VLLM_URL="http://localhost:8000/v1" INFERENCE_MODEL="meta-llama/Llama-3.2-3B-Instruct" llama stack build --template remote-vllm --image-type venv --run LLAMA_STACK_CONFIG=http://localhost:8321 INFERENCE_MODEL="meta-llama/Llama-3.2-3B-Instruct" python -m pytest -v tests/integration/inference/test_openai_completion.py --text-model "meta-llama/Llama-3.2-3B-Instruct" ``` ### ollama ``` INFERENCE_MODEL="llama3.2:3b-instruct-q8_0" llama stack build --template ollama --image-type venv --run LLAMA_STACK_CONFIG=http://localhost:8321 INFERENCE_MODEL="llama3.2:3b-instruct-q8_0" python -m pytest -v tests/integration/inference/test_openai_completion.py --text-model "llama3.2:3b-instruct-q8_0" ``` ## Documentation Run a Llama Stack distribution that uses one of the providers mentioned in the list above. Then, use your favorite OpenAI client to send completion or chat completion requests with the base_url set to http://localhost:8321/v1/openai/v1 . Replace "localhost:8321" with the host and port of your Llama Stack server, if different. --------- Signed-off-by: Ben Browning <bbrownin@redhat.com>
108 lines
2.8 KiB
Python
108 lines
2.8 KiB
Python
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
# All rights reserved.
|
|
#
|
|
# This source code is licensed under the terms described in the LICENSE file in
|
|
# the root directory of this source tree.
|
|
|
|
from enum import Enum
|
|
from typing import Any, Dict, List, Literal, Optional, Protocol, runtime_checkable
|
|
|
|
from pydantic import BaseModel, ConfigDict, Field
|
|
|
|
from llama_stack.apis.resource import Resource, ResourceType
|
|
from llama_stack.providers.utils.telemetry.trace_protocol import trace_protocol
|
|
from llama_stack.schema_utils import json_schema_type, webmethod
|
|
|
|
|
|
class CommonModelFields(BaseModel):
|
|
metadata: Dict[str, Any] = Field(
|
|
default_factory=dict,
|
|
description="Any additional metadata for this model",
|
|
)
|
|
|
|
|
|
@json_schema_type
|
|
class ModelType(str, Enum):
|
|
llm = "llm"
|
|
embedding = "embedding"
|
|
|
|
|
|
@json_schema_type
|
|
class Model(CommonModelFields, Resource):
|
|
type: Literal[ResourceType.model.value] = ResourceType.model.value
|
|
|
|
@property
|
|
def model_id(self) -> str:
|
|
return self.identifier
|
|
|
|
@property
|
|
def provider_model_id(self) -> str:
|
|
return self.provider_resource_id
|
|
|
|
model_config = ConfigDict(protected_namespaces=())
|
|
|
|
model_type: ModelType = Field(default=ModelType.llm)
|
|
|
|
|
|
class ModelInput(CommonModelFields):
|
|
model_id: str
|
|
provider_id: Optional[str] = None
|
|
provider_model_id: Optional[str] = None
|
|
model_type: Optional[ModelType] = ModelType.llm
|
|
model_config = ConfigDict(protected_namespaces=())
|
|
|
|
|
|
class ListModelsResponse(BaseModel):
|
|
data: List[Model]
|
|
|
|
|
|
@json_schema_type
|
|
class OpenAIModel(BaseModel):
|
|
"""A model from OpenAI.
|
|
|
|
:id: The ID of the model
|
|
:object: The object type, which will be "model"
|
|
:created: The Unix timestamp in seconds when the model was created
|
|
:owned_by: The owner of the model
|
|
"""
|
|
|
|
id: str
|
|
object: Literal["model"] = "model"
|
|
created: int
|
|
owned_by: str
|
|
|
|
|
|
class OpenAIListModelsResponse(BaseModel):
|
|
data: List[OpenAIModel]
|
|
|
|
|
|
@runtime_checkable
|
|
@trace_protocol
|
|
class Models(Protocol):
|
|
@webmethod(route="/models", method="GET")
|
|
async def list_models(self) -> ListModelsResponse: ...
|
|
|
|
@webmethod(route="/openai/v1/models", method="GET")
|
|
async def openai_list_models(self) -> OpenAIListModelsResponse: ...
|
|
|
|
@webmethod(route="/models/{model_id:path}", method="GET")
|
|
async def get_model(
|
|
self,
|
|
model_id: str,
|
|
) -> Model: ...
|
|
|
|
@webmethod(route="/models", method="POST")
|
|
async def register_model(
|
|
self,
|
|
model_id: str,
|
|
provider_model_id: Optional[str] = None,
|
|
provider_id: Optional[str] = None,
|
|
metadata: Optional[Dict[str, Any]] = None,
|
|
model_type: Optional[ModelType] = None,
|
|
) -> Model: ...
|
|
|
|
@webmethod(route="/models/{model_id:path}", method="DELETE")
|
|
async def unregister_model(
|
|
self,
|
|
model_id: str,
|
|
) -> None: ...
|