mirror of
https://github.com/AstrBotDevs/AstrBot
synced 2026-07-17 01:49:15 +08:00
188 lines
5.7 KiB
Python
188 lines
5.7 KiB
Python
"""Mock providers for testing LLM interactions."""
|
|
|
|
from typing import Any
|
|
from collections.abc import AsyncGenerator
|
|
from dataclasses import dataclass, field
|
|
|
|
from astrbot.core.provider.entities import (
|
|
ProviderRequest,
|
|
ProviderMeta,
|
|
ProviderType,
|
|
LLMResponse,
|
|
)
|
|
|
|
|
|
class MockChatCompletionProvider:
|
|
"""Mock provider that returns predefined responses for testing."""
|
|
|
|
def __init__(
|
|
self,
|
|
responses: list[LLMResponse] | None = None,
|
|
streaming_responses: list[list[str]] | None = None,
|
|
):
|
|
self.responses = responses or []
|
|
self.streaming_responses = streaming_responses or []
|
|
self.response_index = 0
|
|
self.call_count = 0
|
|
self.last_request: ProviderRequest | None = None
|
|
|
|
self.meta = ProviderMeta(
|
|
id="mock_provider",
|
|
model="mock-model",
|
|
type="mock",
|
|
provider_type=ProviderType.CHAT_COMPLETION,
|
|
)
|
|
|
|
async def chat(
|
|
self, request: ProviderRequest
|
|
) -> LLMResponse | AsyncGenerator[LLMResponse, None]:
|
|
"""Return mock chat completion."""
|
|
self.call_count += 1
|
|
self.last_request = request
|
|
|
|
if self.responses:
|
|
response = self.responses[self.response_index % len(self.responses)]
|
|
self.response_index += 1
|
|
return response
|
|
|
|
# Default mock response
|
|
return LLMResponse(
|
|
role="assistant",
|
|
completion_text="This is a mock response from the test provider.",
|
|
)
|
|
|
|
async def stream_chat(self, request: ProviderRequest) -> AsyncGenerator[LLMResponse, None]:
|
|
"""Return mock streaming chat completion."""
|
|
self.call_count += 1
|
|
self.last_request = request
|
|
|
|
text = "This is a mock streaming response."
|
|
if self.streaming_responses:
|
|
text = self.streaming_responses[self.response_index % len(self.streaming_responses)]
|
|
self.response_index += 1
|
|
|
|
# Stream word by word
|
|
for word in text.split(): # type: ignore[union-attr]
|
|
yield LLMResponse(
|
|
role="assistant",
|
|
completion_text=word + " ",
|
|
)
|
|
|
|
def get_model(self) -> str:
|
|
return "mock-model"
|
|
|
|
def get_provider_type(self) -> ProviderType:
|
|
return ProviderType.CHAT_COMPLETION
|
|
|
|
|
|
class MockToolCallProvider(MockChatCompletionProvider):
|
|
"""Mock provider that returns tool calls."""
|
|
|
|
def __init__(
|
|
self,
|
|
tool_calls: list[dict] | None = None,
|
|
tool_response: str = "Tool executed successfully",
|
|
):
|
|
super().__init__()
|
|
self.tool_calls = tool_calls or []
|
|
self.tool_response = tool_response
|
|
self.tool_call_count = 0
|
|
|
|
async def chat(self, request: ProviderRequest) -> LLMResponse:
|
|
"""Return mock tool call."""
|
|
self.call_count += 1
|
|
|
|
if self.tool_calls:
|
|
tool_call = self.tool_calls[self.tool_call_count % len(self.tool_calls)]
|
|
self.tool_call_count += 1
|
|
return LLMResponse(
|
|
role="assistant",
|
|
completion_text="",
|
|
tool_calls=[tool_call], # type: ignore[call-arg]
|
|
)
|
|
|
|
# Default tool call
|
|
return LLMResponse(
|
|
role="assistant",
|
|
completion_text="",
|
|
tool_calls=[ # type: ignore[call-arg]
|
|
{
|
|
"id": "call_1",
|
|
"type": "function",
|
|
"function": {
|
|
"name": "mock_tool",
|
|
"arguments": '{"input": "test"}',
|
|
},
|
|
}
|
|
],
|
|
)
|
|
|
|
|
|
class MockErrorProvider(MockChatCompletionProvider):
|
|
"""Mock provider that raises errors for testing error handling."""
|
|
|
|
def __init__(self, error_message: str = "Mock provider error"):
|
|
super().__init__()
|
|
self.error_message = error_message
|
|
|
|
async def chat(self, request: ProviderRequest) -> LLMResponse:
|
|
"""Raise mock error."""
|
|
raise RuntimeError(self.error_message)
|
|
|
|
async def stream_chat(self, request: ProviderRequest) -> AsyncGenerator[LLMResponse, None]: # type: ignore[method-assign]
|
|
"""Raise mock error."""
|
|
raise RuntimeError(self.error_message)
|
|
|
|
|
|
class MockMultiModalProvider(MockChatCompletionProvider):
|
|
"""Mock provider for multimodal interactions."""
|
|
|
|
def __init__(
|
|
self,
|
|
has_image_support: bool = True,
|
|
has_audio_support: bool = False,
|
|
):
|
|
super().__init__()
|
|
self.has_image_support = has_image_support
|
|
self.has_audio_support = has_audio_support
|
|
|
|
async def chat(self, request: ProviderRequest) -> LLMResponse:
|
|
"""Return response based on request content."""
|
|
has_images = bool(request.image_urls)
|
|
|
|
if has_images and not self.has_image_support:
|
|
return LLMResponse(
|
|
role="assistant",
|
|
completion_text="Image support not enabled.",
|
|
)
|
|
|
|
return LLMResponse(
|
|
role="assistant",
|
|
completion_text=f"Received request with {len(request.image_urls)} images.",
|
|
)
|
|
|
|
|
|
def create_mock_provider_request(
|
|
prompt: str = "Hello, test!",
|
|
session_id: str = "test-session-1",
|
|
image_urls: list[str] | None = None,
|
|
) -> ProviderRequest:
|
|
"""Create a mock provider request for testing."""
|
|
return ProviderRequest(
|
|
prompt=prompt,
|
|
session_id=session_id,
|
|
image_urls=image_urls or [],
|
|
)
|
|
|
|
|
|
def create_mock_llm_response(
|
|
text: str = "Mock response",
|
|
tool_calls: list[dict] | None = None,
|
|
) -> LLMResponse:
|
|
"""Create a mock LLM response for testing."""
|
|
return LLMResponse(
|
|
role="assistant",
|
|
completion_text=text,
|
|
tool_calls=tool_calls, # type: ignore[call-arg]
|
|
)
|