Skip to content

Commit

Permalink
Testing around community creation
Browse files Browse the repository at this point in the history
  • Loading branch information
NolanTrem committed Nov 29, 2024
1 parent 19eca5c commit 2a4c8e6
Show file tree
Hide file tree
Showing 9 changed files with 224 additions and 127 deletions.
60 changes: 60 additions & 0 deletions js/sdk/__tests__/GraphsIntegrationSuperUser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ describe("r2rClient V3 Graphs Integration Tests", () => {
let entity1Id: string;
let entity2Id: string;
let relationshipId: string;
let communityId: string;

beforeAll(async () => {
client = new r2rClient(baseUrl);
Expand Down Expand Up @@ -250,6 +251,65 @@ describe("r2rClient V3 Graphs Integration Tests", () => {
expect(response.results.predicate).toBe("falls in love with");
});

test("Create a new community", async () => {
const response = await client.graphs.createCommunity({
collectionId: collectionId,
name: "Raskolnikov and Dunia Community",
summary:
"Raskolnikov and Dunia are siblings, the children of Pulcheria Alexandrovna",
findings: [
"Raskolnikov and Dunia are siblings",
"They are the children of Pulcheria Alexandrovna",
"Their family comes from a modest background",
"Dunia works as a governess to support the family",
"Raskolnikov is a former university student",
"Both siblings are intelligent and well-educated",
"They maintain a close relationship despite living apart",
"Their mother Pulcheria writes letters to keep them connected",
],
rating: 10,
ratingExplanation:
"Raskolnikov and Dunia are central to the story and have a complex relationship",
});

communityId = response.results.id;

expect(response.results).toBeDefined();
expect(response.results.name).toBe("Raskolnikov and Dunia Community");
expect(response.results.summary).toBe(
"Raskolnikov and Dunia are siblings, the children of Pulcheria Alexandrovna",
);
expect(response.results.findings).toContain(
"Raskolnikov and Dunia are siblings",
);
expect(response.results.findings).toContain(
"They are the children of Pulcheria Alexandrovna",
);
expect(response.results.findings).toContain(
"Their family comes from a modest background",
);
expect(response.results.findings).toContain(
"Dunia works as a governess to support the family",
);
expect(response.results.findings).toContain(
"Raskolnikov is a former university student",
);
expect(response.results.findings).toContain(
"Both siblings are intelligent and well-educated",
);
expect(response.results.findings).toContain(
"They maintain a close relationship despite living apart",
);
expect(response.results.findings).toContain(
"Their mother Pulcheria writes letters to keep them connected",
);
expect(response.results.rating).toBe(10);
//TODO: Why is this failing?
// expect(response.results.ratingExplanation).toBe(
// "Raskolnikov and Dunia are central to the story and have a complex relationship",
// );
});

test("Update the entity", async () => {
const response = await client.graphs.updateEntity({
collectionId: collectionId,
Expand Down
14 changes: 12 additions & 2 deletions js/sdk/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,19 @@ export interface CollectionResponse {
document_count: number;
}

//TODO: Sync this with the finished API response model
// Community types
export interface CommunityResponse {}
export interface CommunityResponse {
id: string;
name: string;
summary: string;
findings: string[];
communityId?: string;
graphId?: string;
collectionId?: string;
rating?: number;
ratingExplanation?: string;
descriptionEmbedding?: string;
}

// Conversation types
export interface ConversationResponse {
Expand Down
56 changes: 53 additions & 3 deletions js/sdk/src/v3/clients/graphs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,59 @@ export class GraphsClient {
);
}

// TODO: Create community
/**
* Creates a new community in the graph.
*
* While communities are typically built automatically via the /graphs/{id}/communities/build endpoint,
* this endpoint allows you to manually create your own communities.
*
* This can be useful when you want to:
* - Define custom groupings of entities based on domain knowledge
* - Add communities that weren't detected by the automatic process
* - Create hierarchical organization structures
* - Tag groups of entities with specific metadata
*
* The created communities will be integrated with any existing automatically detected communities
* in the graph's community structure.
*
* @param collectionId The collection ID corresponding to the graph
* @param name Name of the community
* @param summary Summary of the community
* @param findings Findings or insights about the community
* @param rating Rating of the community
* @param ratingExplanation Explanation of the community rating
* @param attributes Additional attributes to associate with the community
* @returns WrappedCommunityResponse
*/
@feature("graphs.createCommunity")
async createCommunity(options: {
collectionId: string;
name: string;
summary: string;
findings?: string[];
rating?: number;
ratingExplanation?: string;
attributes?: Record<string, any>;
}): Promise<WrappedCommunityResponse> {
const data = {
name: options.name,
...(options.summary && { summary: options.summary }),
...(options.findings && { findings: options.findings }),
...(options.rating && { rating: options.rating }),
...(options.ratingExplanation && {
rating_explanation: options.ratingExplanation,
}),
...(options.attributes && { attributes: options.attributes }),
};

return this.client.makeRequest(
"POST",
`graphs/${options.collectionId}/communities`,
{
data,
},
);
}

/**
* List all communities in a graph.
Expand Down Expand Up @@ -420,7 +472,6 @@ export class GraphsClient {
findings?: string[];
rating?: number;
ratingExplanation?: string;
level?: number;
attributes?: Record<string, any>;
}): Promise<WrappedCommunityResponse> {
const data = {
Expand All @@ -431,7 +482,6 @@ export class GraphsClient {
...(options.ratingExplanation && {
rating_explanation: options.ratingExplanation,
}),
...(options.level && { level: options.level }),
...(options.attributes && { attributes: options.attributes }),
};
return this.client.makeRequest(
Expand Down
4 changes: 2 additions & 2 deletions py/core/base/providers/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -648,7 +648,7 @@ async def delete(self, *args: Any, **kwargs: Any) -> None:

class CommunityHandler(Handler):
@abstractmethod
async def create(self, *args: Any, **kwargs: Any) -> None:
async def create(self, *args: Any, **kwargs: Any) -> Community:
"""Create communities in storage."""
pass

Expand All @@ -658,7 +658,7 @@ async def get(self, *args: Any, **kwargs: Any) -> list[Community]:
pass

@abstractmethod
async def update(self, *args: Any, **kwargs: Any) -> None:
async def update(self, *args: Any, **kwargs: Any) -> Community:
"""Update communities in storage."""
pass

Expand Down
31 changes: 15 additions & 16 deletions py/core/main/api/v3/graph_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -1158,7 +1158,7 @@ async def delete_relationship(
},
)
@self.base_endpoint
async def create_communities(
async def create_community(
collection_id: UUID = Path(
...,
description="The collection ID corresponding to the graph to create the community in.",
Expand All @@ -1168,12 +1168,6 @@ async def create_communities(
findings: Optional[list[str]] = Body(
default=[], description="Findings about the community"
),
level: Optional[int] = Body(
default=0,
ge=0,
le=100,
description="The level of the community",
),
rating: Optional[float] = Body(
default=5, ge=1, le=10, description="Rating between 1 and 10"
),
Expand All @@ -1184,13 +1178,14 @@ async def create_communities(
default=None, description="Attributes for the community"
),
auth_user=Depends(self.providers.auth.auth_wrapper),
):
) -> WrappedCommunityResponse:
"""
Creates a new community in the graph.
While communities are typically built automatically via the /graphs/{id}/communities/build endpoint,
this endpoint allows you to manually create your own communities. This can be useful when you want to:
this endpoint allows you to manually create your own communities.
This can be useful when you want to:
- Define custom groupings of entities based on domain knowledge
- Add communities that weren't detected by the automatic process
- Create hierarchical organization structures
Expand All @@ -1199,16 +1194,22 @@ async def create_communities(
The created communities will be integrated with any existing automatically detected communities
in the graph's community structure.
"""
return await self.services["kg"].create_community_v3(
graph_id=collection_id,
if (
not auth_user.is_superuser
and collection_id not in auth_user.graph_ids
):
raise R2RException(
"The currently authenticated user does not have access to the specified graph.",
403,
)

return await self.services["kg"].create_community(
parent_id=collection_id,
name=name,
summary=summary,
findings=findings,
rating=rating,
rating_explanation=rating_explanation,
level=level,
attributes=attributes,
auth_user=auth_user,
)

@self.router.get(
Expand Down Expand Up @@ -1486,7 +1487,6 @@ async def update_community(
findings: Optional[list[str]] = Body(None),
rating: Optional[float] = Body(None),
rating_explanation: Optional[str] = Body(None),
level: Optional[int] = Body(None),
attributes: Optional[dict] = Body(None),
auth_user=Depends(self.providers.auth.auth_wrapper),
) -> WrappedCommunityResponse:
Expand All @@ -1506,7 +1506,6 @@ async def update_community(
findings=findings,
rating=rating,
rating_explanation=rating_explanation,
level=level,
attributes=attributes,
auth_user=auth_user,
)
Expand Down
22 changes: 12 additions & 10 deletions py/core/main/orchestration/simple/kg_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,28 +53,30 @@ async def extract_triples(input_data):
offset = 0
while True:
# Fetch current batch
batch = (await service.providers.database.collections_handler.documents_in_collection(
collection_id=collection_id,
offset=offset,
limit=batch_size
))["results"]

batch = (
await service.providers.database.collections_handler.documents_in_collection(
collection_id=collection_id,
offset=offset,
limit=batch_size,
)
)["results"]

# If no documents returned, we've reached the end
if not batch:
break

# Add current batch to results
documents.extend(batch)

# Update offset for next batch
offset += batch_size

# Optional: If batch is smaller than batch_size, we've reached the end
if len(batch) < batch_size:
break

# documents = service.providers.database.collections_handler.documents_in_collection(input_data.get("collection_id"), offset=0, limit=1000)
print('extracting for documents = ', documents)
print("extracting for documents = ", documents)
document_ids = [document.id for document in documents]

logger.info(
Expand Down
Loading

0 comments on commit 2a4c8e6

Please sign in to comment.