Skip to content

Commit

Permalink
Merge pull request #4199 from alkem-io/develop
Browse files Browse the repository at this point in the history
Release: Context embeddings
  • Loading branch information
valentinyanakiev authored Jul 1, 2024
2 parents a3e03a6 + 0cc89d3 commit 1250490
Show file tree
Hide file tree
Showing 8 changed files with 60 additions and 179 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "alkemio-server",
"version": "0.82.8",
"version": "0.82.9",
"description": "Alkemio server, responsible for managing the shared Alkemio platform",
"author": "Alkemio Foundation",
"private": false,
Expand Down
6 changes: 1 addition & 5 deletions src/domain/community/ai-persona/ai.persona.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import { AiServerAdapter } from '@services/adapters/ai-server-adapter/ai.server.
import { AiServerAdapterAskQuestionInput } from '@services/adapters/ai-server-adapter/dto/ai.server.adapter.dto.ask.question';
import { AiPersonaDataAccessMode } from '@common/enums/ai.persona.data.access.mode';
import { AiPersonaInteractionMode } from '@common/enums/ai.persona.interaction.mode';
import { SpaceIngestionPurpose } from '@services/infrastructure/event-bus/commands';
import { IMessageAnswerToQuestion } from '@domain/communication/message.answer.to.question/message.answer.to.question.interface';

@Injectable()
Expand Down Expand Up @@ -45,10 +44,7 @@ export class AiPersonaService {
aiPersonaData.aiPersonaServiceID
);

this.aiServerAdapter.ensurePersonaIsUsable(
personaService.id,
SpaceIngestionPurpose.KNOWLEDGE
);
this.aiServerAdapter.refreshBodyOfKnowlege(personaService.id);
aiPersona.aiPersonaServiceID = personaService.id;
} else if (aiPersonaData.aiPersonaService) {
const aiPersonaService =
Expand Down
2 changes: 2 additions & 0 deletions src/domain/community/community/community.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { VirtualContributorModule } from '../virtual-contributor/virtual.contrib
import { LicenseEngineModule } from '@core/license-engine/license.engine.module';
import { ContributorModule } from '../contributor/contributor.module';
import { PlatformInvitationModule } from '@platform/invitation/platform.invitation.module';
import { AiServerAdapterModule } from '@services/adapters/ai-server-adapter/ai.server.adapter.module';

@Module({
imports: [
Expand Down Expand Up @@ -59,6 +60,7 @@ import { PlatformInvitationModule } from '@platform/invitation/platform.invitati
TypeOrmModule.forFeature([Community]),
TrustRegistryAdapterModule,
ContributionReporterModule,
AiServerAdapterModule, // TODO REMOVE
],
providers: [
CommunityService,
Expand Down
8 changes: 8 additions & 0 deletions src/domain/community/community/community.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ import { IContributor } from '../contributor/contributor.interface';
import { PlatformInvitationService } from '@platform/invitation/platform.invitation.service';
import { IPlatformInvitation } from '@platform/invitation';
import { CreatePlatformInvitationInput } from '@platform/invitation/dto/platform.invitation.dto.create';
import { AiServerAdapter } from '@services/adapters/ai-server-adapter/ai.server.adapter';

@Injectable()
export class CommunityService {
Expand All @@ -84,6 +85,7 @@ export class CommunityService {
private formService: FormService,
private communityPolicyService: CommunityPolicyService,
private storageAggregatorResolverService: StorageAggregatorResolverService,
private aiServerAdapter: AiServerAdapter, //TODO: remove this asap
@InjectRepository(Community)
private communityRepository: Repository<Community>,
@Inject(WINSTON_MODULE_NEST_PROVIDER) private readonly logger: LoggerService
Expand Down Expand Up @@ -719,6 +721,12 @@ export class CommunityService {
agentInfo,
triggerNewMemberEvents
);
// TO: THIS BREAKS THE DECOUPLING
const space =
await this.communityResolverService.getSpaceForCommunityOrFail(
community.id
);
this.aiServerAdapter.ensureContextIsLoaded(space.id);
return virtualContributor;
}

Expand Down
22 changes: 4 additions & 18 deletions src/services/adapters/ai-server-adapter/ai.server.adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { AiServerService } from '@services/ai-server/ai-server/ai.server.service
import { CreateAiPersonaServiceInput } from '@services/ai-server/ai-persona-service/dto';
import { IAiPersonaService } from '@services/ai-server/ai-persona-service';
import { AgentInfo } from '@core/authentication.agent.info/agent.info';
import { SpaceIngestionPurpose } from '@services/infrastructure/event-bus/commands';
import { IMessageAnswerToQuestion } from '@domain/communication/message.answer.to.question/message.answer.to.question.interface';
import { AiPersonaBodyOfKnowledgeType } from '@common/enums/ai.persona.body.of.knowledge.type';

Expand All @@ -17,25 +16,12 @@ export class AiServerAdapter {
private readonly logger: LoggerService
) {}

async ensureSpaceIsUsable(
spaceID: string,
purpose: SpaceIngestionPurpose
): Promise<void> {
return this.aiServer.ensureSpaceIsUsable(spaceID, purpose);
}

async ensurePersonaIsUsable(
personaServiceId: string,
purpose: SpaceIngestionPurpose
): Promise<boolean> {
return this.aiServer.ensurePersonaIsUsable(personaServiceId, purpose);
async refreshBodyOfKnowlege(personaServiceId: string): Promise<boolean> {
return this.aiServer.ensurePersonaIsUsable(personaServiceId);
}

async refreshBodyOfKnowlege(personaServiceId: string): Promise<boolean> {
return this.aiServer.ensurePersonaIsUsable(
personaServiceId,
SpaceIngestionPurpose.KNOWLEDGE
);
async ensureContextIsLoaded(spaceID: string): Promise<void> {
await this.aiServer.ensureContextIsIngested(spaceID);
}

async getPersonaServiceBodyOfKnowledgeType(
Expand Down
62 changes: 0 additions & 62 deletions src/services/ai-server/ai-server/ai.server.resolver.mutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import { CreateAiPersonaServiceInput } from '../ai-persona-service/dto/ai.person
import { IAiPersonaService } from '../ai-persona-service/ai.persona.service.interface';
import { ConfigurationTypes } from '@common/enums';
import { Space } from '@domain/space/space/space.entity';
import { SpaceIngestionPurpose } from '@services/infrastructure/event-bus/commands';
import { ChromaClient } from 'chromadb';
import { ConfigService } from '@nestjs/config';
import { InjectEntityManager } from '@nestjs/typeorm';
Expand Down Expand Up @@ -109,67 +108,6 @@ export class AiServerResolverMutations {

return { success: true };
}
@UseGuards(GraphqlGuard)
@Mutation(() => IMigrateEmbeddingsResponse, {
description: 'Copies collections nameID-... into UUID-...',
})
@Profiling.api
async copyCollections(
@CurrentUser() agentInfo: AgentInfo
): Promise<IMigrateEmbeddingsResponse> {
const platformAuthorization =
await this.platformAuthorizationService.getPlatformAuthorizationPolicy();
this.authorizationService.grantAccessOrFail(
agentInfo,
platformAuthorization,
AuthorizationPrivilege.PLATFORM_ADMIN,
'User not authenticated to migrate embeddings'
);

const vectorDb = this.config.get(ConfigurationTypes.PLATFORM).vector_db;

const chroma = new ChromaClient({
path: `http://${vectorDb.host}:${vectorDb.port}`,
});
// get all chroma collections
const collections = await chroma.listCollections();

for (const collection of collections) {
// extract collection identifier and purpose
const [, nameID, purpose] =
collection.name.match(/(.*)-(knowledge|context)/) || [];

// if the identifier is of UUID format, skip it
if (
/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/.test(
nameID
)
) {
continue;
}
// get the space by nameID
const space = await this.entityManager.findOne(Space, {
where: { nameID: nameID },
});

// if the space doesn't exit skip the colletion
if (!space) {
this.logger.warn(
`Space with nameID ${nameID} does't exist but ${collection.name} is still in Chroma`
);
continue;
}

// ask the AI server to ingest the space again;
// the ingest space service uses the UUID as collection identifier now
this.aiServerService.ensureSpaceIsUsable(
space.id,
purpose as SpaceIngestionPurpose
);
}

return { success: true };
}

@UseGuards(GraphqlGuard)
@Mutation(() => IAiServer, {
Expand Down
133 changes: 42 additions & 91 deletions src/services/ai-server/ai-server/ai.server.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,11 @@ import { FindOneOptions, FindOptionsRelations, Repository } from 'typeorm';
import { AiServer } from './ai.server.entity';
import { IAiServer } from './ai.server.interface';
import { IAuthorizationPolicy } from '@domain/common/authorization-policy';
import { ForbiddenException } from '@common/exceptions/forbidden.exception';
import { AuthorizationCredential } from '@common/enums/authorization.credential';
import { ICredentialDefinition } from '@domain/agent/credential/credential.definition.interface';
import {
AiPersonaService,
IAiPersonaService,
} from '@services/ai-server/ai-persona-service';
import { AiPersonaServiceService } from '../ai-persona-service/ai.persona.service.service';
import { AiServerRole } from '@common/enums/ai.server.role';
import { AiPersonaEngineAdapter } from '../ai-persona-engine-adapter/ai.persona.engine.adapter';
import { AiServerIngestAiPersonaServiceInput } from './dto/ai.server.dto.ingest.ai.persona.service';
import { AiPersonaEngineAdapterInputBase } from '../ai-persona-engine-adapter/dto/ai.persona.engine.adapter.dto.base';
Expand All @@ -27,49 +23,76 @@ import {
SpaceIngestionPurpose,
} from '@services/infrastructure/event-bus/commands';
import { EventBus } from '@nestjs/cqrs';
import { ConfigService } from '@nestjs/config';
import { ChromaClient } from 'chromadb';
import { ConfigurationTypes } from '@common/enums/configuration.type';

@Injectable()
export class AiServerService {
constructor(
// private userService: UserService,
// private agentService: AgentService,
private aiPersonaServiceService: AiPersonaServiceService,
private aiPersonaEngineAdapter: AiPersonaEngineAdapter,
private config: ConfigService,
@InjectRepository(AiServer)
private aiServerRepository: Repository<AiServer>,
@Inject(WINSTON_MODULE_NEST_PROVIDER)
private readonly logger: LoggerService,
private eventBus: EventBus
) {}

async ensurePersonaIsUsable(
personaServiceId: string,
purpose: SpaceIngestionPurpose
): Promise<boolean> {
async ensurePersonaIsUsable(personaServiceId: string): Promise<boolean> {
const aiPersonaService =
await this.aiPersonaServiceService.getAiPersonaServiceOrFail(
personaServiceId
);
await this.ensureSpaceIsUsable(aiPersonaService.bodyOfKnowledgeID, purpose);
await this.ensureSpaceBoNIsIngested(aiPersonaService.bodyOfKnowledgeID);
return true;
}

async ensureSpaceIsUsable(
spaceID: string,
purpose: SpaceIngestionPurpose
): Promise<void> {
this.eventBus.publish(new IngestSpace(spaceID, purpose));
public async ensureSpaceBoNIsIngested(spaceID: string): Promise<void> {
this.eventBus.publish(
new IngestSpace(spaceID, SpaceIngestionPurpose.KNOWLEDGE)
);
}

public async ensureContextIsIngested(spaceID: string): Promise<void> {
this.eventBus.publish(
new IngestSpace(spaceID, SpaceIngestionPurpose.CONTEXT)
);
}

async askQuestion(
public async askQuestion(
questionInput: AiPersonaServiceQuestionInput,
agentInfo: AgentInfo,
contextSapceNameID: string
contextID: string
) {
if (!(await this.isContextLoaded(contextID))) {
this.eventBus.publish(
new IngestSpace(contextID, SpaceIngestionPurpose.CONTEXT)
);
}
return this.aiPersonaServiceService.askQuestion(
questionInput,
agentInfo,
contextSapceNameID
contextID
);
}

private getContextCollectionID(contextID: string): string {
return `${contextID}-${SpaceIngestionPurpose.CONTEXT}`;
}

private async isContextLoaded(contextID: string): Promise<boolean> {
const { host, port } = this.config.get(
ConfigurationTypes.PLATFORM
).vector_db;
const chroma = new ChromaClient({ path: `http://${host}:${port}` });

const collections = await chroma.listCollections();
const collectionSearchedFor = this.getContextCollectionID(contextID);

return collections.some(entry =>
entry.name.includes(collectionSearchedFor)
);
}

Expand Down Expand Up @@ -171,43 +194,6 @@ export class AiServerService {
return authorization;
}

// public async assignAiServerRoleToUser(
// assignData: AssignAiServerRoleToUserInput
// ): Promise<IUser> {
// const agent = await this.userService.getAgent(assignData.userID);

// const credential = this.getCredentialForRole(assignData.role);

// // assign the credential
// await this.agentService.grantCredential({
// agentID: agent.id,
// ...credential,
// });

// return await this.userService.getUserWithAgent(assignData.userID);
// }

// public async removeAiServerRoleFromUser(
// removeData: RemoveAiServerRoleFromUserInput
// ): Promise<IUser> {
// const agent = await this.userService.getAgent(removeData.userID);

// // Validation logic
// if (removeData.role === AiServerRole.GLOBAL_ADMIN) {
// // Check not the last global admin
// await this.removeValidationSingleGlobalAdmin();
// }

// const credential = this.getCredentialForRole(removeData.role);

// await this.agentService.revokeCredential({
// agentID: agent.id,
// ...credential,
// });

// return await this.userService.getUserWithAgent(removeData.userID);
// }

public async ingestAiPersonaService(
ingestData: AiServerIngestAiPersonaServiceInput
): Promise<boolean> {
Expand All @@ -224,39 +210,4 @@ export class AiServerService {
);
return result;
}

// private async removeValidationSingleGlobalAdmin(): Promise<boolean> {
// // Check more than one
// const globalAdmins = await this.userService.usersWithCredentials({
// type: AuthorizationCredential.GLOBAL_ADMIN,
// });
// if (globalAdmins.length < 2)
// throw new ForbiddenException(
// `Not allowed to remove ${AuthorizationCredential.GLOBAL_ADMIN}: last AI Server global-admin`,
// LogContext.AUTH
// );

// return true;
// }

private getCredentialForRole(role: AiServerRole): ICredentialDefinition {
const result: ICredentialDefinition = {
type: '',
resourceID: '',
};
switch (role) {
case AiServerRole.GLOBAL_ADMIN:
result.type = AuthorizationCredential.GLOBAL_ADMIN;
break;
case AiServerRole.SUPPORT:
result.type = AuthorizationCredential.GLOBAL_SUPPORT;
break;
default:
throw new ForbiddenException(
`Role not supported: ${role}`,
LogContext.AI_SERVER
);
}
return result;
}
}

0 comments on commit 1250490

Please sign in to comment.