u
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
examples/.env
|
examples/.env
|
||||||
examples/invoice.pdf
|
examples/invoice.pdf
|
||||||
|
examples/prompts.log
|
||||||
|
|||||||
75
AGENTS.md
Normal file
75
AGENTS.md
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
# AGENTS.md
|
||||||
|
|
||||||
|
Orientation notes for AI coding assistants working in this repo. Read this first.
|
||||||
|
|
||||||
|
## What this is
|
||||||
|
|
||||||
|
`llmorbiter` — a small (v0.1.0, 0BSD) **LLM tool-calling orchestrator with provider
|
||||||
|
abstraction**. Pure ES modules (`"type": "module"`). Only runtime dependency is `dotenv`.
|
||||||
|
No build step, no test runner, no README (this file is the orientation doc).
|
||||||
|
|
||||||
|
## Layout
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
index.js # public entry — re-exports everything
|
||||||
|
LLMOrbiter.js # core orchestrator (~364 lines)
|
||||||
|
Tool.js # defineTool() + ToolError + DirectReturnError
|
||||||
|
providers/
|
||||||
|
OpenRouterProvider.js # streaming OpenRouter SSE provider
|
||||||
|
|
||||||
|
examples/
|
||||||
|
weather.js # minimal tool-calling demo
|
||||||
|
tool-error-retry.js # tool-error + LLM self-correction demo (has PASS/FAIL asserts)
|
||||||
|
pdf-pages.js # PDF -> per-page LLM processing (500 lines, largest)
|
||||||
|
tools/weather.js # sample static-data weather tool
|
||||||
|
```
|
||||||
|
|
||||||
|
## Public API (`src/index.js`)
|
||||||
|
|
||||||
|
Exports: `LLMOrbiter`, `OpenRouterProvider`, `defineTool`, `ToolError`, `DirectReturnError`.
|
||||||
|
|
||||||
|
- `defineTool({ name, description, parameters, strict=true, invoke })` -> `{ spec, invoke, name }`.
|
||||||
|
`spec` is the OpenAI-compatible function schema sent to the LLM; `invoke(args, { signal })`
|
||||||
|
is the local implementation.
|
||||||
|
- `ToolError(message)` — throw from a tool to signal a recoverable error back to the LLM.
|
||||||
|
- `DirectReturnError(data)` — throw from a tool to stop the loop immediately; `run()` resolves
|
||||||
|
with `{ directReturn: true, data, ... }`.
|
||||||
|
|
||||||
|
### `new LLMOrbiter(provider, opts)`
|
||||||
|
|
||||||
|
`opts` are lifecycle callbacks: `onLLMCall`, `onUsage`, `onToolCall`, `onToolError`,
|
||||||
|
`onToolResult`, `onStatus`, `onContent`, `onReasoning`.
|
||||||
|
|
||||||
|
### `orbiter.run(opts)`
|
||||||
|
|
||||||
|
`{ prompt, messages, model, tools, maxIterations=10, tool_choice, extraBody, signal }`.
|
||||||
|
|
||||||
|
Loop: send messages + tool specs -> if `tool_calls` come back, invoke locals and feed results
|
||||||
|
back -> repeat until no more tool calls or `maxIterations`. Returns
|
||||||
|
`{ content, toolResults, usage, rawUsage, iterations, messages, ... }`; the returned `messages`
|
||||||
|
can seed the next `run()` for conversation continuity (a leading `{role:'system'}` entry is the
|
||||||
|
system prompt). `orbiter.abort(reason)` cancels the active run; aborts surface as
|
||||||
|
`DOMException('...', 'AbortError')`.
|
||||||
|
|
||||||
|
### `OpenRouterProvider`
|
||||||
|
|
||||||
|
`new OpenRouterProvider({ apiKey, apiUrl, siteUrl, siteName })`. Method:
|
||||||
|
`chatCompletions({ model, messages, tools, tool_choice, signal, extraBody, onStatus, onContent, onReasoning })`
|
||||||
|
— streams SSE, assembles `{ content, tool_calls, finish_reason, usage, reasoning_details, reasoning }`.
|
||||||
|
`apiUrl` defaults to `https://openrouter.ai/api/v1/chat/completions`.
|
||||||
|
|
||||||
|
## Gotchas / notes
|
||||||
|
|
||||||
|
- Examples need `OPENROUTER_API_KEY` (in `examples/.env` or via `--key`). Default model in the
|
||||||
|
examples: `deepseek/deepseek-v4-flash-0731`.
|
||||||
|
- `.gitignore` ignores `node_modules/`, `examples/.env`, `examples/invoice.pdf`.
|
||||||
|
- `pdf-pages.js` shells out to `pdftotext -layout`, splits pages on `\f`, and appends prompts
|
||||||
|
to `examples/prompts.log`.
|
||||||
|
- `OpenRouterProvider._readStream` currently writes reasoning straight to `process.stdout`
|
||||||
|
(debug-style side effect) rather than only via `onReasoning` — worth knowing if refactoring.
|
||||||
|
|
||||||
|
## Style conventions observed
|
||||||
|
|
||||||
|
- 4-space indent, single quotes, semicolons, `'use strict';` header.
|
||||||
|
- Comments use em dashes (`—`) and `───` section dividers in examples.
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
* Create a tool from a definition.
|
* Create a tool from a definition.
|
||||||
* Returns { spec, invoke } where spec is the wire-format object sent to the LLM.
|
* Returns { spec, invoke } where spec is the wire-format object sent to the LLM.
|
||||||
*/
|
*/
|
||||||
export function defineTool({ name, description, parameters, strict = true, invoke }) {
|
export function defineTool({ name, description, parameters, strict = false, invoke }) {
|
||||||
if (!name) throw new Error('Tool must have a name');
|
if (!name) throw new Error('Tool must have a name');
|
||||||
if (typeof invoke !== 'function') throw new Error(`Tool "${name}" must have an invoke function`);
|
if (typeof invoke !== 'function') throw new Error(`Tool "${name}" must have an invoke function`);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user