跳转到内容

LLM

wippy/llm 模块提供了统一的接口,用于与多个提供商(OpenAI、Anthropic、Google、本地模型)的大语言模型进行交互。支持文本生成、工具调用、结构化输出、向量嵌入和流式传输。

将模块添加到项目中:

Terminal window
wippy add wippy/llm
wippy install

_index.yaml 中声明依赖。LLM 模块需要环境存储(用于 API 密钥)和进程宿主:

version: "1.0"
namespace: app
entries:
- name: os_env
kind: env.storage.os
- name: processes
kind: process.host
lifecycle:
auto_start: true
- name: dep.llm
kind: ns.dependency
component: wippy/llm
version: "*"
parameters:
- name: env_storage
value: app:os_env
- name: process_host
value: app:processes

env.storage.os 条目将操作系统环境变量暴露给 LLM 提供商。将 API 密钥设置为环境变量(例如 OPENAI_API_KEYANTHROPIC_API_KEY)。

在条目中导入 llm 库并调用 generate()

entries:
- name: ask
kind: function.lua
source: file://ask.lua
method: handler
imports:
llm: wippy.llm:llm
local llm = require("llm")
local function handler()
local response, err = llm.generate("What are the three laws of robotics?", {
model = "gpt-4o"
})
if err then
return nil, err
end
return response.result
end
return { handler = handler }

generate() 的第一个参数可以是字符串提示词、提示词构建器或消息表。第二个参数是选项表。

选项类型说明
modelstring模型名称或类别(必填)
temperaturenumber随机性控制,0-1
max_tokensnumber最大生成令牌数
top_pnumber核采样参数
top_knumberTop-k 过滤
thinking_effortnumber思考深度 0-100(具有思考能力的模型)
toolstable工具定义数组
tool_choicestring"auto""none""any" 或工具名称
streamtable流式配置:{ reply_to, topic, buffer_size }
timeoutnumber请求超时秒数(默认 600)
字段类型说明
resultstring生成的文本内容
tokenstable令牌用量:prompt_tokenscompletion_tokensthinking_tokenstotal_tokens
finish_reasonstring生成停止原因:"stop""length""tool_call""filtered""error"
tool_callstable?工具调用数组(如果模型调用了工具)
metadatatable提供商特定的元数据
usage_recordtable?使用量跟踪记录

对于多轮对话和复杂提示词,使用提示词构建器:

imports:
llm: wippy.llm:llm
prompt: wippy.llm:prompt
local llm = require("llm")
local prompt = require("prompt")
local conversation = prompt.new()
conversation:add_system("You are a helpful assistant.")
conversation:add_user("What is the capital of France?")
local response, err = llm.generate(conversation, {
model = "gpt-4o",
temperature = 0.7,
max_tokens = 500
})
方法说明
prompt.new()创建空构建器
prompt.with_system(content)创建带系统消息的构建器
:add_system(content, meta?)添加系统消息
:add_user(content, meta?)添加用户消息
:add_assistant(content, meta?)添加助手消息
:add_developer(content, meta?)添加开发者消息
:add_message(role, content_parts, name?, meta?)添加指定角色和内容部分的消息
:add_function_call(name, args, id?)添加助手的工具调用
:add_function_result(name, result, id?)添加工具执行结果
:add_cache_marker(id?)标记缓存边界(Claude 模型)
:get_messages()获取消息数组
:build()获取用于 llm.generate(){ messages = ... }
:clone()深拷贝构建器
:clear()清除所有消息

所有 add_* 方法均返回构建器自身,支持链式调用。

通过追加消息来构建跨轮次的上下文:

local conversation = prompt.new()
conversation:add_system("You are a helpful assistant.")
-- first turn
conversation:add_user("What is Lua?")
local r1 = llm.generate(conversation, { model = "gpt-4o" })
conversation:add_assistant(r1.result)
-- second turn with full context
conversation:add_user("What makes it different from Python?")
local r2 = llm.generate(conversation, { model = "gpt-4o" })

在单条消息中组合文本和图像:

local conversation = prompt.new()
conversation:add_message(prompt.ROLE.USER, {
prompt.text("What's in this image?"),
prompt.image("https://example.com/photo.jpg")
})
函数说明
prompt.text(content)文本内容部分
prompt.image(url, mime_type?)来自 URL 的图像
prompt.image_base64(mime_type, data)Base64 编码的图像
常量
prompt.ROLE.SYSTEM"system"
prompt.ROLE.USER"user"
prompt.ROLE.ASSISTANT"assistant"
prompt.ROLE.DEVELOPER"developer"
prompt.ROLE.FUNCTION_CALL"function_call"
prompt.ROLE.FUNCTION_RESULT"function_result"
prompt.ROLE.CACHE_MARKER"cache_marker"

克隆构建器以创建变体,而不修改原始构建器:

local base = prompt.new()
base:add_system("You are a helpful assistant.")
local conv1 = base:clone()
conv1:add_user("What is AI?")
local conv2 = base:clone()
conv2:add_user("What is ML?")

使用进程通信实时流式传输响应。需要 process.lua 条目:

local llm = require("llm")
local TOPIC = "llm_stream"
local function main()
local stream_ch = process.listen(TOPIC)
local response = llm.generate("Write a short story", {
model = "gpt-4o",
stream = {
reply_to = process.pid(),
topic = TOPIC,
},
})
while true do
local chunk, ok = stream_ch:receive()
if not ok then break end
if chunk.type == "chunk" then
io.write(chunk.content)
elseif chunk.type == "thinking" then
io.write(chunk.content)
elseif chunk.type == "error" then
io.print("Error: " .. chunk.error.message)
break
elseif chunk.type == "done" then
break
end
end
process.unlisten(stream_ch)
end
类型字段说明
"chunk"content文本内容片段
"thinking"content模型思考过程
"tool_call"nameargumentsid工具调用
"error"error.messageerror.type流错误
"done"meta流传输完成
流式传输需要 process.lua 条目,因为它使用 Wippy 的进程通信系统(process.pid()process.listen())。

将工具定义为内联模式并传递给 generate()

local llm = require("llm")
local prompt = require("prompt")
local json = require("json")
local tools = {
{
name = "get_weather",
description = "Get current weather for a location",
schema = {
type = "object",
properties = {
location = { type = "string", description = "City name" },
},
required = { "location" },
},
},
}
local conversation = prompt.new()
conversation:add_user("What's the weather in Tokyo?")
local response = llm.generate(conversation, {
model = "gpt-4o",
tools = tools,
tool_choice = "auto",
})
if response.tool_calls and #response.tool_calls > 0 then
for _, tc in ipairs(response.tool_calls) do
-- execute the tool and get a result
local result = { temperature = 22, condition = "sunny" }
-- add the exchange to the conversation
conversation:add_function_call(tc.name, tc.arguments, tc.id)
conversation:add_function_result(tc.name, json.encode(result), tc.id)
end
-- continue generation with tool results
local final = llm.generate(conversation, { model = "gpt-4o" })
print(final.result)
end
字段类型说明
idstring唯一调用标识符
namestring工具名称
argumentstable匹配模式的已解析参数
行为
"auto"模型决定何时使用工具(默认)
"none"从不使用工具
"any"必须使用至少一个工具
"tool_name"必须使用指定的工具

生成匹配模式的已验证 JSON:

local llm = require("llm")
local schema = {
type = "object",
properties = {
name = { type = "string" },
age = { type = "number" },
hobbies = {
type = "array",
items = { type = "string" },
},
},
required = { "name", "age", "hobbies" },
additionalProperties = false,
}
local response, err = llm.structured_output(schema, "Describe a fictional character", {
model = "gpt-4o",
})
if not err then
print(response.result.name)
print(response.result.age)
end
对于 OpenAI 模型,所有属性必须在 required 数组中。对于可选字段使用联合类型:type = {"string", "null"}。设置 additionalProperties = false

模型定义为带有 meta.type: llm.model 的注册表条目:

entries:
- name: gpt-4o
kind: registry.entry
meta:
name: gpt-4o
type: llm.model
title: GPT-4o
comment: OpenAI's flagship model
capabilities:
- generate
- tool_use
- structured_output
- vision
class:
- balanced
priority: 100
max_tokens: 128000
output_tokens: 16384
pricing:
input: 2.5
output: 10
providers:
- id: wippy.llm.openai:provider
provider_model: gpt-4o
字段说明
meta.nameAPI 调用中使用的模型标识符
meta.type必须为 llm.model
meta.capabilities功能列表:generatetool_usestructured_outputembedthinkingvisioncaching
meta.class类别归属:fastbalancedreasoning
meta.priority基于类别解析的数值优先级(越高越优先)
max_tokens最大上下文窗口
output_tokens最大输出令牌数
pricing每百万令牌成本:inputoutput
providers包含 id(提供商条目)和 provider_model(提供商特定模型名称)的数组

对于本地托管的模型(LM Studio、Ollama),定义一个带有自定义 base_url 的单独提供商条目:

- name: local_provider
kind: registry.entry
meta:
name: ollama
type: llm.provider
title: Ollama Local
driver:
id: wippy.llm.openai:driver
options:
api_key_env: none
base_url: http://127.0.0.1:11434/v1
- name: local-llama
kind: registry.entry
meta:
name: local-llama
type: llm.model
title: Local Llama
capabilities:
- generate
max_tokens: 4096
output_tokens: 4096
pricing:
input: 0
output: 0
providers:
- id: app:local_provider
provider_model: llama-3.2

模型可以通过精确名称、类别或显式类别前缀引用:

-- exact model name
llm.generate("Hello", { model = "gpt-4o" })
-- model class (picks highest priority in that class)
llm.generate("Hello", { model = "fast" })
-- explicit class syntax
llm.generate("Hello", { model = "class:reasoning" })

解析顺序:

  1. 按精确 meta.name 匹配
  2. 按类别名称匹配(meta.priority 最高者优先)
  3. 使用 class: 前缀时,仅在该类别中搜索

在运行时查询可用模型及其能力:

local llm = require("llm")
-- all models
local models = llm.available_models()
-- filter by capability
local tool_models = llm.available_models("tool_use")
local embed_models = llm.available_models("embed")
-- list model classes
local classes = llm.get_classes()
for _, c in ipairs(classes) do
print(c.name .. ": " .. c.title)
end

生成用于语义搜索的向量嵌入:

local llm = require("llm")
-- single text
local response = llm.embed("The quick brown fox", {
model = "text-embedding-3-small",
dimensions = 512,
})
-- response.result is a float array
-- multiple texts
local response = llm.embed({
"First document",
"Second document",
}, { model = "text-embedding-3-small" })
-- response.result is an array of float arrays

在发送任务前探测提供商。适用于就绪性检查和轻量级健康监控:

local status, err = llm.status({
model = "gpt-4o",
})
选项说明
model必填。要检查的模型。
provider_id可选。跳过模型解析,直接指向特定提供商。

返回提供商的 StatusResponse(内容取决于提供商)。

错误作为第二个返回值返回。发生错误时,第一个返回值为 nil

local response, err = llm.generate("Hello", { model = "gpt-4o" })
if err then
io.print("Error: " .. tostring(err))
return
end
io.print(response.result)
常量说明
llm.ERROR_TYPE.INVALID_REQUEST请求格式错误
llm.ERROR_TYPE.AUTHENTICATIONAPI 密钥无效
llm.ERROR_TYPE.RATE_LIMIT超出提供商速率限制
llm.ERROR_TYPE.SERVER_ERROR提供商服务器错误
llm.ERROR_TYPE.CONTEXT_LENGTH输入超出上下文窗口
llm.ERROR_TYPE.CONTENT_FILTER内容被安全系统过滤
llm.ERROR_TYPE.TIMEOUT请求超时
llm.ERROR_TYPE.MODEL_ERROR模型无效或不可用
常量说明
llm.FINISH_REASON.STOP正常完成
llm.FINISH_REASON.LENGTH达到最大令牌数
llm.FINISH_REASON.CONTENT_FILTER内容被过滤
llm.FINISH_REASON.TOOL_CALL模型进行了工具调用
llm.FINISH_REASON.ERROR生成过程中出错
常量说明
llm.CAPABILITY.GENERATE文本生成
llm.CAPABILITY.TOOL_USE工具/函数调用
llm.CAPABILITY.STRUCTURED_OUTPUTJSON 结构化输出
llm.CAPABILITY.EMBED向量嵌入
llm.CAPABILITY.THINKING扩展思考
llm.CAPABILITY.VISION图像理解
llm.CAPABILITY.CACHING提示词缓存