feat: enable feeeback and activity log

This commit is contained in:
Boris Arzentar 2025-05-27 20:56:52 +02:00
parent 6da557565a
commit fac930dc59
27 changed files with 969 additions and 1294 deletions

View file

@ -1,5 +1,6 @@
"use client";
import { v4 as uuid4 } from "uuid";
import { ChangeEvent, useEffect } from "react";
import { CTAButton, StatusIndicator } from "@/ui/elements";
@ -30,15 +31,16 @@ export default function CogneeAddWidget({ onData }: CogneeAddWidgetProps) {
.then((datasets) => {
const dataset = datasets?.[0];
if (dataset) {
getDatasetGraph(dataset)
.then((graph) => onData({
nodes: graph.nodes,
links: graph.edges,
}));
}
// For CrewAI we don't have a dataset.
// if (dataset) {
getDatasetGraph(dataset || { id: uuid4() })
.then((graph) => onData({
nodes: graph.nodes,
links: graph.edges,
}));
// }
});
}, [refreshDatasets]);
}, [onData, refreshDatasets]);
const handleAddFiles = (dataset: { id?: string, name?: string }, event: ChangeEvent<HTMLInputElement>) => {
event.stopPropagation();
@ -51,8 +53,6 @@ export default function CogneeAddWidget({ onData }: CogneeAddWidgetProps) {
return addData(dataset, files)
.then(() => {
console.log("Data added successfully.");
const onUpdate = (data: any) => {
onData({
nodes: data.payload.nodes,
@ -60,11 +60,12 @@ export default function CogneeAddWidget({ onData }: CogneeAddWidgetProps) {
});
};
return cognifyDataset(dataset, onUpdate)
.then((data) => console.log(data));
return cognifyDataset(dataset, onUpdate);
});
};
return null;
return (
<div className="flex flex-col gap-4 mb-4">
{datasets.length ? datasets.map((dataset) => (

View file

@ -1,4 +1,7 @@
import { useState } from "react";
import { fetch } from "@/utils";
import { v4 as uuid4 } from "uuid";
import { LoadingIndicator } from "@/ui/App";
import { CTAButton, Input } from "@/ui/elements";
interface CrewAIFormPayload extends HTMLFormElement {
@ -6,21 +9,98 @@ interface CrewAIFormPayload extends HTMLFormElement {
username2: HTMLInputElement;
}
export default function CrewAITrigger() {
interface CrewAITriggerProps {
onData: (data: any) => void;
onActivity: (activities: any) => void;
}
export default function CrewAITrigger({ onData, onActivity }: CrewAITriggerProps) {
const [isCrewAIRunning, setIsCrewAIRunning] = useState(false);
const handleRunCrewAI = (event: React.FormEvent<CrewAIFormPayload>) => {
fetch("/v1/crew-ai/run", {
event.preventDefault();
const formElements = event.currentTarget;
const crewAIConfig = {
username1: formElements.username1.value,
username2: formElements.username2.value,
};
const websocket = new WebSocket("ws://localhost:8000/api/v1/crewai/subscribe");
websocket.onopen = () => {
websocket.send(JSON.stringify({
"Authorization": `Bearer ${localStorage.getItem("access_token")}`,
}));
};
let isCrewAIDone = false;
onActivity([{ id: uuid4(), timestamp: Date.now(), activity: "Running CrewAI" }]);
websocket.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.status === "PipelineRunActivity") {
onActivity(data.payload);
return;
}
onData({
nodes: data.payload.nodes,
links: data.payload.edges,
});
const nodes_type_map: { [key: string]: number } = {};
for (let i = 0; i < data.payload.nodes.length; i++) {
const node = data.payload.nodes[i];
if (!nodes_type_map[node.type]) {
nodes_type_map[node.type] = 0;
}
nodes_type_map[node.type] += 1;
}
const activityMessage = Object.entries(nodes_type_map).reduce((message, [type, count]) => {
return `${message}\n | ${type}: ${count}`;
}, "Graph updated:");
onActivity([{
id: uuid4(),
timestamp: Date.now(),
activity: activityMessage,
}]);
if (data.status === "PipelineRunCompleted") {
isCrewAIDone = true;
websocket.close();
}
};
setIsCrewAIRunning(true);
return fetch("/v1/crewai/run", {
method: "POST",
body: new FormData(event.currentTarget),
body: JSON.stringify(crewAIConfig),
headers: {
"Content-Type": "application/json",
},
})
.then(response => response.json())
.then((data) => console.log(data));
.finally(() => {
websocket.close();
setIsCrewAIRunning(false);
onActivity([{ id: uuid4(), timestamp: Date.now(), activity: "CrewAI run done" }]);
});
};
return (
<form className="w-full flex flex-row gap-2 items-center" onSubmit={handleRunCrewAI}>
<Input type="text" placeholder="Github Username" required />
<Input type="text" placeholder="Github Username" required />
<CTAButton type="submit" className="whitespace-nowrap">Run CrewAI</CTAButton>
<Input name="username1" type="text" placeholder="Github Username" required defaultValue="hajdul88" />
<Input name="username2" type="text" placeholder="Github Username" required defaultValue="lxobr" />
<CTAButton type="submit" className="whitespace-nowrap">
Run CrewAI
{isCrewAIRunning && <LoadingIndicator />}
</CTAButton>
</form>
);
}

View file

@ -19,31 +19,38 @@ interface GraphControlsProps {
export interface GraphControlsAPI {
setSelectedNode: (node: NodeObject | null) => void;
getSelectedNode: () => NodeObject | null;
updateActivity: (activities: ActivityLog[]) => void;
}
type ActivityLog = {
id: string;
timestamp: number;
activity: string;
}[];
};
type NodeProperties = {
type NodeProperty = {
id: string;
name: string;
value: string;
}[];
};
const formatter = new Intl.DateTimeFormat("en-GB", { dateStyle: "short", timeStyle: "medium" });
export default function GraphControls({ isAddNodeFormOpen, onGraphShapeChange, onFitIntoView, ref }: GraphControlsProps) {
const [selectedNode, setSelectedNode] = useState<NodeObject | null>(null);
const [activityLog, setActivityLog] = useState<ActivityLog>([]);
const [nodeProperties, setNodeProperties] = useState<NodeProperties>([]);
const [newProperty, setNewProperty] = useState<NodeProperties[0]>({
const [activityLog, setActivityLog] = useState<ActivityLog[]>([]);
const [nodeProperties, setNodeProperties] = useState<NodeProperty[]>([]);
const [newProperty, setNewProperty] = useState<NodeProperty>({
id: uuid4(),
name: "",
value: "",
});
const handlePropertyChange = (property: NodeProperties[0], property_key: string, event: ChangeEvent<HTMLInputElement>) => {
const updateActivity = (newActivities: ActivityLog[]) => {
setActivityLog((activities) => [...activities, ...newActivities]);
};
const handlePropertyChange = (property: NodeProperty, property_key: string, event: ChangeEvent<HTMLInputElement>) => {
const value = event.target.value;
setNodeProperties(nodeProperties.map((nodeProperty) => (nodeProperty.id === property.id ? {...nodeProperty, [property_key]: value } : nodeProperty)));
@ -58,11 +65,11 @@ export default function GraphControls({ isAddNodeFormOpen, onGraphShapeChange, o
}
};
const handlePropertyDelete = (property: NodeProperties[0]) => {
const handlePropertyDelete = (property: NodeProperty) => {
setNodeProperties(nodeProperties.filter((nodeProperty) => nodeProperty.id !== property.id));
};
const handleNewPropertyChange = (property: NodeProperties[0], property_key: string, event: ChangeEvent<HTMLInputElement>) => {
const handleNewPropertyChange = (property: NodeProperty, property_key: string, event: ChangeEvent<HTMLInputElement>) => {
const value = event.target.value;
setNewProperty({...property, [property_key]: value });
@ -71,6 +78,7 @@ export default function GraphControls({ isAddNodeFormOpen, onGraphShapeChange, o
useImperativeHandle(ref, () => ({
setSelectedNode,
getSelectedNode: () => selectedNode,
updateActivity,
}));
const [selectedTab, setSelectedTab] = useState("nodeDetails");
@ -81,14 +89,14 @@ export default function GraphControls({ isAddNodeFormOpen, onGraphShapeChange, o
return (
<>
<div className="flex">
<button onClick={() => setSelectedTab("nodeDetails")} className={classNames("cursor-pointer pt-4 pb-4 align-center text-gray-300 border-b-2 w-30", { "border-b-indigo-600 text-white": selectedTab === "nodeDetails" })}>
<div className="flex w-full">
<button onClick={() => setSelectedTab("nodeDetails")} className={classNames("cursor-pointer pt-4 pb-4 align-center text-gray-300 border-b-2 w-30 flex-1/3", { "border-b-indigo-600 text-white": selectedTab === "nodeDetails" })}>
<span className="whitespace-nowrap">Node Details</span>
</button>
<button onClick={() => setSelectedTab("activityLog")} className={classNames("cursor-pointer pt-4 pb-4 align-center text-gray-300 border-b-2 w-30", { "border-b-indigo-600 text-white": selectedTab === "activityLog" })}>
<button onClick={() => setSelectedTab("activityLog")} className={classNames("cursor-pointer pt-4 pb-4 align-center text-gray-300 border-b-2 w-30 flex-1/3", { "border-b-indigo-600 text-white": selectedTab === "activityLog" })}>
<span className="whitespace-nowrap">Activity Log</span>
</button>
<button onClick={() => setSelectedTab("feedback")} className={classNames("cursor-pointer pt-4 pb-4 align-center text-gray-300 border-b-2 w-30", { "border-b-indigo-600 text-white": selectedTab === "feedback" })}>
<button onClick={() => setSelectedTab("feedback")} className={classNames("cursor-pointer pt-4 pb-4 align-center text-gray-300 border-b-2 w-30 flex-1/3", { "border-b-indigo-600 text-white": selectedTab === "feedback" })}>
<span className="whitespace-nowrap">Feedback</span>
</button>
</div>
@ -97,9 +105,9 @@ export default function GraphControls({ isAddNodeFormOpen, onGraphShapeChange, o
{selectedTab === "nodeDetails" && (
<>
<div className="w-full flex flex-row gap-2 items-center mb-4">
<label className="text-gray-300 whitespace-nowrap">Graph Shape:</label>
<Select onChange={handleGraphShapeControl}>
<option selected value="none">None</option>
<label className="text-gray-300 whitespace-nowrap flex-1/5">Graph Shape:</label>
<Select defaultValue="none" onChange={handleGraphShapeControl} className="flex-2/5">
<option value="none">None</option>
<option value="td">Top-down</option>
<option value="bu">Bottom-up</option>
<option value="lr">Left-right</option>
@ -107,9 +115,9 @@ export default function GraphControls({ isAddNodeFormOpen, onGraphShapeChange, o
<option value="radialin">Radial-in</option>
<option value="radialout">Radial-out</option>
</Select>
<NeutralButton onClick={onFitIntoView} className="flex-2/5 whitespace-nowrap">Fit Graph into View</NeutralButton>
</div>
<NeutralButton onClick={onFitIntoView} className="mb-4">Fit Graph into View</NeutralButton>
{isAddNodeFormOpen ? (
<form className="flex flex-col gap-4" onSubmit={() => {}}>
@ -138,21 +146,25 @@ export default function GraphControls({ isAddNodeFormOpen, onGraphShapeChange, o
) : (
selectedNode ? (
<div className="flex flex-col gap-4">
<div className="flex flex-col gap-2">
<div className="flex gap-2 items-center">
<div className="flex flex-col gap-2 overflow-y-auto max-h-96">
<div className="flex gap-2 items-top">
<span className="text-gray-300">ID:</span>
<span className="text-white">{selectedNode.id}</span>
</div>
<div className="flex gap-2 items-top">
<span className="text-gray-300">Label:</span>
<span className="text-white">{selectedNode.label}</span>
</div>
{Object.entries(selectedNode.properties).map(([key, value]) => (
<div key={key} className="flex gap-2 items-center">
<span className="text-gray-300">{key}:</span>
<span className="text-white">{typeof value === "object" ? JSON.stringify(value) : value as string}</span>
<div key={key} className="flex gap-2 items-top">
<span className="text-gray-300">{key.charAt(0).toUpperCase() + key.slice(1)}:</span>
<span className="text-white truncate">{typeof value === "object" ? JSON.stringify(value) : value as string}</span>
</div>
))}
</div>
<CTAButton type="button" onClick={() => {}}>Edit Node</CTAButton>
{/* <CTAButton type="button" onClick={() => {}}>Edit Node</CTAButton> */}
</div>
) : (
<span className="text-white">No node selected.</span>
@ -164,9 +176,9 @@ export default function GraphControls({ isAddNodeFormOpen, onGraphShapeChange, o
{selectedTab === "activityLog" && (
<div className="flex flex-col gap-2">
{activityLog.map((activity) => (
<div key={activity.id} className="flex gap-2 items-center">
<span className="text-gray-300">{activity.timestamp}</span>
<span className="text-white">{activity.activity}</span>
<div key={activity.id} className="flex gap-2 items-top">
<span className="text-gray-300 whitespace-nowrap">{formatter.format(activity.timestamp)}: </span>
<span className="text-white whitespace-normal">{activity.activity}</span>
</div>
))}
{!activityLog.length && <span className="text-white">No activity logged.</span>}

View file

@ -1,7 +1,7 @@
"use client";
import { forceCollide, forceManyBody } from "d3-force-3d";
import { useEffect, useRef, useState } from "react";
import { useCallback, useEffect, useRef, useState } from "react";
import ForceGraph, { ForceGraphMethods, LinkObject, NodeObject } from "react-force-graph-2d";
import { TextLogo } from "@/ui/App";
@ -35,24 +35,25 @@ export default function GraphView() {
const [data, updateData] = useState<GraphData | null>(null);
const onDataChange = (newData: NodesAndEdges) => {
if (data === null) {
updateData({
nodes: newData.nodes,
links: newData.links,
});
} else {
updateData({
nodes: [...data.nodes, ...newData.nodes],
links: [...data.links, ...newData.links],
});
const onDataChange = useCallback((newData: NodesAndEdges) => {
if (!newData.nodes.length && !newData.links.length) {
return;
}
};
updateData({
nodes: newData.nodes,
links: newData.links,
});
}, []);
const graphRef = useRef<ForceGraphMethods>();
const graphControls = useRef<GraphControlsAPI>(null);
const onActivityChange = (activities: any) => {
graphControls.current?.updateActivity(activities);
};
const handleNodeClick = (node: NodeObject) => {
graphControls.current?.setSelectedNode(node);
graphRef.current?.d3ReheatSimulation();
@ -63,29 +64,31 @@ export default function GraphView() {
const addNodeDistanceFromSourceNode = 15;
const handleBackgroundClick = (event: MouseEvent) => {
const graphBoundingBox = document.getElementById("graph-container")?.querySelector("canvas")?.getBoundingClientRect();
const x = event.clientX - graphBoundingBox!.x;
const y = event.clientY - graphBoundingBox!.y;
const graphClickCoords = graphRef.current!.screen2GraphCoords(x, y);
const selectedNode = graphControls.current?.getSelectedNode();
if (!selectedNode) {
return;
}
const distanceFromAddNode = Math.sqrt(
Math.pow(graphClickCoords.x - (selectedNode!.x! + addNodeDistanceFromSourceNode), 2)
+ Math.pow(graphClickCoords.y - (selectedNode!.y! + addNodeDistanceFromSourceNode), 2)
);
graphControls.current?.setSelectedNode(null);
if (distanceFromAddNode <= 10) {
enableAddNodeForm();
} else {
disableAddNodeForm();
graphControls.current?.setSelectedNode(null);
}
// const graphBoundingBox = document.getElementById("graph-container")?.querySelector("canvas")?.getBoundingClientRect();
// const x = event.clientX - graphBoundingBox!.x;
// const y = event.clientY - graphBoundingBox!.y;
// const graphClickCoords = graphRef.current!.screen2GraphCoords(x, y);
// const distanceFromAddNode = Math.sqrt(
// Math.pow(graphClickCoords.x - (selectedNode!.x! + addNodeDistanceFromSourceNode), 2)
// + Math.pow(graphClickCoords.y - (selectedNode!.y! + addNodeDistanceFromSourceNode), 2)
// );
// if (distanceFromAddNode <= 10) {
// enableAddNodeForm();
// } else {
// disableAddNodeForm();
// graphControls.current?.setSelectedNode(null);
// }
};
function renderNode(node: NodeObject, ctx: CanvasRenderingContext2D, globalScale: number) {
@ -93,23 +96,23 @@ export default function GraphView() {
ctx.save();
if (node.id === selectedNode?.id) {
ctx.fillStyle = "gray";
// if (node.id === selectedNode?.id) {
// ctx.fillStyle = "gray";
ctx.beginPath();
ctx.arc(node.x! + addNodeDistanceFromSourceNode, node.y! + addNodeDistanceFromSourceNode, 10, 0, 2 * Math.PI);
ctx.fill();
// ctx.beginPath();
// ctx.arc(node.x! + addNodeDistanceFromSourceNode, node.y! + addNodeDistanceFromSourceNode, 10, 0, 2 * Math.PI);
// ctx.fill();
ctx.beginPath();
ctx.moveTo(node.x! + addNodeDistanceFromSourceNode - 5, node.y! + addNodeDistanceFromSourceNode)
ctx.lineTo(node.x! + addNodeDistanceFromSourceNode - 5 + 10, node.y! + addNodeDistanceFromSourceNode);
ctx.stroke();
// ctx.beginPath();
// ctx.moveTo(node.x! + addNodeDistanceFromSourceNode - 5, node.y! + addNodeDistanceFromSourceNode)
// ctx.lineTo(node.x! + addNodeDistanceFromSourceNode - 5 + 10, node.y! + addNodeDistanceFromSourceNode);
// ctx.stroke();
ctx.beginPath();
ctx.moveTo(node.x! + addNodeDistanceFromSourceNode, node.y! + addNodeDistanceFromSourceNode - 5)
ctx.lineTo(node.x! + addNodeDistanceFromSourceNode, node.y! + addNodeDistanceFromSourceNode - 5 + 10);
ctx.stroke();
}
// ctx.beginPath();
// ctx.moveTo(node.x! + addNodeDistanceFromSourceNode, node.y! + addNodeDistanceFromSourceNode - 5)
// ctx.lineTo(node.x! + addNodeDistanceFromSourceNode, node.y! + addNodeDistanceFromSourceNode - 5 + 10);
// ctx.stroke();
// }
// ctx.beginPath();
// ctx.arc(node.x, node.y, nodeSize, 0, 2 * Math.PI);
@ -179,9 +182,7 @@ export default function GraphView() {
ctx.restore();
}
function handleDagError(loopNodeIds: (string | number)[]) {
console.log(loopNodeIds);
}
function handleDagError(loopNodeIds: (string | number)[]) {}
useEffect(() => {
// add collision force
@ -211,7 +212,7 @@ export default function GraphView() {
nodeRelSize={nodeSize}
nodeCanvasObject={renderNode}
nodeCanvasObjectMode={() => "after"}
nodeAutoColorBy="group"
nodeAutoColorBy="type"
linkLabel="label"
linkCanvasObject={renderLink}
@ -237,7 +238,7 @@ export default function GraphView() {
nodeRelSize={20}
nodeCanvasObject={renderNode}
nodeCanvasObjectMode={() => "after"}
nodeAutoColorBy="group"
nodeAutoColorBy="type"
linkLabel="label"
linkCanvasObject={renderLink}
@ -250,10 +251,10 @@ export default function GraphView() {
<div className="absolute top-2 left-2 bg-gray-500 pt-4 pr-4 pb-4 pl-4 rounded-md max-w-2xl">
<CogneeAddWidget onData={onDataChange} />
<CrewAITrigger />
<CrewAITrigger onData={onDataChange} onActivity={onActivityChange} />
</div>
<div className="absolute top-2 right-2 bg-gray-500 pt-4 pr-4 pb-4 pl-4 rounded-md">
<div className="absolute top-2 right-2 bg-gray-500 pt-4 pr-4 pb-4 pl-4 rounded-md w-110">
<GraphControls
ref={graphControls}
isAddNodeFormOpen={isAddNodeFormOpen}
@ -265,10 +266,12 @@ export default function GraphView() {
<Divider />
<div className="pl-6 pr-6">
<Footer>
<div className="flex flex-row items-center gap-6">
<span>Nodes: {data?.nodes.length}</span>
<span>Edges: {data?.links.length}</span>
</div>
{(data?.nodes.length || data?.links.length) && (
<div className="flex flex-row items-center gap-6">
<span>Nodes: {data?.nodes.length || 0}</span>
<span>Edges: {data?.links.length || 0}</span>
</div>
)}
</Footer>
</div>
</main>

View file

@ -42,7 +42,7 @@ function useDatasets() {
statusTimeout.current = setTimeout(() => {
checkDatasetStatuses(datasets);
}, 5000);
}, 50000);
}, [fetchDatasetStatuses]);
useEffect(() => {

View file

@ -26,19 +26,22 @@ export default function FeedbackForm({ onSuccess }: FeedbackFormProps) {
event.preventDefault();
const formElements = event.currentTarget;
const authCredentials = new FormData();
authCredentials.append("feedback", formElements.feedback.value);
setFeedbackError(null);
disableFeedbackSubmit();
fetch("/v1/feedback/reasoning", {
fetch("/v1/crewai/feedback", {
method: "POST",
body: authCredentials,
body: JSON.stringify({
feedback: formElements.feedback.value,
}),
headers: {
"Content-Type": "application/json",
},
})
.then(response => response.json())
.then(() => {
onSuccess();
formElements.feedback.value = "";
})
.catch(error => setFeedbackError(error.detail))
.finally(() => enableFeedbackSubmit());
@ -48,7 +51,7 @@ export default function FeedbackForm({ onSuccess }: FeedbackFormProps) {
<form onSubmit={signIn} className="flex flex-col gap-2">
<div className="flex flex-col gap-2">
<div className="mb-4">
<label className="block text-white" htmlFor="feedback">Your feedback on agents reasoning</label>
<label className="block text-white" htmlFor="feedback">Feedback on agent&apos;s reasoning</label>
<TextArea id="feedback" name="feedback" type="text" placeholder="Your feedback" />
</div>
</div>

View file

@ -47,9 +47,10 @@ app_environment = os.getenv("ENV", "prod")
@asynccontextmanager
async def lifespan(app: FastAPI):
# from cognee.modules.data.deletion import prune_system, prune_data
# await prune_data()
# await prune_system(metadata = True)
from cognee.modules.data.deletion import prune_system, prune_data
await prune_data()
await prune_system(metadata=True)
# if app_environment == "local" or app_environment == "dev":
from cognee.infrastructure.databases.relational import get_relational_engine
@ -189,7 +190,7 @@ def start_api_server(host: str = "0.0.0.0", port: int = 8000):
try:
logger.info("Starting server at %s:%s", host, port)
uvicorn.run(app, host=host, port=port)
uvicorn.run(app, host=host, port=port, loop="asyncio")
except Exception as e:
logger.exception(f"Failed to start server: {e}")
# Here you could add any cleanup code or error recovery code.

View file

@ -2,17 +2,19 @@ import asyncio
from pydantic import BaseModel
from typing import Union, Optional
from cognee.modules.users.methods import get_default_user
from cognee.shared.logging_utils import get_logger
from cognee.shared.data_models import KnowledgeGraph
from cognee.infrastructure.llm import get_max_chunk_tokens
from cognee.modules.users.models import User
from cognee.modules.pipelines import cognee_pipeline
from cognee.modules.pipelines.tasks.task import Task
from cognee.modules.chunking.TextChunker import TextChunker
from cognee.modules.ontology.rdf_xml.OntologyResolver import OntologyResolver
from cognee.modules.pipelines.models.PipelineRunInfo import PipelineRunCompleted
from cognee.modules.pipelines.models.PipelineRunInfo import PipelineRunCompleted, PipelineRunStarted
from cognee.modules.pipelines.queues.pipeline_run_info_queues import push_to_queue
from cognee.modules.users.models import User
from cognee.modules.graph.operations import get_formatted_graph_data
from cognee.modules.crewai.get_crewai_pipeline_run_id import get_crewai_pipeline_run_id
from cognee.tasks.documents import (
check_permissions_on_documents,
@ -36,16 +38,20 @@ async def cognify(
chunk_size: int = None,
ontology_file_path: Optional[str] = None,
run_in_background: bool = False,
is_stream_info_enabled: bool = False,
):
tasks = await get_default_tasks(user, graph_model, chunker, chunk_size, ontology_file_path)
if not user:
user = await get_default_user()
if run_in_background:
return await run_cognify_as_background_process(tasks, user, datasets)
else:
return await run_cognify_blocking(tasks, user, datasets)
return await run_cognify_blocking(tasks, user, datasets, is_stream_info_enabled)
async def run_cognify_blocking(tasks, user, datasets):
async def run_cognify_blocking(tasks, user, datasets, is_stream_info_enabled=False):
pipeline_run_info = None
async for run_info in cognee_pipeline(
@ -53,6 +59,15 @@ async def run_cognify_blocking(tasks, user, datasets):
):
pipeline_run_info = run_info
if (
is_stream_info_enabled
and not isinstance(pipeline_run_info, PipelineRunStarted)
and not isinstance(pipeline_run_info, PipelineRunCompleted)
):
pipeline_run_id = get_crewai_pipeline_run_id(user.id)
pipeline_run_info.payload = await get_formatted_graph_data()
push_to_queue(pipeline_run_id, pipeline_run_info)
return pipeline_run_info
@ -68,6 +83,8 @@ async def run_cognify_as_background_process(tasks, user, datasets):
try:
pipeline_run_info = await anext(pipeline_run)
pipeline_run_info.payload = await get_formatted_graph_data()
push_to_queue(pipeline_run_info.pipeline_run_id, pipeline_run_info)
if isinstance(pipeline_run_info, PipelineRunCompleted):

View file

@ -69,11 +69,15 @@ def get_cognify_router() -> APIRouter:
continue
try:
await websocket.send_json({
"pipeline_run_id": str(pipeline_run_info.pipeline_run_id),
"status": pipeline_run_info.status,
"payload": await get_nodes_and_edges(pipeline_run_info.payload) if pipeline_run_info.payload else None,
})
await websocket.send_json(
{
"pipeline_run_id": str(pipeline_run_info.pipeline_run_id),
"status": pipeline_run_info.status,
"payload": await get_nodes_and_edges(pipeline_run_info.payload)
if pipeline_run_info.payload
else None,
}
)
if isinstance(pipeline_run_info, PipelineRunCompleted):
remove_queue(pipeline_run_id)
@ -85,6 +89,7 @@ def get_cognify_router() -> APIRouter:
return router
async def get_nodes_and_edges(data_points):
nodes = []
edges = []
@ -112,14 +117,24 @@ async def get_nodes_and_edges(data_points):
nodes, edges = deduplicate_nodes_and_edges(nodes, edges)
return {
"nodes": list(map(lambda node: {
"id": str(node.id),
"label": node.name if hasattr(node, "name") else f"{node.type}_{str(node.id)}",
"properties": {},
}, nodes)),
"edges": list(map(lambda edge: {
"source": str(edge[0]),
"target": str(edge[1]),
"label": edge[2],
}, edges)),
"nodes": list(
map(
lambda node: {
"id": str(node.id),
"label": node.name if hasattr(node, "name") else f"{node.type}_{str(node.id)}",
"properties": {},
},
nodes,
)
),
"edges": list(
map(
lambda edge: {
"source": str(edge[0]),
"target": str(edge[1]),
"label": edge[2],
},
edges,
)
),
}

View file

@ -1,23 +1,117 @@
from fastapi import APIRouter, Depends
import os
import asyncio
from fastapi import APIRouter, Depends, WebSocket, WebSocketDisconnect
from starlette.status import WS_1000_NORMAL_CLOSURE, WS_1008_POLICY_VIOLATION
from cognee.api.DTO import InDTO
from cognee.modules.users.get_fastapi_users import get_fastapi_users
from cognee.modules.users.authentication.get_auth_backend import get_auth_backend
from cognee.modules.users.methods import get_authenticated_user
from cognee.complex_demos.crewai_demo.src.crewai_demo.github_ingest_datapoints import (
cognify_github_data_from_username,
)
from cognee.modules.crewai.get_crewai_pipeline_run_id import get_crewai_pipeline_run_id
from cognee.modules.users.models import User
from cognee.modules.users.methods import get_authenticated_user
from cognee.modules.pipelines.models import PipelineRunInfo, PipelineRunCompleted
from cognee.complex_demos.crewai_demo.src.crewai_demo.main import (
# run_github_ingestion,
run_hiring_crew,
)
from cognee.modules.pipelines.queues.pipeline_run_info_queues import (
get_from_queue,
initialize_queue,
remove_queue,
)
class CrewAIRunPayloadDTO(InDTO):
username1: str
username2: str
class CrewAIFeedbackPayloadDTO(InDTO):
feedback: str
def get_crewai_router() -> APIRouter:
router = APIRouter()
@router.post("/run", response_model=str)
async def run_crewai(payload: CrewAIRunPayloadDTO, user: User = Depends(get_authenticated_user)):
@router.post("/run", response_model=bool)
async def run_crewai(
payload: CrewAIRunPayloadDTO,
user: User = Depends(get_authenticated_user),
):
# Run CrewAI with the provided usernames
print(payload.username1, payload.username2)
# run_future = run_github_ingestion(payload.username1, payload.username2)
token = os.getenv("GITHUB_TOKEN")
return "CrewAI run started"
await cognify_github_data_from_username(payload.username1, token)
await cognify_github_data_from_username(payload.username2, token)
applicants = {
"applicant_1": payload.username1,
"applicant_2": payload.username2,
}
run_hiring_crew(applicants=applicants, number_of_rounds=2)
return True
@router.post("/feedback", response_model=None)
async def send_feedback(
payload: CrewAIFeedbackPayloadDTO,
user: User = Depends(
get_authenticated_user,
),
):
from cognee import add, cognify
# from secrets import choice
# from string import ascii_letters, digits
# hash6 = "".join(choice(ascii_letters + digits) for _ in range(6))
dataset_name = "final_reports"
await add(payload.feedback, node_set=["final_report"], dataset_name=dataset_name)
await cognify(datasets=dataset_name, is_stream_info_enabled=True)
@router.websocket("/subscribe")
async def subscribe_to_crewai_info(websocket: WebSocket):
await websocket.accept()
auth_message = await websocket.receive_json()
try:
user = await get_authenticated_user(auth_message.get("Authorization"))
except Exception:
await websocket.close(code=WS_1008_POLICY_VIOLATION, reason="Unauthorized")
return
pipeline_run_id = get_crewai_pipeline_run_id(user.id)
initialize_queue(pipeline_run_id)
while True:
pipeline_run_info = get_from_queue(pipeline_run_id)
if not pipeline_run_info:
await asyncio.sleep(2)
continue
if not isinstance(pipeline_run_info, PipelineRunInfo):
continue
try:
await websocket.send_json(
{
"pipeline_run_id": str(pipeline_run_info.pipeline_run_id),
"status": pipeline_run_info.status,
"payload": pipeline_run_info.payload if pipeline_run_info.payload else None,
}
)
if isinstance(pipeline_run_info, PipelineRunCompleted):
remove_queue(pipeline_run_id)
await websocket.close(code=WS_1000_NORMAL_CLOSURE)
break
except WebSocketDisconnect:
remove_queue(pipeline_run_id)
break
return router

View file

@ -1,3 +1,4 @@
from cognee.modules.graph.operations import get_formatted_graph_data
from cognee.shared.logging_utils import get_logger
from fastapi import APIRouter
from datetime import datetime
@ -44,11 +45,13 @@ class GraphNodeDTO(OutDTO):
label: str
properties: dict
class GraphEdgeDTO(OutDTO):
source: UUID
target: UUID
label: str
class GraphDTO(OutDTO):
nodes: List[GraphNodeDTO]
edges: List[GraphEdgeDTO]
@ -111,26 +114,10 @@ def get_datasets_router() -> APIRouter:
@router.get("/{dataset_id}/graph", response_model=GraphDTO)
async def get_dataset_graph(dataset_id: UUID, user: User = Depends(get_authenticated_user)):
from cognee.infrastructure.databases.graph import get_graph_engine
try:
graph_client = await get_graph_engine()
(nodes, edges) = await graph_client.get_graph_data()
return JSONResponse(
status_code=200,
content={
"nodes": list(map(lambda node: {
"id": str(node[0]),
"label": node[1]["name"] if hasattr(node[1], "name") else f"{node[1]['type']}_{str(node[0])}",
"properties": {},
}, nodes)),
"edges": list(map(lambda edge: {
"source": str(edge[0]),
"target": str(edge[1]),
"label": edge[2],
}, edges)),
},
content=await get_formatted_graph_data(),
)
except Exception as error:
print(error)

View file

@ -19,7 +19,7 @@ class CogneeBuild(BaseTool):
node_set = meta["nodeset"]
await cognee.add(text, node_set=node_set)
await cognee.cognify()
await cognee.cognify(is_stream_info_enabled=True)
return "Knowledge Graph is done."
except Exception as e:
@ -27,10 +27,11 @@ class CogneeBuild(BaseTool):
try:
loop = asyncio.get_event_loop()
if loop.is_running():
if not loop.is_running():
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
results = loop.run_until_complete(main())
return results
return loop.create_task(main())
except Exception as e:
return f"Tool execution error: {str(e)}"

View file

@ -1,8 +1,8 @@
from crewai.tools import BaseTool
from typing import Type, List
from pydantic import BaseModel, Field, PrivateAttr
from cognee.modules.engine.models import NodeSet
import asyncio
import nest_asyncio
from crewai.tools import BaseTool
from typing import Type
from pydantic import BaseModel, Field
class CogneeIngestionInput(BaseModel):
@ -24,14 +24,15 @@ class CogneeIngestion(BaseTool):
def _run(self, text: str) -> str:
import cognee
from secrets import choice
from string import ascii_letters, digits
# from secrets import choice
# from string import ascii_letters, digits
async def main():
try:
hash6 = "".join(choice(ascii_letters + digits) for _ in range(6))
await cognee.add(text, node_set=[self._nodeset_name], dataset_name=hash6)
await cognee.cognify(datasets=hash6)
# hash6 = "".join(choice(ascii_letters + digits) for _ in range(6))
dataset_name = "final_reports"
await cognee.add(text, node_set=[self._nodeset_name], dataset_name=dataset_name)
await cognee.cognify(datasets=dataset_name, is_stream_info_enabled=True)
return "Report ingested successfully into Cognee memory."
except Exception as e:
@ -39,9 +40,15 @@ class CogneeIngestion(BaseTool):
try:
loop = asyncio.get_event_loop()
if loop.is_running():
if not loop.is_running():
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
return loop.run_until_complete(main())
nest_asyncio.apply(loop)
result = loop.run_until_complete(main())
return result
except Exception as e:
return f"Tool execution error: {str(e)}"

View file

@ -1,3 +1,5 @@
import nest_asyncio
from crewai.tools import BaseTool
from typing import Type
from pydantic import BaseModel, Field, PrivateAttr
@ -47,10 +49,15 @@ class CogneeSearch(BaseTool):
try:
loop = asyncio.get_event_loop()
if loop.is_running():
if not loop.is_running():
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
search_results = loop.run_until_complete(main())
return search_results
nest_asyncio.apply(loop)
result = loop.run_until_complete(main())
return result
except Exception as e:
return f"Tool execution error: {str(e)}"

View file

@ -1,7 +1,5 @@
from crewai.tools import BaseTool
from cognee.modules.engine.models import NodeSet
from cognee.modules.retrieval.graph_completion_retriever import GraphCompletionRetriever
from ..github_ingest_datapoints import cognify_github_data_from_username
@ -11,30 +9,32 @@ class GithubIngestion(BaseTool):
def _run(self, applicant_1, applicant_2) -> str:
import asyncio
import cognee
# import cognee
import os
from cognee.low_level import DataPoint, setup as cognee_setup
# from cognee.low_level import setup as cognee_setup
async def main():
try:
await cognee.prune.prune_data()
await cognee.prune.prune_system(metadata=True)
await cognee_setup()
# await cognee.prune.prune_data()
# await cognee.prune.prune_system(metadata=True)
# await cognee_setup()
token = os.getenv("GITHUB_TOKEN")
await cognify_github_data_from_username(applicant_1, token)
await cognify_github_data_from_username(applicant_2, token)
return "Github ingestion finished"
return True
except Exception as e:
return f"Error: {str(e)}"
try:
loop = asyncio.get_event_loop()
if loop.is_running():
if not loop.is_running():
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
results = loop.run_until_complete(main())
return results
return loop.create_task(main())
except Exception as e:
return f"Tool execution error: {str(e)}"

View file

@ -1,7 +1,6 @@
from uuid import uuid5, NAMESPACE_OID
from typing import Dict, Any, List, Tuple, Optional
from typing import Dict, Any, List
from cognee.low_level import DataPoint
from cognee.modules.engine.models.node_set import NodeSet
from cognee.shared.logging_utils import get_logger
from cognee.complex_demos.crewai_demo.src.crewai_demo.github_datapoints import (
@ -60,7 +59,9 @@ def create_file_datapoint(filename: str, repo_name: str, nodesets: List[NodeSet]
"""Creates a File DataPoint with a consistent ID."""
file_key = f"{repo_name}:{filename}"
file_id = uuid5(NAMESPACE_OID, file_key)
file = File(id=file_id, filename=filename, repo=repo_name, belongs_to_set=nodesets)
file = File(
id=file_id, name=filename, filename=filename, repo=repo_name, belongs_to_set=nodesets
)
logger.debug(f"Created File with ID: {file_id} for {filename}")
return file
@ -72,6 +73,7 @@ def create_commit_datapoint(
commit_id = uuid5(NAMESPACE_OID, commit_data.get("commit_sha", ""))
commit = Commit(
id=commit_id,
name=commit_data.get("commit_sha", ""),
commit_sha=commit_data.get("commit_sha", ""),
text="Commit message:" + (str)(commit_data.get("commit_message", "")),
commit_date=commit_data.get("commit_date", ""),
@ -96,6 +98,7 @@ def create_file_change_datapoint(
file_change = FileChange(
id=fc_id,
name=fc_data.get("filename", ""),
filename=fc_data.get("filename", ""),
status=fc_data.get("status", ""),
additions=fc_data.get("additions", 0),
@ -121,6 +124,7 @@ def create_issue_datapoint(
issue = Issue(
id=issue_id,
name=str(issue_data.get("issue_number", 0)),
number=issue_data.get("issue_number", 0),
text=issue_data.get("issue_title", ""),
state=issue_data.get("issue_state", ""),
@ -142,6 +146,7 @@ def create_comment_datapoint(
comment = Comment(
id=comment_id,
name=str(comment_data.get("comment_id", "")),
comment_id=str(comment_data.get("comment_id", "")),
text=comment_data.get("body", ""),
created_at=comment_data.get("created_at", ""),

View file

@ -7,6 +7,7 @@ class File(DataPoint):
"""File is now a leaf node without any lists of other DataPoints"""
filename: str
name: str
repo: str
metadata: dict = {"index_fields": ["filename"]}
@ -25,6 +26,7 @@ class GitHubUser(DataPoint):
class FileChange(DataPoint):
filename: str
name: str
status: str
additions: int
deletions: int
@ -39,6 +41,7 @@ class FileChange(DataPoint):
class Comment(DataPoint):
comment_id: str
name: str
text: str
created_at: str
updated_at: str
@ -51,6 +54,7 @@ class Comment(DataPoint):
class Issue(DataPoint):
number: int
name: str
text: str
state: str
repository: str
@ -60,6 +64,7 @@ class Issue(DataPoint):
class Commit(DataPoint):
commit_sha: str
name: str
text: str
commit_date: str
commit_url: str

View file

@ -2,11 +2,8 @@ import json
import asyncio
from uuid import uuid5, NAMESPACE_OID
from typing import Optional, List, Dict, Any
from pathlib import Path
from cognee.api.v1.search import SearchType
import cognee
from cognee.low_level import DataPoint, setup as cognee_setup
from cognee.modules.retrieval.graph_completion_retriever import GraphCompletionRetriever
from cognee.low_level import DataPoint
from cognee.tasks.storage import add_data_points
from cognee.modules.pipelines.tasks.task import Task
from cognee.modules.pipelines import run_tasks
@ -16,6 +13,9 @@ from cognee.shared.logging_utils import get_logger
from cognee.complex_demos.crewai_demo.src.crewai_demo.github_ingest import (
get_github_data_for_cognee,
)
from cognee.modules.pipelines.models.PipelineRunInfo import PipelineRunCompleted, PipelineRunStarted
from cognee.modules.graph.operations import get_formatted_graph_data
from cognee.modules.crewai.get_crewai_pipeline_run_id import get_crewai_pipeline_run_id
# Import DataPoint classes from github_datapoints.py
from cognee.complex_demos.crewai_demo.src.crewai_demo.github_datapoints import (
@ -205,6 +205,27 @@ def build_github_datapoints_from_dict(github_data: Dict[str, Any]):
return all_datapoints
async def run_with_info_stream(tasks, user, data, dataset_id, pipeline_name):
from cognee.modules.pipelines.queues.pipeline_run_info_queues import push_to_queue
pipeline_run = run_tasks(
tasks=tasks,
data=data,
dataset_id=dataset_id,
pipeline_name=pipeline_name,
user=user,
)
pipeline_run_id = get_crewai_pipeline_run_id(user.id)
async for pipeline_run_info in pipeline_run:
if not isinstance(pipeline_run_info, PipelineRunStarted) and not isinstance(
pipeline_run_info, PipelineRunCompleted
):
pipeline_run_info.payload = await get_formatted_graph_data()
push_to_queue(pipeline_run_id, pipeline_run_info)
async def cognify_github_data(github_data: dict):
"""Process GitHub user, file changes, and comments data from a loaded dictionary."""
all_datapoints = build_github_datapoints_from_dict(github_data)
@ -216,18 +237,16 @@ async def cognify_github_data(github_data: dict):
cognee_user = await get_default_user()
tasks = [Task(add_data_points, task_config={"batch_size": 50})]
results = run_tasks(
await run_with_info_stream(
tasks=tasks,
data=all_datapoints,
dataset_id=dataset_id,
pipeline_name="github_pipeline",
user=cognee_user,
)
async for result in results:
print(result)
logger.info(f"Done processing {len(all_datapoints)} datapoints")
return True
async def cognify_github_data_from_username(
@ -263,8 +282,6 @@ async def cognify_github_data_from_username(
await cognify_github_data(github_data)
return None
async def process_github_from_file(json_file_path: str):
"""Process GitHub data from a JSON file."""

View file

@ -1,6 +1,5 @@
import os
from crewai import Agent, Crew, Process, Task
from crewai.project import CrewBase, agent, crew, task, before_kickoff
from crewai.project import CrewBase, agent, crew, task
from pydantic import BaseModel
from cognee.complex_demos.crewai_demo.src.crewai_demo.custom_tools.cognee_ingestion import (

View file

@ -1,6 +1,6 @@
import warnings
import os
from hiring_crew import HiringCrew
from .hiring_crew import HiringCrew
from cognee.complex_demos.crewai_demo.src.crewai_demo.custom_tools.github_ingestion import (
GithubIngestion,
)
@ -14,7 +14,7 @@ def print_environment():
def run_github_ingestion(applicant_1, applicant_2):
GithubIngestion().run(applicant_1=applicant_1, applicant_2=applicant_2)
return GithubIngestion().run(applicant_1=applicant_1, applicant_2=applicant_2)
def run_hiring_crew(applicants: dict, number_of_rounds: int = 1, llm_client=None):

View file

@ -50,7 +50,7 @@ class OpenAIAdapter(LLMInterface):
self.max_tokens = max_tokens
self.streaming = streaming
@observe(as_type="generation")
# @observe(as_type="generation")
@sleep_and_retry_async()
@rate_limit_async
async def acreate_structured_output(
@ -77,7 +77,7 @@ class OpenAIAdapter(LLMInterface):
max_retries=self.MAX_RETRIES,
)
@observe
# @observe
@sleep_and_retry_sync()
@rate_limit_sync
def create_structured_output(

View file

@ -0,0 +1,11 @@
from uuid import NAMESPACE_OID, UUID, uuid5
from cognee.modules.pipelines.utils import generate_pipeline_id, generate_pipeline_run_id
def get_crewai_pipeline_run_id(user_id: UUID):
dataset_id = uuid5(NAMESPACE_OID, "GitHub")
pipeline_id = generate_pipeline_id(user_id, "github_pipeline")
pipeline_run_id = generate_pipeline_run_id(pipeline_id, dataset_id)
return pipeline_run_id

View file

@ -0,0 +1 @@
from .get_formatted_graph_data import get_formatted_graph_data

View file

@ -0,0 +1,36 @@
from cognee.infrastructure.databases.graph import get_graph_engine
async def get_formatted_graph_data():
graph_client = await get_graph_engine()
(nodes, edges) = await graph_client.get_graph_data()
return {
"nodes": list(
map(
lambda node: {
"id": str(node[0]),
"label": node[1]["name"]
if ("name" in node[1] and node[1]["name"] != "")
else f"{node[1]['type']}_{str(node[0])}",
"type": node[1]["type"],
"properties": {
key: value
for key, value in node[1].items()
if key not in ["id", "type", "name"] and value is not None
},
},
nodes,
)
),
"edges": list(
map(
lambda edge: {
"source": str(edge[0]),
"target": str(edge[1]),
"label": edge[2],
},
edges,
)
),
}

View file

@ -15,19 +15,19 @@ class PipelineRunInfo(BaseModel):
class PipelineRunStarted(PipelineRunInfo):
status: str = "PipelineRunStarted"
pass
class PipelineRunYield(PipelineRunInfo):
status: str = "PipelineRunYield"
pass
class PipelineRunCompleted(PipelineRunInfo):
status: str = "PipelineRunCompleted"
pass
class PipelineRunErrored(PipelineRunInfo):
status: str = "PipelineRunErrored"
pass
class PipelineRunActivity(BaseModel):
status: str = "PipelineRunActivity"

View file

@ -6,7 +6,7 @@ authors = [
{ name = "Vasilije Markovic" },
{ name = "Boris Arzentar" },
]
requires-python = ">=3.10,<=3.13"
requires-python = ">=3.10,<3.13"
readme = "README.md"
license = "Apache-2.0"
classifiers = [
@ -58,6 +58,7 @@ dependencies = [
"structlog>=25.2.0,<26",
"onnxruntime<=1.21.1",
"pylance==0.22.0",
"nest-asyncio>=1.6.0",
]
[project.optional-dependencies]
@ -91,10 +92,6 @@ falkordb = ["falkordb==1.0.9"]
kuzu = ["kuzu==0.8.2"]
groq = ["groq==0.8.0"]
milvus = ["pymilvus>=2.5.0,<3"]
chromadb = [
"chromadb>=0.3.0,<0.7",
"pypika==0.48.8",
]
docs = ["unstructured[csv, doc, docx, epub, md, odt, org, ppt, pptx, rst, rtf, tsv, xlsx]>=0.16.13,<0.17"]
codegraph = [
"fastembed<=0.6.0 ; python_version < '3.13'",
@ -128,6 +125,10 @@ dev = [
"mkdocstrings[python]>=0.26.2,<0.27",
]
debug = ["debugpy==1.8.9"]
crewai = [
"crewai>=0.117.1",
"pygithub>=2.6.1",
]
[project.urls]
Homepage = "https://www.cognee.ai"

1534
uv.lock generated

File diff suppressed because it is too large Load diff