@@ -1101,3 +1101,67 @@ async def stream_chat(self, cred, payload, model):
11011101 waited .clear ()
11021102 _ = [e async for e in provider2 .stream_chat ({"bearer_token" : "t" }, {}, "m" )]
11031103 assert not waited
1104+
1105+
1106+ async def test_stream_inner_error_other_is_logged_and_counted (dual_repo , caplog ):
1107+ """流内非 4001/1005 错误 → 记冷却并打日志(可观测性回归)。"""
1108+ import logging
1109+
1110+ from src .provider .base import Event
1111+
1112+ repo , _db = dual_repo
1113+ repo .add (provider = "codebuddy" , credential_data = {"bearer_token" : "cb" })
1114+ calls = {"n" : 0 }
1115+
1116+ async def gen (_cred , _payload , _model ):
1117+ calls ["n" ] += 1
1118+ yield Event (kind = EventKind .ERROR , error_code = 41291 , error_message = "rate limited" )
1119+
1120+ class P :
1121+ id = "codebuddy"
1122+ stream_chat = staticmethod (gen )
1123+
1124+ executor = Executor (ExecutorDeps (
1125+ providers = {"codebuddy" : P ()}, credentials = repo ,
1126+ scheduler = Scheduler (max_rotate = 1 ), default_model = "m" ))
1127+ frames = [f async for f in executor .stream (_request ("m" ), username = "u" )]
1128+ assert any (b"error" in f for f in frames )
1129+ assert calls ["n" ] == 1
1130+ assert any ("流内错误" in r .getMessage () for r in caplog .records
1131+ if r .levelno == logging .WARNING )
1132+
1133+
1134+ async def test_stream_inner_4001_yields_invalid_frame (dual_repo , caplog ):
1135+ """流内 4001 → INVALID:跳过上游 + invalid_request 帧。"""
1136+ import json as _json
1137+ import logging
1138+
1139+ from src .provider .base import Event
1140+
1141+ repo , db = dual_repo
1142+ repo .add (provider = "codebuddy" , credential_data = {"bearer_token" : "cb" })
1143+ calls = {"n" : 0 }
1144+
1145+ async def gen (_cred , _payload , _model ):
1146+ calls ["n" ] += 1
1147+ yield Event (kind = EventKind .ERROR , error_code = 4001 , error_message = "param invalid" )
1148+
1149+ class P :
1150+ id = "codebuddy"
1151+ stream_chat = staticmethod (gen )
1152+
1153+ executor = Executor (ExecutorDeps (
1154+ providers = {"codebuddy" : P ()}, credentials = repo ,
1155+ scheduler = Scheduler (max_rotate = 1 ), default_model = "m" ,
1156+ model_suggestions = lambda _n : ["alt-model" ]))
1157+
1158+ with caplog .at_level (logging .WARNING ):
1159+ frames = [f async for f in executor .stream (_request ("m" ), username = "u" )]
1160+ payload = _json .loads (frames [0 ].split (b"\n \n " )[0 ].removeprefix (b"data: " ))
1161+ assert payload ["error" ]["code" ] == "invalid_request"
1162+ assert "alt-model" in payload ["error" ]["message" ]
1163+ assert calls ["n" ] == 1 # 不轮换
1164+ row = db .connect ().execute (
1165+ "SELECT err_count, cooling_until FROM credentials" ).fetchone ()
1166+ assert tuple (row ) == (0 , None ) # 不冷却
1167+ assert any ("流内拒绝模型" in r .getMessage () for r in caplog .records )
0 commit comments