Skip to content

Commit

Permalink
Merge branch 'main' into fix/ParseJSONDataComponent
Browse files Browse the repository at this point in the history
  • Loading branch information
bhatsanket authored Nov 11, 2024
2 parents 0007826 + 859b72d commit 159f705
Show file tree
Hide file tree
Showing 16 changed files with 949 additions and 724 deletions.
2 changes: 1 addition & 1 deletion src/backend/base/langflow/base/agents/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ async def run_agent(
msg_id = e.agent_message.id
await asyncio.to_thread(delete_message, id_=msg_id)
self._send_message_event(e.agent_message, category="remove_message")
raise e.exception # noqa: B904
raise
except Exception:
raise

Expand Down
18 changes: 11 additions & 7 deletions src/backend/base/langflow/base/agents/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,8 @@


class ExceptionWithMessageError(Exception):
def __init__(self, e: Exception, agent_message: Message):
def __init__(self, agent_message: Message):
self.agent_message = agent_message
self.exception = e
super().__init__()


Expand Down Expand Up @@ -106,6 +105,7 @@ def handle_on_tool_start(
tool_name = event["name"]
tool_input = event["data"].get("input")
run_id = event.get("run_id", "")
tool_key = f"{tool_name}_{run_id}"

# Create content blocks if they don't exist
if not agent_message.content_blocks:
Expand All @@ -123,11 +123,11 @@ def handle_on_tool_start(
)

# Store in map and append to message
tool_blocks_map[run_id] = tool_content
tool_blocks_map[tool_key] = tool_content
agent_message.content_blocks[0].contents.append(tool_content)

agent_message = send_message_method(message=agent_message)
tool_blocks_map[run_id] = agent_message.content_blocks[0].contents[-1]
tool_blocks_map[tool_key] = agent_message.content_blocks[0].contents[-1]
return agent_message, start_time


Expand All @@ -139,7 +139,9 @@ def handle_on_tool_end(
start_time: float,
) -> tuple[Message, float]:
run_id = event.get("run_id", "")
tool_content = tool_blocks_map.get(run_id)
tool_name = event.get("name", "")
tool_key = f"{tool_name}_{run_id}"
tool_content = tool_blocks_map.get(tool_key)

if tool_content and isinstance(tool_content, ToolContent):
tool_content.output = event["data"].get("output")
Expand All @@ -160,7 +162,9 @@ def handle_on_tool_error(
start_time: float,
) -> tuple[Message, float]:
run_id = event.get("run_id", "")
tool_content = tool_blocks_map.get(run_id)
tool_name = event.get("name", "")
tool_key = f"{tool_name}_{run_id}"
tool_content = tool_blocks_map.get(tool_key)

if tool_content and isinstance(tool_content, ToolContent):
tool_content.error = event["data"].get("error", "Unknown error")
Expand Down Expand Up @@ -253,5 +257,5 @@ async def process_agent_events(
start_time = start_time or perf_counter()
agent_message.properties.state = "complete"
except Exception as e:
raise ExceptionWithMessageError(e, agent_message) from e
raise ExceptionWithMessageError(agent_message) from e
return Message(**agent_message.model_dump())
5 changes: 4 additions & 1 deletion src/backend/base/langflow/schema/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -339,12 +339,15 @@ class ErrorMessage(Message):

def __init__(
self,
exception: Exception,
exception: BaseException,
session_id: str,
source: Source,
trace_name: str | None = None,
flow_id: str | None = None,
) -> None:
# This is done to avoid circular imports
if exception.__class__.__name__ == "ExceptionWithMessageError" and exception.__cause__ is not None:
exception = exception.__cause__
# Get the error reason
reason = f"**{exception.__class__.__name__}**\n"
if hasattr(exception, "body") and "message" in exception.body:
Expand Down
5 changes: 4 additions & 1 deletion src/backend/base/langflow/utils/validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ def validate_code(code):
try:
tree = ast.parse(code)
except Exception as e: # noqa: BLE001
logger.opt(exception=True).debug("Error parsing code")
if hasattr(logger, "opt"):
logger.opt(exception=True).debug("Error parsing code")
else:
logger.debug("Error parsing code")
errors["function"]["errors"].append(str(e))
return errors

Expand Down
1,370 changes: 719 additions & 651 deletions src/backend/tests/.test_durations

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions src/backend/tests/unit/components/agents/test_agent_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,7 @@ def __init__(self):
assert isinstance(start_time, float)


def test_handle_on_tool_start():
async def test_handle_on_tool_start():
"""Test handle_on_tool_start event."""
send_message = MagicMock(side_effect=lambda message: message)
tool_blocks_map = {}
Expand All @@ -414,8 +414,9 @@ def test_handle_on_tool_start():

assert len(updated_message.content_blocks) == 1
assert len(updated_message.content_blocks[0].contents) > 0
tool_key = f"{event['name']}_{event['run_id']}"
tool_content = updated_message.content_blocks[0].contents[-1]
assert tool_content == tool_blocks_map.get("test_run")
assert tool_content == tool_blocks_map.get(tool_key)
assert isinstance(tool_content, ToolContent)
assert tool_content.name == "test_tool"
assert tool_content.tool_input == {"query": "tool input"}
Expand Down Expand Up @@ -452,6 +453,7 @@ async def test_handle_on_tool_end():

updated_message, start_time = handle_on_tool_end(end_event, agent_message, tool_blocks_map, send_message, 0.0)

f"{end_event['name']}_{end_event['run_id']}"
tool_content = updated_message.content_blocks[0].contents[-1]
assert tool_content.name == "test_tool"
assert tool_content.output == "tool output"
Expand Down
82 changes: 54 additions & 28 deletions src/frontend/src/components/chatComponents/ContentBlockDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,11 @@ export function ContentBlockDisplay({
contentBlocks[0]?.contents[contentBlocks[0]?.contents.length - 1];
const headerIcon =
state === "partial" ? lastContent?.header?.icon || "Bot" : "Bot";

const headerTitle =
(state === "partial"
? lastContent?.header?.title
: contentBlocks[0]?.title) || "Steps";
state === "partial" ? (lastContent?.header?.title ?? "Steps") : "Finished";
// show the block title only if state === "partial"
const showBlockTitle = state === "partial";

return (
<div className="relative py-3">
Expand All @@ -61,11 +62,10 @@ export function ContentBlockDisplay({
>
{isLoading && (
<BorderTrail
className="bg-zinc-600 opacity-50 dark:bg-zinc-400"
size={60}
size={100}
transition={{
repeat: Infinity,
duration: 2,
duration: 10,
ease: "linear",
}}
/>
Expand All @@ -92,7 +92,7 @@ export function ContentBlockDisplay({
<Markdown
remarkPlugins={[remarkGfm]}
rehypePlugins={[rehypeMathjax]}
className="inline-block w-fit max-w-full font-semibold text-primary"
className="inline-block w-fit max-w-full text-[14px] font-semibold text-primary"
>
{headerTitle}
</Markdown>
Expand Down Expand Up @@ -139,33 +139,59 @@ export function ContentBlockDisplay({
animate={{ opacity: 1 }}
transition={{ duration: 0.2, delay: 0.1 }}
className={cn(
"relative p-4",
"relative",
index !== contentBlocks.length - 1 &&
"border-b border-border",
)}
>
<div className="mb-2 font-medium">
<Markdown
remarkPlugins={[remarkGfm]}
linkTarget="_blank"
rehypePlugins={[rehypeMathjax]}
components={{
p({ node, ...props }) {
return (
<span className="inline">{props.children}</span>
);
},
}}
>
{block.title}
</Markdown>
</div>
<AnimatePresence>
{showBlockTitle && (
<motion.div
initial={{ opacity: 0, height: 0, marginBottom: 0 }}
animate={{
opacity: 1,
height: "auto",
marginBottom: 8,
}}
exit={{ opacity: 0, height: 0, marginBottom: 0 }}
transition={{ duration: 0.2 }}
className="overflow-hidden font-medium"
>
<Markdown
className="text-[14px] font-semibold text-foreground"
remarkPlugins={[remarkGfm]}
linkTarget="_blank"
rehypePlugins={[rehypeMathjax]}
components={{
p({ node, ...props }) {
return (
<span className="inline">{props.children}</span>
);
},
}}
>
{block.title}
</Markdown>
</motion.div>
)}
</AnimatePresence>
<div className="text-sm text-muted-foreground">
{block.contents.map((content, index) => (
<>
<Separator orientation="horizontal" className="my-2" />
<ContentDisplay key={index} content={content} />
</>
<motion.div key={index}>
<AnimatePresence>
{index !== 0 && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.2 }}
>
<Separator orientation="horizontal" />
</motion.div>
)}
</AnimatePresence>
<ContentDisplay content={content} />
</motion.div>
))}
</div>
</motion.div>
Expand Down
97 changes: 86 additions & 11 deletions src/frontend/src/components/chatComponents/ContentDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export default function ContentDisplay({ content }: { content: ContentType }) {
// First render the common BaseContent elements if they exist
const renderHeader = content.header && (
<>
<div className="flex items-center gap-2">
<div className="flex items-center gap-2 pb-[12px]">
{content.header.icon && (
<ForwardedIconComponent
name={content.header.icon}
Expand All @@ -25,7 +25,7 @@ export default function ContentDisplay({ content }: { content: ContentType }) {
<Markdown
remarkPlugins={[remarkGfm]}
rehypePlugins={[rehypeMathjax]}
className="inline-block w-fit max-w-full"
className="inline-block w-fit max-w-full text-[14px] font-semibold text-foreground"
>
{content.header.title}
</Markdown>
Expand All @@ -35,7 +35,7 @@ export default function ContentDisplay({ content }: { content: ContentType }) {
</>
);
const renderDuration = content.duration !== undefined && (
<div className="absolute right-2 top-0">
<div className="absolute right-2 top-4">
<DurationDisplay duration={content.duration} />
</div>
);
Expand All @@ -54,7 +54,7 @@ export default function ContentDisplay({ content }: { content: ContentType }) {
components={{
p({ node, ...props }) {
return (
<span className="inline-block w-fit max-w-full">
<span className="block w-fit max-w-full">
{props.children}
</span>
);
Expand Down Expand Up @@ -135,16 +135,91 @@ export default function ContentDisplay({ content }: { content: ContentType }) {
break;

case "tool_use":
const formatToolOutput = (output: any) => {
if (output === null || output === undefined) return "";

// If it's a string, render as markdown
if (typeof output === "string") {
return (
<Markdown
remarkPlugins={[remarkGfm]}
rehypePlugins={[rehypeMathjax]}
className="markdown prose max-w-full text-[14px] font-normal dark:prose-invert"
components={{
pre({ node, ...props }) {
return <>{props.children}</>;
},
code: ({ node, inline, className, children, ...props }) => {
const match = /language-(\w+)/.exec(className || "");
return !inline ? (
<SimplifiedCodeTabComponent
language={(match && match[1]) || ""}
code={String(children).replace(/\n$/, "")}
/>
) : (
<code className={className} {...props}>
{children}
</code>
);
},
}}
>
{output}
</Markdown>
);
}

// For objects/arrays, format as JSON
try {
return (
<CodeBlock
language="json"
value={JSON.stringify(output, null, 2)}
/>
);
} catch {
return String(output);
}
};

contentData = (
<div>
{content.name && <div>Tool: {content.name}</div>}
<div>Input: {JSON.stringify(content.tool_input, null, 2)}</div>
{content.output && (
<div>Output: {JSON.stringify(content.output)}</div>
<div className="flex flex-col gap-2">
<Markdown
remarkPlugins={[remarkGfm]}
rehypePlugins={[rehypeMathjax]}
className="markdown prose max-w-full text-[14px] font-normal dark:prose-invert"
>
{`${content.name ? `**Tool:** ${content.name}\n\n` : ""}**Input:**`}
</Markdown>
<CodeBlock
language="json"
value={JSON.stringify(content.tool_input, null, 2)}
/>
{content.output !== undefined && (
<>
<Markdown
remarkPlugins={[remarkGfm]}
rehypePlugins={[rehypeMathjax]}
className="markdown prose max-w-full text-[14px] font-normal dark:prose-invert"
>
**Output:**
</Markdown>
<div className="mt-1">{formatToolOutput(content.output)}</div>
</>
)}
{content.error && (
<div className="text-red-500">
Error: {JSON.stringify(content.error)}
<Markdown
remarkPlugins={[remarkGfm]}
rehypePlugins={[rehypeMathjax]}
className="markdown prose max-w-full text-[14px] font-normal dark:prose-invert"
>
**Error:**
</Markdown>
<CodeBlock
language="json"
value={JSON.stringify(content.error, null, 2)}
/>
</div>
)}
</div>
Expand All @@ -168,7 +243,7 @@ export default function ContentDisplay({ content }: { content: ContentType }) {
}

return (
<div className="relative">
<div className="relative p-[16px]">
{renderHeader}
{renderDuration}
{contentData}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export default function DurationDisplay({ duration }: { duration?: number }) {
bounce: 0,
duration: 300,
}}
className="tabular-nums"
className="text-[11px] font-bold tabular-nums"
/>
</div>
</div>
Expand Down
Loading

0 comments on commit 159f705

Please sign in to comment.