reasoning-replay-debug.spec.ts 2.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
  1. import { afterEach, describe, expect, it, vi } from "vitest"
  2. const invokeMock = vi.hoisted(() => vi.fn().mockResolvedValue(undefined))
  3. vi.mock("@tauri-apps/api/core", () => ({ invoke: invokeMock }))
  4. vi.mock("./platform", () => ({ isTauri: () => true }))
  5. import {
  6. formatReasoningReplayRiskForError,
  7. isReasoningContentRequiredError,
  8. logReasoningReplay,
  9. summarizeReasoningReplayRisk,
  10. } from "./reasoning-replay-debug"
  11. afterEach(() => {
  12. invokeMock.mockClear()
  13. vi.restoreAllMocks()
  14. })
  15. describe("reasoning replay debug", () => {
  16. it("detects the DeepSeek/Kimi reasoning_content 400 message", () => {
  17. expect(isReasoningContentRequiredError(
  18. 'HTTP 400: Bad Request — {"error":{"message":"The reasoning_content in the thinking mode must be passed back to the API."}}',
  19. )).toBe(true)
  20. })
  21. it("flags tool-call assistants missing reasoning_content", () => {
  22. const summary = summarizeReasoningReplayRisk([
  23. { role: "user", content: "写第一章" },
  24. {
  25. role: "assistant",
  26. content: "",
  27. tool_calls: [{
  28. id: "call_1",
  29. type: "function",
  30. function: { name: "read_chapter", arguments: "{}" },
  31. }],
  32. },
  33. {
  34. role: "assistant",
  35. content: "",
  36. tool_calls: [{
  37. id: "call_2",
  38. type: "function",
  39. function: { name: "read_outline", arguments: "{}" },
  40. }],
  41. reasoning_content: "",
  42. },
  43. ])
  44. expect(summary.assistantWithTools).toBe(2)
  45. expect(summary.missingReasoningOnToolAssistants).toBe(1)
  46. expect(summary.emptyReasoningOnToolAssistants).toBe(1)
  47. expect(formatReasoningReplayRiskForError(summary)).toContain("missing=1")
  48. expect(formatReasoningReplayRiskForError(summary)).toContain("reasoning=MISSING")
  49. })
  50. it("forwards collection diagnostics without using the frontend error channel", async () => {
  51. vi.spyOn(console, "warn").mockImplementation(() => {})
  52. logReasoningReplay("stream.collected", {
  53. model: "deepseek/deepseek-v4-flash",
  54. contentCharsEmitted: 499,
  55. reasoningCharsObserved: 469,
  56. })
  57. await vi.waitFor(() => {
  58. expect(invokeMock).toHaveBeenCalledWith("log_diagnostic", {
  59. message: expect.stringContaining("[reasoning-replay] stream.collected"),
  60. })
  61. })
  62. expect(invokeMock).not.toHaveBeenCalledWith("log_error", expect.anything())
  63. })
  64. })