Skip to content

Commit

Permalink
GH-56 WIP Ai History Persistance Service
Browse files Browse the repository at this point in the history
Co-authored-by: Olaf Lessenich <[email protected]>
  • Loading branch information
ndoschek and xai committed Jul 30, 2024
1 parent 328d26a commit 7b76804
Show file tree
Hide file tree
Showing 8 changed files with 169 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,6 @@ export interface CommunicationRecordingService {
recordRequest(requestEntry: CommunicationHistoryEntry): void;
recordResponse(responseEntry: CommunicationHistoryEntry): void;
getHistory(agentId: string): CommunicationHistory;
setHistory(agentId: string, history: CommunicationHistory): void;
getRecordedAgents(): string[];
}
2 changes: 1 addition & 1 deletion packages/ai-core/src/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
export * from './agent';
export * from './communication-recording-service';
export * from './communication-recording-types';
export * from './language-model';
export * from './language-model-delegate';
export * from './prompt-service';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// *****************************************************************************
// Copyright (C) 2024 EclipseSource GmbH.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************

import { CommunicationRecordingService } from '@theia/ai-core';
import { FrontendApplication, FrontendApplicationContribution } from '@theia/core/lib/browser';
import { inject, injectable } from '@theia/core/shared/inversify';
import { AiHistoryPersistenceService } from '../common/history-persistence';

@injectable()
export class AIHistoryFrontendContribution implements FrontendApplicationContribution {

@inject(AiHistoryPersistenceService)
protected persistenceService: AiHistoryPersistenceService;
@inject(CommunicationRecordingService)
protected recordingService: CommunicationRecordingService;

async onStart(app: FrontendApplication): Promise<void> {
this.persistenceService.loadHistory();
}

onStop(app: FrontendApplication): void {
this.recordingService.getRecordedAgents().forEach(agentId => {
const history = this.recordingService.getHistory(agentId);
this.persistenceService.saveHistory(agentId, history);
});
}

}
11 changes: 11 additions & 0 deletions packages/ai-history/src/browser/ai-history-frontend-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,21 @@
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import { CommunicationRecordingService } from '@theia/ai-core';
import { FrontendApplicationContribution, WebSocketConnectionProvider } from '@theia/core/lib/browser';
import { ContainerModule } from '@theia/core/shared/inversify';
import { DefaultCommunicationRecordingService } from '../common/communication-recording-service';
import { AiHistoryPersistenceService, aiHistoryPersistenceServicePath } from '../common/history-persistence';
import { AIHistoryFrontendContribution } from './ai-history-frontend-contribution';

export default new ContainerModule(bind => {
bind(FrontendApplicationContribution).to(AIHistoryFrontendContribution).inSingletonScope();

bind(DefaultCommunicationRecordingService).toSelf().inSingletonScope();
bind(CommunicationRecordingService).toService(DefaultCommunicationRecordingService);

bind(AiHistoryPersistenceService).toDynamicValue(ctx => {
const connection = ctx.container.get(WebSocketConnectionProvider);
return connection.createProxy<AiHistoryPersistenceService>(aiHistoryPersistenceServicePath);
}).inSingletonScope();

});
10 changes: 10 additions & 0 deletions packages/ai-history/src/common/communication-recording-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,26 @@ export class DefaultCommunicationRecordingService implements CommunicationRecord

protected history: Map<string, CommunicationHistory> = new Map();

getRecordedAgents(): string[] {
return Object.keys(this.history);
}

getHistory(agentId: string): CommunicationHistory {
return this.history.get(agentId) || [];
}

setHistory(agentId: string, history: CommunicationHistory): void {
this.history.set(agentId, history);
}

recordRequest(requestEntry: CommunicationHistoryEntry): void {
console.log('Recording request:', requestEntry.request);
if (this.history.has(requestEntry.agentId)) {
this.history.get(requestEntry.agentId)?.push(requestEntry);
} else {
this.history.set(requestEntry.agentId, [requestEntry]);
}
// this.persistenceService.saveHistory(requestEntry.agentId, this.history.get(requestEntry.agentId)!);
}

recordResponse(responseEntry: CommunicationHistoryEntry): void {
Expand All @@ -46,6 +55,7 @@ export class DefaultCommunicationRecordingService implements CommunicationRecord
matchingEntry.response = responseEntry.response;
matchingEntry.responseTime = responseEntry.timestamp - matchingEntry.timestamp;
}
// this.persistenceService.saveHistory(responseEntry.agentId, this.history.get(responseEntry.agentId)!);
}
}
}
25 changes: 25 additions & 0 deletions packages/ai-history/src/common/history-persistence.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// *****************************************************************************
// Copyright (C) 2024 EclipseSource GmbH.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************

import { CommunicationHistory } from '@theia/ai-core';

export const aiHistoryPersistenceServicePath = '/services/aiHistoryPersistenceService';

export const AiHistoryPersistenceService = Symbol('AiHistoryPersistenceService');
export interface AiHistoryPersistenceService {
saveHistory(agentId: string, history: CommunicationHistory): Promise<void>;
loadHistory(): Promise<void>;
}
21 changes: 19 additions & 2 deletions packages/ai-history/src/node/ai-history-backend-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,26 @@
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import { CommunicationRecordingService } from '@theia/ai-core';
import { ConnectionHandler, RpcConnectionHandler } from '@theia/core';
import { ContainerModule } from '@theia/core/shared/inversify';
import { DefaultCommunicationRecordingService } from '../common';
import { AiHistoryPersistenceService, aiHistoryPersistenceServicePath } from '../common/history-persistence';
import { FileCommunicationPersistenceService } from './communication-persistence-service';

export default new ContainerModule(bind => {
// bind(DefaultCommunicationRecordingService).toSelf().inSingletonScope();
// bind(CommunicationRecordingService).toService(DefaultCommunicationRecordingService);
bind(DefaultCommunicationRecordingService).toSelf().inSingletonScope();
bind(CommunicationRecordingService).toService(DefaultCommunicationRecordingService);

bind(FileCommunicationPersistenceService).toSelf().inSingletonScope();
bind(AiHistoryPersistenceService).to(FileCommunicationPersistenceService);
bind(ConnectionHandler)
.toDynamicValue(
ctx =>
new RpcConnectionHandler(aiHistoryPersistenceServicePath, () =>
ctx.container.get(AiHistoryPersistenceService)
)
)
.inSingletonScope();

});
60 changes: 60 additions & 0 deletions packages/ai-history/src/node/communication-persistence-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// *****************************************************************************
// Copyright (C) 2024 EclipseSource GmbH.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import { CommunicationHistory, CommunicationRecordingService } from '@theia/ai-core';
import { URI } from '@theia/core';
import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
import { inject, injectable } from '@theia/core/shared/inversify';
import { readdirSync, readFileSync, writeFileSync } from 'fs';
import { AiHistoryPersistenceService } from '../common/history-persistence';

@injectable()
export class FileCommunicationPersistenceService implements AiHistoryPersistenceService {

@inject(EnvVariablesServer)
protected envServer: EnvVariablesServer;
@inject(CommunicationRecordingService)
protected recordingService: CommunicationRecordingService;

async saveHistory(agentId: string, history: CommunicationHistory): Promise<void> {
const historyDir = await this.getHistoryDirectoryPath();
const fileName = `${historyDir}/${agentId}.json`;
writeFileSync(fileName, JSON.stringify(history, undefined, 2));
console.log(`Saving communication history for agent ${agentId} to ${fileName}`);
}

private async getHistoryDirectoryPath(): Promise<string> {
const configDir = new URI(await this.envServer.getConfigDirUri());
const historyDir = `${configDir.path.fsPath()}/agent-communication`;
return historyDir;
}

async loadHistory(): Promise<void> {
const historyDir = await this.getHistoryDirectoryPath();
const fileNames = readdirSync(historyDir);
for (const fileName of fileNames) {
const agentId = fileName.replace('.json', '');
const filePath = `${historyDir}/${fileName}`;
try {
const historyJson = readFileSync(filePath, 'utf-8');
const communicationHistory = JSON.parse(historyJson);
console.log(`Loaded communication history from ${agentId} from ${filePath}`);
this.recordingService.setHistory(agentId, communicationHistory);
} catch (error) {
console.log(`Could not load communication history for agent ${agentId}. Returning empty history.`);
}
}
}
}

0 comments on commit 7b76804

Please sign in to comment.