better cli interface

This commit is contained in:
dexhorthy
2025-05-31 11:22:02 -07:00
parent aa90a59f05
commit 7bc62442a1
5 changed files with 67 additions and 44 deletions

View File

@@ -14,26 +14,14 @@ class DoneForNow {
"#)
}
client<llm> Qwen3 {
provider "openai-generic"
options {
base_url env.BASETEN_BASE_URL
api_key env.BASETEN_API_KEY
}
}
function DetermineNextStep(
thread: string
) -> HumanTools | CalculatorTools {
client Qwen3
// client "openai/gpt-4o"
client "openai/gpt-4o-mini"
prompt #"
{{ _.role("system") }}
/nothink
You are a helpful assistant that can help with tasks.
{{ _.role("user") }}

View File

@@ -10,6 +10,7 @@
"dependencies": {
"@boundaryml/baml": "^0.88.0",
"@humanlayer/sdk": "^0.7.8",
"chalk": "^5.4.1",
"express": "^5.1.0",
"tsx": "^4.15.0",
"typescript": "^5.0.0"
@@ -1317,17 +1318,12 @@
}
},
"node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dev": true,
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz",
"integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
},
"engines": {
"node": ">=10"
"node": "^12.17.0 || ^14.13 || >=16.0.0"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
@@ -1756,6 +1752,23 @@
"concat-map": "0.0.1"
}
},
"node_modules/eslint/node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/eslint/node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",

View File

@@ -9,6 +9,7 @@
"dependencies": {
"@boundaryml/baml": "^0.88.0",
"@humanlayer/sdk": "^0.7.8",
"chalk": "^5.4.1",
"express": "^5.1.0",
"tsx": "^4.15.0",
"typescript": "^5.0.0"

View File

@@ -82,7 +82,6 @@ export async function handleNextStep(nextStep: CalculatorTool, thread: Thread):
}
export async function agentLoop(thread: Thread): Promise<Thread> {
while (true) {
const nextStep = await b.DetermineNextStep(thread.serializeForLLM());
console.log("nextStep", nextStep);

View File

@@ -1,20 +1,10 @@
// cli.ts lets you invoke the agent loop from the command line
import { humanlayer } from "humanlayer";
import { agentLoop, Thread, Event } from "../src/agent";
export async function cli() {
// Get command line arguments, skipping the first two (node and script name)
const args = process.argv.slice(2);
if (args.length === 0) {
console.error("Error: Please provide a message as a command line argument");
process.exit(1);
}
// Join all arguments into a single message
const message = args.join(" ");
import { agentLoop, Thread, Event, handleNextStep } from "../src/agent";
import chalk from "chalk";
export async function cliOuterLoop(message: string) {
// Create a new thread with the user's message as the initial event
const thread = new Thread([{ type: "user_input", data: message }]);
@@ -22,24 +12,49 @@ export async function cli() {
let newThread = await agentLoop(thread);
let lastEvent = newThread.events.slice(-1)[0];
while (lastEvent.data.intent !== "done_for_now") {
// loop until ctrl+c
// optional, you could exit on done_for_now and print the final result
// while (lastEvent.data.intent !== "done_for_now") {
while (true) {
const responseEvent = await askHuman(lastEvent);
thread.events.push(responseEvent);
newThread = await agentLoop(thread);
lastEvent = newThread.events.slice(-1)[0];
}
}
// print the final result
// optional - you could loop here too
console.log(lastEvent.data.message);
process.exit(0);
export async function cli() {
// Get command line arguments, skipping the first two (node and script name)
const args = process.argv.slice(2);
const message = args.length === 0 ? "hello!" : args.join(" ");
await cliOuterLoop(message);
}
async function askHuman(lastEvent: Event): Promise<Event> {
if (process.env.HUMANLAYER_API_KEY) {
if (process.env.HUMANLAYER_API_KEY && process.env.HUMANLAYER_EMAIL) {
return await askHumanEmail(lastEvent);
} else {
return await askHumanCLI(lastEvent.data.message);
switch (lastEvent.data.intent) {
case "divide":
const result = await askHumanCLI(`agent wants to run ${chalk.green(JSON.stringify(lastEvent.data))}\nPress Enter to approve, or type feedback to cancel:`);
if (result.data.approved) {
const thread = new Thread([lastEvent]);
const result = await handleNextStep(lastEvent.data, thread);
return result.events[result.events.length - 1];
} else {
return {
type: "tool_response",
data: `user denied operation ${lastEvent.data.intent} with feedback: ${result.data.comment}`
};
}
case "request_more_information":
case "done_for_now":
return await askHumanCLI(lastEvent.data.message);
default:
throw new Error(`unknown tool in outer loop: ${lastEvent.data.intent}`)
}
}
}
@@ -51,7 +66,14 @@ async function askHumanCLI(message: string): Promise<Event> {
return new Promise((resolve) => {
readline.question(`${message}\n> `, (answer: string) => {
resolve({ type: "human_response", data: answer });
readline.close();
// If the answer is empty (just pressed enter), treat it as approval
if (answer.trim() === '') {
resolve({ type: "human_response", data: { approved: true } });
} else {
// Any non-empty response is treated as rejection with feedback
resolve({ type: "human_response", data: { approved: false, comment: answer } });
}
});
});
}