Skip to content
Draft
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {
} from './conversation-manager';
import { PingManager } from './ping-manager';
import { AgentRegistry } from './render-registry';
import { DataSourceManager } from './data-source-manager';

//

Expand All @@ -51,7 +52,7 @@ export class ActiveAgentObject extends AgentObject {
telnyxManager: TelnyxManager;
pingManager: PingManager;
generativeAgentsMap = new WeakMap<ConversationObject, GenerativeAgentObject>();

dataSourceManager: DataSourceManager;
//

constructor(
Expand Down Expand Up @@ -91,6 +92,9 @@ export class ActiveAgentObject extends AgentObject {
codecs: appContextValue.useCodecs(),
});
this.telnyxManager = new TelnyxManager();

this.dataSourceManager = new DataSourceManager();

this.pingManager = new PingManager({
userId: this.id,
supabase: this.useSupabase(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { AgentObjectData } from '../types';
import {
AgentObjectData,
DataSourceConfig,
} from '../types';

export class AgentObject extends EventTarget {
id: string;
Expand All @@ -13,6 +16,8 @@ export class AgentObject extends EventTarget {
features: string[];
address: string;
stripeConnectAccountId: string;
dataSources?: Record<string, DataSourceConfig>;


constructor(config: AgentObjectData) {
super();
Expand All @@ -31,6 +36,7 @@ export class AgentObject extends EventTarget {
features,
address,
stripeConnectAccountId,
dataSources,
}: AgentObjectData) {
this.id = id;
this.ownerId = ownerId;
Expand All @@ -44,5 +50,6 @@ export class AgentObject extends EventTarget {
this.features = features;
this.address = address;
this.stripeConnectAccountId = stripeConnectAccountId;
this.dataSources = dataSources;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { z } from "zod";
import { APIDataSourceProps } from "../types/react-agents";
import { BaseDataSource } from '../types/react-agents';

export class APIDataSourceManager implements BaseDataSource {
id: string;
type: 'api';
name: string;
description: string;
endpoint: string;
headers?: Record<string, string>;
params?: Record<string, string>;
requiredArgs?: string[];
examples?: string[];
schema: z.ZodSchema;

constructor(config: APIDataSourceProps) {
this.id = config.id;
this.type = 'api';
this.name = config.name || config.id;
this.description = config.description || '';
this.endpoint = config.endpoint;
this.headers = config.headers;
this.params = config.params;
this.requiredArgs = config.requiredArgs;
this.examples = config.examples;
this.schema = config.schema;
}

async pull(args: object = {}): Promise<any> {
try {
// Validate args against schema
const validatedArgs = this.schema.parse(args);

const url = new URL(this.endpoint);
const params = { ...this.params, ...validatedArgs };

Object.entries(params || {}).forEach(([key, value]) => {
url.searchParams.append(key, String(value));
});

const response = await fetch(url.toString(), {
headers: this.headers,
});

if (!response.ok) {
throw new Error(`API request failed: ${response.statusText}`);
}

return response.json();
} catch (error) {
if (error instanceof z.ZodError) {
throw new Error(`Invalid arguments: ${error.message}`);
}
console.error(`Error fetching from API ${this.id}:`, error);
throw error;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { BaseDataSource } from '../types/react-agents';

export class DataSourceManager {
private dataSources: Map<string, BaseDataSource>;

constructor() {
this.dataSources = new Map();
}

addDataSource(dataSource: BaseDataSource): void {
this.dataSources.set(dataSource.id, dataSource);
}

removeDataSource(id: string): boolean {
return this.dataSources.delete(id);
}

getDataSource(id: string): BaseDataSource | undefined {
return this.dataSources.get(id);
}

getAllDataSources(): BaseDataSource[] {
return Array.from(this.dataSources.values());
}

pullFromDataSource(id: string, args: object): Promise<any> {
const source = this.getDataSource(id);
if (!source) {
throw new Error(`Data source ${id} not found`);
}
return source.pull(args);
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import {
BaseDataSource,
DataSourceType,
APIDataSourceProps,
} from '../../types/react-agents';
import React, { useEffect } from 'react';
import { useAgent } from '../../hooks';
import { APIDataSourceManager } from '../../classes/api-data-source-manager';


export const APIDataSource: React.FC<APIDataSourceProps> = (props) => {
const agent = useAgent();

useEffect(() => {
const dataSource = new APIDataSourceManager(props);
agent.dataSourceManager.addDataSource(dataSource);
return () => {
agent.dataSourceManager.removeDataSource(dataSource.id);
};
}, [props.endpoint, JSON.stringify(props.headers), JSON.stringify(props.params)]);

return null;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React from 'react';
import { Action } from '../core/action';
import { Prompt } from '../core/prompt';
import { useAgent } from '../../hooks';
import { z } from 'zod';
import dedent from 'dedent';
import type { PendingActionEvent } from '../../types';
import { AutoTask } from './auto-task';
import { ConversationProvider } from '../core/conversation';
import { RAGMemory } from './rag-memory';

export const DataSourceLearner: React.FC = () => {
const agent = useAgent();

return (
<>
<Prompt>
{dedent`\
# Data Source Learning System

You can learn from available data sources and store the knowledge in your memory.
Use the queryAndLearn action when you need to:
- Gather new information about a topic
- Verify or update your existing knowledge
- Get real-time data for user queries

Available data sources:
${agent.dataSourceManager.getAllDataSources().map(source => dedent`
- ${source.name} (ID: ${source.id})
${source.description}
REQUIRED: You must include these arguments in your query:
${source.requiredArgs?.map(arg => ` - '${arg}': (string) REQUIRED`)
.join('\n') || ' - No required arguments'}`).join('\n')}

NOTE: Queries will fail if required arguments are not provided!
`}
</Prompt>

{/* add the RAG Memory Component */}
<ConversationProvider>
<RAGMemory chunkMessages={10} refreshMemoryEveryNMessages={1} />
</ConversationProvider>

{/* Add AutoTask for Self-learning from the data sources */}
<AutoTask hint="You are provided with data sources to help you learn and obtain knowledge. Use the data sources to learn and use the knowledge to answer the user's question." />
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import type {
import {
featureRenderers,
} from '../../util/agent-features-renderer';
import {
dataSourceRenderers,
} from '../../util/data-source-renderer';

// defaults

Expand All @@ -18,6 +21,8 @@ type ConfigAgentComponentProps = {
*/
export const ConfigAgentComponents = (props: ConfigAgentComponentProps) => {
const features = props.config?.features ?? {};
const dataSources = props.config?.dataSources ?? {};

return (
<>
{Object.keys(features).map((key) => {
Expand All @@ -27,6 +32,15 @@ export const ConfigAgentComponents = (props: ConfigAgentComponentProps) => {
<FeatureRenderer {...value} key={key} />
);
})}

{Object.entries(dataSources).map(([key, config]) => {
if (!config.type) {
throw new Error(`Data source ${key} is missing required 'type' field`);
}
const DataSourceRenderer = dataSourceRenderers[config.type];
if (!DataSourceRenderer) return null;
return <DataSourceRenderer key={key} {...(config as any)} />;
})}
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ const DefaultPrompts = () => {
<DefaultHeaderPrompt />
<ConversationEnvironmentPrompt />
<ActionsPrompt />
<DataSourcesPrompt />
<StorePrompt />
<ConversationMessagesPrompt />
<InstructionsPrompt />
Expand All @@ -91,6 +92,29 @@ const DefaultHeaderPrompt = () => {
</Prompt>
);
};
const DataSourcesPrompt = () => {
const agent = useAgent();
const dataSources = agent.dataSourceManager.getAllDataSources();

if (dataSources.length === 0) return null;

return (
<Prompt>
{dedent`
# Data Sources
You have access to the following data sources that you can query:
${dataSources.map(source => dedent`
- ${source.name} (ID: ${source.id})
Description: ${source.description}
Type: ${source.type}
${source.type === 'api' ? `Required args: ${(source as any).requiredArgs}` : ''}
${source.type === 'api' ? `Examples: ${(source as any).examples}` : ''}
`).join('\n')}
`}
</Prompt>
);
};

const ConversationEnvironmentPrompt = () => {
return (
<>
Expand Down Expand Up @@ -320,14 +344,6 @@ const InstructionsPrompt = () => {
# Instructions
Respond with the next action taken by your character: ${agent.name}
The method/args of your response must match one of the allowed actions.

Before choosing an action, decide if you should respond at all:
- Return null (no action) if:
* Message is clearly meant for others (unless you have crucial information)
* Your input wouldn't add value to the conversation
* The conversation is naturally concluding
* You've already responded frequently in the last few messages (2-3 messages max)
* Multiple other agents are already actively participating
`}
</Prompt>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,6 @@ export * from './components/plugins/video-perception';
export * from './loops/chat-loop';
export * from './loops/action-loop';
export * from './components/plugins/auto-task';
export * from './components/plugins/data-source-learner';

export * from './hooks';
Loading