diff --git a/zig/src/server/cortheon/cortheon_test.zig b/zig/src/server/cortheon/cortheon_test.zig index 15413efef1..250126f2ff 100644 --- a/zig/src/server/cortheon/cortheon_test.zig +++ b/zig/src/server/cortheon/cortheon_test.zig @@ -354,3 +354,26 @@ test "the web budget ends the research and the reply still comes back without Co try std.testing.expect(m.get("tool_calls") == null); try std.testing.expect(std.mem.indexOf(u8, m.strField("content").?, "cortheon_search") == null); } + +test "research expansion keeps a context-window refusal streamed" { + for ([_]u32{ 2000, 2400 }) |window| { + var rig: Rig = .{ .eng = .{ .script = &.{"Zig latest release notes"} } }; + try rig.start(true); + defer rig.stop(); + rig.srv.info.context_window = window; + var arena: std.heap.ArenaAllocator = .init(gpa); + defer arena.deinit(); + const got = try rig.ask(arena.allocator(), "{\"stream\":true,\"max_tokens\":1,\"messages\":[{\"role\":\"user\",\"content\":\"What is the latest release of Zig?\"}]}"); + try std.testing.expect(rig.web.searches > 0); + try std.testing.expectEqual(@as(usize, 1), rig.eng.prompts.items.len); + try std.testing.expect(got.opened); + try std.testing.expectEqual(@as(?u16, null), got.status); + try std.testing.expect(got.body == null); + var context_error = false; + for (got.events.items) |event| if (event.get("error")) |err| { + if (err.strField("code")) |code| context_error = std.mem.eql(u8, code, "context_length_exceeded"); + }; + try std.testing.expect(context_error); + try std.testing.expectEqual(@as(i64, 0), rig.srv.preparing.load(.acquire)); + } +} diff --git a/zig/src/server/openai.zig b/zig/src/server/openai.zig index 45cf437364..91afd101b5 100644 --- a/zig/src/server/openai.zig +++ b/zig/src/server/openai.zig @@ -516,7 +516,10 @@ const Run = struct { handed = true; const sink: chat.Sink = .{ .ctx = &sink_state, .call = StreamSink.call, .begin = StreamSink.begin }; var reply = (if (r.turn) |t| cortheon.loop.stream(t, r.srv, &cx, &r.plan.input, prepared, sink, gone) else chat.generate(r.srv, &cx, prepared, sink, gone)) catch |e| { - if (!sink_state.opened) return r.unsent(&cx, e, field); + if (!sink_state.opened) { + if (e == error.Refused and cx.kind == .context_length) return r.streamedContext(&cx, tools, field); + return r.unsent(&cx, e, field); + } switch (e) { error.Cancelled => return, error.Refused => if (cx.kind != .other and cx.kind != .server) {