Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(WIP) Data sources #796

Draft
wants to merge 10 commits into
base: dev
Choose a base branch
from
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