6b7e6b44f1
Python Build and Type Check / python-ci (ubuntu-latest, 3.11) (push) Has been cancelled
Python Build and Type Check / python-ci (ubuntu-latest, 3.13) (push) Has been cancelled
Python Build and Type Check / python-ci (windows-latest, 3.11) (push) Has been cancelled
Python Build and Type Check / python-ci (windows-latest, 3.13) (push) Has been cancelled
Python Integration Tests / python-ci (ubuntu-latest, 3.13) (push) Has been cancelled
Python Integration Tests / python-ci (windows-latest, 3.13) (push) Has been cancelled
Python Notebook Tests / python-ci (ubuntu-latest, 3.13) (push) Has been cancelled
Python Notebook Tests / python-ci (windows-latest, 3.13) (push) Has been cancelled
Python Smoke Tests / python-ci (ubuntu-latest, 3.13) (push) Has been cancelled
Python Smoke Tests / python-ci (windows-latest, 3.13) (push) Has been cancelled
Python Unit Tests / python-ci (ubuntu-latest, 3.13) (push) Has been cancelled
Python Unit Tests / python-ci (windows-latest, 3.13) (push) Has been cancelled
gh-pages / build (push) Has been cancelled
Python Publish (pypi) / Upload release to PyPI (push) Has been cancelled
Spellcheck / spellcheck (push) Has been cancelled
304 lines
11 KiB
Python
304 lines
11 KiB
Python
# Copyright (c) 2024 Microsoft Corporation.
|
|
# Licensed under the MIT License
|
|
|
|
"""LLMCompletion based on litellm."""
|
|
|
|
from collections.abc import AsyncIterator, Iterator
|
|
from typing import TYPE_CHECKING, Any, Unpack
|
|
|
|
import litellm
|
|
from azure.identity import DefaultAzureCredential, get_bearer_token_provider
|
|
from litellm import ModelResponse # type: ignore
|
|
|
|
from graphrag_llm.completion.completion import LLMCompletion
|
|
from graphrag_llm.config.types import AuthMethod
|
|
from graphrag_llm.middleware import (
|
|
with_middleware_pipeline,
|
|
)
|
|
from graphrag_llm.types import LLMCompletionChunk, LLMCompletionResponse
|
|
from graphrag_llm.utils import (
|
|
structure_completion_response,
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
from graphrag_cache import Cache, CacheKeyCreator
|
|
|
|
from graphrag_llm.config import ModelConfig
|
|
from graphrag_llm.metrics import MetricsProcessor, MetricsStore
|
|
from graphrag_llm.rate_limit import RateLimiter
|
|
from graphrag_llm.retry import Retry
|
|
from graphrag_llm.tokenizer import Tokenizer
|
|
from graphrag_llm.types import (
|
|
AsyncLLMCompletionFunction,
|
|
LLMCompletionArgs,
|
|
LLMCompletionFunction,
|
|
LLMCompletionMessagesParam,
|
|
Metrics,
|
|
ResponseFormat,
|
|
)
|
|
|
|
|
|
litellm.suppress_debug_info = True
|
|
litellm.enable_json_schema_validation = True
|
|
|
|
|
|
class LiteLLMCompletion(LLMCompletion):
|
|
"""LLMCompletion based on litellm."""
|
|
|
|
_model_config: "ModelConfig"
|
|
_model_id: str
|
|
_track_metrics: bool = False
|
|
_metrics_store: "MetricsStore"
|
|
_metrics_processor: "MetricsProcessor | None"
|
|
_cache: "Cache | None"
|
|
_cache_key_creator: "CacheKeyCreator"
|
|
_tokenizer: "Tokenizer"
|
|
_rate_limiter: "RateLimiter | None"
|
|
_retrier: "Retry | None"
|
|
|
|
def __init__(
|
|
self,
|
|
*,
|
|
model_id: str,
|
|
model_config: "ModelConfig",
|
|
tokenizer: "Tokenizer",
|
|
metrics_store: "MetricsStore",
|
|
metrics_processor: "MetricsProcessor | None" = None,
|
|
rate_limiter: "RateLimiter | None" = None,
|
|
retrier: "Retry | None" = None,
|
|
cache: "Cache | None" = None,
|
|
cache_key_creator: "CacheKeyCreator",
|
|
azure_cognitive_services_audience: str = "https://cognitiveservices.azure.com/.default",
|
|
drop_unsupported_params: bool = True,
|
|
**kwargs: Any,
|
|
) -> None:
|
|
"""Initialize LiteLLMCompletion.
|
|
|
|
Args
|
|
----
|
|
model_id: str
|
|
The LiteLLM model ID, e.g., "openai/gpt-4o"
|
|
model_config: ModelConfig
|
|
The configuration for the model.
|
|
tokenizer: Tokenizer
|
|
The tokenizer to use.
|
|
metrics_store: MetricsStore | None (default: None)
|
|
The metrics store to use.
|
|
metrics_processor: MetricsProcessor | None (default: None)
|
|
The metrics processor to use.
|
|
cache: Cache | None (default: None)
|
|
An optional cache instance.
|
|
cache_key_prefix: str | None (default: "chat")
|
|
The cache key prefix. Required if cache is provided.
|
|
rate_limiter: RateLimiter | None (default: None)
|
|
The rate limiter to use.
|
|
retrier: Retry | None (default: None)
|
|
The retry strategy to use.
|
|
azure_cognitive_services_audience: str (default: "https://cognitiveservices.azure.com/.default")
|
|
The audience for Azure Cognitive Services when using Managed Identity.
|
|
drop_unsupported_params: bool (default: True)
|
|
Whether to drop unsupported parameters for the model provider.
|
|
"""
|
|
self._model_id = model_id
|
|
self._model_config = model_config
|
|
self._tokenizer = tokenizer
|
|
self._metrics_store = metrics_store
|
|
self._metrics_processor = metrics_processor
|
|
self._cache = cache
|
|
self._track_metrics = metrics_processor is not None
|
|
self._cache_key_creator = cache_key_creator
|
|
self._rate_limiter = rate_limiter
|
|
self._retrier = retrier
|
|
|
|
self._completion, self._completion_async = _create_base_completions(
|
|
model_config=model_config,
|
|
drop_unsupported_params=drop_unsupported_params,
|
|
azure_cognitive_services_audience=azure_cognitive_services_audience,
|
|
)
|
|
|
|
self._completion, self._completion_async = with_middleware_pipeline(
|
|
model_config=self._model_config,
|
|
model_fn=self._completion,
|
|
async_model_fn=self._completion_async,
|
|
request_type="chat",
|
|
cache=self._cache,
|
|
cache_key_creator=self._cache_key_creator,
|
|
tokenizer=self._tokenizer,
|
|
metrics_processor=self._metrics_processor,
|
|
rate_limiter=self._rate_limiter,
|
|
retrier=self._retrier,
|
|
)
|
|
|
|
def completion(
|
|
self,
|
|
/,
|
|
**kwargs: Unpack["LLMCompletionArgs[ResponseFormat]"],
|
|
) -> "LLMCompletionResponse[ResponseFormat] | Iterator[LLMCompletionChunk]":
|
|
"""Sync completion method."""
|
|
messages: LLMCompletionMessagesParam = kwargs.pop("messages")
|
|
response_format = kwargs.pop("response_format", None)
|
|
|
|
is_streaming = kwargs.get("stream") or False
|
|
|
|
if response_format is not None and is_streaming:
|
|
msg = "response_format is not supported for streaming completions."
|
|
raise ValueError(msg)
|
|
|
|
request_metrics: Metrics | None = kwargs.pop("metrics", None) or {}
|
|
if not self._track_metrics:
|
|
request_metrics = None
|
|
|
|
if isinstance(messages, str):
|
|
messages = [{"role": "user", "content": messages}]
|
|
|
|
try:
|
|
response = self._completion(
|
|
messages=messages,
|
|
metrics=request_metrics,
|
|
response_format=response_format,
|
|
**kwargs, # type: ignore
|
|
)
|
|
if response_format is not None:
|
|
structured_response = structure_completion_response(
|
|
response.content, response_format
|
|
)
|
|
response.formatted_response = structured_response
|
|
return response
|
|
finally:
|
|
if request_metrics is not None:
|
|
self._metrics_store.update_metrics(metrics=request_metrics)
|
|
|
|
async def completion_async(
|
|
self,
|
|
/,
|
|
**kwargs: Unpack["LLMCompletionArgs[ResponseFormat]"],
|
|
) -> "LLMCompletionResponse[ResponseFormat] | AsyncIterator[LLMCompletionChunk]":
|
|
"""Async completion method."""
|
|
messages: LLMCompletionMessagesParam = kwargs.pop("messages")
|
|
response_format = kwargs.pop("response_format", None)
|
|
|
|
is_streaming = kwargs.get("stream") or False
|
|
|
|
if response_format is not None and is_streaming:
|
|
msg = "response_format is not supported for streaming completions."
|
|
raise ValueError(msg)
|
|
|
|
request_metrics: Metrics | None = kwargs.pop("metrics", None) or {}
|
|
if not self._track_metrics:
|
|
request_metrics = None
|
|
|
|
if isinstance(messages, str):
|
|
messages = [{"role": "user", "content": messages}]
|
|
|
|
try:
|
|
response = await self._completion_async(
|
|
messages=messages,
|
|
metrics=request_metrics,
|
|
response_format=response_format,
|
|
**kwargs, # type: ignore
|
|
)
|
|
if response_format is not None:
|
|
structured_response = structure_completion_response(
|
|
response.content, response_format
|
|
)
|
|
response.formatted_response = structured_response
|
|
return response
|
|
finally:
|
|
if request_metrics is not None:
|
|
self._metrics_store.update_metrics(metrics=request_metrics)
|
|
|
|
@property
|
|
def metrics_store(self) -> "MetricsStore":
|
|
"""Get metrics store."""
|
|
return self._metrics_store
|
|
|
|
@property
|
|
def tokenizer(self) -> "Tokenizer":
|
|
"""Get tokenizer."""
|
|
return self._tokenizer
|
|
|
|
|
|
def _create_base_completions(
|
|
*,
|
|
model_config: "ModelConfig",
|
|
drop_unsupported_params: bool,
|
|
azure_cognitive_services_audience: str,
|
|
) -> tuple["LLMCompletionFunction", "AsyncLLMCompletionFunction"]:
|
|
"""Create base completions for LiteLLM.
|
|
|
|
Convert litellm completion functions to graphrag_llm LLMCompletionFunction.
|
|
LLMCompletionFunction is close to the litellm completion function signature,
|
|
but uses a few extra params such as metrics. Remove graphrag_llm LLMCompletionFunction
|
|
specific params before calling litellm completion functions.
|
|
"""
|
|
model_provider = model_config.model_provider
|
|
model = model_config.azure_deployment_name or model_config.model
|
|
|
|
base_args: dict[str, Any] = {
|
|
"drop_params": drop_unsupported_params,
|
|
"model": f"{model_provider}/{model}",
|
|
"api_key": model_config.api_key,
|
|
"api_base": model_config.api_base,
|
|
"api_version": model_config.api_version,
|
|
**model_config.call_args,
|
|
}
|
|
|
|
if model_config.auth_method == AuthMethod.AzureManagedIdentity:
|
|
base_args["azure_ad_token_provider"] = get_bearer_token_provider(
|
|
DefaultAzureCredential(), azure_cognitive_services_audience
|
|
)
|
|
|
|
def _base_completion(
|
|
**kwargs: Any,
|
|
) -> LLMCompletionResponse | Iterator[LLMCompletionChunk]:
|
|
kwargs.pop("metrics", None)
|
|
mock_response: str | None = kwargs.pop("mock_response", None)
|
|
json_object: bool | None = kwargs.pop("response_format_json_object", None)
|
|
new_args: dict[str, Any] = {**base_args, **kwargs}
|
|
|
|
if model_config.mock_responses and mock_response is not None:
|
|
new_args["mock_response"] = mock_response
|
|
|
|
if json_object and "response_format" not in new_args:
|
|
new_args["response_format"] = {"type": "json_object"}
|
|
|
|
response = litellm.completion(
|
|
**new_args,
|
|
)
|
|
if isinstance(response, ModelResponse):
|
|
return LLMCompletionResponse(**response.model_dump())
|
|
|
|
def _run_iterator() -> Iterator[LLMCompletionChunk]:
|
|
for chunk in response:
|
|
yield LLMCompletionChunk(**chunk.model_dump())
|
|
|
|
return _run_iterator()
|
|
|
|
async def _base_completion_async(
|
|
**kwargs: Any,
|
|
) -> LLMCompletionResponse | AsyncIterator[LLMCompletionChunk]:
|
|
kwargs.pop("metrics", None)
|
|
mock_response: str | None = kwargs.pop("mock_response", None)
|
|
json_object: bool | None = kwargs.pop("response_format_json_object", None)
|
|
new_args: dict[str, Any] = {**base_args, **kwargs}
|
|
|
|
if model_config.mock_responses and mock_response is not None:
|
|
new_args["mock_response"] = mock_response
|
|
|
|
if json_object and "response_format" not in new_args:
|
|
new_args["response_format"] = {"type": "json_object"}
|
|
|
|
response = await litellm.acompletion(
|
|
**new_args,
|
|
)
|
|
if isinstance(response, ModelResponse):
|
|
return LLMCompletionResponse(**response.model_dump())
|
|
|
|
async def _run_iterator() -> AsyncIterator[LLMCompletionChunk]:
|
|
async for chunk in response:
|
|
yield LLMCompletionChunk(**chunk.model_dump()) # type: ignore
|
|
|
|
return _run_iterator()
|
|
|
|
return (_base_completion, _base_completion_async)
|