mirror of
https://github.com/BerriAI/litellm.git
synced 2025-04-27 11:43:54 +00:00
Merge d3b75abc86
into b82af5b826
This commit is contained in:
commit
29eea8fe9b
7 changed files with 432 additions and 4 deletions
|
@ -17,9 +17,14 @@ LiteLLM currently covers:
|
||||||
|
|
||||||
|
|
||||||
## **Supported Providers**:
|
## **Supported Providers**:
|
||||||
- [OpenAI](#quick-start)
|
- [Assistants API](#assistants-api)
|
||||||
- [Azure OpenAI](#azure-openai)
|
- [**Supported Providers**:](#supported-providers)
|
||||||
- [OpenAI-Compatible APIs](#openai-compatible-apis)
|
- [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
|
## Quick Start
|
||||||
|
|
||||||
|
@ -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.
|
- 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
|
### SDK + PROXY
|
||||||
<Tabs>
|
<Tabs>
|
||||||
<TabItem value="sdk" label="SDK">
|
<TabItem value="sdk" label="SDK">
|
||||||
|
@ -80,6 +87,36 @@ assistants = get_assistants(custom_llm_provider="openai")
|
||||||
# assistants = await aget_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**
|
**Create a Thread**
|
||||||
|
|
||||||
```python
|
```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>
|
</TabItem>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
|
|
|
@ -581,6 +581,155 @@ def delete_assistant(
|
||||||
)
|
)
|
||||||
return response
|
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 ###
|
### THREADS ###
|
||||||
|
|
||||||
|
|
|
@ -2222,6 +2222,69 @@ class OpenAIAssistantsAPI(BaseLLM):
|
||||||
response = openai_client.beta.assistants.delete(assistant_id=assistant_id)
|
response = openai_client.beta.assistants.delete(assistant_id=assistant_id)
|
||||||
return response
|
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 ###
|
### MESSAGES ###
|
||||||
|
|
||||||
async def a_add_message(
|
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(
|
@router.post(
|
||||||
"/v1/threads",
|
"/v1/threads",
|
||||||
dependencies=[Depends(user_api_key_auth)],
|
dependencies=[Depends(user_api_key_auth)],
|
||||||
|
|
|
@ -721,6 +721,7 @@ class Router:
|
||||||
self.a_add_message = self.factory_function(litellm.a_add_message)
|
self.a_add_message = self.factory_function(litellm.a_add_message)
|
||||||
self.aget_messages = self.factory_function(litellm.aget_messages)
|
self.aget_messages = self.factory_function(litellm.aget_messages)
|
||||||
self.arun_thread = self.factory_function(litellm.arun_thread)
|
self.arun_thread = self.factory_function(litellm.arun_thread)
|
||||||
|
self.amodify_assistants = self.factory_function(litellm.amodify_assistants)
|
||||||
|
|
||||||
def initialize_router_endpoints(self):
|
def initialize_router_endpoints(self):
|
||||||
self.amoderation = self.factory_function(
|
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:
|
except openai.APIError as e:
|
||||||
pass
|
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.acreate_assistants is not None
|
||||||
assert router.adelete_assistant is not None
|
assert router.adelete_assistant is not None
|
||||||
assert router.aget_assistants 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.acreate_thread is not None
|
||||||
assert router.aget_thread is not None
|
assert router.aget_thread is not None
|
||||||
assert router.arun_thread is not None
|
assert router.arun_thread is not None
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue