mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 01:10:23 +08:00
chore: 升级到 Python 3.14 并现代化代码
- 升级 Docker 基础镜像从 Python 3.12 到 3.14 - 更新 pyproject.toml 支持 Python 3.13/3.14 - 移除 Python 3.8/3.9/3.10/3.11 分类器 - 更新 black 和 mypy 配置目标版本 - 将 get_event_loop() 替换为 get_running_loop() 加上 RuntimeError 处理 - 简化 compute_cost_sync 中的 asyncio.run 使用 - Dict/List/Tuple/Set → dict/list/tuple/set (PEP 585) - Optional[T] → T | None (PEP 604) - Union[A, B] → A | B (PEP 604) - 移除废弃的 typing 导入 - 移除不必要的字符串引号注解
This commit is contained in:
@@ -11,7 +11,7 @@ ClaudeNormalizer 单元测试
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, cast
|
||||
from typing import Any, cast
|
||||
|
||||
from src.core.api_format.conversion.internal import (
|
||||
ErrorType,
|
||||
@@ -62,7 +62,7 @@ def test_claude_request_system_roundtrip() -> None:
|
||||
assert out["model"] == "claude-3-opus"
|
||||
assert out["system"] == "sys"
|
||||
|
||||
out_messages: List[Dict[str, Any]] = out["messages"]
|
||||
out_messages: list[dict[str, Any]] = out["messages"]
|
||||
assert [m["role"] for m in out_messages] == ["user", "assistant"]
|
||||
assert out_messages[0]["content"] == "hi"
|
||||
assert out_messages[1]["content"] == "ok"
|
||||
@@ -128,19 +128,19 @@ def test_claude_request_tool_blocks_roundtrip() -> None:
|
||||
assert tool_result.is_error is False
|
||||
|
||||
out = n.request_from_internal(internal)
|
||||
out_messages: List[Dict[str, Any]] = out["messages"]
|
||||
out_messages: list[dict[str, Any]] = out["messages"]
|
||||
assert [m["role"] for m in out_messages] == ["user", "assistant", "user"]
|
||||
|
||||
assistant_out = out_messages[1]
|
||||
assert isinstance(assistant_out["content"], list)
|
||||
a_blocks = cast(List[Dict[str, Any]], assistant_out["content"])
|
||||
a_blocks = cast(list[dict[str, Any]], assistant_out["content"])
|
||||
assert a_blocks[0]["type"] == "tool_use"
|
||||
assert a_blocks[0]["id"] == "toolu_1"
|
||||
assert a_blocks[0]["name"] == "get_weather"
|
||||
|
||||
user_out = out_messages[2]
|
||||
assert isinstance(user_out["content"], list)
|
||||
u_blocks = cast(List[Dict[str, Any]], user_out["content"])
|
||||
u_blocks = cast(list[dict[str, Any]], user_out["content"])
|
||||
assert u_blocks[0]["type"] == "tool_result"
|
||||
assert u_blocks[0]["tool_use_id"] == "toolu_1"
|
||||
assert u_blocks[0]["content"] == {"temp_c": 20}
|
||||
@@ -173,7 +173,7 @@ def test_claude_unknown_block_drop_on_output() -> None:
|
||||
|
||||
out = n.request_from_internal(internal)
|
||||
# Claude 要求以 user 开头,且会做最小修复:插入空 user
|
||||
out_messages: List[Dict[str, Any]] = out["messages"]
|
||||
out_messages: list[dict[str, Any]] = out["messages"]
|
||||
assert out_messages[0]["role"] == "user"
|
||||
assert out_messages[1]["role"] == "assistant"
|
||||
assert out_messages[1]["content"] == "ok"
|
||||
@@ -255,7 +255,7 @@ def test_claude_stream_chunk_and_event_roundtrip_basic() -> None:
|
||||
{"type": "message_stop"},
|
||||
]
|
||||
|
||||
events: List[Any] = []
|
||||
events: list[Any] = []
|
||||
for ch in chunks:
|
||||
events.extend(n.stream_chunk_to_internal(ch, state))
|
||||
|
||||
@@ -266,7 +266,7 @@ def test_claude_stream_chunk_and_event_roundtrip_basic() -> None:
|
||||
|
||||
# internal events -> Claude events
|
||||
state2 = StreamState()
|
||||
out_events: List[Dict[str, Any]] = []
|
||||
out_events: list[dict[str, Any]] = []
|
||||
for e in events:
|
||||
out_events.extend(n.stream_event_from_internal(e, state2))
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ CLI 格式参与转换的单元测试
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, cast
|
||||
from typing import Any, cast
|
||||
|
||||
from src.core.api_format.conversion.normalizers.claude import ClaudeNormalizer
|
||||
from src.core.api_format.conversion.normalizers.claude_cli import ClaudeCliNormalizer
|
||||
@@ -72,10 +72,10 @@ def test_claude_response_to_openai_cli() -> None:
|
||||
openai_cli_resp = reg.convert_response(claude_resp, "CLAUDE", "OPENAI_CLI")
|
||||
assert openai_cli_resp["object"] == "response"
|
||||
assert isinstance(openai_cli_resp.get("output"), list)
|
||||
msg = cast(Dict[str, Any], openai_cli_resp["output"][0])
|
||||
msg = cast(dict[str, Any], openai_cli_resp["output"][0])
|
||||
assert msg["type"] == "message"
|
||||
assert msg["role"] == "assistant"
|
||||
content = cast(List[Dict[str, Any]], msg.get("content") or [])
|
||||
content = cast(list[dict[str, Any]], msg.get("content") or [])
|
||||
assert content and content[0]["type"] == "output_text"
|
||||
assert content[0]["text"] == "hello"
|
||||
|
||||
@@ -117,8 +117,8 @@ def test_stream_openai_cli_to_openai_delta() -> None:
|
||||
# 第二个 chunk 才是文本增量
|
||||
choices = out_events[1].get("choices") or []
|
||||
assert isinstance(choices, list) and choices
|
||||
delta = cast(Dict[str, Any], choices[0]).get("delta") or {}
|
||||
assert cast(Dict[str, Any], delta).get("content") == "hi"
|
||||
delta = cast(dict[str, Any], choices[0]).get("delta") or {}
|
||||
assert cast(dict[str, Any], delta).get("content") == "hi"
|
||||
|
||||
|
||||
def test_openai_cli_function_call_to_claude() -> None:
|
||||
@@ -484,7 +484,7 @@ def test_real_claude_cli_stream_response_conversion() -> None:
|
||||
]
|
||||
|
||||
# 收集所有转换后的 OpenAI 格式事件
|
||||
all_openai_events: List[Dict[str, Any]] = []
|
||||
all_openai_events: list[dict[str, Any]] = []
|
||||
for chunk in chunks:
|
||||
events = reg.convert_stream_chunk(chunk, "CLAUDE_CLI", "OPENAI", state=state)
|
||||
all_openai_events.extend(events)
|
||||
@@ -553,7 +553,7 @@ def test_real_claude_cli_stream_to_openai_cli() -> None:
|
||||
{"type": "message_stop"},
|
||||
]
|
||||
|
||||
all_events: List[Dict[str, Any]] = []
|
||||
all_events: list[dict[str, Any]] = []
|
||||
for chunk in chunks:
|
||||
events = reg.convert_stream_chunk(chunk, "CLAUDE_CLI", "OPENAI_CLI", state=state)
|
||||
all_events.extend(events)
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, cast
|
||||
from typing import Any, cast
|
||||
|
||||
from src.core.api_format.conversion.internal import ErrorType, InternalError
|
||||
from src.core.api_format.conversion.normalizers.claude import ClaudeNormalizer
|
||||
@@ -65,7 +65,7 @@ def test_error_event_stream_openai_to_claude_via_registry() -> None:
|
||||
chunk = {"error": {"message": "bad", "type": "invalid_request_error"}}
|
||||
out = reg.convert_stream_chunk(chunk, "OPENAI", "CLAUDE", state=StreamState())
|
||||
assert isinstance(out, list) and out
|
||||
evt0 = cast(Dict[str, Any], out[0])
|
||||
evt0 = cast(dict[str, Any], out[0])
|
||||
assert evt0.get("type") == "error"
|
||||
assert isinstance(evt0.get("error"), dict)
|
||||
assert evt0["error"]["message"] == "bad"
|
||||
|
||||
@@ -12,7 +12,7 @@ GeminiNormalizer 单元测试
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from typing import Any, Dict, List, cast
|
||||
from typing import Any, cast
|
||||
|
||||
from src.core.api_format.conversion.internal import (
|
||||
ErrorType,
|
||||
@@ -138,18 +138,18 @@ def test_gemini_request_parts_image_tool_and_unknown_drop() -> None:
|
||||
assert tool_result.output == {"temp_c": 20}
|
||||
|
||||
out = n.request_from_internal(internal)
|
||||
out_contents: List[Dict[str, Any]] = out["contents"]
|
||||
out_contents: list[dict[str, Any]] = out["contents"]
|
||||
|
||||
# unknown 被丢弃
|
||||
user_parts = cast(List[Dict[str, Any]], out_contents[0]["parts"])
|
||||
user_parts = cast(list[dict[str, Any]], out_contents[0]["parts"])
|
||||
assert any(p.get("text") == "look" for p in user_parts)
|
||||
assert any(p.get("inline_data", {}).get("mime_type") == "image/png" for p in user_parts)
|
||||
assert all("foo" not in p for p in user_parts)
|
||||
|
||||
model_parts = cast(List[Dict[str, Any]], out_contents[1]["parts"])
|
||||
model_parts = cast(list[dict[str, Any]], out_contents[1]["parts"])
|
||||
assert model_parts[0]["function_call"]["name"] == "get_weather"
|
||||
|
||||
tool_parts = cast(List[Dict[str, Any]], out_contents[2]["parts"])
|
||||
tool_parts = cast(list[dict[str, Any]], out_contents[2]["parts"])
|
||||
assert tool_parts[0]["function_response"]["name"] == "call_1"
|
||||
assert tool_parts[0]["function_response"]["response"]["result"] == {"temp_c": 20}
|
||||
|
||||
@@ -231,7 +231,7 @@ def test_gemini_stream_chunk_and_event_roundtrip_basic() -> None:
|
||||
},
|
||||
]
|
||||
|
||||
events: List[Any] = []
|
||||
events: list[Any] = []
|
||||
for ch in chunks:
|
||||
events.extend(n.stream_chunk_to_internal(ch, state))
|
||||
|
||||
@@ -241,7 +241,7 @@ def test_gemini_stream_chunk_and_event_roundtrip_basic() -> None:
|
||||
assert any(isinstance(e, MessageStopEvent) and e.stop_reason == StopReason.END_TURN for e in events)
|
||||
|
||||
state2 = StreamState()
|
||||
out_chunks: List[Dict[str, Any]] = []
|
||||
out_chunks: list[dict[str, Any]] = []
|
||||
for e in events:
|
||||
out_chunks.extend(n.stream_event_from_internal(e, state2))
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List
|
||||
from typing import Any
|
||||
|
||||
from src.core.api_format.conversion.normalizers.claude import ClaudeNormalizer
|
||||
from src.core.api_format.conversion.normalizers.gemini import GeminiNormalizer
|
||||
@@ -28,7 +28,7 @@ def _scrub(obj: Any) -> Any:
|
||||
if isinstance(obj, list):
|
||||
return [_scrub(x) for x in obj]
|
||||
if isinstance(obj, dict):
|
||||
out: Dict[str, Any] = {}
|
||||
out: dict[str, Any] = {}
|
||||
for k, v in obj.items():
|
||||
if k in {"created"}:
|
||||
continue
|
||||
@@ -93,7 +93,7 @@ def test_golden_streams() -> None:
|
||||
reg = _make_registry()
|
||||
formats = ["OPENAI", "CLAUDE", "GEMINI"]
|
||||
|
||||
inputs: Dict[str, List[Dict[str, Any]]] = {
|
||||
inputs: dict[str, list[dict[str, Any]]] = {
|
||||
"OPENAI": _load_json(INPUT_DIR / "stream_openai.json"),
|
||||
"CLAUDE": _load_json(INPUT_DIR / "stream_claude.json"),
|
||||
"GEMINI": _load_json(INPUT_DIR / "stream_gemini.json"),
|
||||
@@ -109,7 +109,7 @@ def test_golden_streams() -> None:
|
||||
if source == "GEMINI":
|
||||
state.message_id = "gemini_1"
|
||||
|
||||
out: List[Dict[str, Any]] = []
|
||||
out: list[dict[str, Any]] = []
|
||||
for chunk in inputs[source]:
|
||||
out.extend(reg.convert_stream_chunk(chunk, source, target, state=state))
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ OpenAINormalizer 单元测试
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from typing import Any, Dict, List, cast
|
||||
from typing import Any, cast
|
||||
|
||||
from src.core.api_format.conversion.internal import (
|
||||
ContentType,
|
||||
@@ -35,14 +35,14 @@ from src.core.api_format.conversion.stream_events import (
|
||||
from src.core.api_format.conversion.stream_state import StreamState
|
||||
|
||||
|
||||
def _first_choice_message(response: Dict[str, Any]) -> Dict[str, Any]:
|
||||
def _first_choice_message(response: dict[str, Any]) -> dict[str, Any]:
|
||||
choices = response.get("choices") or []
|
||||
assert isinstance(choices, list) and choices
|
||||
c0 = choices[0]
|
||||
assert isinstance(c0, dict)
|
||||
msg = c0.get("message")
|
||||
assert isinstance(msg, dict)
|
||||
return cast(Dict[str, Any], msg)
|
||||
return cast(dict[str, Any], msg)
|
||||
|
||||
|
||||
def test_openai_request_instructions_roundtrip() -> None:
|
||||
@@ -169,7 +169,7 @@ def test_openai_request_tool_calls_and_tool_role_roundtrip() -> None:
|
||||
assert tool_result.output == {"temp_c": 20, "unit": "C"}
|
||||
|
||||
out = n.request_from_internal(internal)
|
||||
out_messages: List[Dict[str, Any]] = out["messages"]
|
||||
out_messages: list[dict[str, Any]] = out["messages"]
|
||||
|
||||
roles = [m.get("role") for m in out_messages]
|
||||
assert roles == ["user", "assistant", "tool", "assistant"]
|
||||
@@ -217,7 +217,7 @@ def test_openai_request_content_image_and_unknown_drop() -> None:
|
||||
out_msg = out["messages"][0]
|
||||
assert out_msg["role"] == "user"
|
||||
assert isinstance(out_msg["content"], list)
|
||||
parts = cast(List[Dict[str, Any]], out_msg["content"])
|
||||
parts = cast(list[dict[str, Any]], out_msg["content"])
|
||||
assert any(p.get("type") == "image_url" for p in parts)
|
||||
assert all(p.get("type") != "foo" for p in parts)
|
||||
|
||||
@@ -289,7 +289,7 @@ def test_openai_stream_chunk_and_event_roundtrip_basic() -> None:
|
||||
{"choices": [{"index": 0, "delta": {}, "finish_reason": "tool_calls"}]},
|
||||
]
|
||||
|
||||
events: List[Any] = []
|
||||
events: list[Any] = []
|
||||
for ch in chunks:
|
||||
events.extend(n.stream_chunk_to_internal(ch, state))
|
||||
|
||||
@@ -301,7 +301,7 @@ def test_openai_stream_chunk_and_event_roundtrip_basic() -> None:
|
||||
|
||||
# internal events -> OpenAI chunks(验证关键字段与 tool_calls index 稳定)
|
||||
state2 = StreamState()
|
||||
out_chunks: List[Dict[str, Any]] = []
|
||||
out_chunks: list[dict[str, Any]] = []
|
||||
for e in events:
|
||||
out_chunks.extend(n.stream_event_from_internal(e, state2))
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ Canonical Registry 单元测试
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, cast
|
||||
from typing import Any, cast
|
||||
|
||||
from src.core.api_format.conversion.normalizers.claude import ClaudeNormalizer
|
||||
from src.core.api_format.conversion.normalizers.gemini import GeminiNormalizer
|
||||
@@ -25,14 +25,14 @@ def _make_registry() -> FormatConversionRegistry:
|
||||
return reg
|
||||
|
||||
|
||||
def _first_openai_choice_message(resp: Dict[str, Any]) -> Dict[str, Any]:
|
||||
def _first_openai_choice_message(resp: dict[str, Any]) -> dict[str, Any]:
|
||||
choices = resp.get("choices") or []
|
||||
assert isinstance(choices, list) and choices
|
||||
c0 = choices[0]
|
||||
assert isinstance(c0, dict)
|
||||
msg = c0.get("message")
|
||||
assert isinstance(msg, dict)
|
||||
return cast(Dict[str, Any], msg)
|
||||
return cast(dict[str, Any], msg)
|
||||
|
||||
|
||||
def test_registry_canonical_can_convert_full_stream() -> None:
|
||||
@@ -101,5 +101,5 @@ def test_registry_canonical_stream_openai_to_claude() -> None:
|
||||
out_events = reg.convert_stream_chunk(chunk, "OPENAI", "CLAUDE", state=state)
|
||||
assert isinstance(out_events, list) and out_events
|
||||
|
||||
types = [cast(Dict[str, Any], e).get("type") for e in cast(List[Dict[str, Any]], out_events)]
|
||||
types = [cast(dict[str, Any], e).get("type") for e in cast(list[dict[str, Any]], out_events)]
|
||||
assert types[:3] == ["message_start", "content_block_start", "content_block_delta"]
|
||||
|
||||
Reference in New Issue
Block a user