tests: fix and refactor tests (#1262)
Co-authored-by: bytecii <bytecii@users.noreply.github.com> Co-authored-by: Wendong-Fan <133094783+Wendong-Fan@users.noreply.github.com>
30
.github/workflows/pre-commit.yml
vendored
|
|
@ -30,34 +30,6 @@ jobs:
|
|||
run: uv sync --group dev
|
||||
|
||||
- name: Run pre-commit
|
||||
run: |
|
||||
uv run pre-commit run --files \
|
||||
$(find \
|
||||
app/agent \
|
||||
app/controller \
|
||||
app/exception \
|
||||
app/middleware \
|
||||
app/model \
|
||||
app/service \
|
||||
benchmark \
|
||||
tests/app \
|
||||
-type f ! -path '*__pycache__*') \
|
||||
app/__init__.py \
|
||||
app/router.py \
|
||||
app/component/__init__.py \
|
||||
app/component/pydantic/__init__.py \
|
||||
app/utils/listen/__init__.py \
|
||||
app/utils/server/__init__.py \
|
||||
app/utils/toolkit/__init__.py \
|
||||
app/utils/toolkit/google_calendar_toolkit.py \
|
||||
app/utils/toolkit/google_gmail_mcp_toolkit.py \
|
||||
app/utils/toolkit/linkedin_toolkit.py \
|
||||
app/utils/toolkit/reddit_toolkit.py \
|
||||
app/utils/toolkit/slack_toolkit.py \
|
||||
app/utils/toolkit/twitter_toolkit.py \
|
||||
app/utils/toolkit/whatsapp_toolkit.py \
|
||||
app/utils/workforce.py \
|
||||
app/utils/single_agent_worker.py \
|
||||
tests/conftest.py
|
||||
run: uv run pre-commit run --files $(git ls-files .)
|
||||
env:
|
||||
SKIP: no-commit-to-branch
|
||||
|
|
|
|||
|
|
@ -168,20 +168,18 @@ def initialize_tracer_provider() -> None:
|
|||
_GLOBAL_TRACER_PROVIDER = provider
|
||||
|
||||
|
||||
def get_tracer_provider() -> TracerProvider:
|
||||
def get_tracer_provider() -> TracerProvider | None:
|
||||
"""Get the global TracerProvider instance.
|
||||
|
||||
Returns:
|
||||
TracerProvider: The global tracer provider
|
||||
|
||||
Raises:
|
||||
RuntimeError: If called before initialization
|
||||
TracerProvider if initialized, None otherwise
|
||||
"""
|
||||
if _GLOBAL_TRACER_PROVIDER is None:
|
||||
raise RuntimeError(
|
||||
logger.warning(
|
||||
"TracerProvider not initialized. "
|
||||
"Call initialize_tracer_provider() during app startup."
|
||||
)
|
||||
return None
|
||||
return _GLOBAL_TRACER_PROVIDER
|
||||
|
||||
|
||||
|
|
@ -258,22 +256,28 @@ class WorkforceMetricsCallback(WorkforceMetrics):
|
|||
# Get the global shared tracer provider
|
||||
# This ensures only one BatchSpanProcessor is running
|
||||
provider = get_tracer_provider()
|
||||
|
||||
# Get tracer from the shared provider
|
||||
# Use CAMEL version for instrumentation versioning
|
||||
self.tracer = provider.get_tracer(
|
||||
TRACER_NAME_WORKFORCE, camel.__version__
|
||||
)
|
||||
self.root_span = self.tracer.start_span(
|
||||
f"{SPAN_WORKFORCE_EXECUTION}:{task_id}"
|
||||
)
|
||||
# Langfuse-specific attributes
|
||||
self.root_span.set_attribute(ATTR_LANGFUSE_SESSION_ID, project_id)
|
||||
tags = json.dumps(DEFAULT_LANGFUSE_TAGS.copy())
|
||||
self.root_span.set_attribute(ATTR_LANGFUSE_TAGS, tags)
|
||||
# Custom attributes
|
||||
self.root_span.set_attribute(ATTR_PROJECT_ID, project_id)
|
||||
self.root_span.set_attribute(ATTR_TASK_ID, task_id)
|
||||
if provider is None:
|
||||
# TracerProvider not initialized (e.g., app startup not
|
||||
# completed or running in test environment)
|
||||
self.enabled = False
|
||||
else:
|
||||
# Get tracer from the shared provider
|
||||
# Use CAMEL version for instrumentation versioning
|
||||
self.tracer = provider.get_tracer(
|
||||
TRACER_NAME_WORKFORCE, camel.__version__
|
||||
)
|
||||
self.root_span = self.tracer.start_span(
|
||||
f"{SPAN_WORKFORCE_EXECUTION}:{task_id}"
|
||||
)
|
||||
# Langfuse-specific attributes
|
||||
self.root_span.set_attribute(
|
||||
ATTR_LANGFUSE_SESSION_ID, project_id
|
||||
)
|
||||
tags = json.dumps(DEFAULT_LANGFUSE_TAGS.copy())
|
||||
self.root_span.set_attribute(ATTR_LANGFUSE_TAGS, tags)
|
||||
# Custom attributes
|
||||
self.root_span.set_attribute(ATTR_PROJECT_ID, project_id)
|
||||
self.root_span.set_attribute(ATTR_TASK_ID, task_id)
|
||||
|
||||
# Track active spans for task execution
|
||||
self.task_spans = {}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,9 @@ def test_browser_agent_creation(sample_chat_data):
|
|||
_mod = "app.agent.factory.browser"
|
||||
with (
|
||||
patch(f"{_mod}.agent_model") as mock_agent_model,
|
||||
patch(
|
||||
f"{_mod}.get_working_directory", return_value="/tmp/test_workdir"
|
||||
),
|
||||
patch("asyncio.create_task"),
|
||||
patch(f"{_mod}.HumanToolkit") as mock_human_toolkit,
|
||||
patch(f"{_mod}.HybridBrowserToolkit") as mock_browser_toolkit,
|
||||
|
|
|
|||
|
|
@ -36,6 +36,9 @@ async def test_developer_agent_creation(sample_chat_data):
|
|||
_mod = "app.agent.factory.developer"
|
||||
with (
|
||||
patch(f"{_mod}.agent_model") as mock_agent_model,
|
||||
patch(
|
||||
f"{_mod}.get_working_directory", return_value="/tmp/test_workdir"
|
||||
),
|
||||
patch("asyncio.create_task"),
|
||||
patch(f"{_mod}.HumanToolkit") as mock_human_toolkit,
|
||||
patch(f"{_mod}.NoteTakingToolkit") as mock_note_toolkit,
|
||||
|
|
@ -82,6 +85,9 @@ async def test_developer_agent_with_multiple_toolkits(sample_chat_data):
|
|||
_mod = "app.agent.factory.developer"
|
||||
with (
|
||||
patch(f"{_mod}.agent_model") as mock_agent_model,
|
||||
patch(
|
||||
f"{_mod}.get_working_directory", return_value="/tmp/test_workdir"
|
||||
),
|
||||
patch("asyncio.create_task"),
|
||||
patch(f"{_mod}.HumanToolkit") as mock_human_toolkit,
|
||||
patch(f"{_mod}.NoteTakingToolkit") as mock_note_toolkit,
|
||||
|
|
|
|||
|
|
@ -36,6 +36,9 @@ async def test_document_agent_creation(sample_chat_data):
|
|||
_mod = "app.agent.factory.document"
|
||||
with (
|
||||
patch(f"{_mod}.agent_model") as mock_agent_model,
|
||||
patch(
|
||||
f"{_mod}.get_working_directory", return_value="/tmp/test_workdir"
|
||||
),
|
||||
patch("asyncio.create_task"),
|
||||
patch(f"{_mod}.HumanToolkit") as mock_human_toolkit,
|
||||
patch(f"{_mod}.FileToolkit") as mock_file_toolkit,
|
||||
|
|
|
|||
|
|
@ -35,6 +35,9 @@ def test_multi_modal_agent_creation(sample_chat_data):
|
|||
_mod = "app.agent.factory.multi_modal"
|
||||
with (
|
||||
patch(f"{_mod}.agent_model") as mock_agent_model,
|
||||
patch(
|
||||
f"{_mod}.get_working_directory", return_value="/tmp/test_workdir"
|
||||
),
|
||||
patch("asyncio.create_task"),
|
||||
patch(f"{_mod}.HumanToolkit") as mock_human_toolkit,
|
||||
patch(f"{_mod}.VideoDownloaderToolkit") as mock_video_toolkit,
|
||||
|
|
|
|||
|
|
@ -36,6 +36,9 @@ async def test_social_media_agent_creation(sample_chat_data):
|
|||
mod = "app.agent.factory.social_media"
|
||||
with (
|
||||
patch(f"{mod}.agent_model") as mock_agent_model,
|
||||
patch(
|
||||
f"{mod}.get_working_directory", return_value="/tmp/test_workdir"
|
||||
),
|
||||
patch("asyncio.create_task"),
|
||||
patch(f"{mod}.WhatsAppToolkit") as mock_whatsapp_toolkit,
|
||||
patch(f"{mod}.TwitterToolkit") as mock_twitter_toolkit,
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
# ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
|
||||
|
||||
import os
|
||||
from unittest.mock import MagicMock, patch
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from fastapi import Response
|
||||
|
|
@ -50,13 +50,14 @@ class TestChatController:
|
|||
|
||||
with (
|
||||
patch(
|
||||
"app.controller.chat_controller.create_task_lock",
|
||||
"app.controller.chat_controller.get_or_create_task_lock",
|
||||
return_value=mock_task_lock,
|
||||
),
|
||||
patch(
|
||||
"app.controller.chat_controller.step_solve"
|
||||
) as mock_step_solve,
|
||||
patch("app.controller.chat_controller.load_dotenv"),
|
||||
patch("app.controller.chat_controller.set_current_task_id"),
|
||||
patch("pathlib.Path.mkdir"),
|
||||
patch("pathlib.Path.home", return_value=MagicMock()),
|
||||
):
|
||||
|
|
@ -71,9 +72,6 @@ class TestChatController:
|
|||
|
||||
assert isinstance(response, StreamingResponse)
|
||||
assert response.media_type == "text/event-stream"
|
||||
mock_step_solve.assert_called_once_with(
|
||||
chat_data, mock_request, mock_task_lock
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_post_chat_sets_environment_variables(
|
||||
|
|
@ -84,13 +82,14 @@ class TestChatController:
|
|||
|
||||
with (
|
||||
patch(
|
||||
"app.controller.chat_controller.create_task_lock",
|
||||
"app.controller.chat_controller.get_or_create_task_lock",
|
||||
return_value=mock_task_lock,
|
||||
),
|
||||
patch(
|
||||
"app.controller.chat_controller.step_solve"
|
||||
) as mock_step_solve,
|
||||
patch("app.controller.chat_controller.load_dotenv"),
|
||||
patch("app.controller.chat_controller.set_current_task_id"),
|
||||
patch("pathlib.Path.mkdir"),
|
||||
patch("pathlib.Path.home", return_value=MagicMock()),
|
||||
patch.dict(os.environ, {}, clear=True),
|
||||
|
|
@ -133,18 +132,24 @@ class TestChatController:
|
|||
# put_queue is invoked when creating the coroutine passed to asyncio.run
|
||||
mock_task_lock.put_queue.assert_called_once()
|
||||
|
||||
def test_improve_chat_task_done_error(self, mock_task_lock):
|
||||
"""Test improvement fails when task is done."""
|
||||
def test_improve_chat_task_done_resets_to_confirming(self, mock_task_lock):
|
||||
"""Test improvement when task is done resets status to confirming."""
|
||||
task_id = "test_task_123"
|
||||
supplement_data = SupplementChat(question="Improve this code")
|
||||
mock_task_lock.status = Status.done
|
||||
|
||||
with patch(
|
||||
"app.controller.chat_controller.get_task_lock",
|
||||
return_value=mock_task_lock,
|
||||
with (
|
||||
patch(
|
||||
"app.controller.chat_controller.get_task_lock",
|
||||
return_value=mock_task_lock,
|
||||
),
|
||||
patch("asyncio.run") as mock_run,
|
||||
):
|
||||
with pytest.raises(UserException):
|
||||
improve(task_id, supplement_data)
|
||||
response = improve(task_id, supplement_data)
|
||||
|
||||
assert mock_task_lock.status == Status.confirming
|
||||
assert isinstance(response, Response)
|
||||
assert response.status_code == 201
|
||||
|
||||
def test_supplement_chat_success(self, mock_task_lock):
|
||||
"""Test successful chat supplementation."""
|
||||
|
|
@ -244,16 +249,18 @@ class TestChatControllerIntegration:
|
|||
"""Test chat endpoint through FastAPI test client."""
|
||||
with (
|
||||
patch(
|
||||
"app.controller.chat_controller.create_task_lock"
|
||||
"app.controller.chat_controller.get_or_create_task_lock"
|
||||
) as mock_create_lock,
|
||||
patch(
|
||||
"app.controller.chat_controller.step_solve"
|
||||
) as mock_step_solve,
|
||||
patch("app.controller.chat_controller.load_dotenv"),
|
||||
patch("app.controller.chat_controller.set_current_task_id"),
|
||||
patch("pathlib.Path.mkdir"),
|
||||
patch("pathlib.Path.home", return_value=MagicMock()),
|
||||
):
|
||||
mock_task_lock = MagicMock()
|
||||
mock_task_lock.put_queue = AsyncMock()
|
||||
mock_create_lock.return_value = mock_task_lock
|
||||
|
||||
async def mock_generator():
|
||||
|
|
@ -455,8 +462,12 @@ class TestChatControllerErrorCases:
|
|||
|
||||
with (
|
||||
patch(
|
||||
"app.controller.chat_controller.create_task_lock"
|
||||
"app.controller.chat_controller.get_or_create_task_lock"
|
||||
) as mock_create_lock,
|
||||
patch(
|
||||
"app.controller.chat_controller.sanitize_env_path",
|
||||
return_value="/tmp/fake.env",
|
||||
),
|
||||
patch(
|
||||
"app.controller.chat_controller.load_dotenv",
|
||||
side_effect=Exception("Env load failed"),
|
||||
|
|
@ -15,6 +15,7 @@
|
|||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from fastapi import HTTPException
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from app.controller.model_controller import (
|
||||
|
|
@ -73,11 +74,9 @@ class TestModelController:
|
|||
"app.controller.model_controller.create_agent",
|
||||
side_effect=Exception("Invalid model configuration"),
|
||||
):
|
||||
response = await validate_model(request_data)
|
||||
assert isinstance(response, ValidateModelResponse)
|
||||
assert response.is_valid is False
|
||||
assert response.is_tool_calls is False
|
||||
assert "Invalid model name" in response.message
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
await validate_model(request_data)
|
||||
assert exc_info.value.status_code == 400
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_validate_model_step_failure(self):
|
||||
|
|
@ -93,12 +92,9 @@ class TestModelController:
|
|||
"app.controller.model_controller.create_agent",
|
||||
return_value=mock_agent,
|
||||
):
|
||||
response = await validate_model(request_data)
|
||||
|
||||
assert isinstance(response, ValidateModelResponse)
|
||||
assert response.is_valid is False
|
||||
assert response.is_tool_calls is False
|
||||
assert "API call failed" in response.message
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
await validate_model(request_data)
|
||||
assert exc_info.value.status_code == 400
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_validate_model_tool_calls_false(self):
|
||||
|
|
@ -130,8 +126,10 @@ class TestModelController:
|
|||
|
||||
@pytest.mark.asyncio
|
||||
async def test_validate_model_with_minimal_parameters(self):
|
||||
"""Test model validation with minimal parameters."""
|
||||
request_data = ValidateModelRequest() # Uses default values
|
||||
"""Test model validation with minimal parameters (no API key)."""
|
||||
request_data = (
|
||||
ValidateModelRequest()
|
||||
) # Uses default values, api_key is None
|
||||
|
||||
mock_agent = MagicMock()
|
||||
mock_response = MagicMock()
|
||||
|
|
@ -144,12 +142,12 @@ class TestModelController:
|
|||
"app.controller.model_controller.create_agent",
|
||||
return_value=mock_agent,
|
||||
):
|
||||
# api_key is None by default, which passes the empty string check
|
||||
# The agent step succeeds, so validation should pass
|
||||
response = await validate_model(request_data)
|
||||
assert isinstance(response, ValidateModelResponse)
|
||||
assert response.is_valid is False
|
||||
assert response.is_tool_calls is False
|
||||
assert response.error_code is not None
|
||||
assert response.error is not None
|
||||
assert response.is_valid is True
|
||||
assert response.is_tool_calls is True
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_validate_model_no_response(self):
|
||||
|
|
@ -222,13 +220,7 @@ class TestModelControllerIntegration:
|
|||
):
|
||||
response = client.post("/model/validate", json=request_data)
|
||||
|
||||
assert (
|
||||
response.status_code == 200
|
||||
) # Returns 200 with error in response body
|
||||
response_data = response.json()
|
||||
assert response_data["is_valid"] is False
|
||||
assert response_data["is_tool_calls"] is False
|
||||
assert "Invalid model name" in response_data["message"]
|
||||
assert response.status_code == 400
|
||||
|
||||
|
||||
@pytest.mark.model_backend
|
||||
|
|
@ -267,10 +259,9 @@ class TestModelControllerErrorCases:
|
|||
"app.controller.model_controller.create_agent",
|
||||
side_effect=ValueError("Invalid configuration"),
|
||||
):
|
||||
response = await validate_model(request_data)
|
||||
|
||||
assert response.is_valid is False
|
||||
assert "Invalid configuration" in response.message
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
await validate_model(request_data)
|
||||
assert exc_info.value.status_code == 400
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_validate_model_with_network_error(self):
|
||||
|
|
@ -288,10 +279,9 @@ class TestModelControllerErrorCases:
|
|||
"app.controller.model_controller.create_agent",
|
||||
return_value=mock_agent,
|
||||
):
|
||||
response = await validate_model(request_data)
|
||||
|
||||
assert response.is_valid is False
|
||||
assert "Network unreachable" in response.message
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
await validate_model(request_data)
|
||||
assert exc_info.value.status_code == 400
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_validate_model_with_malformed_tool_calls_response(self):
|
||||
|
|
@ -346,36 +336,21 @@ class TestModelControllerErrorCases:
|
|||
api_key="", # Empty API key
|
||||
)
|
||||
|
||||
response = await validate_model(request_data)
|
||||
|
||||
assert response.is_valid is False
|
||||
assert response.is_tool_calls is False
|
||||
assert response.message == "Invalid key. Validation failed."
|
||||
assert response.error_code == "invalid_api_key"
|
||||
assert response.error is not None
|
||||
assert response.error["message"] == "Invalid key. Validation failed."
|
||||
assert response.error["type"] == "invalid_request_error"
|
||||
assert response.error["code"] == "invalid_api_key"
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
await validate_model(request_data)
|
||||
assert exc_info.value.status_code == 400
|
||||
detail = exc_info.value.detail
|
||||
assert detail["error_code"] == "invalid_api_key"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_validate_model_invalid_model_type(self):
|
||||
"""Test model validation with invalid model type."""
|
||||
"""Test model validation with invalid model type raises HTTPException."""
|
||||
request_data = ValidateModelRequest(
|
||||
model_platform="openai",
|
||||
model_type="INVALID_MODEL_TYPE",
|
||||
api_key="test_key",
|
||||
)
|
||||
|
||||
response = await validate_model(request_data)
|
||||
assert response.is_valid is False
|
||||
assert response.is_tool_calls is False
|
||||
assert response.message == "Invalid model name. Validation failed."
|
||||
assert response.error_code is not None
|
||||
assert "model_not_found" in response.error_code
|
||||
assert response.error is not None
|
||||
assert (
|
||||
response.error["message"]
|
||||
== "Invalid model name. Validation failed."
|
||||
)
|
||||
assert response.error["type"] == "invalid_request_error"
|
||||
assert response.error["code"] == "model_not_found"
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
await validate_model(request_data)
|
||||
assert exc_info.value.status_code == 400
|
||||
|
|
@ -15,6 +15,7 @@
|
|||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from fastapi import HTTPException
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from app.controller.tool_controller import install_tool
|
||||
|
|
@ -37,14 +38,18 @@ class TestToolController:
|
|||
return_value=mock_toolkit,
|
||||
):
|
||||
result = await install_tool(tool_name)
|
||||
assert result == ["create_page", "update_page"]
|
||||
assert result["success"] is True
|
||||
assert result["tools"] == ["create_page", "update_page"]
|
||||
assert result["count"] == 2
|
||||
assert result["toolkit_name"] == "NotionMCPToolkit"
|
||||
mock_toolkit.connect.assert_called_once()
|
||||
mock_toolkit.disconnect.assert_called_once()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_install_unknown_tool(self):
|
||||
result = await install_tool("unknown_tool")
|
||||
assert result == {"error": "Tool not found"}
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
await install_tool("unknown_tool")
|
||||
assert exc_info.value.status_code == 404
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_install_notion_tool_connection_failure(self):
|
||||
|
|
@ -54,8 +59,11 @@ class TestToolController:
|
|||
"app.controller.tool_controller.NotionMCPToolkit",
|
||||
return_value=mock_toolkit,
|
||||
):
|
||||
with pytest.raises(Exception, match="Connection failed"):
|
||||
await install_tool("notion")
|
||||
result = await install_tool("notion")
|
||||
assert result["success"] is True
|
||||
assert result["tools"] == []
|
||||
assert result["count"] == 0
|
||||
assert "warning" in result
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_install_notion_tool_get_tools_failure(self):
|
||||
|
|
@ -67,8 +75,11 @@ class TestToolController:
|
|||
"app.controller.tool_controller.NotionMCPToolkit",
|
||||
return_value=mock_toolkit,
|
||||
):
|
||||
with pytest.raises(Exception, match="Failed to get tools"):
|
||||
await install_tool("notion")
|
||||
result = await install_tool("notion")
|
||||
assert result["success"] is True
|
||||
assert result["tools"] == []
|
||||
assert result["count"] == 0
|
||||
assert "warning" in result
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_install_notion_tool_disconnect_failure(self):
|
||||
|
|
@ -81,8 +92,11 @@ class TestToolController:
|
|||
"app.controller.tool_controller.NotionMCPToolkit",
|
||||
return_value=mock_toolkit,
|
||||
):
|
||||
with pytest.raises(Exception, match="Disconnect failed"):
|
||||
await install_tool("notion")
|
||||
result = await install_tool("notion")
|
||||
assert result["success"] is True
|
||||
assert result["tools"] == []
|
||||
assert result["count"] == 0
|
||||
assert "warning" in result
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_install_notion_tool_empty_tools(self):
|
||||
|
|
@ -93,7 +107,9 @@ class TestToolController:
|
|||
return_value=mock_toolkit,
|
||||
):
|
||||
result = await install_tool("notion")
|
||||
assert result == []
|
||||
assert result["success"] is True
|
||||
assert result["tools"] == []
|
||||
assert result["count"] == 0
|
||||
mock_toolkit.connect.assert_called_once()
|
||||
mock_toolkit.disconnect.assert_called_once()
|
||||
|
||||
|
|
@ -117,7 +133,9 @@ class TestToolController:
|
|||
return_value=mock_toolkit,
|
||||
):
|
||||
result = await install_tool("notion")
|
||||
assert result == names
|
||||
assert result["success"] is True
|
||||
assert result["tools"] == names
|
||||
assert result["count"] == 4
|
||||
mock_toolkit.connect.assert_called_once()
|
||||
mock_toolkit.disconnect.assert_called_once()
|
||||
|
||||
|
|
@ -145,7 +163,10 @@ class TestToolControllerIntegration:
|
|||
response = client.post(f"/install/tool/{tool_name}")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json() == ["create_page", "update_page"]
|
||||
data = response.json()
|
||||
assert data["success"] is True
|
||||
assert data["tools"] == ["create_page", "update_page"]
|
||||
assert data["count"] == 2
|
||||
|
||||
def test_install_unknown_tool_endpoint_integration(
|
||||
self, client: TestClient
|
||||
|
|
@ -155,8 +176,7 @@ class TestToolControllerIntegration:
|
|||
|
||||
response = client.post(f"/install/tool/{tool_name}")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"error": "Tool not found"}
|
||||
assert response.status_code == 404
|
||||
|
||||
def test_install_notion_tool_endpoint_with_connection_error(
|
||||
self, client: TestClient
|
||||
|
|
@ -171,9 +191,12 @@ class TestToolControllerIntegration:
|
|||
"app.controller.tool_controller.NotionMCPToolkit",
|
||||
return_value=mock_toolkit,
|
||||
):
|
||||
# The exception should be raised by the endpoint since there's no error handling
|
||||
with pytest.raises(Exception, match="Connection failed"):
|
||||
client.post(f"/install/tool/{tool_name}")
|
||||
response = client.post(f"/install/tool/{tool_name}")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["success"] is True
|
||||
assert data["tools"] == []
|
||||
assert "warning" in data
|
||||
|
||||
|
||||
@pytest.mark.model_backend
|
||||
|
|
@ -211,8 +234,11 @@ class TestToolControllerErrorCases:
|
|||
"app.controller.tool_controller.NotionMCPToolkit",
|
||||
return_value=mock_toolkit,
|
||||
):
|
||||
with pytest.raises(AttributeError):
|
||||
await install_tool("notion")
|
||||
# Inner except catches the AttributeError and returns success with empty tools
|
||||
result = await install_tool("notion")
|
||||
assert result["success"] is True
|
||||
assert result["tools"] == []
|
||||
assert "warning" in result
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_install_tool_with_none_toolkit(self):
|
||||
|
|
@ -220,23 +246,29 @@ class TestToolControllerErrorCases:
|
|||
"app.controller.tool_controller.NotionMCPToolkit",
|
||||
return_value=None,
|
||||
):
|
||||
with pytest.raises(AttributeError):
|
||||
await install_tool("notion")
|
||||
# Inner except catches AttributeError on None.connect()
|
||||
result = await install_tool("notion")
|
||||
assert result["success"] is True
|
||||
assert result["tools"] == []
|
||||
assert "warning" in result
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_install_tool_with_special_characters_in_name(self):
|
||||
result = await install_tool("notion@#$%")
|
||||
assert result == {"error": "Tool not found"}
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
await install_tool("notion@#$%")
|
||||
assert exc_info.value.status_code == 404
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_install_tool_with_empty_string_name(self):
|
||||
result = await install_tool("")
|
||||
assert result == {"error": "Tool not found"}
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
await install_tool("")
|
||||
assert exc_info.value.status_code == 404
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_install_tool_with_none_name(self):
|
||||
result = await install_tool(None)
|
||||
assert result == {"error": "Tool not found"}
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
await install_tool(None)
|
||||
assert exc_info.value.status_code == 404
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_install_notion_tool_partial_failure(self):
|
||||
|
|
@ -252,5 +284,8 @@ class TestToolControllerErrorCases:
|
|||
"app.controller.tool_controller.NotionMCPToolkit",
|
||||
return_value=mock_toolkit,
|
||||
):
|
||||
with pytest.raises(AttributeError):
|
||||
await install_tool("notion")
|
||||
# Inner except catches the AttributeError from tools[2].func
|
||||
result = await install_tool("notion")
|
||||
assert result["success"] is True
|
||||
assert result["tools"] == []
|
||||
assert "warning" in result
|
||||
|
|
@ -71,7 +71,6 @@ class TestCollectPreviousTaskContext:
|
|||
assert "Previous Task Result:" in result
|
||||
assert "Successfully created script.py" in result
|
||||
assert "=== END OF PREVIOUS TASK CONTEXT ===" in result
|
||||
assert "=== NEW TASK ===" in result
|
||||
|
||||
def test_collect_previous_task_context_with_generated_files(
|
||||
self, temp_dir
|
||||
|
|
@ -208,7 +207,6 @@ class TestCollectPreviousTaskContext:
|
|||
# Should still have the structural elements
|
||||
assert "=== CONTEXT FROM PREVIOUS TASK ===" in result
|
||||
assert "=== END OF PREVIOUS TASK CONTEXT ===" in result
|
||||
assert "=== NEW TASK ===" in result
|
||||
|
||||
# Should not have content sections for empty inputs
|
||||
assert "Previous Task:" not in result
|
||||
|
|
@ -289,7 +287,6 @@ class TestBuildContextForWorkforce:
|
|||
# Create mock TaskLock
|
||||
task_lock = MagicMock(spec=TaskLock)
|
||||
task_lock.conversation_history = [
|
||||
{"role": "user", "content": "Create a Python script"},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "I will create a Python script for you",
|
||||
|
|
@ -304,14 +301,10 @@ class TestBuildContextForWorkforce:
|
|||
|
||||
result = build_context_for_workforce(task_lock, options)
|
||||
|
||||
# Should include conversation history
|
||||
# Should include conversation history header
|
||||
assert "=== CONVERSATION HISTORY ===" in result
|
||||
assert "user: Create a Python script" in result
|
||||
assert "assistant: I will create a Python script for you" in result
|
||||
|
||||
# Should include previous task context
|
||||
assert "=== CONTEXT FROM PREVIOUS TASK ===" in result
|
||||
assert "Script created successfully" in result
|
||||
# build_conversation_context only processes assistant and task_result roles
|
||||
assert "I will create a Python script for you" in result
|
||||
|
||||
def test_build_context_for_workforce_empty_history(self, temp_dir):
|
||||
"""Test build_context_for_workforce with empty conversation history."""
|
||||
|
|
@ -329,15 +322,17 @@ class TestBuildContextForWorkforce:
|
|||
assert result == ""
|
||||
|
||||
def test_build_context_for_workforce_task_result_role(self, temp_dir):
|
||||
"""Test build_context_for_workforce handles 'task_result' role specially."""
|
||||
"""Test build_context_for_workforce handles 'task_result' role."""
|
||||
task_lock = MagicMock(spec=TaskLock)
|
||||
task_lock.conversation_history = [
|
||||
{"role": "user", "content": "First question"},
|
||||
{
|
||||
"role": "task_result",
|
||||
"content": "Full task context from previous task",
|
||||
},
|
||||
{"role": "user", "content": "Second question"},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "Task completed successfully",
|
||||
},
|
||||
]
|
||||
task_lock.last_task_result = "Final result"
|
||||
task_lock.last_task_summary = "Task summary"
|
||||
|
|
@ -347,22 +342,18 @@ class TestBuildContextForWorkforce:
|
|||
|
||||
result = build_context_for_workforce(task_lock, options)
|
||||
|
||||
# Should simplify task_result display
|
||||
assert "[Previous Task Completed]" in result
|
||||
assert (
|
||||
"Full task context from previous task" not in result
|
||||
) # Should not show full content
|
||||
assert "user: First question" in result
|
||||
assert "user: Second question" in result
|
||||
# build_conversation_context appends string task_result content directly
|
||||
assert "Full task context from previous task" in result
|
||||
assert "Task completed successfully" in result
|
||||
|
||||
def test_build_context_for_workforce_with_last_task_result(self, temp_dir):
|
||||
"""Test build_context_for_workforce includes last task result context."""
|
||||
# Create some files in temp directory
|
||||
(temp_dir / "output.txt").write_text("Task output")
|
||||
|
||||
"""Test build_context_for_workforce with assistant entries."""
|
||||
task_lock = MagicMock(spec=TaskLock)
|
||||
task_lock.conversation_history = [
|
||||
{"role": "user", "content": "Test question"}
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "Task completed with output.txt",
|
||||
},
|
||||
]
|
||||
task_lock.last_task_result = "Task completed with output.txt"
|
||||
task_lock.last_task_summary = "File creation task"
|
||||
|
|
@ -372,13 +363,9 @@ class TestBuildContextForWorkforce:
|
|||
|
||||
result = build_context_for_workforce(task_lock, options)
|
||||
|
||||
# Should include conversation history and task context
|
||||
# Should include conversation history
|
||||
assert "=== CONVERSATION HISTORY ===" in result
|
||||
assert "user: Test question" in result
|
||||
assert "=== CONTEXT FROM PREVIOUS TASK ===" in result
|
||||
assert "Task completed with output.txt" in result
|
||||
assert "File creation task" in result
|
||||
assert "output.txt" in result # Generated file should be listed
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
|
|
@ -586,17 +573,14 @@ class TestChatServiceAgentOperations:
|
|||
|
||||
@pytest.mark.asyncio
|
||||
async def test_question_confirm_simple_query(self, mock_camel_agent):
|
||||
"""Test question_confirm with simple query that gets direct response."""
|
||||
mock_camel_agent.step.return_value.msgs[
|
||||
0
|
||||
].content = "Hello! How can I help you today?"
|
||||
"""Test question_confirm with simple query returns False."""
|
||||
mock_camel_agent.step.return_value.msgs[0].content = "no"
|
||||
mock_camel_agent.chat_history = []
|
||||
|
||||
result = await question_confirm(mock_camel_agent, "hello")
|
||||
|
||||
# Should return SSE formatted response for simple queries
|
||||
assert "wait_confirm" in result
|
||||
assert "Hello! How can I help you today?" in result
|
||||
# Should return False for simple queries (no "yes" in response)
|
||||
assert result is False
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_question_confirm_complex_task(self, mock_camel_agent):
|
||||
|
|
@ -666,6 +650,10 @@ class TestChatServiceAgentOperations:
|
|||
|
||||
with (
|
||||
patch("app.service.chat_service.agent_model") as mock_agent_model,
|
||||
patch(
|
||||
"app.service.chat_service.get_working_directory",
|
||||
return_value="/tmp/test_workdir",
|
||||
),
|
||||
patch(
|
||||
"app.service.chat_service.Workforce",
|
||||
return_value=mock_workforce,
|
||||
|
|
@ -682,6 +670,10 @@ class TestChatServiceAgentOperations:
|
|||
"app.agent.toolkit.human_toolkit.get_task_lock",
|
||||
return_value=mock_task_lock,
|
||||
),
|
||||
patch(
|
||||
"app.service.chat_service.WorkforceMetricsCallback",
|
||||
return_value=MagicMock(),
|
||||
),
|
||||
):
|
||||
mock_agent_model.return_value = MagicMock()
|
||||
|
||||
|
|
@ -738,23 +730,15 @@ class TestChatServiceIntegration:
|
|||
"def hello(): print('Hello World')"
|
||||
)
|
||||
|
||||
# Mock file_save_path method to return our temp directory
|
||||
with patch.object(
|
||||
Chat, "file_save_path", return_value=str(working_dir)
|
||||
):
|
||||
# Test the context building directly
|
||||
context = build_context_for_workforce(task_lock, options)
|
||||
# Test the context building directly
|
||||
# build_context_for_workforce now only calls build_conversation_context
|
||||
# which only processes assistant and task_result roles
|
||||
context = build_context_for_workforce(task_lock, options)
|
||||
|
||||
# Verify context includes conversation history
|
||||
assert "=== CONVERSATION HISTORY ===" in context
|
||||
assert "user: Create a Python script" in context
|
||||
assert "assistant: Script created successfully" in context
|
||||
|
||||
# Verify context includes task context with files
|
||||
assert "=== CONTEXT FROM PREVIOUS TASK ===" in context
|
||||
assert "def hello(): print('Hello World')" in context
|
||||
assert "Python Hello World Script" in context
|
||||
assert "script.py" in context
|
||||
# Verify context includes conversation history header
|
||||
assert "=== CONVERSATION HISTORY ===" in context
|
||||
# assistant entries are included
|
||||
assert "Script created successfully" in context
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_step_solve_new_task_state_context_collection(
|
||||
|
|
@ -793,7 +777,6 @@ class TestChatServiceIntegration:
|
|||
assert "main.py" in result
|
||||
assert "config.json" in result
|
||||
assert "=== END OF PREVIOUS TASK CONTEXT ===" in result
|
||||
assert "=== NEW TASK ===" in result
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_step_solve_end_action_context_collection(
|
||||
|
|
@ -1008,26 +991,23 @@ class TestChatServiceErrorCases:
|
|||
# Should log warning
|
||||
mock_logger.warning.assert_called_once()
|
||||
|
||||
def test_collect_previous_task_context_relpath_exception(self, temp_dir):
|
||||
"""Test collect_previous_task_context handles os.path.relpath exceptions."""
|
||||
def test_collect_previous_task_context_abspath_used(self, temp_dir):
|
||||
"""Test collect_previous_task_context uses absolute paths for files."""
|
||||
working_directory = str(temp_dir)
|
||||
|
||||
# Create a test file
|
||||
(temp_dir / "test.txt").write_text("test content")
|
||||
|
||||
with patch("os.path.relpath", side_effect=ValueError("Invalid path")):
|
||||
with patch("app.service.chat_service.logger") as mock_logger:
|
||||
result = collect_previous_task_context(
|
||||
working_directory=working_directory,
|
||||
previous_task_content="Test task",
|
||||
previous_task_result="Test result",
|
||||
previous_summary="Test summary",
|
||||
)
|
||||
result = collect_previous_task_context(
|
||||
working_directory=working_directory,
|
||||
previous_task_content="Test task",
|
||||
previous_task_result="Test result",
|
||||
previous_summary="Test summary",
|
||||
)
|
||||
|
||||
# Should handle the exception gracefully
|
||||
assert "=== CONTEXT FROM PREVIOUS TASK ===" in result
|
||||
# Should log warning about file collection failure
|
||||
mock_logger.warning.assert_called_once()
|
||||
# Should include absolute path for the file
|
||||
assert "=== CONTEXT FROM PREVIOUS TASK ===" in result
|
||||
assert "test.txt" in result
|
||||
|
||||
def test_build_context_for_workforce_missing_attributes(self, temp_dir):
|
||||
"""Test build_context_for_workforce handles missing attributes gracefully."""
|
||||
|
|
@ -1045,20 +1025,18 @@ class TestChatServiceErrorCases:
|
|||
# Should handle missing attributes gracefully
|
||||
assert result == ""
|
||||
|
||||
def test_build_context_for_workforce_file_save_path_exception(self):
|
||||
"""Test build_context_for_workforce handles file_save_path exceptions."""
|
||||
def test_build_context_for_workforce_empty_conversation(self):
|
||||
"""Test build_context_for_workforce returns empty for empty conversation."""
|
||||
task_lock = MagicMock(spec=TaskLock)
|
||||
task_lock.conversation_history = []
|
||||
task_lock.last_task_result = "Test result"
|
||||
task_lock.last_task_summary = "Test summary"
|
||||
|
||||
options = MagicMock()
|
||||
options.file_save_path.side_effect = Exception("Path error")
|
||||
|
||||
with patch("app.service.chat_service.logger") as mock_logger:
|
||||
# Should handle exception when getting file path
|
||||
with pytest.raises(Exception, match="Path error"):
|
||||
build_context_for_workforce(task_lock, options)
|
||||
# Should return empty string for empty conversation history
|
||||
result = build_context_for_workforce(task_lock, options)
|
||||
assert result == ""
|
||||
|
||||
def test_collect_previous_task_context_unicode_handling(self, temp_dir):
|
||||
"""Test collect_previous_task_context handles unicode content correctly."""
|
||||
|
|
@ -1216,6 +1194,22 @@ class TestChatServiceErrorCases:
|
|||
"app.service.chat_service.agent_model",
|
||||
side_effect=Exception("Agent creation failed"),
|
||||
),
|
||||
patch(
|
||||
"app.agent.factory.developer.agent_model",
|
||||
side_effect=Exception("Agent creation failed"),
|
||||
),
|
||||
patch(
|
||||
"app.agent.factory.browser.agent_model",
|
||||
side_effect=Exception("Agent creation failed"),
|
||||
),
|
||||
patch(
|
||||
"app.agent.factory.document.agent_model",
|
||||
side_effect=Exception("Agent creation failed"),
|
||||
),
|
||||
patch(
|
||||
"app.agent.factory.multi_modal.agent_model",
|
||||
side_effect=Exception("Agent creation failed"),
|
||||
),
|
||||
):
|
||||
with pytest.raises(Exception, match="Agent creation failed"):
|
||||
await construct_workforce(options)
|
||||
|
|
@ -519,32 +519,29 @@ class TestPeriodicCleanup:
|
|||
@pytest.mark.asyncio
|
||||
async def test_periodic_cleanup_handles_exceptions(self):
|
||||
"""Test that periodic cleanup handles exceptions gracefully."""
|
||||
import app.service.task as task_module
|
||||
|
||||
# Create a stale task lock
|
||||
task_lock = create_task_lock("test_task")
|
||||
task_lock.last_accessed = datetime.now() - timedelta(hours=3)
|
||||
|
||||
# Mock delete_task_lock to raise exception
|
||||
# Mock delete_task_lock to raise exception and call through module
|
||||
with (
|
||||
patch(
|
||||
"app.service.task.delete_task_lock",
|
||||
patch.object(
|
||||
task_module,
|
||||
"delete_task_lock",
|
||||
side_effect=Exception("Test error"),
|
||||
),
|
||||
patch(
|
||||
"app.service.task.logger.error",
|
||||
) as mock_logger,
|
||||
patch.object(task_module, "logger") as mock_logger,
|
||||
):
|
||||
# Directly call the cleanup logic
|
||||
# that should trigger the exception
|
||||
# Simulate what _periodic_cleanup does when encountering an error
|
||||
try:
|
||||
await delete_task_lock("test_task")
|
||||
await task_module.delete_task_lock("test_task")
|
||||
except Exception as e:
|
||||
import logging
|
||||
|
||||
task_logger = logging.getLogger("task_service")
|
||||
task_logger.error(f"Error during task cleanup: {e}")
|
||||
task_module.logger.error(f"Error in periodic cleanup: {e}")
|
||||
|
||||
# Should have logged the error
|
||||
mock_logger.assert_called()
|
||||
mock_logger.error.assert_called()
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
|
|
@ -33,6 +33,7 @@ class TestSingleAgentWorker:
|
|||
mock_worker = MagicMock(spec=ListenChatAgent)
|
||||
mock_worker.role_name = "test_worker"
|
||||
mock_worker.agent_id = "worker_123"
|
||||
mock_worker.agent_name = "test_worker"
|
||||
|
||||
worker = SingleAgentWorker(
|
||||
description="Test worker description",
|
||||
|
|
@ -57,6 +58,7 @@ class TestSingleAgentWorker:
|
|||
mock_worker = MagicMock(spec=ListenChatAgent)
|
||||
mock_worker.role_name = "test_worker"
|
||||
mock_worker.agent_id = "worker_123"
|
||||
mock_worker.agent_name = "test_worker"
|
||||
|
||||
worker = SingleAgentWorker(
|
||||
description="Test worker",
|
||||
|
|
@ -123,6 +125,7 @@ class TestSingleAgentWorker:
|
|||
mock_worker = MagicMock(spec=ListenChatAgent)
|
||||
mock_worker.role_name = "test_worker"
|
||||
mock_worker.agent_id = "worker_123"
|
||||
mock_worker.agent_name = "test_worker"
|
||||
|
||||
worker = SingleAgentWorker(
|
||||
description="Test worker",
|
||||
|
|
@ -178,6 +181,7 @@ class TestSingleAgentWorker:
|
|||
mock_worker = MagicMock(spec=ListenChatAgent)
|
||||
mock_worker.role_name = "test_worker"
|
||||
mock_worker.agent_id = "test_agent_123"
|
||||
mock_worker.agent_name = "test_worker"
|
||||
|
||||
worker = SingleAgentWorker(
|
||||
description="Test worker",
|
||||
|
|
@ -247,6 +251,7 @@ class TestSingleAgentWorker:
|
|||
mock_worker = MagicMock(spec=ListenChatAgent)
|
||||
mock_worker.role_name = "test_worker"
|
||||
mock_worker.agent_id = "test_agent_123"
|
||||
mock_worker.agent_name = "test_worker"
|
||||
|
||||
worker = SingleAgentWorker(
|
||||
description="Test worker", worker=mock_worker
|
||||
|
|
@ -280,6 +285,7 @@ class TestSingleAgentWorker:
|
|||
mock_worker = MagicMock(spec=ListenChatAgent)
|
||||
mock_worker.role_name = "test_worker"
|
||||
mock_worker.agent_id = "test_agent_123"
|
||||
mock_worker.agent_name = "test_worker"
|
||||
|
||||
worker = SingleAgentWorker(
|
||||
description="Test worker",
|
||||
|
|
@ -333,6 +339,7 @@ class TestSingleAgentWorker:
|
|||
mock_worker = MagicMock(spec=ListenChatAgent)
|
||||
mock_worker.role_name = "test_worker"
|
||||
mock_worker.agent_id = "test_agent_123"
|
||||
mock_worker.agent_name = "test_worker"
|
||||
|
||||
worker = SingleAgentWorker(
|
||||
description="Test worker",
|
||||
|
|
@ -382,6 +389,7 @@ class TestSingleAgentWorker:
|
|||
mock_worker = MagicMock(spec=ListenChatAgent)
|
||||
mock_worker.role_name = "test_worker"
|
||||
mock_worker.agent_id = "test_agent_123"
|
||||
mock_worker.agent_name = "test_worker"
|
||||
|
||||
worker = SingleAgentWorker(
|
||||
description="Test worker",
|
||||
|
|
@ -431,6 +439,7 @@ class TestSingleAgentWorker:
|
|||
mock_worker = MagicMock(spec=ListenChatAgent)
|
||||
mock_worker.role_name = "test_worker"
|
||||
mock_worker.agent_id = "test_agent_123"
|
||||
mock_worker.agent_name = "test_worker"
|
||||
|
||||
worker = SingleAgentWorker(
|
||||
description="Test worker",
|
||||
|
|
@ -476,6 +485,7 @@ class TestSingleAgentWorker:
|
|||
|
||||
mock_worker = MagicMock(spec=ListenChatAgent)
|
||||
mock_worker.agent_id = "test_agent_123"
|
||||
mock_worker.agent_name = "test_worker"
|
||||
worker = SingleAgentWorker(description="Test", worker=mock_worker)
|
||||
|
||||
assert isinstance(worker, BaseSingleAgentWorker)
|
||||
|
|
@ -491,6 +501,7 @@ class TestSingleAgentWorkerIntegration:
|
|||
mock_worker = MagicMock(spec=ListenChatAgent)
|
||||
mock_worker.role_name = "integration_worker"
|
||||
mock_worker.agent_id = "test_agent_123"
|
||||
mock_worker.agent_name = "integration_worker"
|
||||
|
||||
worker = SingleAgentWorker(
|
||||
description="Integration test worker",
|
||||
|
|
@ -568,6 +579,7 @@ class TestSingleAgentWorkerErrorCases:
|
|||
"""Test _process_task when agent returns None response."""
|
||||
mock_worker = MagicMock(spec=ListenChatAgent)
|
||||
mock_worker.agent_id = "test_agent_123"
|
||||
mock_worker.agent_name = "test_worker"
|
||||
worker = SingleAgentWorker(
|
||||
description="Test",
|
||||
worker=mock_worker,
|
||||
|
|
@ -600,6 +612,7 @@ class TestSingleAgentWorkerErrorCases:
|
|||
"""Test _process_task with malformed response structure."""
|
||||
mock_worker = MagicMock(spec=ListenChatAgent)
|
||||
mock_worker.agent_id = "test_agent_123"
|
||||
mock_worker.agent_name = "test_worker"
|
||||
worker = SingleAgentWorker(
|
||||
description="Test",
|
||||
worker=mock_worker,
|
||||
|
|
@ -637,6 +650,7 @@ class TestSingleAgentWorkerErrorCases:
|
|||
mock_worker = MagicMock(spec=ListenChatAgent)
|
||||
mock_worker.agent_id = "test_agent_123"
|
||||
mock_worker.role_name = "test_worker"
|
||||
mock_worker.agent_name = "test_worker"
|
||||
worker = SingleAgentWorker(
|
||||
description="Test",
|
||||
worker=mock_worker,
|
||||
|
|
@ -344,6 +344,20 @@ async def async_mock_agent() -> AsyncGenerator[AsyncMock, None]:
|
|||
yield agent
|
||||
|
||||
|
||||
# Safety net: clean up any MagicMock-named directories that tests may
|
||||
# accidentally create when mock objects are used as file paths.
|
||||
@pytest.fixture(autouse=True, scope="session")
|
||||
def _cleanup_magicmock_dirs():
|
||||
"""Remove MagicMock-named directories from backend/ after test session."""
|
||||
yield
|
||||
import shutil
|
||||
|
||||
backend_dir = Path(__file__).parent.parent
|
||||
for entry in backend_dir.iterdir():
|
||||
if "MagicMock" in entry.name:
|
||||
shutil.rmtree(entry, ignore_errors=True)
|
||||
|
||||
|
||||
# Markers for test categorization
|
||||
pytest_plugins = ["pytest_asyncio"]
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Anthropic</title><path d="M13.827 3.52h3.603L24 20h-3.603l-6.57-16.48zm-7.258 0h3.767L16.906 20h-3.674l-1.343-3.461H5.017l-1.344 3.46H0L6.57 3.522zm4.132 9.959L8.453 7.687 6.205 13.48H10.7z"></path></svg>
|
||||
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Anthropic</title><path d="M13.827 3.52h3.603L24 20h-3.603l-6.57-16.48zm-7.258 0h3.767L16.906 20h-3.674l-1.343-3.461H5.017l-1.344 3.46H0L6.57 3.522zm4.132 9.959L8.453 7.687 6.205 13.48H10.7z"></path></svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 368 B After Width: | Height: | Size: 369 B |
|
|
@ -1 +1 @@
|
|||
<svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Azure</title><path d="M7.242 1.613A1.11 1.11 0 018.295.857h6.977L8.03 22.316a1.11 1.11 0 01-1.052.755h-5.43a1.11 1.11 0 01-1.053-1.466L7.242 1.613z" fill="url(#lobe-icons-azure-fill-0)"></path><path d="M18.397 15.296H7.4a.51.51 0 00-.347.882l7.066 6.595c.206.192.477.298.758.298h6.226l-2.706-7.775z" fill="#0078D4"></path><path d="M15.272.857H7.497L0 23.071h7.775l1.596-4.73 5.068 4.73h6.665l-2.707-7.775h-7.998L15.272.857z" fill="url(#lobe-icons-azure-fill-1)"></path><path d="M17.193 1.613a1.11 1.11 0 00-1.052-.756h-7.81.035c.477 0 .9.304 1.052.756l6.748 19.992a1.11 1.11 0 01-1.052 1.466h-.12 7.895a1.11 1.11 0 001.052-1.466L17.193 1.613z" fill="url(#lobe-icons-azure-fill-2)"></path><defs><linearGradient gradientUnits="userSpaceOnUse" id="lobe-icons-azure-fill-0" x1="8.247" x2="1.002" y1="1.626" y2="23.03"><stop stop-color="#114A8B"></stop><stop offset="1" stop-color="#0669BC"></stop></linearGradient><linearGradient gradientUnits="userSpaceOnUse" id="lobe-icons-azure-fill-1" x1="14.042" x2="12.324" y1="15.302" y2="15.888"><stop stop-opacity=".3"></stop><stop offset=".071" stop-opacity=".2"></stop><stop offset=".321" stop-opacity=".1"></stop><stop offset=".623" stop-opacity=".05"></stop><stop offset="1" stop-opacity="0"></stop></linearGradient><linearGradient gradientUnits="userSpaceOnUse" id="lobe-icons-azure-fill-2" x1="12.841" x2="20.793" y1="1.626" y2="22.814"><stop stop-color="#3CCBF4"></stop><stop offset="1" stop-color="#2892DF"></stop></linearGradient></defs></svg>
|
||||
<svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Azure</title><path d="M7.242 1.613A1.11 1.11 0 018.295.857h6.977L8.03 22.316a1.11 1.11 0 01-1.052.755h-5.43a1.11 1.11 0 01-1.053-1.466L7.242 1.613z" fill="url(#lobe-icons-azure-fill-0)"></path><path d="M18.397 15.296H7.4a.51.51 0 00-.347.882l7.066 6.595c.206.192.477.298.758.298h6.226l-2.706-7.775z" fill="#0078D4"></path><path d="M15.272.857H7.497L0 23.071h7.775l1.596-4.73 5.068 4.73h6.665l-2.707-7.775h-7.998L15.272.857z" fill="url(#lobe-icons-azure-fill-1)"></path><path d="M17.193 1.613a1.11 1.11 0 00-1.052-.756h-7.81.035c.477 0 .9.304 1.052.756l6.748 19.992a1.11 1.11 0 01-1.052 1.466h-.12 7.895a1.11 1.11 0 001.052-1.466L17.193 1.613z" fill="url(#lobe-icons-azure-fill-2)"></path><defs><linearGradient gradientUnits="userSpaceOnUse" id="lobe-icons-azure-fill-0" x1="8.247" x2="1.002" y1="1.626" y2="23.03"><stop stop-color="#114A8B"></stop><stop offset="1" stop-color="#0669BC"></stop></linearGradient><linearGradient gradientUnits="userSpaceOnUse" id="lobe-icons-azure-fill-1" x1="14.042" x2="12.324" y1="15.302" y2="15.888"><stop stop-opacity=".3"></stop><stop offset=".071" stop-opacity=".2"></stop><stop offset=".321" stop-opacity=".1"></stop><stop offset=".623" stop-opacity=".05"></stop><stop offset="1" stop-opacity="0"></stop></linearGradient><linearGradient gradientUnits="userSpaceOnUse" id="lobe-icons-azure-fill-2" x1="12.841" x2="20.793" y1="1.626" y2="22.814"><stop stop-color="#3CCBF4"></stop><stop offset="1" stop-color="#2892DF"></stop></linearGradient></defs></svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
|
@ -1 +1 @@
|
|||
<svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Bedrock</title><defs><linearGradient id="lobe-icons-bedrock-fill" x1="80%" x2="20%" y1="20%" y2="80%"><stop offset="0%" stop-color="#6350FB"></stop><stop offset="50%" stop-color="#3D8FFF"></stop><stop offset="100%" stop-color="#9AD8F8"></stop></linearGradient></defs><path d="M13.05 15.513h3.08c.214 0 .389.177.389.394v1.82a1.704 1.704 0 011.296 1.661c0 .943-.755 1.708-1.685 1.708-.931 0-1.686-.765-1.686-1.708 0-.807.554-1.484 1.297-1.662v-1.425h-2.69v4.663a.395.395 0 01-.188.338l-2.69 1.641a.385.385 0 01-.405-.002l-4.926-3.086a.395.395 0 01-.185-.336V16.3L2.196 14.87A.395.395 0 012 14.555L2 14.528V9.406c0-.14.073-.27.192-.34l2.465-1.462V4.448c0-.129.062-.249.165-.322l.021-.014L9.77 1.058a.385.385 0 01.407 0l2.69 1.675a.395.395 0 01.185.336V7.6h3.856V5.683a1.704 1.704 0 01-1.296-1.662c0-.943.755-1.708 1.685-1.708.931 0 1.685.765 1.685 1.708 0 .807-.553 1.484-1.296 1.662v2.311a.391.391 0 01-.389.394h-4.245v1.806h6.624a1.69 1.69 0 011.64-1.313c.93 0 1.685.764 1.685 1.707 0 .943-.754 1.708-1.685 1.708a1.69 1.69 0 01-1.64-1.314H13.05v1.937h4.953l.915 1.18a1.66 1.66 0 01.84-.227c.931 0 1.685.764 1.685 1.707 0 .943-.754 1.708-1.685 1.708-.93 0-1.685-.765-1.685-1.708 0-.346.102-.668.276-.937l-.724-.935H13.05v1.806zM9.973 1.856L7.93 3.122V6.09h-.778V3.604L5.435 4.669v2.945l2.11 1.36L9.712 7.61V5.334h.778V7.83c0 .136-.07.263-.184.335L7.963 9.638v2.081l1.422 1.009-.446.646-1.406-.998-1.53 1.005-.423-.66 1.605-1.055v-1.99L5.038 8.29l-2.26 1.34v1.676l1.972-1.189.398.677-2.37 1.429V14.3l2.166 1.258 2.27-1.368.397.677-2.176 1.311V19.3l1.876 1.175 2.365-1.426.398.678-2.017 1.216 1.918 1.201 2.298-1.403v-5.78l-4.758 2.893-.4-.675 5.158-3.136V3.289L9.972 1.856zM16.13 18.47a.913.913 0 00-.908.92c0 .507.406.918.908.918a.913.913 0 00.907-.919.913.913 0 00-.907-.92zm3.63-3.81a.913.913 0 00-.908.92c0 .508.406.92.907.92a.913.913 0 00.908-.92.913.913 0 00-.908-.92zm1.555-4.99a.913.913 0 00-.908.92c0 .507.407.918.908.918a.913.913 0 00.907-.919.913.913 0 00-.907-.92zM17.296 3.1a.913.913 0 00-.907.92c0 .508.406.92.907.92a.913.913 0 00.908-.92.913.913 0 00-.908-.92z" fill="url(#lobe-icons-bedrock-fill)" fill-rule="nonzero"></path></svg>
|
||||
<svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Bedrock</title><defs><linearGradient id="lobe-icons-bedrock-fill" x1="80%" x2="20%" y1="20%" y2="80%"><stop offset="0%" stop-color="#6350FB"></stop><stop offset="50%" stop-color="#3D8FFF"></stop><stop offset="100%" stop-color="#9AD8F8"></stop></linearGradient></defs><path d="M13.05 15.513h3.08c.214 0 .389.177.389.394v1.82a1.704 1.704 0 011.296 1.661c0 .943-.755 1.708-1.685 1.708-.931 0-1.686-.765-1.686-1.708 0-.807.554-1.484 1.297-1.662v-1.425h-2.69v4.663a.395.395 0 01-.188.338l-2.69 1.641a.385.385 0 01-.405-.002l-4.926-3.086a.395.395 0 01-.185-.336V16.3L2.196 14.87A.395.395 0 012 14.555L2 14.528V9.406c0-.14.073-.27.192-.34l2.465-1.462V4.448c0-.129.062-.249.165-.322l.021-.014L9.77 1.058a.385.385 0 01.407 0l2.69 1.675a.395.395 0 01.185.336V7.6h3.856V5.683a1.704 1.704 0 01-1.296-1.662c0-.943.755-1.708 1.685-1.708.931 0 1.685.765 1.685 1.708 0 .807-.553 1.484-1.296 1.662v2.311a.391.391 0 01-.389.394h-4.245v1.806h6.624a1.69 1.69 0 011.64-1.313c.93 0 1.685.764 1.685 1.707 0 .943-.754 1.708-1.685 1.708a1.69 1.69 0 01-1.64-1.314H13.05v1.937h4.953l.915 1.18a1.66 1.66 0 01.84-.227c.931 0 1.685.764 1.685 1.707 0 .943-.754 1.708-1.685 1.708-.93 0-1.685-.765-1.685-1.708 0-.346.102-.668.276-.937l-.724-.935H13.05v1.806zM9.973 1.856L7.93 3.122V6.09h-.778V3.604L5.435 4.669v2.945l2.11 1.36L9.712 7.61V5.334h.778V7.83c0 .136-.07.263-.184.335L7.963 9.638v2.081l1.422 1.009-.446.646-1.406-.998-1.53 1.005-.423-.66 1.605-1.055v-1.99L5.038 8.29l-2.26 1.34v1.676l1.972-1.189.398.677-2.37 1.429V14.3l2.166 1.258 2.27-1.368.397.677-2.176 1.311V19.3l1.876 1.175 2.365-1.426.398.678-2.017 1.216 1.918 1.201 2.298-1.403v-5.78l-4.758 2.893-.4-.675 5.158-3.136V3.289L9.972 1.856zM16.13 18.47a.913.913 0 00-.908.92c0 .507.406.918.908.918a.913.913 0 00.907-.919.913.913 0 00-.907-.92zm3.63-3.81a.913.913 0 00-.908.92c0 .508.406.92.907.92a.913.913 0 00.908-.92.913.913 0 00-.908-.92zm1.555-4.99a.913.913 0 00-.908.92c0 .507.407.918.908.918a.913.913 0 00.907-.919.913.913 0 00-.907-.92zM17.296 3.1a.913.913 0 00-.907.92c0 .508.406.92.907.92a.913.913 0 00.908-.92.913.913 0 00-.908-.92z" fill="url(#lobe-icons-bedrock-fill)" fill-rule="nonzero"></path></svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
|
|
@ -1 +1 @@
|
|||
<svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>DeepSeek</title><path d="M23.748 4.482c-.254-.124-.364.113-.512.234-.051.039-.094.09-.137.136-.372.397-.806.657-1.373.626-.829-.046-1.537.214-2.163.848-.133-.782-.575-1.248-1.247-1.548-.352-.156-.708-.311-.955-.65-.172-.241-.219-.51-.305-.774-.055-.16-.11-.323-.293-.35-.2-.031-.278.136-.356.276-.313.572-.434 1.202-.422 1.84.027 1.436.633 2.58 1.838 3.393.137.093.172.187.129.323-.082.28-.18.552-.266.833-.055.179-.137.217-.329.14a5.526 5.526 0 01-1.736-1.18c-.857-.828-1.631-1.742-2.597-2.458a11.365 11.365 0 00-.689-.471c-.985-.957.13-1.743.388-1.836.27-.098.093-.432-.779-.428-.872.004-1.67.295-2.687.684a3.055 3.055 0 01-.465.137 9.597 9.597 0 00-2.883-.102c-1.885.21-3.39 1.102-4.497 2.623C.082 8.606-.231 10.684.152 12.85c.403 2.284 1.569 4.175 3.36 5.653 1.858 1.533 3.997 2.284 6.438 2.14 1.482-.085 3.133-.284 4.994-1.86.47.234.962.327 1.78.397.63.059 1.236-.03 1.705-.128.735-.156.684-.837.419-.961-2.155-1.004-1.682-.595-2.113-.926 1.096-1.296 2.746-2.642 3.392-7.003.05-.347.007-.565 0-.845-.004-.17.035-.237.23-.256a4.173 4.173 0 001.545-.475c1.396-.763 1.96-2.015 2.093-3.517.02-.23-.004-.467-.247-.588zM11.581 18c-2.089-1.642-3.102-2.183-3.52-2.16-.392.024-.321.471-.235.763.09.288.207.486.371.739.114.167.192.416-.113.603-.673.416-1.842-.14-1.897-.167-1.361-.802-2.5-1.86-3.301-3.307-.774-1.393-1.224-2.887-1.298-4.482-.02-.386.093-.522.477-.592a4.696 4.696 0 011.529-.039c2.132.312 3.946 1.265 5.468 2.774.868.86 1.525 1.887 2.202 2.891.72 1.066 1.494 2.082 2.48 2.914.348.292.625.514.891.677-.802.09-2.14.11-3.054-.614zm1-6.44a.306.306 0 01.415-.287.302.302 0 01.2.288.306.306 0 01-.31.307.303.303 0 01-.304-.308zm3.11 1.596c-.2.081-.399.151-.59.16a1.245 1.245 0 01-.798-.254c-.274-.23-.47-.358-.552-.758a1.73 1.73 0 01.016-.588c.07-.327-.008-.537-.239-.727-.187-.156-.426-.199-.688-.199a.559.559 0 01-.254-.078c-.11-.054-.2-.19-.114-.358.028-.054.16-.186.192-.21.356-.202.767-.136 1.146.016.352.144.618.408 1.001.782.391.451.462.576.685.914.176.265.336.537.445.848.067.195-.019.354-.25.452z" fill="#4D6BFE"></path></svg>
|
||||
<svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>DeepSeek</title><path d="M23.748 4.482c-.254-.124-.364.113-.512.234-.051.039-.094.09-.137.136-.372.397-.806.657-1.373.626-.829-.046-1.537.214-2.163.848-.133-.782-.575-1.248-1.247-1.548-.352-.156-.708-.311-.955-.65-.172-.241-.219-.51-.305-.774-.055-.16-.11-.323-.293-.35-.2-.031-.278.136-.356.276-.313.572-.434 1.202-.422 1.84.027 1.436.633 2.58 1.838 3.393.137.093.172.187.129.323-.082.28-.18.552-.266.833-.055.179-.137.217-.329.14a5.526 5.526 0 01-1.736-1.18c-.857-.828-1.631-1.742-2.597-2.458a11.365 11.365 0 00-.689-.471c-.985-.957.13-1.743.388-1.836.27-.098.093-.432-.779-.428-.872.004-1.67.295-2.687.684a3.055 3.055 0 01-.465.137 9.597 9.597 0 00-2.883-.102c-1.885.21-3.39 1.102-4.497 2.623C.082 8.606-.231 10.684.152 12.85c.403 2.284 1.569 4.175 3.36 5.653 1.858 1.533 3.997 2.284 6.438 2.14 1.482-.085 3.133-.284 4.994-1.86.47.234.962.327 1.78.397.63.059 1.236-.03 1.705-.128.735-.156.684-.837.419-.961-2.155-1.004-1.682-.595-2.113-.926 1.096-1.296 2.746-2.642 3.392-7.003.05-.347.007-.565 0-.845-.004-.17.035-.237.23-.256a4.173 4.173 0 001.545-.475c1.396-.763 1.96-2.015 2.093-3.517.02-.23-.004-.467-.247-.588zM11.581 18c-2.089-1.642-3.102-2.183-3.52-2.16-.392.024-.321.471-.235.763.09.288.207.486.371.739.114.167.192.416-.113.603-.673.416-1.842-.14-1.897-.167-1.361-.802-2.5-1.86-3.301-3.307-.774-1.393-1.224-2.887-1.298-4.482-.02-.386.093-.522.477-.592a4.696 4.696 0 011.529-.039c2.132.312 3.946 1.265 5.468 2.774.868.86 1.525 1.887 2.202 2.891.72 1.066 1.494 2.082 2.48 2.914.348.292.625.514.891.677-.802.09-2.14.11-3.054-.614zm1-6.44a.306.306 0 01.415-.287.302.302 0 01.2.288.306.306 0 01-.31.307.303.303 0 01-.304-.308zm3.11 1.596c-.2.081-.399.151-.59.16a1.245 1.245 0 01-.798-.254c-.274-.23-.47-.358-.552-.758a1.73 1.73 0 01.016-.588c.07-.327-.008-.537-.239-.727-.187-.156-.426-.199-.688-.199a.559.559 0 01-.254-.078c-.11-.054-.2-.19-.114-.358.028-.054.16-.186.192-.21.356-.202.767-.136 1.146.016.352.144.618.408 1.001.782.391.451.462.576.685.914.176.265.336.537.445.848.067.195-.019.354-.25.452z" fill="#4D6BFE"></path></svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
|
@ -1 +1 @@
|
|||
<svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Gemini</title><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z" fill="#3186FF"></path><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z" fill="url(#lobe-icons-gemini-fill-0)"></path><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z" fill="url(#lobe-icons-gemini-fill-1)"></path><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z" fill="url(#lobe-icons-gemini-fill-2)"></path><defs><linearGradient gradientUnits="userSpaceOnUse" id="lobe-icons-gemini-fill-0" x1="7" x2="11" y1="15.5" y2="12"><stop stop-color="#08B962"></stop><stop offset="1" stop-color="#08B962" stop-opacity="0"></stop></linearGradient><linearGradient gradientUnits="userSpaceOnUse" id="lobe-icons-gemini-fill-1" x1="8" x2="11.5" y1="5.5" y2="11"><stop stop-color="#F94543"></stop><stop offset="1" stop-color="#F94543" stop-opacity="0"></stop></linearGradient><linearGradient gradientUnits="userSpaceOnUse" id="lobe-icons-gemini-fill-2" x1="3.5" x2="17.5" y1="13.5" y2="12"><stop stop-color="#FABC12"></stop><stop offset=".46" stop-color="#FABC12" stop-opacity="0"></stop></linearGradient></defs></svg>
|
||||
<svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Gemini</title><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z" fill="#3186FF"></path><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z" fill="url(#lobe-icons-gemini-fill-0)"></path><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z" fill="url(#lobe-icons-gemini-fill-1)"></path><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z" fill="url(#lobe-icons-gemini-fill-2)"></path><defs><linearGradient gradientUnits="userSpaceOnUse" id="lobe-icons-gemini-fill-0" x1="7" x2="11" y1="15.5" y2="12"><stop stop-color="#08B962"></stop><stop offset="1" stop-color="#08B962" stop-opacity="0"></stop></linearGradient><linearGradient gradientUnits="userSpaceOnUse" id="lobe-icons-gemini-fill-1" x1="8" x2="11.5" y1="5.5" y2="11"><stop stop-color="#F94543"></stop><stop offset="1" stop-color="#F94543" stop-opacity="0"></stop></linearGradient><linearGradient gradientUnits="userSpaceOnUse" id="lobe-icons-gemini-fill-2" x1="3.5" x2="17.5" y1="13.5" y2="12"><stop stop-color="#FABC12"></stop><stop offset=".46" stop-color="#FABC12" stop-opacity="0"></stop></linearGradient></defs></svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
|
|
@ -1 +1 @@
|
|||
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>LM Studio</title><path d="M2.84 2a1.273 1.273 0 100 2.547h14.107a1.273 1.273 0 100-2.547H2.84zM7.935 5.33a1.273 1.273 0 000 2.548H22.04a1.274 1.274 0 000-2.547H7.935zM3.624 9.935c0-.704.57-1.274 1.274-1.274h14.106a1.274 1.274 0 010 2.547H4.898c-.703 0-1.274-.57-1.274-1.273zM1.273 12.188a1.273 1.273 0 100 2.547H15.38a1.274 1.274 0 000-2.547H1.273zM3.624 16.792c0-.704.57-1.274 1.274-1.274h14.106a1.273 1.273 0 110 2.547H4.898c-.703 0-1.274-.57-1.274-1.273zM13.029 18.849a1.273 1.273 0 100 2.547h9.698a1.273 1.273 0 100-2.547h-9.698z" fill-opacity=".3"></path><path d="M2.84 2a1.273 1.273 0 100 2.547h10.287a1.274 1.274 0 000-2.547H2.84zM7.935 5.33a1.273 1.273 0 000 2.548H18.22a1.274 1.274 0 000-2.547H7.935zM3.624 9.935c0-.704.57-1.274 1.274-1.274h10.286a1.273 1.273 0 010 2.547H4.898c-.703 0-1.274-.57-1.274-1.273zM1.273 12.188a1.273 1.273 0 100 2.547H11.56a1.274 1.274 0 000-2.547H1.273zM3.624 16.792c0-.704.57-1.274 1.274-1.274h10.286a1.273 1.273 0 110 2.547H4.898c-.703 0-1.274-.57-1.274-1.273zM13.029 18.849a1.273 1.273 0 100 2.547h5.78a1.273 1.273 0 100-2.547h-5.78z"></path></svg>
|
||||
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>LM Studio</title><path d="M2.84 2a1.273 1.273 0 100 2.547h14.107a1.273 1.273 0 100-2.547H2.84zM7.935 5.33a1.273 1.273 0 000 2.548H22.04a1.274 1.274 0 000-2.547H7.935zM3.624 9.935c0-.704.57-1.274 1.274-1.274h14.106a1.274 1.274 0 010 2.547H4.898c-.703 0-1.274-.57-1.274-1.273zM1.273 12.188a1.273 1.273 0 100 2.547H15.38a1.274 1.274 0 000-2.547H1.273zM3.624 16.792c0-.704.57-1.274 1.274-1.274h14.106a1.273 1.273 0 110 2.547H4.898c-.703 0-1.274-.57-1.274-1.273zM13.029 18.849a1.273 1.273 0 100 2.547h9.698a1.273 1.273 0 100-2.547h-9.698z" fill-opacity=".3"></path><path d="M2.84 2a1.273 1.273 0 100 2.547h10.287a1.274 1.274 0 000-2.547H2.84zM7.935 5.33a1.273 1.273 0 000 2.548H18.22a1.274 1.274 0 000-2.547H7.935zM3.624 9.935c0-.704.57-1.274 1.274-1.274h10.286a1.273 1.273 0 010 2.547H4.898c-.703 0-1.274-.57-1.274-1.273zM1.273 12.188a1.273 1.273 0 100 2.547H11.56a1.274 1.274 0 000-2.547H1.273zM3.624 16.792c0-.704.57-1.274 1.274-1.274h10.286a1.273 1.273 0 110 2.547H4.898c-.703 0-1.274-.57-1.274-1.273zM13.029 18.849a1.273 1.273 0 100 2.547h5.78a1.273 1.273 0 100-2.547h-5.78z"></path></svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
|
@ -1 +1 @@
|
|||
<svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Minimax</title><defs><linearGradient id="lobe-icons-minimax-fill" x1="0%" x2="100.182%" y1="50.057%" y2="50.057%"><stop offset="0%" stop-color="#E2167E"></stop><stop offset="100%" stop-color="#FE603C"></stop></linearGradient></defs><path d="M16.278 2c1.156 0 2.093.927 2.093 2.07v12.501a.74.74 0 00.744.709.74.74 0 00.743-.709V9.099a2.06 2.06 0 012.071-2.049A2.06 2.06 0 0124 9.1v6.561a.649.649 0 01-.652.645.649.649 0 01-.653-.645V9.1a.762.762 0 00-.766-.758.762.762 0 00-.766.758v7.472a2.037 2.037 0 01-2.048 2.026 2.037 2.037 0 01-2.048-2.026v-12.5a.785.785 0 00-.788-.753.785.785 0 00-.789.752l-.001 15.904A2.037 2.037 0 0113.441 22a2.037 2.037 0 01-2.048-2.026V18.04c0-.356.292-.645.652-.645.36 0 .652.289.652.645v1.934c0 .263.142.506.372.638.23.131.514.131.744 0a.734.734 0 00.372-.638V4.07c0-1.143.937-2.07 2.093-2.07zm-5.674 0c1.156 0 2.093.927 2.093 2.07v11.523a.648.648 0 01-.652.645.648.648 0 01-.652-.645V4.07a.785.785 0 00-.789-.78.785.785 0 00-.789.78v14.013a2.06 2.06 0 01-2.07 2.048 2.06 2.06 0 01-2.071-2.048V9.1a.762.762 0 00-.766-.758.762.762 0 00-.766.758v3.8a2.06 2.06 0 01-2.071 2.049A2.06 2.06 0 010 12.9v-1.378c0-.357.292-.646.652-.646.36 0 .653.29.653.646V12.9c0 .418.343.757.766.757s.766-.339.766-.757V9.099a2.06 2.06 0 012.07-2.048 2.06 2.06 0 012.071 2.048v8.984c0 .419.343.758.767.758.423 0 .766-.339.766-.758V4.07c0-1.143.937-2.07 2.093-2.07z" fill="url(#lobe-icons-minimax-fill)" fill-rule="nonzero"></path></svg>
|
||||
<svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Minimax</title><defs><linearGradient id="lobe-icons-minimax-fill" x1="0%" x2="100.182%" y1="50.057%" y2="50.057%"><stop offset="0%" stop-color="#E2167E"></stop><stop offset="100%" stop-color="#FE603C"></stop></linearGradient></defs><path d="M16.278 2c1.156 0 2.093.927 2.093 2.07v12.501a.74.74 0 00.744.709.74.74 0 00.743-.709V9.099a2.06 2.06 0 012.071-2.049A2.06 2.06 0 0124 9.1v6.561a.649.649 0 01-.652.645.649.649 0 01-.653-.645V9.1a.762.762 0 00-.766-.758.762.762 0 00-.766.758v7.472a2.037 2.037 0 01-2.048 2.026 2.037 2.037 0 01-2.048-2.026v-12.5a.785.785 0 00-.788-.753.785.785 0 00-.789.752l-.001 15.904A2.037 2.037 0 0113.441 22a2.037 2.037 0 01-2.048-2.026V18.04c0-.356.292-.645.652-.645.36 0 .652.289.652.645v1.934c0 .263.142.506.372.638.23.131.514.131.744 0a.734.734 0 00.372-.638V4.07c0-1.143.937-2.07 2.093-2.07zm-5.674 0c1.156 0 2.093.927 2.093 2.07v11.523a.648.648 0 01-.652.645.648.648 0 01-.652-.645V4.07a.785.785 0 00-.789-.78.785.785 0 00-.789.78v14.013a2.06 2.06 0 01-2.07 2.048 2.06 2.06 0 01-2.071-2.048V9.1a.762.762 0 00-.766-.758.762.762 0 00-.766.758v3.8a2.06 2.06 0 01-2.071 2.049A2.06 2.06 0 010 12.9v-1.378c0-.357.292-.646.652-.646.36 0 .653.29.653.646V12.9c0 .418.343.757.766.757s.766-.339.766-.757V9.099a2.06 2.06 0 012.07-2.048 2.06 2.06 0 012.071 2.048v8.984c0 .419.343.758.767.758.423 0 .766-.339.766-.758V4.07c0-1.143.937-2.07 2.093-2.07z" fill="url(#lobe-icons-minimax-fill)" fill-rule="nonzero"></path></svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
|
@ -1 +1 @@
|
|||
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>MoonshotAI</title><path d="M1.052 16.916l9.539 2.552a21.007 21.007 0 00.06 2.033l5.956 1.593a11.997 11.997 0 01-5.586.865l-.18-.016-.044-.004-.084-.009-.094-.01a11.605 11.605 0 01-.157-.02l-.107-.014-.11-.016a11.962 11.962 0 01-.32-.051l-.042-.008-.075-.013-.107-.02-.07-.015-.093-.019-.075-.016-.095-.02-.097-.023-.094-.022-.068-.017-.088-.022-.09-.024-.095-.025-.082-.023-.109-.03-.062-.02-.084-.025-.093-.028-.105-.034-.058-.019-.08-.026-.09-.031-.066-.024a6.293 6.293 0 01-.044-.015l-.068-.025-.101-.037-.057-.022-.08-.03-.087-.035-.088-.035-.079-.032-.095-.04-.063-.028-.063-.027a5.655 5.655 0 01-.041-.018l-.066-.03-.103-.047-.052-.024-.096-.046-.062-.03-.084-.04-.086-.044-.093-.047-.052-.027-.103-.055-.057-.03-.058-.032a6.49 6.49 0 01-.046-.026l-.094-.053-.06-.034-.051-.03-.072-.041-.082-.05-.093-.056-.052-.032-.084-.053-.061-.039-.079-.05-.07-.047-.053-.035a7.785 7.785 0 01-.054-.036l-.044-.03-.044-.03a6.066 6.066 0 01-.04-.028l-.057-.04-.076-.054-.069-.05-.074-.054-.056-.042-.076-.057-.076-.059-.086-.067-.045-.035-.064-.052-.074-.06-.089-.073-.046-.039-.046-.039a7.516 7.516 0 01-.043-.037l-.045-.04-.061-.053-.07-.062-.068-.06-.062-.058-.067-.062-.053-.05-.088-.084a13.28 13.28 0 01-.099-.097l-.029-.028-.041-.042-.069-.07-.05-.051-.05-.053a6.457 6.457 0 01-.168-.179l-.08-.088-.062-.07-.071-.08-.042-.049-.053-.062-.058-.068-.046-.056a7.175 7.175 0 01-.027-.033l-.045-.055-.066-.082-.041-.052-.05-.064-.02-.025a11.99 11.99 0 01-1.44-2.402zm-1.02-5.794l11.353 3.037a20.468 20.468 0 00-.469 2.011l10.817 2.894a12.076 12.076 0 01-1.845 2.005L.657 15.923l-.016-.046-.035-.104a11.965 11.965 0 01-.05-.153l-.007-.023a11.896 11.896 0 01-.207-.741l-.03-.126-.018-.08-.021-.097-.018-.081-.018-.09-.017-.084-.018-.094c-.026-.141-.05-.283-.071-.426l-.017-.118-.011-.083-.013-.102a12.01 12.01 0 01-.019-.161l-.005-.047a12.12 12.12 0 01-.034-2.145zm1.593-5.15l11.948 3.196c-.368.605-.705 1.231-1.01 1.875l11.295 3.022c-.142.82-.368 1.612-.668 2.365l-11.55-3.09L.124 10.26l.015-.1.008-.049.01-.067.015-.087.018-.098c.026-.148.056-.295.088-.442l.028-.124.02-.085.024-.097c.022-.09.045-.18.07-.268l.028-.102.023-.083.03-.1.025-.082.03-.096.026-.082.031-.095a11.896 11.896 0 011.01-2.232zm4.442-4.4L17.352 4.59a20.77 20.77 0 00-1.688 1.721l7.823 2.093c.267.852.442 1.744.513 2.665L2.106 5.213l.045-.065.027-.04.04-.055.046-.065.055-.076.054-.072.064-.086.05-.065.057-.073.055-.07.06-.074.055-.069.065-.077.054-.066.066-.077.053-.06.072-.082.053-.06.067-.074.054-.058.073-.078.058-.06.063-.067.168-.17.1-.098.059-.056.076-.071a12.084 12.084 0 012.272-1.677zM12.017 0h.097l.082.001.069.001.054.002.068.002.046.001.076.003.047.002.06.003.054.002.087.005.105.007.144.011.088.007.044.004.077.008.082.008.047.005.102.012.05.006.108.014.081.01.042.006.065.01.207.032.07.012.065.011.14.026.092.018.11.022.046.01.075.016.041.01L14.7.3l.042.01.065.015.049.012.071.017.096.024.112.03.113.03.113.032.05.015.07.02.078.024.073.023.05.016.05.016.076.025.099.033.102.036.048.017.064.023.093.034.11.041.116.045.1.04.047.02.06.024.041.018.063.026.04.018.057.025.11.048.1.046.074.035.075.036.06.028.092.046.091.045.102.052.053.028.049.026.046.024.06.033.041.022.052.029.088.05.106.06.087.051.057.034.053.032.096.059.088.055.098.062.036.024.064.041.084.056.04.027.062.042.062.043.023.017c.054.037.108.075.161.114l.083.06.065.048.056.043.086.065.082.064.04.03.05.041.086.069.079.065.085.071c.712.6 1.353 1.283 1.909 2.031L7.222.994l.062-.027.065-.028.081-.034.086-.035c.113-.045.227-.09.341-.131l.096-.035.093-.033.084-.03.096-.031c.087-.03.176-.058.264-.085l.091-.027.086-.025.102-.03.085-.023.1-.026L9.04.37l.09-.023.091-.022.095-.022.09-.02.098-.021.091-.02.095-.018.092-.018.1-.018.091-.016.098-.017.092-.014.097-.015.092-.013.102-.013.091-.012.105-.012.09-.01.105-.01c.093-.01.186-.018.28-.024l.106-.008.09-.005.11-.006.093-.004.1-.004.097-.002.099-.002.197-.002z"></path></svg>
|
||||
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>MoonshotAI</title><path d="M1.052 16.916l9.539 2.552a21.007 21.007 0 00.06 2.033l5.956 1.593a11.997 11.997 0 01-5.586.865l-.18-.016-.044-.004-.084-.009-.094-.01a11.605 11.605 0 01-.157-.02l-.107-.014-.11-.016a11.962 11.962 0 01-.32-.051l-.042-.008-.075-.013-.107-.02-.07-.015-.093-.019-.075-.016-.095-.02-.097-.023-.094-.022-.068-.017-.088-.022-.09-.024-.095-.025-.082-.023-.109-.03-.062-.02-.084-.025-.093-.028-.105-.034-.058-.019-.08-.026-.09-.031-.066-.024a6.293 6.293 0 01-.044-.015l-.068-.025-.101-.037-.057-.022-.08-.03-.087-.035-.088-.035-.079-.032-.095-.04-.063-.028-.063-.027a5.655 5.655 0 01-.041-.018l-.066-.03-.103-.047-.052-.024-.096-.046-.062-.03-.084-.04-.086-.044-.093-.047-.052-.027-.103-.055-.057-.03-.058-.032a6.49 6.49 0 01-.046-.026l-.094-.053-.06-.034-.051-.03-.072-.041-.082-.05-.093-.056-.052-.032-.084-.053-.061-.039-.079-.05-.07-.047-.053-.035a7.785 7.785 0 01-.054-.036l-.044-.03-.044-.03a6.066 6.066 0 01-.04-.028l-.057-.04-.076-.054-.069-.05-.074-.054-.056-.042-.076-.057-.076-.059-.086-.067-.045-.035-.064-.052-.074-.06-.089-.073-.046-.039-.046-.039a7.516 7.516 0 01-.043-.037l-.045-.04-.061-.053-.07-.062-.068-.06-.062-.058-.067-.062-.053-.05-.088-.084a13.28 13.28 0 01-.099-.097l-.029-.028-.041-.042-.069-.07-.05-.051-.05-.053a6.457 6.457 0 01-.168-.179l-.08-.088-.062-.07-.071-.08-.042-.049-.053-.062-.058-.068-.046-.056a7.175 7.175 0 01-.027-.033l-.045-.055-.066-.082-.041-.052-.05-.064-.02-.025a11.99 11.99 0 01-1.44-2.402zm-1.02-5.794l11.353 3.037a20.468 20.468 0 00-.469 2.011l10.817 2.894a12.076 12.076 0 01-1.845 2.005L.657 15.923l-.016-.046-.035-.104a11.965 11.965 0 01-.05-.153l-.007-.023a11.896 11.896 0 01-.207-.741l-.03-.126-.018-.08-.021-.097-.018-.081-.018-.09-.017-.084-.018-.094c-.026-.141-.05-.283-.071-.426l-.017-.118-.011-.083-.013-.102a12.01 12.01 0 01-.019-.161l-.005-.047a12.12 12.12 0 01-.034-2.145zm1.593-5.15l11.948 3.196c-.368.605-.705 1.231-1.01 1.875l11.295 3.022c-.142.82-.368 1.612-.668 2.365l-11.55-3.09L.124 10.26l.015-.1.008-.049.01-.067.015-.087.018-.098c.026-.148.056-.295.088-.442l.028-.124.02-.085.024-.097c.022-.09.045-.18.07-.268l.028-.102.023-.083.03-.1.025-.082.03-.096.026-.082.031-.095a11.896 11.896 0 011.01-2.232zm4.442-4.4L17.352 4.59a20.77 20.77 0 00-1.688 1.721l7.823 2.093c.267.852.442 1.744.513 2.665L2.106 5.213l.045-.065.027-.04.04-.055.046-.065.055-.076.054-.072.064-.086.05-.065.057-.073.055-.07.06-.074.055-.069.065-.077.054-.066.066-.077.053-.06.072-.082.053-.06.067-.074.054-.058.073-.078.058-.06.063-.067.168-.17.1-.098.059-.056.076-.071a12.084 12.084 0 012.272-1.677zM12.017 0h.097l.082.001.069.001.054.002.068.002.046.001.076.003.047.002.06.003.054.002.087.005.105.007.144.011.088.007.044.004.077.008.082.008.047.005.102.012.05.006.108.014.081.01.042.006.065.01.207.032.07.012.065.011.14.026.092.018.11.022.046.01.075.016.041.01L14.7.3l.042.01.065.015.049.012.071.017.096.024.112.03.113.03.113.032.05.015.07.02.078.024.073.023.05.016.05.016.076.025.099.033.102.036.048.017.064.023.093.034.11.041.116.045.1.04.047.02.06.024.041.018.063.026.04.018.057.025.11.048.1.046.074.035.075.036.06.028.092.046.091.045.102.052.053.028.049.026.046.024.06.033.041.022.052.029.088.05.106.06.087.051.057.034.053.032.096.059.088.055.098.062.036.024.064.041.084.056.04.027.062.042.062.043.023.017c.054.037.108.075.161.114l.083.06.065.048.056.043.086.065.082.064.04.03.05.041.086.069.079.065.085.071c.712.6 1.353 1.283 1.909 2.031L7.222.994l.062-.027.065-.028.081-.034.086-.035c.113-.045.227-.09.341-.131l.096-.035.093-.033.084-.03.096-.031c.087-.03.176-.058.264-.085l.091-.027.086-.025.102-.03.085-.023.1-.026L9.04.37l.09-.023.091-.022.095-.022.09-.02.098-.021.091-.02.095-.018.092-.018.1-.018.091-.016.098-.017.092-.014.097-.015.092-.013.102-.013.091-.012.105-.012.09-.01.105-.01c.093-.01.186-.018.28-.024l.106-.008.09-.005.11-.006.093-.004.1-.004.097-.002.099-.002.197-.002z"></path></svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
|
|
@ -1 +1 @@
|
|||
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Ollama</title><path d="M7.905 1.09c.216.085.411.225.588.41.295.306.544.744.734 1.263.191.522.315 1.1.362 1.68a5.054 5.054 0 012.049-.636l.051-.004c.87-.07 1.73.087 2.48.474.101.053.2.11.297.17.05-.569.172-1.134.36-1.644.19-.52.439-.957.733-1.264a1.67 1.67 0 01.589-.41c.257-.1.53-.118.796-.042.401.114.745.368 1.016.737.248.337.434.769.561 1.287.23.934.27 2.163.115 3.645l.053.04.026.019c.757.576 1.284 1.397 1.563 2.35.435 1.487.216 3.155-.534 4.088l-.018.021.002.003c.417.762.67 1.567.724 2.4l.002.03c.064 1.065-.2 2.137-.814 3.19l-.007.01.01.024c.472 1.157.62 2.322.438 3.486l-.006.039a.651.651 0 01-.747.536.648.648 0 01-.54-.742c.167-1.033.01-2.069-.48-3.123a.643.643 0 01.04-.617l.004-.006c.604-.924.854-1.83.8-2.72-.046-.779-.325-1.544-.8-2.273a.644.644 0 01.18-.886l.009-.006c.243-.159.467-.565.58-1.12a4.229 4.229 0 00-.095-1.974c-.205-.7-.58-1.284-1.105-1.683-.595-.454-1.383-.673-2.38-.61a.653.653 0 01-.632-.371c-.314-.665-.772-1.141-1.343-1.436a3.288 3.288 0 00-1.772-.332c-1.245.099-2.343.801-2.67 1.686a.652.652 0 01-.61.425c-1.067.002-1.893.252-2.497.703-.522.39-.878.935-1.066 1.588a4.07 4.07 0 00-.068 1.886c.112.558.331 1.02.582 1.269l.008.007c.212.207.257.53.109.785-.36.622-.629 1.549-.673 2.44-.05 1.018.186 1.902.719 2.536l.016.019a.643.643 0 01.095.69c-.576 1.236-.753 2.252-.562 3.052a.652.652 0 01-1.269.298c-.243-1.018-.078-2.184.473-3.498l.014-.035-.008-.012a4.339 4.339 0 01-.598-1.309l-.005-.019a5.764 5.764 0 01-.177-1.785c.044-.91.278-1.842.622-2.59l.012-.026-.002-.002c-.293-.418-.51-.953-.63-1.545l-.005-.024a5.352 5.352 0 01.093-2.49c.262-.915.777-1.701 1.536-2.269.06-.045.123-.09.186-.132-.159-1.493-.119-2.73.112-3.67.127-.518.314-.95.562-1.287.27-.368.614-.622 1.015-.737.266-.076.54-.059.797.042zm4.116 9.09c.936 0 1.8.313 2.446.855.63.527 1.005 1.235 1.005 1.94 0 .888-.406 1.58-1.133 2.022-.62.375-1.451.557-2.403.557-1.009 0-1.871-.259-2.493-.734-.617-.47-.963-1.13-.963-1.845 0-.707.398-1.417 1.056-1.946.668-.537 1.55-.849 2.485-.849zm0 .896a3.07 3.07 0 00-1.916.65c-.461.37-.722.835-.722 1.25 0 .428.21.829.61 1.134.455.347 1.124.548 1.943.548.799 0 1.473-.147 1.932-.426.463-.28.7-.686.7-1.257 0-.423-.246-.89-.683-1.256-.484-.405-1.14-.643-1.864-.643zm.662 1.21l.004.004c.12.151.095.37-.056.49l-.292.23v.446a.375.375 0 01-.376.373.375.375 0 01-.376-.373v-.46l-.271-.218a.347.347 0 01-.052-.49.353.353 0 01.494-.051l.215.172.22-.174a.353.353 0 01.49.051zm-5.04-1.919c.478 0 .867.39.867.871a.87.87 0 01-.868.871.87.87 0 01-.867-.87.87.87 0 01.867-.872zm8.706 0c.48 0 .868.39.868.871a.87.87 0 01-.868.871.87.87 0 01-.867-.87.87.87 0 01.867-.872zM7.44 2.3l-.003.002a.659.659 0 00-.285.238l-.005.006c-.138.189-.258.467-.348.832-.17.692-.216 1.631-.124 2.782.43-.128.899-.208 1.404-.237l.01-.001.019-.034c.046-.082.095-.161.148-.239.123-.771.022-1.692-.253-2.444-.134-.364-.297-.65-.453-.813a.628.628 0 00-.107-.09L7.44 2.3zm9.174.04l-.002.001a.628.628 0 00-.107.09c-.156.163-.32.45-.453.814-.29.794-.387 1.776-.23 2.572l.058.097.008.014h.03a5.184 5.184 0 011.466.212c.086-1.124.038-2.043-.128-2.722-.09-.365-.21-.643-.349-.832l-.004-.006a.659.659 0 00-.285-.239h-.004z"></path></svg>
|
||||
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Ollama</title><path d="M7.905 1.09c.216.085.411.225.588.41.295.306.544.744.734 1.263.191.522.315 1.1.362 1.68a5.054 5.054 0 012.049-.636l.051-.004c.87-.07 1.73.087 2.48.474.101.053.2.11.297.17.05-.569.172-1.134.36-1.644.19-.52.439-.957.733-1.264a1.67 1.67 0 01.589-.41c.257-.1.53-.118.796-.042.401.114.745.368 1.016.737.248.337.434.769.561 1.287.23.934.27 2.163.115 3.645l.053.04.026.019c.757.576 1.284 1.397 1.563 2.35.435 1.487.216 3.155-.534 4.088l-.018.021.002.003c.417.762.67 1.567.724 2.4l.002.03c.064 1.065-.2 2.137-.814 3.19l-.007.01.01.024c.472 1.157.62 2.322.438 3.486l-.006.039a.651.651 0 01-.747.536.648.648 0 01-.54-.742c.167-1.033.01-2.069-.48-3.123a.643.643 0 01.04-.617l.004-.006c.604-.924.854-1.83.8-2.72-.046-.779-.325-1.544-.8-2.273a.644.644 0 01.18-.886l.009-.006c.243-.159.467-.565.58-1.12a4.229 4.229 0 00-.095-1.974c-.205-.7-.58-1.284-1.105-1.683-.595-.454-1.383-.673-2.38-.61a.653.653 0 01-.632-.371c-.314-.665-.772-1.141-1.343-1.436a3.288 3.288 0 00-1.772-.332c-1.245.099-2.343.801-2.67 1.686a.652.652 0 01-.61.425c-1.067.002-1.893.252-2.497.703-.522.39-.878.935-1.066 1.588a4.07 4.07 0 00-.068 1.886c.112.558.331 1.02.582 1.269l.008.007c.212.207.257.53.109.785-.36.622-.629 1.549-.673 2.44-.05 1.018.186 1.902.719 2.536l.016.019a.643.643 0 01.095.69c-.576 1.236-.753 2.252-.562 3.052a.652.652 0 01-1.269.298c-.243-1.018-.078-2.184.473-3.498l.014-.035-.008-.012a4.339 4.339 0 01-.598-1.309l-.005-.019a5.764 5.764 0 01-.177-1.785c.044-.91.278-1.842.622-2.59l.012-.026-.002-.002c-.293-.418-.51-.953-.63-1.545l-.005-.024a5.352 5.352 0 01.093-2.49c.262-.915.777-1.701 1.536-2.269.06-.045.123-.09.186-.132-.159-1.493-.119-2.73.112-3.67.127-.518.314-.95.562-1.287.27-.368.614-.622 1.015-.737.266-.076.54-.059.797.042zm4.116 9.09c.936 0 1.8.313 2.446.855.63.527 1.005 1.235 1.005 1.94 0 .888-.406 1.58-1.133 2.022-.62.375-1.451.557-2.403.557-1.009 0-1.871-.259-2.493-.734-.617-.47-.963-1.13-.963-1.845 0-.707.398-1.417 1.056-1.946.668-.537 1.55-.849 2.485-.849zm0 .896a3.07 3.07 0 00-1.916.65c-.461.37-.722.835-.722 1.25 0 .428.21.829.61 1.134.455.347 1.124.548 1.943.548.799 0 1.473-.147 1.932-.426.463-.28.7-.686.7-1.257 0-.423-.246-.89-.683-1.256-.484-.405-1.14-.643-1.864-.643zm.662 1.21l.004.004c.12.151.095.37-.056.49l-.292.23v.446a.375.375 0 01-.376.373.375.375 0 01-.376-.373v-.46l-.271-.218a.347.347 0 01-.052-.49.353.353 0 01.494-.051l.215.172.22-.174a.353.353 0 01.49.051zm-5.04-1.919c.478 0 .867.39.867.871a.87.87 0 01-.868.871.87.87 0 01-.867-.87.87.87 0 01.867-.872zm8.706 0c.48 0 .868.39.868.871a.87.87 0 01-.868.871.87.87 0 01-.867-.87.87.87 0 01.867-.872zM7.44 2.3l-.003.002a.659.659 0 00-.285.238l-.005.006c-.138.189-.258.467-.348.832-.17.692-.216 1.631-.124 2.782.43-.128.899-.208 1.404-.237l.01-.001.019-.034c.046-.082.095-.161.148-.239.123-.771.022-1.692-.253-2.444-.134-.364-.297-.65-.453-.813a.628.628 0 00-.107-.09L7.44 2.3zm9.174.04l-.002.001a.628.628 0 00-.107.09c-.156.163-.32.45-.453.814-.29.794-.387 1.776-.23 2.572l.058.097.008.014h.03a5.184 5.184 0 011.466.212c.086-1.124.038-2.043-.128-2.722-.09-.365-.21-.643-.349-.832l-.004-.006a.659.659 0 00-.285-.239h-.004z"></path></svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
|
|
@ -1 +1 @@
|
|||
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>OpenRouter</title><path d="M16.804 1.957l7.22 4.105v.087L16.73 10.21l.017-2.117-.821-.03c-1.059-.028-1.611.002-2.268.11-1.064.175-2.038.577-3.147 1.352L8.345 11.03c-.284.195-.495.336-.68.455l-.515.322-.397.234.385.23.53.338c.476.314 1.17.796 2.701 1.866 1.11.775 2.083 1.177 3.147 1.352l.3.045c.694.091 1.375.094 2.825.033l.022-2.159 7.22 4.105v.087L16.589 22l.014-1.862-.635.022c-1.386.042-2.137.002-3.138-.162-1.694-.28-3.26-.926-4.881-2.059l-2.158-1.5a21.997 21.997 0 00-.755-.498l-.467-.28a55.927 55.927 0 00-.76-.43C2.908 14.73.563 14.116 0 14.116V9.888l.14.004c.564-.007 2.91-.622 3.809-1.124l1.016-.58.438-.274c.428-.28 1.072-.726 2.686-1.853 1.621-1.133 3.186-1.78 4.881-2.059 1.152-.19 1.974-.213 3.814-.138l.02-1.907z"></path></svg>
|
||||
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>OpenRouter</title><path d="M16.804 1.957l7.22 4.105v.087L16.73 10.21l.017-2.117-.821-.03c-1.059-.028-1.611.002-2.268.11-1.064.175-2.038.577-3.147 1.352L8.345 11.03c-.284.195-.495.336-.68.455l-.515.322-.397.234.385.23.53.338c.476.314 1.17.796 2.701 1.866 1.11.775 2.083 1.177 3.147 1.352l.3.045c.694.091 1.375.094 2.825.033l.022-2.159 7.22 4.105v.087L16.589 22l.014-1.862-.635.022c-1.386.042-2.137.002-3.138-.162-1.694-.28-3.26-.926-4.881-2.059l-2.158-1.5a21.997 21.997 0 00-.755-.498l-.467-.28a55.927 55.927 0 00-.76-.43C2.908 14.73.563 14.116 0 14.116V9.888l.14.004c.564-.007 2.91-.622 3.809-1.124l1.016-.58.438-.274c.428-.28 1.072-.726 2.686-1.853 1.621-1.133 3.186-1.78 4.881-2.059 1.152-.19 1.974-.213 3.814-.138l.02-1.907z"></path></svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 906 B After Width: | Height: | Size: 907 B |
|
|
@ -1 +1 @@
|
|||
<svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Qwen</title><path d="M12.604 1.34c.393.69.784 1.382 1.174 2.075a.18.18 0 00.157.091h5.552c.174 0 .322.11.446.327l1.454 2.57c.19.337.24.478.024.837-.26.43-.513.864-.76 1.3l-.367.658c-.106.196-.223.28-.04.512l2.652 4.637c.172.301.111.494-.043.77-.437.785-.882 1.564-1.335 2.34-.159.272-.352.375-.68.37-.777-.016-1.552-.01-2.327.016a.099.099 0 00-.081.05 575.097 575.097 0 01-2.705 4.74c-.169.293-.38.363-.725.364-.997.003-2.002.004-3.017.002a.537.537 0 01-.465-.271l-1.335-2.323a.09.09 0 00-.083-.049H4.982c-.285.03-.553-.001-.805-.092l-1.603-2.77a.543.543 0 01-.002-.54l1.207-2.12a.198.198 0 000-.197 550.951 550.951 0 01-1.875-3.272l-.79-1.395c-.16-.31-.173-.496.095-.965.465-.813.927-1.625 1.387-2.436.132-.234.304-.334.584-.335a338.3 338.3 0 012.589-.001.124.124 0 00.107-.063l2.806-4.895a.488.488 0 01.422-.246c.524-.001 1.053 0 1.583-.006L11.704 1c.341-.003.724.032.9.34zm-3.432.403a.06.06 0 00-.052.03L6.254 6.788a.157.157 0 01-.135.078H3.253c-.056 0-.07.025-.041.074l5.81 10.156c.025.042.013.062-.034.063l-2.795.015a.218.218 0 00-.2.116l-1.32 2.31c-.044.078-.021.118.068.118l5.716.008c.046 0 .08.02.104.061l1.403 2.454c.046.081.092.082.139 0l5.006-8.76.783-1.382a.055.055 0 01.096 0l1.424 2.53a.122.122 0 00.107.062l2.763-.02a.04.04 0 00.035-.02.041.041 0 000-.04l-2.9-5.086a.108.108 0 010-.113l.293-.507 1.12-1.977c.024-.041.012-.062-.035-.062H9.2c-.059 0-.073-.026-.043-.077l1.434-2.505a.107.107 0 000-.114L9.225 1.774a.06.06 0 00-.053-.031zm6.29 8.02c.046 0 .058.02.034.06l-.832 1.465-2.613 4.585a.056.056 0 01-.05.029.058.058 0 01-.05-.029L8.498 9.841c-.02-.034-.01-.052.028-.054l.216-.012 6.722-.012z" fill="url(#lobe-icons-qwen-fill)" fill-rule="nonzero"></path><defs><linearGradient id="lobe-icons-qwen-fill" x1="0%" x2="100%" y1="0%" y2="0%"><stop offset="0%" stop-color="#6336E7" stop-opacity=".84"></stop><stop offset="100%" stop-color="#6F69F7" stop-opacity=".84"></stop></linearGradient></defs></svg>
|
||||
<svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Qwen</title><path d="M12.604 1.34c.393.69.784 1.382 1.174 2.075a.18.18 0 00.157.091h5.552c.174 0 .322.11.446.327l1.454 2.57c.19.337.24.478.024.837-.26.43-.513.864-.76 1.3l-.367.658c-.106.196-.223.28-.04.512l2.652 4.637c.172.301.111.494-.043.77-.437.785-.882 1.564-1.335 2.34-.159.272-.352.375-.68.37-.777-.016-1.552-.01-2.327.016a.099.099 0 00-.081.05 575.097 575.097 0 01-2.705 4.74c-.169.293-.38.363-.725.364-.997.003-2.002.004-3.017.002a.537.537 0 01-.465-.271l-1.335-2.323a.09.09 0 00-.083-.049H4.982c-.285.03-.553-.001-.805-.092l-1.603-2.77a.543.543 0 01-.002-.54l1.207-2.12a.198.198 0 000-.197 550.951 550.951 0 01-1.875-3.272l-.79-1.395c-.16-.31-.173-.496.095-.965.465-.813.927-1.625 1.387-2.436.132-.234.304-.334.584-.335a338.3 338.3 0 012.589-.001.124.124 0 00.107-.063l2.806-4.895a.488.488 0 01.422-.246c.524-.001 1.053 0 1.583-.006L11.704 1c.341-.003.724.032.9.34zm-3.432.403a.06.06 0 00-.052.03L6.254 6.788a.157.157 0 01-.135.078H3.253c-.056 0-.07.025-.041.074l5.81 10.156c.025.042.013.062-.034.063l-2.795.015a.218.218 0 00-.2.116l-1.32 2.31c-.044.078-.021.118.068.118l5.716.008c.046 0 .08.02.104.061l1.403 2.454c.046.081.092.082.139 0l5.006-8.76.783-1.382a.055.055 0 01.096 0l1.424 2.53a.122.122 0 00.107.062l2.763-.02a.04.04 0 00.035-.02.041.041 0 000-.04l-2.9-5.086a.108.108 0 010-.113l.293-.507 1.12-1.977c.024-.041.012-.062-.035-.062H9.2c-.059 0-.073-.026-.043-.077l1.434-2.505a.107.107 0 000-.114L9.225 1.774a.06.06 0 00-.053-.031zm6.29 8.02c.046 0 .058.02.034.06l-.832 1.465-2.613 4.585a.056.056 0 01-.05.029.058.058 0 01-.05-.029L8.498 9.841c-.02-.034-.01-.052.028-.054l.216-.012 6.722-.012z" fill="url(#lobe-icons-qwen-fill)" fill-rule="nonzero"></path><defs><linearGradient id="lobe-icons-qwen-fill" x1="0%" x2="100%" y1="0%" y2="0%"><stop offset="0%" stop-color="#6336E7" stop-opacity=".84"></stop><stop offset="100%" stop-color="#6F69F7" stop-opacity=".84"></stop></linearGradient></defs></svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 2 KiB After Width: | Height: | Size: 2 KiB |
|
|
@ -1 +1 @@
|
|||
<svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>vLLM</title><path d="M0 4.973h9.324V23L0 4.973z" fill="#FDB515"></path><path d="M13.986 4.351L22.378 0l-6.216 23H9.324l4.662-18.649z" fill="#30A2FF"></path></svg>
|
||||
<svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>vLLM</title><path d="M0 4.973h9.324V23L0 4.973z" fill="#FDB515"></path><path d="M13.986 4.351L22.378 0l-6.216 23H9.324l4.662-18.649z" fill="#30A2FF"></path></svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 286 B After Width: | Height: | Size: 287 B |
|
|
@ -1 +1 @@
|
|||
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Z.ai</title><path d="M12.105 2L9.927 4.953H.653L2.83 2h9.276zM23.254 19.048L21.078 22h-9.242l2.174-2.952h9.244zM24 2L9.264 22H0L14.736 2H24z"></path></svg>
|
||||
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Z.ai</title><path d="M12.105 2L9.927 4.953H.653L2.83 2h9.276zM23.254 19.048L21.078 22h-9.242l2.174-2.952h9.244zM24 2L9.264 22H0L14.736 2H24z"></path></svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 319 B After Width: | Height: | Size: 320 B |