@@ -973,7 +973,7 @@ def handler(request: httpx.Request) -> httpx.Response:
973973
974974
975975class TestRoutingContinuity :
976- def test_agent_tool_session_locks_route_pool_to_previous_model (
976+ def test_agent_tool_session_keeps_full_route_pool (
977977 self ,
978978 monkeypatch : pytest .MonkeyPatch ,
979979 ) -> None :
@@ -993,7 +993,7 @@ def fake_route(*_args, **kwargs) -> RoutingDecision:
993993 mode = RoutingMode .AUTO ,
994994 confidence = 0.9 ,
995995 method = "pool" ,
996- reasoning = "sticky session route" ,
996+ reasoning = "session route" ,
997997 cost_estimate = 0.001 ,
998998 baseline_cost = 0.002 ,
999999 savings = 0.5 ,
@@ -1064,11 +1064,147 @@ def handler(request: httpx.Request) -> httpx.Response:
10641064 )
10651065
10661066 assert resp .status_code == 200
1067- assert routed ["available_models" ] == ["anthropic/claude-opus-4-5" ]
1067+ assert routed ["available_models" ] == [
1068+ "anthropic/claude-opus-4-5" ,
1069+ "anthropic/claude-sonnet-4-6" ,
1070+ ]
10681071 assert captured ["body" ]["model" ] == "anthropic/claude-opus-4-5"
10691072 trace = traces .find (resp .headers ["x-uncommon-route-request-id" ])
10701073 assert trace is not None
1071- assert "session-sticky=previous-model=anthropic/claude-opus-4-5" in trace ["route_reasoning" ]
1074+ assert "session-sticky" not in trace ["route_reasoning" ]
1075+ assert "previous-model" not in trace ["route_reasoning" ]
1076+ finally :
1077+ asyncio .run (async_client .aclose ())
1078+
1079+ def test_semantic_tool_failure_keeps_full_route_pool_and_records_failure (
1080+ self ,
1081+ monkeypatch : pytest .MonkeyPatch ,
1082+ ) -> None :
1083+ routed : dict [str , object ] = {}
1084+ captured : dict [str , object ] = {}
1085+
1086+ def fake_route (* _args , ** kwargs ) -> RoutingDecision :
1087+ available_models = list (kwargs .get ("available_models" ) or [])
1088+ routed ["available_models" ] = available_models
1089+ selected_model = available_models [0 ]
1090+ return RoutingDecision (
1091+ model = selected_model ,
1092+ tier = Tier .COMPLEX ,
1093+ capability_lane = CapabilityLane .GENERAL ,
1094+ served_quality = ServedQuality .PREMIUM ,
1095+ served_quality_target = ServedQuality .PREMIUM ,
1096+ served_quality_floor = ServedQuality .BALANCED ,
1097+ continuity_quality_floor = kwargs ["routing_features" ].continuity_quality_floor ,
1098+ mode = RoutingMode .AUTO ,
1099+ confidence = 0.8 ,
1100+ method = "pool" ,
1101+ reasoning = "semantic failure route" ,
1102+ cost_estimate = 0.001 ,
1103+ baseline_cost = 0.002 ,
1104+ savings = 0.5 ,
1105+ routing_features = kwargs ["routing_features" ],
1106+ )
1107+
1108+ def handler (request : httpx .Request ) -> httpx .Response :
1109+ captured ["body" ] = json .loads (request .content .decode ("utf-8" ))
1110+ return httpx .Response (
1111+ 200 ,
1112+ json = {
1113+ "id" : "chatcmpl_semantic_retry" ,
1114+ "object" : "chat.completion" ,
1115+ "created" : 1 ,
1116+ "model" : captured ["body" ].get ("model" , "google/gemini-2.5-pro" ),
1117+ "choices" : [{
1118+ "index" : 0 ,
1119+ "message" : {"role" : "assistant" , "content" : "retrying" },
1120+ "finish_reason" : "stop" ,
1121+ }],
1122+ "usage" : {"prompt_tokens" : 1 , "completion_tokens" : 1 , "total_tokens" : 2 },
1123+ },
1124+ headers = {"content-type" : "application/json" },
1125+ )
1126+
1127+ traces = TraceStore (storage = InMemoryTraceStorage (), now_fn = lambda : 1.0 )
1128+ traces .record (RequestTrace (
1129+ timestamp = 1.0 ,
1130+ request_id = "prev_req" ,
1131+ requested_model = "uncommon-route/auto" ,
1132+ model = "google/gemini-2.5-pro" ,
1133+ status_code = 200 ,
1134+ api_format = "openai" ,
1135+ endpoint = "chat_completions" ,
1136+ is_virtual = True ,
1137+ session_id = "agent-session" ,
1138+ step_type = "tool-result-followup" ,
1139+ transport = "openai-chat" ,
1140+ served_quality = "premium" ,
1141+ ))
1142+
1143+ async_client = httpx .AsyncClient (transport = httpx .MockTransport (handler ))
1144+ monkeypatch .setattr ("uncommon_route.proxy._get_client" , lambda : async_client )
1145+ monkeypatch .setattr ("uncommon_route.proxy.route" , fake_route )
1146+
1147+ try :
1148+ app = create_app (
1149+ upstream = "https://api.example.test/v1" ,
1150+ model_mapper = _build_test_mapper (
1151+ "google/gemini-2.5-pro" ,
1152+ "anthropic/claude-opus-4-6" ,
1153+ "openai/gpt-5.4-2026-03-05" ,
1154+ ),
1155+ trace_store = traces ,
1156+ spend_control = SpendControl (storage = InMemorySpendControlStorage ()),
1157+ )
1158+ client = TestClient (app , raise_server_exceptions = False )
1159+ resp = client .post (
1160+ "/v1/chat/completions" ,
1161+ json = {
1162+ "model" : "uncommon-route/auto" ,
1163+ "messages" : [
1164+ {"role" : "user" , "content" : "Run the tests and fix the bug." },
1165+ {
1166+ "role" : "assistant" ,
1167+ "tool_calls" : [{
1168+ "id" : "call_1" ,
1169+ "type" : "function" ,
1170+ "function" : {
1171+ "name" : "run_tests" ,
1172+ "arguments" : json .dumps ({"command" : "pytest" }),
1173+ },
1174+ }],
1175+ },
1176+ {
1177+ "role" : "tool" ,
1178+ "tool_call_id" : "call_1" ,
1179+ "content" : (
1180+ "pytest verification failed: 1 failed\n "
1181+ "AssertionError: expected 2 actual 3\n "
1182+ "<returncode>1</returncode>"
1183+ ),
1184+ },
1185+ ],
1186+ "tools" : [{
1187+ "type" : "function" ,
1188+ "function" : {"name" : "run_tests" , "parameters" : {"type" : "object" }},
1189+ }],
1190+ },
1191+ headers = {"x-session-id" : "agent-session" },
1192+ )
1193+
1194+ assert resp .status_code == 200
1195+ assert set (routed ["available_models" ]) == {
1196+ "google/gemini-2.5-pro" ,
1197+ "anthropic/claude-opus-4-6" ,
1198+ "openai/gpt-5.4-2026-03-05" ,
1199+ }
1200+ assert "google/gemini-2.5-pro" in routed ["available_models" ]
1201+ assert captured ["body" ]["model" ] == routed ["available_models" ][0 ]
1202+ trace = traces .find (resp .headers ["x-uncommon-route-request-id" ])
1203+ assert trace is not None
1204+ assert "session-sticky" not in trace ["route_reasoning" ]
1205+ assert "session-retry" not in trace ["route_reasoning" ]
1206+ assert trace ["routing_features_payload" ]["verification_failed" ] is True
1207+ assert trace ["routing_features_payload" ]["failure_kind" ] == "semantic"
10721208 finally :
10731209 asyncio .run (async_client .aclose ())
10741210
0 commit comments