feat: logs on component output (#2740)

* Added tabs and passed the type of the table to the SwitchOutputView component

* Added type to SwitchOutputViewProps and changed results based on type

* Removed unused import

* Changed type of VertexDataTypeAPI to include logs

* Added condition where type is Logs to display only table

* Added handling when resultMessage is empty to SwitchOutputView

* Removed return that made Logs not appear when its empty

* Fixed logs not appearing

* feat: Add _output_logs attribute to CustomComponent

The code changes include adding a new attribute `_output_logs` to the `CustomComponent` class. This attribute is a dictionary that stores logs related to output values. This change is necessary to enhance the logging functionality of the component.

Note: Please remove any meta information such as issue references, tags, or author names from the commit message.

* feat: Serialize messages with to_json() in BaseCrewComponent

This commit modifies the `BaseCrewComponent` class in the `crew.py` file. It adds serialization of messages using the `to_json()` method to avoid circular reference issues. The `_messages_dict` dictionary is serialized by converting each message object to JSON format. This change enhances the functionality of the component and improves the logging process.

Note: Please remove any meta information such as issue references, tags, or author names from the commit message.

---------

Co-authored-by: Gabriel Luiz Freitas Almeida <gabriel@langflow.org>
This commit is contained in:
Lucas Oliveira 2024-07-16 19:36:10 -03:00 • committed by GitHub
commit 0897ee542a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 58 additions and 13 deletions

View file

@ -67,7 +67,9 @@ class BaseCrewComponent(Component):
self.log(cast(dict, messages[0].to_json()), name=f"Finish (Agent: {_id})") self.log(cast(dict, messages[0].to_json()), name=f"Finish (Agent: {_id})")
elif isinstance(agent_output, list): elif isinstance(agent_output, list):
_messages_dict = {f"Action {i}": action.messages for i, (action, _) in enumerate(agent_output)} _messages_dict = {f"Action {i}": action.messages for i, (action, _) in enumerate(agent_output)}
messages_dict = {k: v[0] if len(v) == 1 else v for k, v in _messages_dict.items()} # Serialize the messages with to_json() to avoid issues with circular references
serializable_dict = {k: [m.to_json() for m in v] for k, v in _messages_dict.items()}
messages_dict = {k: v[0] if len(v) == 1 else v for k, v in serializable_dict.items()}
self.log(messages_dict, name=f"Step (Agent: {_id})") self.log(messages_dict, name=f"Step (Agent: {_id})")
return step_callback return step_callback
@ -76,5 +78,5 @@ class BaseCrewComponent(Component):
crew = self.build_crew() crew = self.build_crew()
result = await crew.kickoff_async() result = await crew.kickoff_async()
message = Message(text=result, sender="Machine") message = Message(text=result, sender="Machine")
self.status = "\n\n".join([result] + [str(message) for message in self._logs]) self.status = message
return message return message

View file

@ -9,7 +9,6 @@ from langflow.inputs.inputs import InputTypes
from langflow.schema.artifact import get_artifact_type, post_process_raw from langflow.schema.artifact import get_artifact_type, post_process_raw
from langflow.schema.data import Data from langflow.schema.data import Data
from langflow.schema.message import Message from langflow.schema.message import Message
from langflow.services.tracing.schema import Log
from langflow.template.field.base import UNDEFINED, Output from langflow.template.field.base import UNDEFINED, Output
from .custom_component import CustomComponent from .custom_component import CustomComponent
@ -39,7 +38,6 @@ class Component(CustomComponent):
inputs: List[InputTypes] = [] inputs: List[InputTypes] = []
outputs: List[Output] = [] outputs: List[Output] = []
code_class_base_inheritance: ClassVar[str] = "Component" code_class_base_inheritance: ClassVar[str] = "Component"
_output_logs: dict[str, Log] = {}
def __init__(self, **data): def __init__(self, **data):
self._inputs: dict[str, InputTypes] = {} self._inputs: dict[str, InputTypes] = {}

View file

@ -86,6 +86,7 @@ class CustomComponent(BaseComponent):
_flows_data: Optional[List[Data]] = None _flows_data: Optional[List[Data]] = None
_outputs: List[OutputValue] = [] _outputs: List[OutputValue] = []
_logs: List[Log] = [] _logs: List[Log] = []
_output_logs: dict[str, Log] = {}
tracing_service: Optional["TracingService"] = None tracing_service: Optional["TracingService"] = None
def set_attributes(self, parameters: dict): def set_attributes(self, parameters: dict):

View file

@ -1,3 +1,4 @@
import { LogsLogType, OutputLogType } from "@/types/api";
import DataOutputComponent from "../../../../../../components/dataOutputComponent"; import DataOutputComponent from "../../../../../../components/dataOutputComponent";
import ForwardedIconComponent from "../../../../../../components/genericIconComponent"; import ForwardedIconComponent from "../../../../../../components/genericIconComponent";
import { import {
@ -13,26 +14,28 @@ import ErrorOutput from "./components";
interface SwitchOutputViewProps { interface SwitchOutputViewProps {
nodeId: string; nodeId: string;
outputName: string; outputName: string;
type: "Outputs" | "Logs";
} }
const SwitchOutputView: React.FC<SwitchOutputViewProps> = ({ const SwitchOutputView: React.FC<SwitchOutputViewProps> = ({
nodeId, nodeId,
outputName, outputName,
type,
}) => { }) => {
const flowPool = useFlowStore((state) => state.flowPool); const flowPool = useFlowStore((state) => state.flowPool);
const flowPoolNode = (flowPool[nodeId] ?? [])[ const flowPoolNode = (flowPool[nodeId] ?? [])[
(flowPool[nodeId]?.length ?? 1) - 1 (flowPool[nodeId]?.length ?? 1) - 1
]; ];
let results = flowPoolNode?.data?.outputs[outputName] ?? ""; let results: OutputLogType | LogsLogType =
if (Array.isArray(results)) { (type === "Outputs"
return; ? flowPoolNode?.data?.outputs[outputName]
} : flowPoolNode?.data?.logs[outputName]) ?? {};
const resultType = results?.type; const resultType = results?.type;
let resultMessage = results?.message; let resultMessage = results?.message ?? {};
const RECORD_TYPES = ["data", "object", "array", "message"]; const RECORD_TYPES = ["data", "object", "array", "message"];
if (resultMessage?.raw) { if (resultMessage?.raw) {
resultMessage = resultMessage.raw; resultMessage = resultMessage.raw;
} }
return ( return type === "Outputs" ? (
<> <>
<Case condition={!resultType || resultType === "unknown"}> <Case condition={!resultType || resultType === "unknown"}>
<div>NO OUTPUT</div> <div>NO OUTPUT</div>
@ -54,7 +57,9 @@ const SwitchOutputView: React.FC<SwitchOutputViewProps> = ({
? (resultMessage as Array<any>).every((item) => item.data) ? (resultMessage as Array<any>).every((item) => item.data)
? (resultMessage as Array<any>).map((item) => item.data) ? (resultMessage as Array<any>).map((item) => item.data)
: resultMessage : resultMessage
: [resultMessage] : Object.keys(resultMessage).length > 0
? [resultMessage]
: []
} }
pagination={true} pagination={true}
columnMode="union" columnMode="union"
@ -78,6 +83,20 @@ const SwitchOutputView: React.FC<SwitchOutputViewProps> = ({
</div> </div>
</Case> </Case>
</> </>
) : (
<DataOutputComponent
rows={
Array.isArray(results)
? (results as Array<any>).every((item) => item.data)
? (results as Array<any>).map((item) => item.data)
: results
: Object.keys(results).length > 0
? [results]
: []
}
pagination={true}
columnMode="union"
/>
); );
}; };

View file

@ -1,3 +1,5 @@
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { useState } from "react";
import { Button } from "../../../../components/ui/button"; import { Button } from "../../../../components/ui/button";
import BaseModal from "../../../../modals/baseModal"; import BaseModal from "../../../../modals/baseModal";
import SwitchOutputView from "./components/switchOutputView"; import SwitchOutputView from "./components/switchOutputView";
@ -8,15 +10,32 @@ export default function OutputModal({
nodeId, nodeId,
outputName, outputName,
}): JSX.Element { }): JSX.Element {
const [activeTab, setActiveTab] = useState<"Outputs" | "Logs">("Outputs");
return ( return (
<BaseModal open={open} setOpen={setOpen} size="medium-tall"> <BaseModal open={open} setOpen={setOpen} size="large">
<BaseModal.Header description="Inspect the output of the component below."> <BaseModal.Header description="Inspect the output of the component below.">
<div className="flex items-center"> <div className="flex items-center">
<span className="pr-2">Component Output</span> <span className="pr-2">Component Output</span>
</div> </div>
</BaseModal.Header> </BaseModal.Header>
<BaseModal.Content> <BaseModal.Content>
<SwitchOutputView nodeId={nodeId} outputName={outputName} /> <Tabs
value={activeTab}
onValueChange={(value) => setActiveTab(value as "Outputs" | "Logs")}
className={
"absolute top-6 flex flex-col self-center overflow-hidden rounded-md border bg-muted text-center"
}
>
<TabsList>
<TabsTrigger value="Outputs">Outputs</TabsTrigger>
<TabsTrigger value="Logs">Logs</TabsTrigger>
</TabsList>
</Tabs>
<SwitchOutputView
nodeId={nodeId}
outputName={outputName}
type={activeTab}
/>
</BaseModal.Content> </BaseModal.Content>
<BaseModal.Footer> <BaseModal.Footer>
<div className="flex w-full justify-end pt-2"> <div className="flex w-full justify-end pt-2">

View file

@ -201,12 +201,18 @@ export type OutputLogType = {
message: any | ErrorLogType; message: any | ErrorLogType;
type: string; type: string;
}; };
export type LogsLogType = {
name: string;
message: any | ErrorLogType;
type: string;
};
// data is the object received by the API // data is the object received by the API
// it has results, artifacts, timedelta, duration // it has results, artifacts, timedelta, duration
export type VertexDataTypeAPI = { export type VertexDataTypeAPI = {
results: { [key: string]: string }; results: { [key: string]: string };
outputs: { [key: string]: OutputLogType }; outputs: { [key: string]: OutputLogType };
logs: { [key: string]: LogsLogType };
messages: ChatOutputType[] | ChatInputType[]; messages: ChatOutputType[] | ChatInputType[];
inactive?: boolean; inactive?: boolean;
timedelta?: number; timedelta?: number;