Skip to content

Commit

Permalink
feat: Add agents, an example on how to use the agents
Browse files Browse the repository at this point in the history
  • Loading branch information
pranavrajs committed Dec 8, 2024
1 parent f5dc916 commit d85bc1f
Show file tree
Hide file tree
Showing 9 changed files with 2,525 additions and 139 deletions.
16 changes: 16 additions & 0 deletions examples/weatherAgent/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "@neuron.js/examples-weatheragent",
"type": "module",
"scripts": {
"dev": "tsx src/index.ts"
},
"dependencies": {
"@neuron.js/core": "workspace:*",
"axios": "^1.7.9",
"dotenv": "^16.4.7"
},
"devDependencies": {
"@types/node": "^22.10.1",
"tsx": "^4.19.2"
}
}
58 changes: 58 additions & 0 deletions examples/weatherAgent/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { Agent, Tool } from '@neuron.js/core';
import 'dotenv/config'

const weatherTool = new Tool(
'weather_gov_query',
'Fetches real-time weather data from an weather.gov.',
{
properties: {
latitude: {
type: 'string',
description: 'latitude of the location where weather data is required',
required: true
},
longitude: {
type: 'string',
description: 'latitude of the location where weather data is required',
required: true
},
locationName: {
type: 'string',
description: 'name of the location where weather data is required',
required: true
}
},
},
async (inputs, secrets) => {
try {
const response = await fetch(
`https://api.weather.gov/points/${inputs.latitude},${inputs.longitude}`
);
const data = await response.json();
const forecastResponse = await fetch(data.properties.forecast);
const forecastData = await forecastResponse.json();
return `Weather forecast in ${inputs.locationName} is ${forecastData.properties.periods[0].detailedForecast}`;
} catch (error) {
console.error('Error:', error);
throw error;
}
}
)


const WeatherAgent = new Agent(
'WeatherAgent',
{
persona: 'You are a cheerful and approachable virtual assistant dedicated to delivering accurate, concise, and engaging weather updates. Your tone is warm, lively, and always focused on making weather information easy to understand and fun to receive.',
goal: 'Provide the current weather for a specified location as soon as the city or location details are provided. Your response should be both informative and conversational, ensuring clarity and usefulness for the user.',
secrets: {
OPEN_WEATHER_API_KEY: process.env.OPEN_WEATHER_API_KEY || '',
OPENAI_API_KEY: process.env.OPENAI_API_KEY || ''
}
}
)

WeatherAgent.registerTool(weatherTool)

const result = await WeatherAgent.execute("I'm travelling to Tahoe, what is the weather there?")
console.log("RESULT:", result);
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
"format": "prettier --write \"**/*.{ts,tsx,md}\""
},
"devDependencies": {
"@parcel/packager-ts": "2.13.2",
"@parcel/transformer-typescript-types": "2.13.2",
"@types/node": "^22.10.1",
"prettier": "^3.2.5",
"turbo": "^2.3.3",
"typescript": "5.5.4"
Expand Down
8 changes: 7 additions & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
{
"name": "@neuron.js/core",
"version": "0.0.0",
"main": "index.js",
"type": "module",
"private": "true",
"source": "src/index.ts",
"main": "dist/main.mjs",
"types": "dist/types.d.ts",
"scripts": {
"build": "parcel build --no-cache",
"test": "vitest --no-watch --no-cache"
},
"devDependencies": {
"parcel": "^2.13.2",
"vitest": "^2.1.8"
},
"dependencies": {
Expand Down
86 changes: 51 additions & 35 deletions packages/core/src/agent/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import { Tool } from "../tool";
import { AgentConfig, AgentResponse, LLMResult, Message, Provider, ToolConfig } from "../types";

export class Agent {
public name: string;
public prompt: string;
public messages: Message[];
public maxIterations: number;
private name: string;
private prompt: string;
private messages: Message[];
private maxIterations: number;
private tools: Tool[];
private llm: any;
private logger: any;
Expand All @@ -23,7 +23,7 @@ export class Agent {
this.maxIterations = config.maxIterations || 10;
this.secrets = config.secrets;
this.logger = config.logger || console;
this.logger.info(this.prompt);
this.logger.debug(this.prompt);

this.llm = new LLM({ provider: this.provider, apiKey: config.secrets.OPENAI_API_KEY, logger: this.logger });
}
Expand All @@ -40,11 +40,16 @@ export class Agent {
this.pushToMessages({ role: "system", content: "Provide a final answer" });
}

result = await this.llm.call(this.messages, this.functions());
try {
result = await this.llm.call(this.messages, this.functions());

await this.handleLlmResult(result);
await this.handleLlmResult(result);

if (result.content?.stop) break;
} catch (error) {
this.pushToMessages({ role: "assistant", content: `There was an error ${error.message}` });
}

if (result.content?.stop) break;
iterationCount++;
}

Expand Down Expand Up @@ -85,19 +90,22 @@ export class Agent {
return `
Persona: ${config.persona}
Objective: ${config.goal}
Guidelines:
- Work diligently until the stated objective is achieved.
- Utilize only the provided tools for solving the task. Do not make up names of the functions
- Set 'stop: true' when the objective is complete.
- If you have enough information to provide the details to the user, prepare a final result collecting all the information you have.
Output Structure:
If you find a function, that can be used, directly call the function.
When providing the final answer:
{
'thoughtProcess': 'Describe the reasoning and steps that will lead to the final result.',
'output': 'The complete answer in text form.',
'stop': true
}
1. Make sure that you break the task into logical steps and execute methodically until the objective is achieved. Do not attempt to solve before breaking it into steps.
2. Provide the breakdown steps as thought process in the first step.
3. Use only the provided tools, avoiding unnecessary or improvised function calls. If a tool can assist in solving the task, do not invoke it immediately rather provide reasoning first and then invoke the tool.
4. Include thoughtProcess during intermediate steps, but omit it from the final answer.
5. Mark the completion of the task by setting 'stop': true.
6. Ensure outputs are structured in the specified JSON format:
- thoughtProcess: A concise explanation of reasoning and next steps (only for intermediate responses).
- output: The complete, user-friendly answer (final response only).
- stop: Boolean indicator of task status (false for intermediate, true for final).
Make sure that the format is followed properly:
While processing: {thoughtProcess: '<reasoning>', stop: false}
Upon completion: {output: '<final result>', stop: true}
`;
}

Expand All @@ -108,13 +116,17 @@ export class Agent {
}

private functions(): Array<{
name: string;
description: string;
parameters: {
type: string;
properties: Record<string, { type: string; description: string }>;
required: string[];
};
type: string,
function:{
name: string;
description: string;
parameters: {
type: string;
properties: Record<string, { type: string; description: string }>;
required: string[];
};
}

}> {
return this.tools.map(tool => {
const properties: Record<string, { type: string; description: string }> = {};
Expand All @@ -131,19 +143,22 @@ export class Agent {
.map(([name]) => name);

return {
name: tool.name,
description: tool.description,
parameters: {
type: "object",
properties,
required
type: 'function',
function: {
name: tool.name,
description: tool.description,
parameters: {
type: "object",
properties,
required
}
}
};
});
}

private pushToMessages(message: Message): void {
this.logger.info(`Message: ${JSON.stringify(message)}`);
this.logger.debug(`Message: ${JSON.stringify(message)}`);
this.messages.push(message);
}

Expand All @@ -156,9 +171,10 @@ export class Agent {
return { output: "Invalid tool_name, please try again", stop: false };
}

this.logger.info(
this.logger.debug(
`tool_call: ${toolCall.function.name}, ${toolCall.function.arguments}`,
);
this.pushToMessages({ role: "assistant", content: `Used the tool ${toolCall.function.name}` });

const functionArgs = JSON.parse(toolCall.function.arguments);

Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export * from './agent';
export * from './tool';
export * from './llm';
export * from './errors';
export * from './types';
4 changes: 2 additions & 2 deletions packages/core/src/tool/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export class Tool {
properties: Record<string, FunctionInput>,
secrets?: string[],
},
private implementation?: (input: Record<string, unknown>, secrets: Record<string, unknown>) => void
private implementation?: (input: Record<string, any>, secrets: Record<string, any>) => void
) {
this.validateConfig();
}
Expand All @@ -20,7 +20,7 @@ export class Tool {
this.implementation = implementation;
}

execute(input: Record<string, unknown>, providedSecrets: Record<string, string> = {}): any {
execute(input: Record<string, any>, providedSecrets: Record<string, string> = {}): any {
this.validateSecrets(providedSecrets);
this.validateInput(input);

Expand Down
8 changes: 3 additions & 5 deletions packages/core/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,10 @@ export interface AgentConfig {
messages?: Message[];
maxIterations?: number;
persona?: string;
provider: Provider;
provider?: Provider;
goal?: string;
secrets: {
OPENAI_API_KEY: string;
};
logger: Console
secrets: Record<string, string>;
logger?: Console
}

export interface Message {
Expand Down
Loading

0 comments on commit d85bc1f

Please sign in to comment.