refactor: 切换到 litellm,删除未使用的代码

- 添加 litellm 依赖,统一 LLM 调用层
- 新增 litellm_client.py 支持多厂商
- 更新 llm_catalog.yaml 添加 litellm_model 映射
- 删除旧的 cost_tracker.py (litellm 内置 cost 追踪)
- 删除未使用的 multimodal.py 和 storage_adapter.py
- 删除空文件 crewai/__init__.py, tools/__init__.py
- 更新测试以适配新代码
This commit is contained in:
qzl
2026-03-03 17:52:34 +08:00
parent a4f684466c
commit 80cbb3512f
15 changed files with 200 additions and 578 deletions
@@ -2,81 +2,47 @@ from __future__ import annotations
from decimal import Decimal
import pytest
from core.agent.cost_tracker import CostTracker
from core.agent.litellm_client import get_model_cost
def test_normalize_usage_and_cost_aggregation() -> None:
tracker = CostTracker()
tracker.add_usage(
{
"prompt_tokens": 7,
"completion_tokens": 5,
"cost": "0.002500",
}
)
tracker.add_usage(
{
"input_tokens": 5,
"output_tokens": 3,
"cost": "0.003000",
"currency": "USD",
}
)
snapshot = tracker.snapshot()
assert snapshot["input_tokens"] == 12
assert snapshot["output_tokens"] == 8
assert snapshot["total_tokens"] == 20
assert snapshot["cost"] == Decimal("0.005500")
assert snapshot["currency"] == "USD"
def test_get_model_cost_returns_decimal() -> None:
usage = {
"prompt_tokens": 7,
"completion_tokens": 5,
"total_tokens": 12,
"cost": "0.002500",
}
cost = get_model_cost(usage)
assert cost == Decimal("0.002500")
def test_add_usage_rejects_negative_values() -> None:
tracker = CostTracker()
with pytest.raises(ValueError):
tracker.add_usage({"input_tokens": -1})
with pytest.raises(ValueError):
tracker.add_usage({"cost": "-0.010000"})
def test_get_model_cost_with_no_cost() -> None:
usage = {
"prompt_tokens": 7,
"completion_tokens": 5,
"total_tokens": 12,
}
cost = get_model_cost(usage)
assert cost == Decimal("0")
def test_snapshot_is_zero_before_any_usage() -> None:
tracker = CostTracker()
snapshot = tracker.snapshot()
assert snapshot["input_tokens"] == 0
assert snapshot["output_tokens"] == 0
assert snapshot["total_tokens"] == 0
assert snapshot["cost"] == Decimal("0")
assert snapshot["currency"] == "USD"
def test_get_model_cost_with_zero_cost() -> None:
usage = {
"prompt_tokens": 7,
"completion_tokens": 5,
"total_tokens": 12,
"cost": "0",
}
cost = get_model_cost(usage)
assert cost == Decimal("0")
def test_add_usage_rejects_currency_mismatch() -> None:
tracker = CostTracker(currency="USD")
tracker.add_usage({"input_tokens": 1, "output_tokens": 1, "cost": "0.001000"})
with pytest.raises(ValueError):
tracker.add_usage(
{
"input_tokens": 1,
"output_tokens": 1,
"cost": "0.001000",
"currency": "CNY",
}
)
def test_add_usage_rejects_non_integral_token_values() -> None:
tracker = CostTracker()
with pytest.raises(ValueError):
tracker.add_usage({"input_tokens": 1.5})
with pytest.raises(ValueError):
tracker.add_usage({"output_tokens": True})
def test_get_model_cost_with_numeric_cost() -> None:
usage = {
"prompt_tokens": 7,
"completion_tokens": 5,
"total_tokens": 12,
"cost": 0.0025,
}
cost = get_model_cost(usage)
assert cost == Decimal("0.0025")
@@ -1,89 +0,0 @@
from __future__ import annotations
import pytest
from core.agent.multimodal import AttachmentInput, MultimodalProcessor
from core.agent.storage_adapter import StorageAdapter
from core.agent.tools.asr_fun_asr import FunASRTool
def test_multimodal_processes_audio_and_builds_attachment_context() -> None:
storage = StorageAdapter(bucket="agent-chat-attachments")
def fake_transcribe(*, audio_bytes: bytes, filename: str) -> dict[str, str]:
assert audio_bytes == b"audio"
assert filename == "voice.wav"
return {"text": "hello world", "request_id": "req-1"}
processor = MultimodalProcessor(
storage=storage,
asr_tool=FunASRTool(transcribe_callable=fake_transcribe),
max_file_size_mb=1,
)
result = processor.process(
user_id="u1",
session_id="s1",
message_seq=4,
attachments=[
AttachmentInput(
filename="voice.wav",
mime_type="audio/wav",
content=b"audio",
)
],
)
assert len(result.attachments) == 1
metadata = result.attachments[0]
assert (
metadata["object_path"]
== "agent-chat/u1/s1/4/6ed8919ce20490a5e3ad8630a4fab69475297abd07db73918dd5f36fcfaeb11b.wav"
)
assert metadata["mime_type"] == "audio/wav"
assert result.preview_texts == ["hello world"]
def test_multimodal_rejects_unsupported_mime_type() -> None:
storage = StorageAdapter(bucket="agent-chat-attachments")
processor = MultimodalProcessor(
storage=storage, asr_tool=FunASRTool(lambda **_: {})
)
with pytest.raises(ValueError):
processor.process(
user_id="u1",
session_id="s1",
message_seq=1,
attachments=[
AttachmentInput(
filename="malware.exe",
mime_type="application/octet-stream",
content=b"bad",
)
],
)
def test_multimodal_rejects_attachment_over_max_size() -> None:
storage = StorageAdapter(bucket="agent-chat-attachments")
processor = MultimodalProcessor(
storage=storage,
asr_tool=FunASRTool(lambda **_: {}),
max_file_size_mb=1,
)
oversized = b"x" * (1024 * 1024 + 1)
with pytest.raises(ValueError):
processor.process(
user_id="u1",
session_id="s1",
message_seq=1,
attachments=[
AttachmentInput(
filename="big.wav",
mime_type="audio/wav",
content=oversized,
)
],
)
@@ -1,37 +0,0 @@
from __future__ import annotations
from core.agent.storage_adapter import StorageAdapter
def test_build_object_path_uses_expected_pattern() -> None:
adapter = StorageAdapter(bucket="agent-chat-attachments")
path = adapter.build_object_path(
user_id="u1",
session_id="s1",
message_seq=3,
checksum_sha256="abc123",
extension="wav",
)
assert path == "agent-chat/u1/s1/3/abc123.wav"
def test_build_attachment_metadata_contains_required_fields() -> None:
adapter = StorageAdapter(bucket="agent-chat-attachments")
metadata = adapter.build_attachment_metadata(
object_path="agent-chat/u1/s1/3/abc123.wav",
mime_type="audio/wav",
size=1024,
checksum_sha256="abc123",
origin="user_upload",
preview_text="hello",
)
assert metadata["object_path"] == "agent-chat/u1/s1/3/abc123.wav"
assert metadata["mime_type"] == "audio/wav"
assert metadata["size"] == 1024
assert metadata["checksum_sha256"] == "abc123"
assert metadata["origin"] == "user_upload"
assert metadata["preview_text"] == "hello"
@@ -1,89 +0,0 @@
from __future__ import annotations
import pytest
from core.agent.multimodal import AttachmentInput, MultimodalProcessor
from core.agent.storage_adapter import StorageAdapter
from core.agent.tools.asr_fun_asr import FunASRTool
def test_multimodal_processes_audio_and_builds_attachment_context() -> None:
storage = StorageAdapter(bucket="agent-chat-attachments")
def fake_transcribe(*, audio_bytes: bytes, filename: str) -> dict[str, str]:
assert audio_bytes == b"audio"
assert filename == "voice.wav"
return {"text": "hello world", "request_id": "req-1"}
processor = MultimodalProcessor(
storage=storage,
asr_tool=FunASRTool(transcribe_callable=fake_transcribe),
max_file_size_mb=1,
)
result = processor.process(
user_id="u1",
session_id="s1",
message_seq=4,
attachments=[
AttachmentInput(
filename="voice.wav",
mime_type="audio/wav",
content=b"audio",
)
],
)
assert len(result.attachments) == 1
metadata = result.attachments[0]
assert (
metadata["object_path"]
== "agent-chat/u1/s1/4/6ed8919ce20490a5e3ad8630a4fab69475297abd07db73918dd5f36fcfaeb11b.wav"
)
assert metadata["mime_type"] == "audio/wav"
assert result.preview_texts == ["hello world"]
def test_multimodal_rejects_unsupported_mime_type() -> None:
storage = StorageAdapter(bucket="agent-chat-attachments")
processor = MultimodalProcessor(
storage=storage, asr_tool=FunASRTool(lambda **_: {})
)
with pytest.raises(ValueError):
processor.process(
user_id="u1",
session_id="s1",
message_seq=1,
attachments=[
AttachmentInput(
filename="malware.exe",
mime_type="application/octet-stream",
content=b"bad",
)
],
)
def test_multimodal_rejects_attachment_over_max_size() -> None:
storage = StorageAdapter(bucket="agent-chat-attachments")
processor = MultimodalProcessor(
storage=storage,
asr_tool=FunASRTool(lambda **_: {}),
max_file_size_mb=1,
)
oversized = b"x" * (1024 * 1024 + 1)
with pytest.raises(ValueError):
processor.process(
user_id="u1",
session_id="s1",
message_seq=1,
attachments=[
AttachmentInput(
filename="big.wav",
mime_type="audio/wav",
content=oversized,
)
],
)
@@ -1,37 +0,0 @@
from __future__ import annotations
from core.agent.storage_adapter import StorageAdapter
def test_build_object_path_uses_expected_pattern() -> None:
adapter = StorageAdapter(bucket="agent-chat-attachments")
path = adapter.build_object_path(
user_id="u1",
session_id="s1",
message_seq=3,
checksum_sha256="abc123",
extension="wav",
)
assert path == "agent-chat/u1/s1/3/abc123.wav"
def test_build_attachment_metadata_contains_required_fields() -> None:
adapter = StorageAdapter(bucket="agent-chat-attachments")
metadata = adapter.build_attachment_metadata(
object_path="agent-chat/u1/s1/3/abc123.wav",
mime_type="audio/wav",
size=1024,
checksum_sha256="abc123",
origin="user_upload",
preview_text="hello",
)
assert metadata["object_path"] == "agent-chat/u1/s1/3/abc123.wav"
assert metadata["mime_type"] == "audio/wav"
assert metadata["size"] == 1024
assert metadata["checksum_sha256"] == "abc123"
assert metadata["origin"] == "user_upload"
assert metadata["preview_text"] == "hello"