This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0
|
||||
// The test double of the AI gateway for the documents module: a deterministic reader, standing in for a
|
||||
// model behind `platform.ai.respond` of the fake platform.
|
||||
//
|
||||
// It answers the extraction request exactly as a well-behaved model would: it finds the page's text
|
||||
// between the tags the prompt names, takes every `label: value` line as a field, and quotes the line
|
||||
// itself. It never sees an image, never invents a word, and returns the same lines for the same text,
|
||||
// so a test (or the offline eval) that runs it measures the module's own code -- the request it builds,
|
||||
// the schema it parses, the verification, the cache, the proposals -- and not a model.
|
||||
|
||||
import { PAGE_TEXT_CLOSE, PAGE_TEXT_OPEN } from "@app/modules/documents"
|
||||
import type { FakeChatReply } from "@conusai-kit/testing/fake-platform"
|
||||
|
||||
const FIELD = /^\s*([^::]+?)\s*[::]\s*(\S.*?)\s*$/u
|
||||
const MAX_LABEL_CHARS = 60
|
||||
const MAX_LABEL_WORDS = 6
|
||||
const MAX_LINE_CHARS = 2_000
|
||||
|
||||
export interface ReadLineOfModel {
|
||||
label: string
|
||||
value: string
|
||||
quote: string
|
||||
}
|
||||
|
||||
/**
|
||||
* The fields a reader of two line shapes finds in a page's text: `label: value`, and a table row
|
||||
* `first cell | other cells`, whose label is the first cell and whose value is the rest, as the prompt
|
||||
* asks a model to read it. The first row of a block of rows is the table's header, which names the
|
||||
* columns and is not a field.
|
||||
*/
|
||||
export function keyValueLines(text: string): ReadLineOfModel[] {
|
||||
const found: ReadLineOfModel[] = []
|
||||
let inTable = false
|
||||
for (const line of text.split("\n")) {
|
||||
if (line.includes(" | ")) {
|
||||
const header = !inTable
|
||||
inTable = true
|
||||
const [label, ...rest] = line.split(" | ").map((cell) => cell.trim())
|
||||
if (!header && label && rest.length > 0 && rest.every((cell) => cell !== "") && line.length <= MAX_LINE_CHARS) {
|
||||
found.push({ label, value: rest.join(" | "), quote: line.trim() })
|
||||
}
|
||||
continue
|
||||
}
|
||||
inTable = false
|
||||
const match = FIELD.exec(line)
|
||||
if (!match) continue
|
||||
const label = match[1] as string
|
||||
const value = match[2] as string
|
||||
if (label.length > MAX_LABEL_CHARS || label.split(/\s+/u).length > MAX_LABEL_WORDS) continue
|
||||
if (line.length > MAX_LINE_CHARS) continue
|
||||
found.push({ label, value, quote: line.trim() })
|
||||
}
|
||||
return found
|
||||
}
|
||||
|
||||
type Part = { type?: string; text?: string }
|
||||
|
||||
/** The page text of an extraction request: what lies between the prompt's tags in the user's message. */
|
||||
export function pageTextOf(request: Record<string, unknown>): string | undefined {
|
||||
const messages = (request.messages ?? []) as Array<{ role?: string; content?: string | Part[] }>
|
||||
const user = [...messages].reverse().find((message) => message.role === "user")
|
||||
const content = user?.content
|
||||
const text = typeof content === "string" ? content : (content ?? []).map((part) => part.text ?? "").join("\n")
|
||||
const from = text.indexOf(PAGE_TEXT_OPEN)
|
||||
const to = text.lastIndexOf(PAGE_TEXT_CLOSE)
|
||||
return from < 0 || to < from ? undefined : text.slice(from + PAGE_TEXT_OPEN.length + 1, to - 1)
|
||||
}
|
||||
|
||||
/** What an extraction request is answered with by the deterministic reader. */
|
||||
export const deterministicModel = (request: Record<string, unknown>): FakeChatReply => {
|
||||
const text = pageTextOf(request)
|
||||
return { content: JSON.stringify({ lines: text === undefined ? [] : keyValueLines(text) }) }
|
||||
}
|
||||
Reference in New Issue
Block a user