llm.test.ts 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. import { describe, expect, test } from "bun:test"
  2. import { LLM, LLMResponse } from "../src"
  3. import { LLMRequest, Message, ModelRef, ToolChoice, ToolDefinition } from "../src/schema"
  4. describe("llm constructors", () => {
  5. test("builds canonical schema classes from ergonomic input", () => {
  6. const request = LLM.request({
  7. id: "req_1",
  8. model: LLM.model({ id: "fake-model", provider: "fake", route: "openai-chat", baseURL: "https://fake.local" }),
  9. system: "You are concise.",
  10. prompt: "Say hello.",
  11. })
  12. expect(request).toBeInstanceOf(LLMRequest)
  13. expect(request.model).toBeInstanceOf(ModelRef)
  14. expect(request.messages[0]).toBeInstanceOf(Message)
  15. expect(request.system).toEqual([{ type: "text", text: "You are concise." }])
  16. expect(request.messages[0]?.content).toEqual([{ type: "text", text: "Say hello." }])
  17. expect(request.generation).toBeUndefined()
  18. expect(request.tools).toEqual([])
  19. })
  20. test("updates requests without spreading schema class instances", () => {
  21. const base = LLM.request({
  22. id: "req_1",
  23. model: LLM.model({ id: "fake-model", provider: "fake", route: "openai-chat", baseURL: "https://fake.local" }),
  24. prompt: "Say hello.",
  25. })
  26. const updated = LLM.updateRequest(base, {
  27. generation: { maxTokens: 20 },
  28. messages: [...base.messages, LLM.assistant("Hi.")],
  29. })
  30. expect(updated).toBeInstanceOf(LLMRequest)
  31. expect(updated.id).toBe("req_1")
  32. expect(updated.model).toEqual(base.model)
  33. expect(updated.generation).toEqual({ maxTokens: 20 })
  34. expect(updated.messages.map((message) => message.role)).toEqual(["user", "assistant"])
  35. })
  36. test("keeps request options separate from model defaults", () => {
  37. const request = LLM.request({
  38. model: LLM.model({
  39. id: "fake-model",
  40. provider: "fake",
  41. route: "openai-chat",
  42. baseURL: "https://fake.local",
  43. generation: { maxTokens: 100, temperature: 1 },
  44. providerOptions: { openai: { store: false, metadata: { model: true } } },
  45. http: { body: { metadata: { model: true } }, headers: { "x-shared": "model" }, query: { model: "1" } },
  46. }),
  47. prompt: "Say hello.",
  48. generation: { temperature: 0 },
  49. providerOptions: { openai: { store: true, metadata: { request: true } } },
  50. http: { body: { metadata: { request: true } }, headers: { "x-shared": "request" }, query: { request: "1" } },
  51. })
  52. expect(request.generation).toEqual({ temperature: 0 })
  53. expect(request.providerOptions).toEqual({ openai: { store: true, metadata: { request: true } } })
  54. expect(request.http).toEqual({
  55. body: { metadata: { request: true } },
  56. headers: { "x-shared": "request" },
  57. query: { request: "1" },
  58. })
  59. })
  60. test("updates canonical requests from the request datatype", () => {
  61. const base = LLM.request({
  62. id: "req_1",
  63. model: LLM.model({ id: "fake-model", provider: "fake", route: "openai-chat", baseURL: "https://fake.local" }),
  64. prompt: "Say hello.",
  65. })
  66. const updated = LLMRequest.update(base, { messages: [...base.messages, LLM.assistant("Hi.")] })
  67. expect(updated).toBeInstanceOf(LLMRequest)
  68. expect(updated.id).toBe("req_1")
  69. expect(LLMRequest.input(updated).id).toBe("req_1")
  70. expect(updated.messages.map((message) => message.role)).toEqual(["user", "assistant"])
  71. expect(LLMRequest.update(updated, {})).toBe(updated)
  72. })
  73. test("updates canonical models from the model datatype", () => {
  74. const base = LLM.model({ id: "fake-model", provider: "fake", route: "openai-chat", baseURL: "https://fake.local" })
  75. const updated = ModelRef.update(base, { route: "openai-responses" })
  76. expect(updated).toBeInstanceOf(ModelRef)
  77. expect(String(updated.id)).toBe("fake-model")
  78. expect(updated.route).toBe("openai-responses")
  79. expect(String(ModelRef.input(updated).provider)).toBe("fake")
  80. expect(ModelRef.update(updated, {})).toBe(updated)
  81. })
  82. test("builds tool choices from names and tools", () => {
  83. const tool = LLM.toolDefinition({ name: "lookup", description: "Lookup data", inputSchema: { type: "object" } })
  84. expect(tool).toBeInstanceOf(ToolDefinition)
  85. expect(LLM.toolChoice("lookup")).toEqual(new ToolChoice({ type: "tool", name: "lookup" }))
  86. expect(LLM.toolChoiceName("required")).toEqual(new ToolChoice({ type: "tool", name: "required" }))
  87. expect(LLM.toolChoice(tool)).toEqual(new ToolChoice({ type: "tool", name: "lookup" }))
  88. })
  89. test("builds tool choice modes from reserved strings", () => {
  90. expect(LLM.toolChoice("auto")).toEqual(new ToolChoice({ type: "auto" }))
  91. expect(LLM.toolChoice("none")).toEqual(new ToolChoice({ type: "none" }))
  92. expect(LLM.toolChoice("required")).toEqual(new ToolChoice({ type: "required" }))
  93. expect(
  94. LLM.request({
  95. model: LLM.model({ id: "fake-model", provider: "fake", route: "openai-chat", baseURL: "https://fake.local" }),
  96. prompt: "Use tools if needed.",
  97. toolChoice: "required",
  98. }).toolChoice,
  99. ).toEqual(new ToolChoice({ type: "required" }))
  100. })
  101. test("builds assistant tool calls and tool result messages", () => {
  102. const call = LLM.toolCall({ id: "call_1", name: "lookup", input: { query: "weather" } })
  103. const result = LLM.toolResult({ id: "call_1", name: "lookup", result: { temperature: 72 } })
  104. expect(LLM.assistant([call]).content).toEqual([call])
  105. expect(LLM.toolMessage(result).content).toEqual([
  106. { type: "tool-result", id: "call_1", name: "lookup", result: { type: "json", value: { temperature: 72 } } },
  107. ])
  108. })
  109. test("extracts output text from response events", () => {
  110. expect(
  111. LLMResponse.text({
  112. events: [
  113. { type: "text-delta", text: "hi" },
  114. { type: "request-finish", reason: "stop" },
  115. ],
  116. }),
  117. ).toBe("hi")
  118. })
  119. })