mirror of
https://github.com/humanlayer/12-factor-agents.git
synced 2025-08-20 18:59:53 +03:00
better cli interface
This commit is contained in:
@@ -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") }}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 } });
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user