Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
"echomodel",
"emojify",
"Entra",
"evalprompt",
"Evals",
"execa",
"extname",
Expand Down Expand Up @@ -92,6 +93,7 @@
"htmlescape",
"huggingface",
"icontains",
"importprompt",
"jaegertracing",
"Jamba",
"JSONLLM",
Expand Down Expand Up @@ -163,12 +165,14 @@
"previ",
"PRICINGS",
"priompt",
"promptcontext",
"promptdom",
"promptfoo",
"promptfooconfig",
"promptjson",
"promptrunner",
"prompty",
"proxify",
"pyodide",
"quoteify",
"qwen",
Expand Down Expand Up @@ -210,6 +214,7 @@
"tvly",
"typecheck",
"unfence",
"unmarkdown",
"unthink",
"unwrappers",
"urllib",
Expand Down
5 changes: 2 additions & 3 deletions docs/genaisrc/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,8 @@
"erasableSyntaxOnly": true
},
"include": [
"*.mjs",
"*.mts",
"src/*.mts",
"**/*.mjs",
"**/*.mts",
"./genaiscript.d.ts"
]
}
1 change: 1 addition & 0 deletions docs/src/components/BuiltinTools.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { LinkCard } from '@astrojs/starlight/components';

### Builtin tools

<LinkCard title="fetch" description="Fetch data from a URL from allowed domains." href="/genaiscript/reference/scripts/system#systemfetch" />
<LinkCard title="fs_ask_file" description="Runs a LLM query over the content of a file. Use this tool to extract information from a file." href="/genaiscript/reference/scripts/system#systemfs_ask_file" />
<LinkCard title="fs_data_query" description="Query data in a file using GROQ syntax" href="/genaiscript/reference/scripts/system#systemfs_data_query" />
<LinkCard title="fs_diff_files" description="Computes a diff between two different files. Use git diff instead to compare versions of a file." href="/genaiscript/reference/scripts/system#systemfs_diff_files" />
Expand Down
89 changes: 87 additions & 2 deletions docs/src/content/docs/reference/scripts/system.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,7 @@ system({ ...,
parameters: {
model: {
type: "string",
description: "LLM model to use",
default: "gpt-35-turbo",
description: "LLM model to use"
},
},
})
Expand Down Expand Up @@ -1184,6 +1183,92 @@ export default function (ctx: ChatGenerationContext) {
`````


### `system.fetch`

A tool that can fetch data from a URL



- tool `fetch`: Fetch data from a URL from allowed domains.

`````js wrap title="system.fetch"
system({
title: "A tool that can fetch data from a URL",
parameters: {
domains: {
type: "array",
items: {
type: "string",
description: "A list of allowed domains to fetch data from.",
},
},
},
})

export default function (ctx: ChatGenerationContext) {
const { defTool, env } = ctx

const dbg = host.logger(`system:fetch`)
const domains = env.vars["system.fetch.domains"] || []
dbg(`allowed domains: %o`, domains)

defTool(
"fetch",
"Fetch data from a URL from allowed domains.",
{
url: {
type: "string",
description: "The URL to fetch data from.",
required: true,
},
convert: {
type: "string",
description: "Converts HTML to Markdown or plain text.",
required: false,
enum: ["markdown", "text"],
},
skipToContent: {
type: "string",
description: "Skip to a specific string in the content.",
required: false,
},
},
async ({ context, ...args }) => {
const { url, convert, skipToContent } = args as {
url: string
convert: FetchTextOptions["convert"]
skipToContent: string
}
const method = "GET"
const uri = new URL(url)
const domain = uri.hostname
if (!domains.includes(domain))
return `error: domain ${domain} is not allowed.`

dbg(`${method} ${url}`)
const res = await host.fetchText(url, { convert })
dbg(`response: %d`, res.status)
if (!res.ok) return `error: ${res.status}`
if (!res.text) return res.file ?? res.status

let result = res.text
if (skipToContent) {
const index = result.indexOf(skipToContent)
if (index === -1)
return `error: skipTo '${skipToContent}' not found.`
result = result.slice(index + skipToContent.length)
}
return result
},
{
detectPromptInjection: "available",
}
)
}

`````


### `system.files`

File generation
Expand Down
5 changes: 2 additions & 3 deletions eval/extrism/genaisrc/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,8 @@
"erasableSyntaxOnly": true
},
"include": [
"*.mjs",
"*.mts",
"src/*.mts",
"**/*.mjs",
"**/*.mts",
"./genaiscript.d.ts"
]
}
5 changes: 2 additions & 3 deletions genaisrc/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,8 @@
"erasableSyntaxOnly": true
},
"include": [
"*.mjs",
"*.mts",
"src/*.mts",
"**/*.mjs",
"**/*.mts",
"./genaiscript.d.ts"
]
}
5 changes: 2 additions & 3 deletions packages/auto/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,8 @@
"erasableSyntaxOnly": true
},
"include": [
"*.mjs",
"*.mts",
"src/*.mts",
"**/*.mjs",
"**/*.mts",
"./genaiscript.d.ts"
]
}
73 changes: 73 additions & 0 deletions packages/cli/genaisrc/system.fetch.genai.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
system({
title: "A tool that can fetch data from a URL",
parameters: {
domains: {
type: "array",
items: {
type: "string",
description: "A list of allowed domains to fetch data from.",
},
},
},
})

export default function (ctx: ChatGenerationContext) {
const { defTool, env } = ctx

const dbg = host.logger(`system:fetch`)
const domains = env.vars["system.fetch.domains"] || []
dbg(`allowed domains: %o`, domains)

defTool(
"fetch",
"Fetch data from a URL from allowed domains.",
{
url: {
type: "string",
description: "The URL to fetch data from.",
required: true,
},
convert: {
type: "string",
description: "Converts HTML to Markdown or plain text.",
required: false,
enum: ["markdown", "text"],
},
skipToContent: {
type: "string",
description: "Skip to a specific string in the content.",
required: false,
},
},
async ({ context, ...args }) => {
const { url, convert, skipToContent } = args as {
url: string
convert: FetchTextOptions["convert"]
skipToContent: string
}
const method = "GET"
const uri = new URL(url)
const domain = uri.hostname
if (!domains.includes(domain))
return `error: domain ${domain} is not allowed.`

dbg(`${method} ${url}`)
const res = await host.fetchText(url, { convert })
dbg(`response: %d`, res.status)
if (!res.ok) return `error: ${res.status}`
if (!res.text) return res.file ?? res.status

let result = res.text
if (skipToContent) {
const index = result.indexOf(skipToContent)
if (index === -1)
return `error: skipTo '${skipToContent}' not found.`
result = result.slice(index + skipToContent.length)
}
return result
},
{
detectPromptInjection: "available",
}
)
}
3 changes: 1 addition & 2 deletions packages/core/bundleprompts.js
Original file line number Diff line number Diff line change
Expand Up @@ -218,8 +218,7 @@ system({ ...,
parameters: {
model: {
type: "string",
description: "LLM model to use",
default: "gpt-35-turbo",
description: "LLM model to use"
},
},
})
Expand Down
19 changes: 11 additions & 8 deletions packages/core/src/chatrenderterminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,17 @@ const dbg = genaiscriptDebug("chat:render")

function renderTrimmed(s: string, rows: number, width: number) {
const lines = s.split(/\n/g).filter((l) => !!l)
const head = Math.min(rows >> 1, lines.length - 1)
const tail = rows - head
const trimmed = lines.slice(0, head)
if (tail) {
const hidden = lines.length - head - tail
if (hidden === 1) trimmed.push(lines.at(-tail - 1))
else if (hidden > 0) trimmed.push(`... (${hidden} lines)`)
trimmed.push(...lines.slice(-tail))
let trimmed = lines.slice(0)
if (lines.length > rows) {
const head = Math.min(rows >> 1, lines.length - 1)
const tail = rows - head
trimmed = lines.slice(0, head)
if (tail) {
const hidden = lines.length - head - tail
if (hidden === 1) trimmed.push(lines.at(-tail - 1))
else if (hidden > 0) trimmed.push(`... (${hidden} lines)`)
trimmed.push(...lines.slice(-tail))
}
}
const res = trimmed.map((l) =>
wrapColor(CONSOLE_COLOR_DEBUG, "│" + ellipse(l, width) + "\n")
Expand Down
4 changes: 3 additions & 1 deletion packages/core/src/fetchtext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { deleteUndefinedValues } from "./cleaners"
import { prettyBytes } from "./pretty"
import debug from "debug"
import { uriRedact } from "./url"
import { HTMLToMarkdown, HTMLToText } from "./html"
import { HTMLTablesToJSON, HTMLToMarkdown, HTMLToText } from "./html"
import { createFetch } from "./fetch"
const dbg = debug("genaiscript:fetch:text")

Expand Down Expand Up @@ -110,6 +110,8 @@ export async function fetchText(
})
else if (convert === "text")
content = await HTMLToText(content, { trace, cancellationToken })
else if (convert === "tables")
content = JSON.stringify(await HTMLTablesToJSON(content))
}
ok = true
const file: WorkspaceFile = deleteUndefinedValues({
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/types/prompt_template.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5695,7 +5695,7 @@ type FetchOptions = RequestInit & {
}

type FetchTextOptions = Omit<FetchOptions, "body" | "signal" | "window"> & {
convert?: "markdown" | "text"
convert?: "markdown" | "text" | "tables"
}

interface PythonRuntimeOptions {
Expand Down
Loading
Loading