mirror of
https://github.com/BerriAI/litellm.git
synced 2025-04-24 18:24:20 +00:00
Merge d3b75abc86
into b82af5b826
This commit is contained in:
commit
29eea8fe9b
7 changed files with 432 additions and 4 deletions
|
@ -17,15 +17,20 @@ LiteLLM currently covers:
|
|||
|
||||
|
||||
## **Supported Providers**:
|
||||
- [OpenAI](#quick-start)
|
||||
- [Azure OpenAI](#azure-openai)
|
||||
- [OpenAI-Compatible APIs](#openai-compatible-apis)
|
||||
- [Assistants API](#assistants-api)
|
||||
- [**Supported Providers**:](#supported-providers)
|
||||
- [Quick Start](#quick-start)
|
||||
- [SDK + PROXY](#sdk--proxy)
|
||||
- [Streaming](#streaming)
|
||||
- [👉 Proxy API Reference](#-proxy-api-reference)
|
||||
- [Azure OpenAI](#azure-openai)
|
||||
- [OpenAI-Compatible APIs](#openai-compatible-apis)
|
||||
|
||||
## Quick Start
|
||||
|
||||
Call an existing Assistant.
|
||||
|
||||
- Get the Assistant
|
||||
- Get the Assistant
|
||||
|
||||
- Create a Thread when a user starts a conversation.
|
||||
|
||||
|
@ -33,6 +38,8 @@ Call an existing Assistant.
|
|||
|
||||
- Run the Assistant on the Thread to generate a response by calling the model and the tools.
|
||||
|
||||
- Modify the Assistant's details if necessary (name, instructions, tools, etc.)
|
||||
|
||||
### SDK + PROXY
|
||||
<Tabs>
|
||||
<TabItem value="sdk" label="SDK">
|
||||
|
@ -80,6 +87,36 @@ assistants = get_assistants(custom_llm_provider="openai")
|
|||
# assistants = await aget_assistants(custom_llm_provider="openai")
|
||||
```
|
||||
|
||||
|
||||
**Modify the Assistant**
|
||||
|
||||
```python
|
||||
from litellm import modify_assistants, amodify_assistants
|
||||
|
||||
# setup env
|
||||
os.environ["OPENAI_API_KEY"] = "sk-.."
|
||||
|
||||
# Modify Assistant (sync)
|
||||
modified_assistant = modify_assistants(
|
||||
custom_llm_provider="openai",
|
||||
assistant_id="asst_S4IW1JplxrRtsb5sQOXmztf4",
|
||||
model="gpt-4o",
|
||||
name="Updated Python Assistant",
|
||||
instructions="You are an advanced Python tutor.",
|
||||
tools=[{"type": "code_interpreter"}],
|
||||
)
|
||||
|
||||
### ASYNC USAGE ###
|
||||
# modified_assistant = await amodify_assistants(
|
||||
# custom_llm_provider="openai",
|
||||
# assistant_id="asst_S4IW1JplxrRtsb5sQOXmztf4",
|
||||
# model="gpt-4o",
|
||||
# name="Updated Python Assistant",
|
||||
# instructions="You are an advanced Python tutor.",
|
||||
# tools=[{"type": "code_interpreter"}],
|
||||
# )
|
||||
```
|
||||
|
||||
**Create a Thread**
|
||||
|
||||
```python
|
||||
|
@ -246,6 +283,19 @@ curl http://0.0.0.0:4000/v1/threads/thread_abc123/runs \
|
|||
}'
|
||||
```
|
||||
|
||||
**Modify an Assistant**
|
||||
```bash
|
||||
curl -X PATCH "http://0.0.0.0:4000/v1/assistants/{assistant_id}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer sk-1234" \
|
||||
-d '{
|
||||
"name": "Updated Math Tutor",
|
||||
"instructions": "You are a Python coding expert who can solve complex math problems.",
|
||||
"tools": [{"type": "code_interpreter"}],
|
||||
"model": "gpt-4o"
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
|
|
|
@ -581,6 +581,155 @@ def delete_assistant(
|
|||
)
|
||||
return response
|
||||
|
||||
async def amodify_assistants(
|
||||
custom_llm_provider: Literal["openai", "azure"],
|
||||
assistant_id: str,
|
||||
**kwargs,
|
||||
) -> Assistant:
|
||||
loop = asyncio.get_event_loop()
|
||||
kwargs["async_modify_assistants"] = True
|
||||
|
||||
kwargs = {k: v for k, v in kwargs.items() if v is not None}
|
||||
|
||||
func = partial(modify_assistants, custom_llm_provider, assistant_id, **kwargs)
|
||||
|
||||
ctx = contextvars.copy_context()
|
||||
func_with_context = partial(ctx.run, func)
|
||||
|
||||
_, custom_llm_provider, _, _ = get_llm_provider( # type: ignore
|
||||
model=kwargs.get("model", ""), custom_llm_provider=custom_llm_provider
|
||||
) # type: ignore
|
||||
|
||||
try:
|
||||
init_response = await loop.run_in_executor(None, func_with_context)
|
||||
if asyncio.iscoroutine(init_response):
|
||||
response = await init_response
|
||||
else:
|
||||
response = init_response
|
||||
return response # type: ignore
|
||||
except Exception as e:
|
||||
raise exception_type(
|
||||
model=kwargs.get("model", ""),
|
||||
custom_llm_provider=custom_llm_provider,
|
||||
original_exception=e,
|
||||
completion_kwargs={},
|
||||
extra_kwargs=kwargs,
|
||||
)
|
||||
|
||||
|
||||
def modify_assistants(
|
||||
custom_llm_provider: Literal["openai", "azure"],
|
||||
assistant_id: str,
|
||||
model: Optional[str] = None,
|
||||
name: Optional[str] = None,
|
||||
description: Optional[str] = None,
|
||||
instructions: Optional[str] = None,
|
||||
tools: Optional[List[Dict[str, Any]]] = None,
|
||||
tool_resources: Optional[Dict[str, Any]] = None,
|
||||
metadata: Optional[Dict[str, str]] = None,
|
||||
temperature: Optional[float] = None,
|
||||
top_p: Optional[float] = None,
|
||||
response_format: Optional[Union[str, Dict[str, str]]] = None,
|
||||
client: Optional[Any] = None,
|
||||
api_key: Optional[str] = None,
|
||||
api_base: Optional[str] = None,
|
||||
api_version: Optional[str] = None,
|
||||
**kwargs,
|
||||
) -> Union[Assistant, Coroutine[Any, Any, Assistant]]:
|
||||
async_modify_assistants: Optional[bool] = kwargs.pop("async_modify_assistants", None)
|
||||
if async_modify_assistants is not None and not isinstance(async_modify_assistants, bool):
|
||||
raise ValueError(
|
||||
"Invalid value passed in for async_modify_assistants. Only bool or None allowed."
|
||||
)
|
||||
|
||||
optional_params = GenericLiteLLMParams(
|
||||
api_key=api_key, api_base=api_base, api_version=api_version, **kwargs
|
||||
)
|
||||
|
||||
timeout = optional_params.timeout or kwargs.get("request_timeout", 600) or 600
|
||||
|
||||
if (
|
||||
timeout is not None
|
||||
and isinstance(timeout, httpx.Timeout)
|
||||
and supports_httpx_timeout(custom_llm_provider) is False
|
||||
):
|
||||
read_timeout = timeout.read or 600
|
||||
timeout = read_timeout
|
||||
elif timeout is not None and not isinstance(timeout, httpx.Timeout):
|
||||
timeout = float(timeout) # type: ignore
|
||||
elif timeout is None:
|
||||
timeout = 600.0
|
||||
|
||||
modify_assistant_data = {
|
||||
"model": model,
|
||||
"name": name,
|
||||
"description": description,
|
||||
"instructions": instructions,
|
||||
"tools": tools,
|
||||
"tool_resources": tool_resources,
|
||||
"metadata": metadata,
|
||||
"temperature": temperature,
|
||||
"top_p": top_p,
|
||||
"response_format": response_format,
|
||||
}
|
||||
|
||||
modify_assistant_data = {k: v for k, v in modify_assistant_data.items() if v is not None}
|
||||
|
||||
response: Optional[Union[Coroutine[Any, Any, Assistant], Assistant]] = None
|
||||
|
||||
if custom_llm_provider == "openai":
|
||||
api_base = (
|
||||
optional_params.api_base
|
||||
or litellm.api_base
|
||||
or os.getenv("OPENAI_API_BASE")
|
||||
or "https://api.openai.com/v1"
|
||||
)
|
||||
organization = (
|
||||
optional_params.organization
|
||||
or litellm.organization
|
||||
or os.getenv("OPENAI_ORGANIZATION", None)
|
||||
or None
|
||||
)
|
||||
api_key = (
|
||||
optional_params.api_key
|
||||
or litellm.api_key
|
||||
or litellm.openai_key
|
||||
or os.getenv("OPENAI_API_KEY")
|
||||
)
|
||||
|
||||
response = openai_assistants_api.modify_assistants(
|
||||
assistant_id=assistant_id,
|
||||
api_base=api_base,
|
||||
api_key=api_key,
|
||||
timeout=timeout,
|
||||
max_retries=optional_params.max_retries,
|
||||
organization=organization,
|
||||
modify_assistant_data=modify_assistant_data,
|
||||
client=client,
|
||||
async_modify_assistants=async_modify_assistants, # type: ignore
|
||||
)
|
||||
else:
|
||||
raise litellm.exceptions.BadRequestError(
|
||||
message="LiteLLM doesn't support {} for 'modify_assistants'. Only 'openai' is supported.".format(
|
||||
custom_llm_provider
|
||||
),
|
||||
model="n/a",
|
||||
llm_provider=custom_llm_provider,
|
||||
response=httpx.Response(
|
||||
status_code=400,
|
||||
content="Unsupported provider",
|
||||
request=httpx.Request(method="update_thread", url="https://github.com/BerriAI/litellm"), # type: ignore
|
||||
),
|
||||
)
|
||||
|
||||
if response is None:
|
||||
raise litellm.exceptions.InternalServerError(
|
||||
message="No response returned from 'modify_assistants'",
|
||||
model=model or "n/a",
|
||||
llm_provider=custom_llm_provider,
|
||||
)
|
||||
return response
|
||||
|
||||
|
||||
### THREADS ###
|
||||
|
||||
|
|
|
@ -2222,6 +2222,69 @@ class OpenAIAssistantsAPI(BaseLLM):
|
|||
response = openai_client.beta.assistants.delete(assistant_id=assistant_id)
|
||||
return response
|
||||
|
||||
# Modify Assistant
|
||||
async def async_modify_assistants(
|
||||
self,
|
||||
assistant_id: str,
|
||||
api_key: Optional[str],
|
||||
api_base: Optional[str],
|
||||
timeout: Union[float, httpx.Timeout],
|
||||
max_retries: Optional[int],
|
||||
organization: Optional[str],
|
||||
client: Optional[AsyncOpenAI],
|
||||
modify_assistant_data: dict,
|
||||
) -> Assistant:
|
||||
openai_client = self.async_get_openai_client(
|
||||
api_key=api_key,
|
||||
api_base=api_base,
|
||||
timeout=timeout,
|
||||
max_retries=max_retries,
|
||||
organization=organization,
|
||||
client=client,
|
||||
)
|
||||
|
||||
response = await openai_client.beta.assistants.update(assistant_id, **modify_assistant_data)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
def modify_assistants(
|
||||
self,
|
||||
assistant_id: str,
|
||||
api_key: Optional[str],
|
||||
api_base: Optional[str],
|
||||
timeout: Union[float, httpx.Timeout],
|
||||
max_retries: Optional[int],
|
||||
organization: Optional[str],
|
||||
modify_assistant_data: dict,
|
||||
client=None,
|
||||
async_modify_assistants=None,
|
||||
):
|
||||
if async_modify_assistants is not None and async_modify_assistants is True:
|
||||
return self.async_modify_assistants(
|
||||
assistant_id=assistant_id,
|
||||
api_key=api_key,
|
||||
api_base=api_base,
|
||||
timeout=timeout,
|
||||
max_retries=max_retries,
|
||||
organization=organization,
|
||||
client=client,
|
||||
modify_assistant_data=modify_assistant_data,
|
||||
)
|
||||
|
||||
openai_client = self.get_openai_client(
|
||||
api_key=api_key,
|
||||
api_base=api_base,
|
||||
timeout=timeout,
|
||||
max_retries=max_retries,
|
||||
organization=organization,
|
||||
client=client,
|
||||
)
|
||||
|
||||
response = openai_client.beta.assistants.update(assistant_id, **modify_assistant_data)
|
||||
return response
|
||||
|
||||
|
||||
### MESSAGES ###
|
||||
|
||||
async def a_add_message(
|
||||
|
|
|
@ -4630,6 +4630,100 @@ async def delete_assistant(
|
|||
)
|
||||
|
||||
|
||||
@router.patch(
|
||||
"/v1/assistants/{assistant_id}",
|
||||
dependencies=[Depends(user_api_key_auth)],
|
||||
tags=["assistants"],
|
||||
)
|
||||
@router.patch(
|
||||
"/assistants/{assistant_id}",
|
||||
dependencies=[Depends(user_api_key_auth)],
|
||||
tags=["assistants"],
|
||||
)
|
||||
async def modify_assistant(
|
||||
assistant_id: str,
|
||||
request: Request,
|
||||
fastapi_response: Response,
|
||||
user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
|
||||
):
|
||||
"""
|
||||
Modify assistant
|
||||
|
||||
API Reference docs - https://platform.openai.com/docs/api-reference/assistants/modifyAssistant
|
||||
"""
|
||||
global proxy_logging_obj
|
||||
data = {}
|
||||
try:
|
||||
body = await request.body()
|
||||
data = orjson.loads(body)
|
||||
|
||||
data = await add_litellm_data_to_request(
|
||||
data=data,
|
||||
request=request,
|
||||
general_settings=general_settings,
|
||||
user_api_key_dict=user_api_key_dict,
|
||||
version=version,
|
||||
proxy_config=proxy_config,
|
||||
)
|
||||
|
||||
data["assistant_id"] = assistant_id
|
||||
|
||||
if llm_router is None:
|
||||
raise HTTPException(
|
||||
status_code=500, detail={"error": CommonProxyErrors.no_llm_router.value}
|
||||
)
|
||||
|
||||
response = await llm_router.amodify_assistants(**data)
|
||||
|
||||
asyncio.create_task(
|
||||
proxy_logging_obj.update_request_status(
|
||||
litellm_call_id=data.get("litellm_call_id", ""), status="success"
|
||||
)
|
||||
)
|
||||
|
||||
hidden_params = getattr(response, "_hidden_params", {}) or {}
|
||||
model_id = hidden_params.get("model_id", "") or ""
|
||||
cache_key = hidden_params.get("cache_key", "") or ""
|
||||
api_base = hidden_params.get("api_base", "") or ""
|
||||
|
||||
fastapi_response.headers.update(
|
||||
get_custom_headers(
|
||||
user_api_key_dict=user_api_key_dict,
|
||||
model_id=model_id,
|
||||
cache_key=cache_key,
|
||||
api_base=api_base,
|
||||
version=version,
|
||||
model_region=getattr(user_api_key_dict, "allowed_model_region", ""),
|
||||
request_data=data,
|
||||
)
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
await proxy_logging_obj.post_call_failure_hook(
|
||||
user_api_key_dict=user_api_key_dict, original_exception=e, request_data=data
|
||||
)
|
||||
verbose_proxy_logger.error(
|
||||
f"litellm.proxy.proxy_server.modify_assistant(): Exception occurred - {str(e)}"
|
||||
)
|
||||
verbose_proxy_logger.debug(traceback.format_exc())
|
||||
if isinstance(e, HTTPException):
|
||||
raise ProxyException(
|
||||
message=getattr(e, "message", str(e.detail)),
|
||||
type=getattr(e, "type", "None"),
|
||||
param=getattr(e, "param", "None"),
|
||||
code=getattr(e, "status_code", status.HTTP_400_BAD_REQUEST),
|
||||
)
|
||||
else:
|
||||
error_msg = f"{str(e)}"
|
||||
raise ProxyException(
|
||||
message=error_msg,
|
||||
type=getattr(e, "type", "None"),
|
||||
param=getattr(e, "param", "None"),
|
||||
code=getattr(e, "status_code", 500),
|
||||
)
|
||||
|
||||
@router.post(
|
||||
"/v1/threads",
|
||||
dependencies=[Depends(user_api_key_auth)],
|
||||
|
|
|
@ -721,6 +721,7 @@ class Router:
|
|||
self.a_add_message = self.factory_function(litellm.a_add_message)
|
||||
self.aget_messages = self.factory_function(litellm.aget_messages)
|
||||
self.arun_thread = self.factory_function(litellm.arun_thread)
|
||||
self.amodify_assistants = self.factory_function(litellm.amodify_assistants)
|
||||
|
||||
def initialize_router_endpoints(self):
|
||||
self.amoderation = self.factory_function(
|
||||
|
|
|
@ -329,3 +329,73 @@ async def test_aarun_thread_litellm(sync_mode, provider, is_streaming):
|
|||
)
|
||||
except openai.APIError as e:
|
||||
pass
|
||||
|
||||
|
||||
@pytest.mark.parametrize("provider", ["openai"])
|
||||
@pytest.mark.parametrize("sync_mode", [True, False])
|
||||
@pytest.mark.asyncio
|
||||
async def test_modify_assistant(provider, sync_mode):
|
||||
"""
|
||||
Test for modifying an assistant's configuration.
|
||||
"""
|
||||
model = "gpt-4o"
|
||||
assistant_name = "Test Assistant"
|
||||
initial_instructions = "You are a coding assistant."
|
||||
modified_instructions = "You are an advanced coding assistant specialized in Python."
|
||||
|
||||
# 1. Assistant creation
|
||||
if sync_mode:
|
||||
assistant = litellm.create_assistants(
|
||||
custom_llm_provider=provider,
|
||||
model=model,
|
||||
instructions=initial_instructions,
|
||||
name=assistant_name,
|
||||
tools=[{"type": "code_interpreter"}],
|
||||
)
|
||||
else:
|
||||
assistant = await litellm.acreate_assistants(
|
||||
custom_llm_provider=provider,
|
||||
model=model,
|
||||
instructions=initial_instructions,
|
||||
name=assistant_name,
|
||||
tools=[{"type": "code_interpreter"}],
|
||||
)
|
||||
|
||||
assert isinstance(assistant, Assistant)
|
||||
assert assistant.instructions == initial_instructions
|
||||
assert assistant.id is not None
|
||||
|
||||
# 2. Assistant modification
|
||||
if sync_mode:
|
||||
modified_assistant = litellm.modify_assistants(
|
||||
custom_llm_provider=provider,
|
||||
model=model,
|
||||
assistant_id=assistant.id,
|
||||
instructions=modified_instructions,
|
||||
name="Modified Test Assistant",
|
||||
)
|
||||
else:
|
||||
modified_assistant = await litellm.amodify_assistants(
|
||||
custom_llm_provider=provider,
|
||||
model=model,
|
||||
assistant_id=assistant.id,
|
||||
instructions=modified_instructions,
|
||||
name="Modified Test Assistant",
|
||||
)
|
||||
|
||||
# 3. Assert modifications
|
||||
assert modified_assistant.instructions == modified_instructions
|
||||
assert modified_assistant.name == "Modified Test Assistant"
|
||||
assert modified_assistant.id == assistant.id
|
||||
|
||||
# 4. Clean created assistant for test
|
||||
if sync_mode:
|
||||
response = litellm.delete_assistant(
|
||||
custom_llm_provider=provider, assistant_id=assistant.id
|
||||
)
|
||||
else:
|
||||
response = await litellm.adelete_assistant(
|
||||
custom_llm_provider=provider, assistant_id=assistant.id
|
||||
)
|
||||
|
||||
assert response.id == assistant.id
|
||||
|
|
|
@ -938,6 +938,7 @@ def test_initialize_assistants_endpoint(model_list):
|
|||
assert router.acreate_assistants is not None
|
||||
assert router.adelete_assistant is not None
|
||||
assert router.aget_assistants is not None
|
||||
assert router.amodify_assistants is not None
|
||||
assert router.acreate_thread is not None
|
||||
assert router.aget_thread is not None
|
||||
assert router.arun_thread is not None
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue