Skip to content
This repository has been archived by the owner on Oct 30, 2024. It is now read-only.

Commit

Permalink
Merge pull request #107 from hdresearch/mp/ex
Browse files Browse the repository at this point in the history
examples: update all, amend config prompting
  • Loading branch information
matildepark authored Jul 11, 2024
2 parents 7113943 + 78b8be1 commit 4f42621
Show file tree
Hide file tree
Showing 9 changed files with 71 additions and 147 deletions.
38 changes: 17 additions & 21 deletions examples/login.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import yargs from "yargs/yargs";

import { AgentBrowser } from "../src/agentBrowser";
import { Browser } from "../src/browser";
import { Agent } from "../src/agent/agent";
import { Inventory } from "../src/inventory";
import { completionApiBuilder } from "../src/agent";
import { Logger } from "../src/utils";

import { ModelResponseSchema, ObjectiveComplete } from "../src/types";
import { nolitarc } from "../src/utils/config";

const parser = yargs(process.argv.slice(2)).options({
headless: { type: "boolean", default: true },
});

// this imports your config from running `npx nolita auth`
// if you haven't run `npx nolita auth` yet, you can set ANTHROPIC_API_KEY in your environment
const { agentProvider, agentApiKey, agentModel } = nolitarc();

async function main() {
const argv = await parser.parse();

Expand All @@ -21,14 +23,14 @@ async function main() {
const maxIterations = 10;

const providerOptions = {
apiKey: process.env.ANTHROPIC_API_KEY!,
provider: "anthropic",
apiKey: agentApiKey || process.env.ANTHROPIC_API_KEY!,
provider: agentProvider || "anthropic",
};

// We can create a chat api using the completionApiBuilder.
// These can be swapped out for other providers like OpenAI
const chatApi = completionApiBuilder(providerOptions, {
model: "claude-3-5-sonnet-20240620",
model: agentModel || "claude-3-5-sonnet-20240620",
});

if (!chatApi) {
Expand All @@ -53,24 +55,18 @@ async function main() {
]);

const agent = new Agent({ modelApi: chatApi });
const agentBrowser = new AgentBrowser({
agent,
browser: await Browser.launch(argv.headless, agent),
const browser = await Browser.launch(argv.headless, agent, logger, {
inventory,
logger,
});

const answer = await agentBrowser.browse(
{
startUrl: startUrl,
objective: [objective],
maxIterations: maxIterations,
},
ModelResponseSchema(ObjectiveComplete)
);

console.log("Answer:", answer?.result);
await agentBrowser.close();
const page = await browser.newPage();
await page.goto(startUrl);
const answer = await page.browse(objective, {
maxTurns: maxIterations
});
// @ts-expect-error - we are not using the full response schema
console.log("Answer:", answer?.objectiveComplete?.result);
await browser.close();
}

main();
54 changes: 21 additions & 33 deletions examples/shopping.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import yargs from "yargs/yargs";
import { z } from "zod";

import { AgentBrowser } from "../src/agentBrowser";
import { Browser } from "../src/browser";
import { Agent } from "../src/agent/agent";
import { Inventory } from "../src/inventory";
import { completionApiBuilder } from "../src/agent";
import { Logger } from "../src/utils";
import { nolitarc } from "../src/utils/config";

import { ModelResponseSchema, ObjectiveComplete } from "../src/types";
// these are imported from `npx nolita auth`
// if you haven't set config, you can set the defaults for this example in your environment:
// OPENAI_API_KEY, HDR_API_KEY
const { hdrApiKey, agentProvider, agentModel, agentApiKey } = nolitarc();

const parser = yargs(process.argv.slice(2)).options({
headless: { type: "boolean", default: true },
Expand All @@ -24,10 +27,10 @@ async function main() {
const maxIterations = 15;

const providerOptions = {
apiKey: process.env.OPENAI_API_KEY!,
provider: "openai",
apiKey: agentApiKey || process.env.OPENAI_API_KEY!,
provider: agentProvider || "openai",
};
const chatApi = completionApiBuilder(providerOptions, { model: "gpt-4" });
const chatApi = completionApiBuilder(providerOptions, { model: agentModel || "gpt-4" });

if (!chatApi) {
throw new Error(
Expand All @@ -36,40 +39,25 @@ async function main() {
}
const logger = new Logger(["info"], (msg) => console.log(msg));

// You can pass in collective memory configuration to the agent browser
const collectiveMemoryConfig = {
apiKey: process.env.HDR_API_KEY!,
endpoint: process.env.HDR_ENDPOINT!,
};
const agent = new Agent({ modelApi: chatApi });
const agentBrowser = new AgentBrowser({
agent: new Agent({ modelApi: chatApi }),
browser: await Browser.launch(argv.headless, agent),
logger,
const browser = await Browser.launch(argv.headless, agent, logger, {
apiKey: hdrApiKey || process.env.HDR_API_KEY!,
});
const page = await browser.newPage();
await page.goto(startUrl);
const answer = await page.browse(objective, {
maxTurns: maxIterations,
schema: z.object({
orderTotals: z.array(z.number()).describe("The order total in number format"),
}),
inventory: new Inventory([
{ value: "emma.lopez@gmail.com", name: "email", type: "string" },
{ value: "Password.123", name: "Password", type: "string" },
]),
collectiveMemoryConfig,
});

const orderTotalAnswer = ObjectiveComplete.extend({
orderTotals: z.array(
z.number().describe("The order total in number format")
),
});

const answer = await agentBrowser.browse(
{
startUrl: startUrl,
objective: [objective],
maxIterations: maxIterations,
},
ModelResponseSchema(orderTotalAnswer)
);

console.log("\x1b[32m Answer:", JSON.stringify(answer?.result));
await agentBrowser.close();
// @ts-expect-error - we are not using the full response schema
console.log("\x1b[32m Answer:", JSON.stringify(answer?.objectiveComplete?.orderTotals));
await browser.close();
}

main();
61 changes: 20 additions & 41 deletions examples/wikipedia.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import yargs from "yargs/yargs";
import { z } from "zod";

import { AgentBrowser } from "../src/agentBrowser";
import { Browser } from "../src/browser";
import { Agent } from "../src/agent/agent";
import { completionApiBuilder } from "../src/agent";
import { Logger } from "../src/utils";

import { ModelResponseSchema, ObjectiveComplete } from "../src/types";
import { nolitarc } from "../src/utils/config";

const parser = yargs(process.argv.slice(2)).options({
headless: { type: "boolean", default: true },
Expand All @@ -16,23 +14,19 @@ const parser = yargs(process.argv.slice(2)).options({
maxIterations: { type: "number", default: 10 },
});

// these are imported from `npx nolita auth`
// if you haven't set config, you can set the defaults for this example in your environment:
// OPENAI_API_KEY
const { agentApiKey, agentProvider, agentModel } = nolitarc();

async function main() {
const argv = await parser.parse();
console.log(argv);

if (!argv.startUrl) {
throw new Error("url is not provided");
}

if (!argv.objective) {
throw new Error("objective is not provided");
}

const providerOptions = {
apiKey: process.env.OPENAI_API_KEY!,
provider: "openai",
apiKey: agentApiKey || process.env.OPENAI_API_KEY!,
provider: agentProvider || "openai",
};
const chatApi = completionApiBuilder(providerOptions, { model: "gpt-4" });
const chatApi = completionApiBuilder(providerOptions, { model: agentModel || "gpt-4" });

if (!chatApi) {
throw new Error(
Expand All @@ -42,35 +36,20 @@ async function main() {
const logger = new Logger(["info"], (msg) => console.log(msg));
const agent = new Agent({ modelApi: chatApi });

const agentBrowser = new AgentBrowser({
agent: agent,
browser: await Browser.launch(argv.headless, agent),
logger,
});

// Here we are defining a custom return schema
// Custom schemas extrend `ObjectiveComplete` by adding additional fields
// In addition to returning structured data, we find that using these fields
// improves the performance of the model by constraining the conditions
// under which the model can halt
const wikipediaAnswer = ObjectiveComplete.extend({
numberOfEditors: z
.number()
.int()
.describe("The number of editors in int format"),
const browser = await Browser.launch(argv.headless, agent, logger);
const page = await browser.newPage();
await page.goto(argv.startUrl || "https://google.com");
const answer = await page.browse(argv.objective || "How many accounts are on Wikipedia?", {
maxTurns: argv.maxIterations || 10,
schema: z.object({
numberOfEditors: z.number().int().describe("The number of accounts in int format"),
}),
});
const answer = await agentBrowser.browse(
{
startUrl: argv.startUrl,
objective: [argv.objective],
maxIterations: argv.maxIterations,
},
ModelResponseSchema(wikipediaAnswer)
);

console.log("Answer:", answer?.result);
// @ts-expect-error - we are not using the full response schema
console.log("Answer:", answer?.objectiveComplete?.numberOfEditors);

await agentBrowser.close();
await browser.close();
}

main();
1 change: 1 addition & 0 deletions jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const config: Config = {
verbose: true,
preset: "ts-jest",
testEnvironment: "node",
testTimeout: 20000
};

export default config;
4 changes: 3 additions & 1 deletion src/agent/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,9 @@ export class Agent {
memories: Memory[],
config?: { inventory?: Inventory; systemPrompt?: string }
): ChatRequestMessage[] {
const userPrompt = `Here are examples of a request:
const userPrompt = `
${config?.inventory ? `Use the following information to achieve your objective as needed: ${config?.inventory.toString()}` : ""}
Here are examples of a request:
${stringifyObjects(memories)}
remember to return a result only in the form of an ActionStep.
Expand Down
9 changes: 1 addition & 8 deletions src/agent/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export function handleConfigMessages(
): ChatRequestMessage[] {
const messages: ChatRequestMessage[] = [];

const { systemPrompt, inventory } = config;
const { systemPrompt } = config;

if (systemPrompt) {
console.log("systemPrompt", systemPrompt);
Expand All @@ -87,13 +87,6 @@ export function handleConfigMessages(
});
}

if (inventory) {
messages.push({
role: "user",
content: `Use the following information to achieve your objective as needed: ${inventory.toString()}`,
});
}

return messages;
}

Expand Down
5 changes: 4 additions & 1 deletion src/browser/page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -638,7 +638,10 @@ export class Page {
) {
let currentTurn = 0;
while (currentTurn < opts.maxTurns) {
const step = await this.step(objective, opts?.schema, opts);
const step = await this.step(objective, opts?.schema, {
...opts,
inventory: opts.inventory ?? this.inventory,
});

if (step?.objectiveComplete) {
return step;
Expand Down
16 changes: 0 additions & 16 deletions tests/agent/agent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ import {
ObjectiveComplete,
} from "../../src/types/browser/actionStep.types";

import { Inventory } from "../../src/inventory";

import { z } from "zod";
import { ObjectiveState } from "../../src/types/browser";
import { completionApiBuilder } from "../../src/agent/config";
Expand All @@ -32,20 +30,6 @@ describe("Agent -- configs", () => {
agent = new Agent({ modelApi: chatApi! });
});

test("that configs are handled", async () => {
const prompt = await agent.prompt(
stateActionPair1.objectiveState,
[stateActionPair1],
{
inventory: new Inventory([
{ value: "test", name: "test", type: "string" },
]),
}
);
expect(prompt[0].role).toBe("user");
expect(prompt[0].content).toContain("Use the following information");
});

test("that empty configs are handled", async () => {
const prompt = await agent.prompt(
stateActionPair1.objectiveState,
Expand Down
Loading

0 comments on commit 4f42621

Please sign in to comment.