細分 MCP 呼叫遙測狀態
Some checks failed
CD Pipeline / deploy (push) Has been cancelled

This commit is contained in:
OoO
2026-05-13 09:35:27 +08:00
parent bdb74b1354
commit d15b2215f1
2 changed files with 39 additions and 1 deletions

View File

@@ -152,6 +152,7 @@ class MCPResult:
cost_usd: float = 0.0
error: Optional[str] = None
output_size: int = 0
status: Optional[str] = None
# ─────────────────────────────────────────────────────────────────────────────
@@ -196,7 +197,7 @@ def _async_write_mcp_call(
'args': json.dumps(args_redacted),
'osz': result.output_size,
'dur': result.duration_ms,
'status': 'ok' if result.success else 'error',
'status': result.status or ('ok' if result.success else 'error'),
'err': (result.error or '')[:4000] if result.error else None,
'cost': result.cost_usd,
'cache': result.cache_hit,
@@ -260,6 +261,7 @@ class MCPRouter:
success=True, server=server, tool=tool,
data=cached, cache_hit=True, duration_ms=0,
output_size=len(json.dumps(cached, ensure_ascii=False)),
status='cache_only',
)
_async_write_mcp_call(caller, server, tool, args, result, request_id)
return result
@@ -274,10 +276,12 @@ class MCPRouter:
duration_ms = int((time.monotonic() - t0) * 1000)
if resp.status_code != 200:
status = 'rate_limited' if resp.status_code == 429 else 'error'
result = MCPResult(
success=False, server=server, tool=tool,
duration_ms=duration_ms,
error=f'HTTP {resp.status_code}: {resp.text[:200]}',
status=status,
)
_async_write_mcp_call(caller, server, tool, args, result, request_id)
return result
@@ -299,6 +303,7 @@ class MCPRouter:
success=True, server=server, tool=tool,
data=data, cache_hit=False,
duration_ms=duration_ms, output_size=output_size,
status='ok',
)
_async_write_mcp_call(caller, server, tool, args, result, request_id)
return result
@@ -308,6 +313,7 @@ class MCPRouter:
result = MCPResult(
success=False, server=server, tool=tool,
duration_ms=duration_ms, error=f'timeout ({request_timeout}s)',
status='timeout',
)
_async_write_mcp_call(caller, server, tool, args, result, request_id)
return result
@@ -318,6 +324,7 @@ class MCPRouter:
success=False, server=server, tool=tool,
duration_ms=duration_ms,
error=f'{type(exc).__name__}: {str(exc)[:300]}',
status='error',
)
_async_write_mcp_call(caller, server, tool, args, result, request_id)
return result

View File

@@ -103,8 +103,16 @@ def test_filesystem_registry_is_read_only(monkeypatch):
def test_successful_call_and_cache_hit(monkeypatch):
monkeypatch.setenv('MCP_ROUTER_ENABLED', 'true')
import services.mcp_router as mr
from services.mcp_router import mcp_router
written_statuses = []
monkeypatch.setattr(
mr,
'_async_write_mcp_call',
lambda caller, server, tool, args, result, request_id=None: written_statuses.append(result.status),
)
fake_resp = MagicMock(status_code=200)
fake_resp.json.return_value = {'results': [{'title': '商品 A', 'content': '熱銷'}]}
fake_resp.text = '{"ok": true}'
@@ -117,6 +125,7 @@ def test_successful_call_and_cache_hit(monkeypatch):
)
assert r1.success is True
assert r1.cache_hit is False
assert r1.status == 'ok'
assert r1.data['results'][0]['title'] == '商品 A'
assert mock_post.call_count == 1
@@ -127,7 +136,9 @@ def test_successful_call_and_cache_hit(monkeypatch):
)
assert r2.success is True
assert r2.cache_hit is True
assert r2.status == 'cache_only'
assert mock_post.call_count == 1 # 沒再打 HTTP
assert written_statuses == ['ok', 'cache_only']
# ═══════════════════════════════════════════════════════════════════════════
@@ -146,6 +157,7 @@ def test_http_timeout_returns_failure(monkeypatch):
)
assert result.success is False
assert result.status == 'timeout'
assert 'timeout' in (result.error or '').lower()
@@ -167,9 +179,28 @@ def test_http_500_returns_failure(monkeypatch):
)
assert result.success is False
assert result.status == 'error'
assert 'HTTP 500' in (result.error or '')
def test_http_429_records_rate_limited_status(monkeypatch):
monkeypatch.setenv('MCP_ROUTER_ENABLED', 'true')
from services.mcp_router import mcp_router
fake_resp = MagicMock(status_code=429)
fake_resp.text = 'Too Many Requests'
with patch('services.mcp_router.requests.post', return_value=fake_resp):
result = mcp_router.call(
server='omnisearch', tool='tavily_search',
args={'query': 'x'}, caller='mcp_collector',
)
assert result.success is False
assert result.status == 'rate_limited'
assert 'HTTP 429' in (result.error or '')
# ═══════════════════════════════════════════════════════════════════════════
# T6: cache key 穩定性
# ═══════════════════════════════════════════════════════════════════════════