LLM
Модуль wippy/llm предоставляет унифицированный интерфейс для работы с большими языковыми моделями от различных провайдеров (OpenAI, Anthropic, Google, локальные модели). Поддерживается генерация текста, вызов инструментов, структурированный вывод, эмбеддинги и потоковая передача.
Настройка
Заголовок раздела «Настройка»Добавьте модуль в проект:
wippy add wippy/llmwippy 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_KEY, ANTHROPIC_API_KEY).
Генерация текста
Заголовок раздела «Генерация текста»Импортируйте библиотеку llm в запись и вызовите generate():
entries: - name: ask kind: function.lua source: file://ask.lua method: handler imports: llm: wippy.llm:llmlocal 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.resultend
return { handler = handler }Первый аргумент generate() может быть строковым промптом, построителем промптов или таблицей сообщений. Второй аргумент — таблица параметров.
Параметры генерации
Заголовок раздела «Параметры генерации»| Параметр | Тип | Описание |
|---|---|---|
model | string | Имя модели или класс (обязательный) |
temperature | number | Контроль случайности, 0-1 |
max_tokens | number | Максимальное количество токенов для генерации |
top_p | number | Параметр nucleus-сэмплирования |
top_k | number | Фильтрация top-k |
thinking_effort | number | Глубина размышления 0-100 (модели с возможностью размышления) |
tools | table | Массив определений инструментов |
tool_choice | string | "auto", "none", "any" или имя инструмента |
stream | table | Конфигурация потоковой передачи: { reply_to, topic, buffer_size } |
timeout | number | Таймаут запроса в секундах (по умолчанию 600) |
Структура ответа
Заголовок раздела «Структура ответа»| Поле | Тип | Описание |
|---|---|---|
result | string | Сгенерированный текстовый контент |
tokens | table | Использование токенов: prompt_tokens, completion_tokens, thinking_tokens, total_tokens |
finish_reason | string | Причина остановки генерации: "stop", "length", "tool_call", "filtered", "error" |
tool_calls | table? | Массив вызовов инструментов (если модель использовала инструменты) |
metadata | table | Метаданные, специфичные для провайдера |
usage_record | table? | Запись об использовании |
Построитель промптов
Заголовок раздела «Построитель промптов»Для многоходовых диалогов и сложных промптов используйте построитель промптов:
imports: llm: wippy.llm:llm prompt: wippy.llm:promptlocal 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() | Получить таблицу { messages = ... } для llm.generate() |
:clone() | Глубокая копия построителя |
:clear() | Удалить все сообщения |
Все методы add_* возвращают построитель для цепочечного вызова.
Многоходовые диалоги
Заголовок раздела «Многоходовые диалоги»Накапливайте контекст между ходами, добавляя сообщения:
local conversation = prompt.new()conversation:add_system("You are a helpful assistant.")
-- first turnconversation:add_user("What is Lua?")local r1 = llm.generate(conversation, { model = "gpt-4o" })conversation:add_assistant(r1.result)
-- second turn with full contextconversation: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" | name, arguments, id | Вызов инструмента |
"error" | error.message, error.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Поля вызова инструмента
Заголовок раздела «Поля вызова инструмента»| Поле | Тип | Описание |
|---|---|---|
id | string | Уникальный идентификатор вызова |
name | string | Имя инструмента |
arguments | table | Разобранные аргументы, соответствующие схеме |
Выбор инструмента
Заголовок раздела «Выбор инструмента»| Значение | Поведение |
|---|---|
"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)endrequired. Для необязательных полей используйте union-типы: 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.name | Идентификатор модели, используемый в API-вызовах |
meta.type | Должен быть llm.model |
meta.capabilities | Список возможностей: generate, tool_use, structured_output, embed, thinking, vision, caching |
meta.class | Принадлежность к классу: fast, balanced, reasoning и т.д. |
meta.priority | Числовой приоритет для разрешения по классу (больше — выше приоритет) |
max_tokens | Максимальное контекстное окно |
output_tokens | Максимальное количество выходных токенов |
pricing | Стоимость за миллион токенов: input, output |
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 namellm.generate("Hello", { model = "gpt-4o" })
-- model class (picks highest priority in that class)llm.generate("Hello", { model = "fast" })
-- explicit class syntaxllm.generate("Hello", { model = "class:reasoning" })Порядок разрешения:
- Совпадение по точному
meta.name - Совпадение по имени класса (побеждает наивысший
meta.priority) - С префиксом
class:поиск только в указанном классе
Обнаружение моделей
Заголовок раздела «Обнаружение моделей»Запрашивайте доступные модели и их возможности во время выполнения:
local llm = require("llm")
-- all modelslocal models = llm.available_models()
-- filter by capabilitylocal tool_models = llm.available_models("tool_use")local embed_models = llm.available_models("embed")
-- list model classeslocal classes = llm.get_classes()for _, c in ipairs(classes) do print(c.name .. ": " .. c.title)endЭмбеддинги
Заголовок раздела «Эмбеддинги»Генерация векторных эмбеддингов для семантического поиска:
local llm = require("llm")
-- single textlocal response = llm.embed("The quick brown fox", { model = "text-embedding-3-small", dimensions = 512,})-- response.result is a float array
-- multiple textslocal 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)) returnend
io.print(response.result)Типы ошибок
Заголовок раздела «Типы ошибок»| Константа | Описание |
|---|---|
llm.ERROR_TYPE.INVALID_REQUEST | Некорректный запрос |
llm.ERROR_TYPE.AUTHENTICATION | Неверный API-ключ |
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_OUTPUT | Структурированный вывод в формате JSON |
llm.CAPABILITY.EMBED | Векторные эмбеддинги |
llm.CAPABILITY.THINKING | Расширенное размышление |
llm.CAPABILITY.VISION | Понимание изображений |
llm.CAPABILITY.CACHING | Кэширование промптов |
Смотрите также
Заголовок раздела «Смотрите также»- Агенты - Фреймворк агентов с инструментами, делегатами и памятью
- Создание LLM-агента - Пошаговое руководство
- Обзор фреймворка - Использование модулей фреймворка