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
38 changes: 38 additions & 0 deletions bin/loushy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/usr/bin/env node
'use strict';

const path = require('node:path');

async function main() {
const [command, ...rest] = process.argv.slice(2);

if (command !== 'dev') {
console.error(`loushy: unknown command '${command || ''}'. Usage: loushy dev <config.yaml|config.json> [--port N]`);
process.exitCode = 1;
return;
}

const configPath = rest.find((arg) => !arg.startsWith('--'));
const portFlag = rest.find((arg) => arg.startsWith('--port'));
const port = portFlag ? Number(portFlag.split('=')[1] || rest[rest.indexOf(portFlag) + 1]) : 3737;
const hostFlag = rest.find((arg) => arg.startsWith('--host'));
const host = hostFlag ? (hostFlag.split('=')[1] || rest[rest.indexOf(hostFlag) + 1]) : '127.0.0.1';

if (!configPath) {
console.error('loushy dev: a config file path is required. Usage: loushy dev <config.yaml|config.json> [--port N] [--host H]');
process.exitCode = 1;
return;
}

const { startDevServer } = require(path.join(__dirname, '..', 'dist', 'cli', 'dev.js'));

try {
const handle = await startDevServer(path.resolve(configPath), port, host);
console.log(`loushy dev: listening on http://${host}:${handle.port}`);
} catch (error) {
console.error(error && error.message ? error.message : String(error));
process.exitCode = 1;
}
}

main();
34 changes: 34 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Examples

Runnable examples of `@loushy/build-ai-agent` in use. Each directory below
is runnable directly (see its own README for exact instructions); most use
`tsx` and a free mock provider by default, so nothing here needs an API key
to try.

## [doc-qa](./doc-qa)

A question-answering agent scoped to a single fixed document (LOU-H10).

## [research-assistant](./research-assistant)

A research agent with the built-in `http` tool wired up (LOU-H10).

## [slack-notifier](./slack-notifier)

Turns a raw event description into a short, Slack-ready notification (LOU-H10).

## [support-bot](./support-bot)

A minimal, empathetic customer-support agent (LOU-H10).

## [tracing](./tracing)

Runnable examples of `AgentExecutor.execute()` wired to console and OpenTelemetry trace exporters (LOU-E6).

## [workflow-router](./workflow-router)

An agent that classifies an incoming request into a fixed set of categories (LOU-H10).

---

Not a directory, but also worth knowing about: [OpenRouterProvider.examples.ts](./OpenRouterProvider.examples.ts) - runnable snippets showing `OpenRouterProvider` usage (LOU-B6).
27 changes: 27 additions & 0 deletions examples/README.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* LOU-H11: verifies examples/README.md's `## [dirname](./dirname)`
* headings exactly match the actual directory listing under examples/ -
* no stale, no missing.
*/
import { describe, it, expect } from 'vitest';
import fs from 'node:fs';
import path from 'node:path';

const EXAMPLES_DIR = __dirname;

describe('examples/README.md gallery index', () => {
it('has exactly one heading per example directory, and no stale entries', () => {
const dirs = fs
.readdirSync(EXAMPLES_DIR, { withFileTypes: true })
.filter((entry) => entry.isDirectory())
.map((entry) => entry.name)
.sort();

const readme = fs.readFileSync(path.join(EXAMPLES_DIR, 'README.md'), 'utf8');
const headings = [...readme.matchAll(/^## \[([^\]]+)\]\(\.\/([^)]+)\)/gm)]
.map((m) => m[2])
.sort();

expect(headings).toEqual(dirs);
});
});
11 changes: 11 additions & 0 deletions examples/doc-qa/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# doc-qa

A question-answering agent scoped to a single fixed document (a short
refund policy, inlined in the prompt), built with `createAgent()`.

Runs against a free local mock provider by default - no API key needed.
Set `OLLAMA_BASE_URL` to run it against a local Ollama instead.

```bash
npx tsx examples/doc-qa/index.ts
```
43 changes: 43 additions & 0 deletions examples/doc-qa/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* doc-qa - a question-answering agent scoped to a fixed document, built
* with createAgent() (LOU-H1).
*
* Defaults to a free, local mock provider so it runs with zero setup and
* zero API cost. Set OLLAMA_BASE_URL to run it against a local Ollama
* instead.
*
* Run with: tsx examples/doc-qa/index.ts
*/
import { createAgent } from '../../src/createAgent';
import { createMockProvider } from '../../src/providers/mock';
import { resolveProvider } from '../../src/providers/resolveProvider';

const DOCUMENT = `
Loushy Refund Policy: Refunds are available within 30 days of purchase.
Digital goods are non-refundable once downloaded. Contact support with
your order number to request a refund.
`;

const provider = process.env.OLLAMA_BASE_URL
? resolveProvider('ollama/llama3')
: createMockProvider({
responses: ['According to the policy, refunds are available within 30 days of purchase.'],
});

const agent = createAgent({
name: 'doc-qa',
prompt:
`You answer questions using ONLY the following document. If the answer isn't in the ` +
`document, say so.\n\nDocument:\n${DOCUMENT}`,
provider,
});

async function main() {
const result = await agent.send('How long do I have to request a refund?');
console.log(result.text);
}

main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
9 changes: 9 additions & 0 deletions examples/doc-qa/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "doc-qa-example",
"private": true,
"version": "0.0.0",
"description": "Document-scoped question-answering agent built with @loushy/build-ai-agent's createAgent()",
"scripts": {
"start": "tsx index.ts"
}
}
11 changes: 11 additions & 0 deletions examples/research-assistant/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# research-assistant

An agent with the built-in `http` tool wired up so it can fetch sources
while researching a question, built with `createAgent()`.

Runs against a free local mock provider by default - no API key needed.
Set `ANTHROPIC_API_KEY` to run it against real Anthropic instead.

```bash
npx tsx examples/research-assistant/index.ts
```
40 changes: 40 additions & 0 deletions examples/research-assistant/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* research-assistant - an agent with the built-in `http` tool wired up, so
* it can fetch pages/APIs while researching a question (LOU-H1's
* createAgent()).
*
* Defaults to a free, local mock provider so it runs with zero setup and
* zero API cost. Set ANTHROPIC_API_KEY to run it against real Anthropic
* instead.
*
* Run with: tsx examples/research-assistant/index.ts
*/
import { createAgent } from '../../src/createAgent';
import { createMockProvider } from '../../src/providers/mock';
import { resolveProvider } from '../../src/providers/resolveProvider';
import { httpTool } from '../../src/tools/built-in/http';

const provider = process.env.ANTHROPIC_API_KEY
? resolveProvider('anthropic/claude-3-5-sonnet-latest')
: createMockProvider({
responses: ['Based on my research, here is a concise summary of the topic.'],
});

const agent = createAgent({
name: 'research-assistant',
prompt:
'You are a diligent research assistant. Use the http tool to fetch sources when needed, ' +
'and always summarize findings concisely with citations.',
provider,
tools: { http: httpTool },
});

async function main() {
const result = await agent.send('Summarize the latest news about renewable energy.');
console.log(result.text);
}

main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
9 changes: 9 additions & 0 deletions examples/research-assistant/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "research-assistant-example",
"private": true,
"version": "0.0.0",
"description": "Research agent with the http tool wired up, built with @loushy/build-ai-agent's createAgent()",
"scripts": {
"start": "tsx index.ts"
}
}
11 changes: 11 additions & 0 deletions examples/slack-notifier/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# slack-notifier

Turns a raw event description into a short, Slack-ready notification
message, built with `createAgent()`.

Runs against a free local mock provider by default - no API key needed.
Set `OPENAI_API_KEY` to run it against real OpenAI instead.

```bash
npx tsx examples/slack-notifier/index.ts
```
36 changes: 36 additions & 0 deletions examples/slack-notifier/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* slack-notifier - an agent that drafts a Slack-ready notification message
* from a raw event description, built with createAgent() (LOU-H1).
*
* Defaults to a free, local mock provider so it runs with zero setup and
* zero API cost. Set OPENAI_API_KEY to run it against real OpenAI instead.
*
* Run with: tsx examples/slack-notifier/index.ts
*/
import { createAgent } from '../../src/createAgent';
import { createMockProvider } from '../../src/providers/mock';
import { resolveProvider } from '../../src/providers/resolveProvider';

const provider = process.env.OPENAI_API_KEY
? resolveProvider('openai/gpt-4o-mini')
: createMockProvider({
responses: [':rotating_light: Deploy to production failed - build #482. cc @on-call'],
});

const agent = createAgent({
name: 'slack-notifier',
prompt:
'You turn a raw event description into a short, Slack-ready notification message, using ' +
'Slack emoji shorthand (e.g. :rotating_light:) where it fits.',
provider,
});

async function main() {
const result = await agent.send('Deploy to production failed on build #482.');
console.log(result.text);
}

main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
9 changes: 9 additions & 0 deletions examples/slack-notifier/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "slack-notifier-example",
"private": true,
"version": "0.0.0",
"description": "Drafts a Slack-ready notification from a raw event, built with @loushy/build-ai-agent's createAgent()",
"scripts": {
"start": "tsx index.ts"
}
}
12 changes: 12 additions & 0 deletions examples/support-bot/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# support-bot

A minimal customer-support agent built with `createAgent()`. Empathetic,
concise, and always asks for an order number when a customer reports a
problem.

Runs against a free local mock provider by default - no API key needed.
Set `OPENAI_API_KEY` to run it against real OpenAI instead.

```bash
npx tsx examples/support-bot/index.ts
```
38 changes: 38 additions & 0 deletions examples/support-bot/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* support-bot - a minimal customer-support agent built with createAgent()
* (LOU-H1).
*
* Defaults to a free, local mock provider so it runs with zero setup and
* zero API cost. Set OPENAI_API_KEY to run it against real OpenAI instead.
*
* Run with: tsx examples/support-bot/index.ts
*/
import { createAgent } from '../../src/createAgent';
import { createMockProvider } from '../../src/providers/mock';
import { resolveProvider } from '../../src/providers/resolveProvider';

const provider = process.env.OPENAI_API_KEY
? resolveProvider('openai/gpt-4o-mini')
: createMockProvider({
responses: [
"I'm sorry to hear that! Could you tell me your order number so I can look into it?",
],
});

const agent = createAgent({
name: 'support-bot',
prompt:
'You are a friendly customer support agent. Be concise, empathetic, and always ask for ' +
'an order number when a customer reports a problem with an order.',
provider,
});

async function main() {
const result = await agent.send('Hi, my order arrived damaged.');
console.log(result.text);
}

main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
9 changes: 9 additions & 0 deletions examples/support-bot/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "support-bot-example",
"private": true,
"version": "0.0.0",
"description": "Minimal customer-support agent built with @loushy/build-ai-agent's createAgent()",
"scripts": {
"start": "tsx index.ts"
}
}
11 changes: 11 additions & 0 deletions examples/workflow-router/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# workflow-router

An agent that classifies an incoming request into one of a fixed set of
categories (`billing`/`technical`/`sales`), built with `createAgent()`.

Runs against a free local mock provider by default - no API key needed.
Set `OPENAI_API_KEY` to run it against real OpenAI instead.

```bash
npx tsx examples/workflow-router/index.ts
```
34 changes: 34 additions & 0 deletions examples/workflow-router/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* workflow-router - an agent whose job is to classify an incoming request
* into one of a fixed set of routes/categories (LOU-H1's createAgent()).
*
* Defaults to a free, local mock provider so it runs with zero setup and
* zero API cost. Set OPENAI_API_KEY to run it against real OpenAI instead.
*
* Run with: tsx examples/workflow-router/index.ts
*/
import { createAgent } from '../../src/createAgent';
import { createMockProvider } from '../../src/providers/mock';
import { resolveProvider } from '../../src/providers/resolveProvider';

const provider = process.env.OPENAI_API_KEY
? resolveProvider('openai/gpt-4o-mini')
: createMockProvider({ responses: ['billing'] });

const agent = createAgent({
name: 'workflow-router',
prompt:
'You are a request router. Given a customer message, reply with exactly one word: ' +
"'billing', 'technical', or 'sales' - whichever category best matches the message.",
provider,
});

async function main() {
const result = await agent.send('I was charged twice for my subscription this month.');
console.log(`Routed to: ${result.text}`);
}

main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
Loading
Loading