Compare commits

...

30 Commits

Author SHA1 Message Date
thucpn 5c1de3c3ee add codegen example 2025-06-05 09:50:31 +07:00
thucpn a3dd3c1f0d Merge branch 'main' into tp/bump-chat-ui-with-inline-artifact 2025-06-05 09:29:29 +07:00
leehuwuj 4dca89ddb4 rename ArtifactTransform to InlineAnnotationTransformer 2025-06-04 16:32:22 +07:00
thucpn e20f198707 update comment 2025-06-04 15:41:38 +07:00
thucpn eff763191e refactor: move toInlineAnnotationEvent to inline.ts 2025-06-04 14:24:17 +07:00
thucpn 27de9418ff to_inline_annotation_event in python 2025-06-04 14:20:56 +07:00
thucpn 31cc131eb2 toInlineAnnotationEvent 2025-06-04 14:09:13 +07:00
thucpn cded3ae414 donot use relative imports 2025-06-04 13:23:02 +07:00
Thuc Pham 980f212d54 Create thick-turtles-deny.md 2025-06-04 12:21:57 +07:00
thucpn 3ac65caa2e Transforms ArtifactEvent to AgentStream with inline annotation format 2025-06-04 12:15:07 +07:00
thucpn d8668bfaaf fix imports 2025-06-04 11:37:23 +07:00
thucpn 37d52368de update get_last_artifact to extract inline annotations in Python 2025-06-04 11:30:54 +07:00
thucpn 1c598071dc fix format 2025-06-04 10:42:39 +07:00
thucpn 1f0f01a15d keep contract 2025-06-04 10:37:02 +07:00
thucpn 41898dcf10 revert doc 2025-06-04 10:31:35 +07:00
thucpn 844c2974ca do workflow transformmation internal 2025-06-04 10:24:23 +07:00
thucpn 2165247dab update doc to use toArtifactEvent 2025-06-04 10:08:10 +07:00
thucpn bdd624242f toArtifactEvent internal 2025-06-04 10:03:40 +07:00
thucpn 75409c9106 bump chat-ui 0.5.2 2025-06-04 09:52:53 +07:00
thucpn f59b46df62 bump chat-ui 0.5.1 to fix parsing $ 2025-06-03 16:44:51 +07:00
Thuc Pham 0d7a32f421 Merge branch 'main' into tp/bump-chat-ui-with-inline-artifact 2025-06-03 16:43:26 +07:00
thucpn dc73a34f61 update document 2025-06-03 16:15:34 +07:00
thucpn 472dbc4c21 remove artifactEvent for annotations 2025-06-03 16:10:45 +07:00
thucpn e3187fac1a update document gen workflow 2025-06-03 14:20:22 +07:00
thucpn b493548a13 missing export 2025-06-03 13:44:22 +07:00
thucpn 4be5c0e1ca fix: circle import 2025-06-03 11:57:20 +07:00
thucpn b9f61c77e9 fix: imports 2025-06-03 11:54:32 +07:00
thucpn b2f804c8af update extractLastArtifact 2025-06-03 11:51:52 +07:00
thucpn 03c7c4505d bump chat-ui 0.5.0 2025-06-03 11:24:07 +07:00
thucpn e4ada3cad2 feat: bump chat-ui with inline artifact 2025-06-03 10:29:02 +07:00
21 changed files with 1057 additions and 74 deletions
+7
View File
@@ -0,0 +1,7 @@
---
"create-llama": patch
"@llamaindex/server": patch
"@create-llama/llama-index-server": patch
---
feat: bump chat-ui with inline artifact
@@ -1,4 +1,4 @@
import { extractLastArtifact } from "@llamaindex/server";
import { artifactEvent, extractLastArtifact } from "@llamaindex/server";
import { ChatMemoryBuffer, MessageContent, Settings } from "llamaindex";
import {
@@ -52,19 +52,6 @@ const synthesizeAnswerEvent = workflowEvent<object>();
const uiEvent = workflowEvent<UIEvent>();
const artifactEvent = workflowEvent<{
type: "artifact";
data: {
type: "code";
created_at: number;
data: {
language: string;
file_name: string;
code: string;
};
};
}>();
export function workflowFactory(reqBody: any) {
const llm = Settings.llm;
@@ -1,4 +1,4 @@
import { extractLastArtifact } from "@llamaindex/server";
import { artifactEvent, extractLastArtifact } from "@llamaindex/server";
import { ChatMemoryBuffer, MessageContent, Settings } from "llamaindex";
import {
@@ -55,19 +55,6 @@ const synthesizeAnswerEvent = workflowEvent<{
const uiEvent = workflowEvent<UIEvent>();
const artifactEvent = workflowEvent<{
type: "artifact";
data: {
type: "document";
created_at: number;
data: {
title: string;
content: string;
type: "markdown" | "html";
};
};
}>();
export function workflowFactory(reqBody: any) {
const llm = Settings.llm;
@@ -0,0 +1,22 @@
This example demonstrates how to use the code generation workflow.
```ts
new LlamaIndexServer({
workflow: workflowFactory,
uiConfig: {
starterQuestions: [
"Generate a calculator app",
"Create a simple todo list app",
],
componentsDir: "components",
},
port: 3000,
}).start();
```
Export OpenAI API key and start the server in dev mode.
```bash
export OPENAI_API_KEY=<your-openai-api-key>
npx nodemon --exec tsx index.ts
```
@@ -0,0 +1,132 @@
import { Badge } from "@/components/ui/badge";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Progress } from "@/components/ui/progress";
import { Skeleton } from "@/components/ui/skeleton";
import { cn } from "@/lib/utils";
import { Markdown } from "@llamaindex/chat-ui/widgets";
import { ListChecks, Loader2, Wand2 } from "lucide-react";
import { useEffect, useState } from "react";
const STAGE_META = {
plan: {
icon: ListChecks,
badgeText: "Step 1/2: Planning",
gradient: "from-blue-100 via-blue-50 to-white",
progress: 33,
iconBg: "bg-blue-100 text-blue-600",
badge: "bg-blue-100 text-blue-700",
},
generate: {
icon: Wand2,
badgeText: "Step 2/2: Generating",
gradient: "from-violet-100 via-violet-50 to-white",
progress: 66,
iconBg: "bg-violet-100 text-violet-600",
badge: "bg-violet-100 text-violet-700",
},
};
function ArtifactWorkflowCard({ event }) {
const [visible, setVisible] = useState(event?.state !== "completed");
const [fade, setFade] = useState(false);
useEffect(() => {
if (event?.state === "completed") {
setVisible(false);
} else {
setVisible(true);
setFade(false);
}
}, [event?.state]);
if (!event || !visible) return null;
const { state, requirement } = event;
const meta = STAGE_META[state];
if (!meta) return null;
return (
<div className="flex min-h-[180px] w-full items-center justify-center py-2">
<Card
className={cn(
"w-full rounded-xl shadow-md transition-all duration-500",
"border-0",
fade && "pointer-events-none opacity-0",
`bg-gradient-to-br ${meta.gradient}`,
)}
style={{
boxShadow:
"0 2px 12px 0 rgba(80, 80, 120, 0.08), 0 1px 3px 0 rgba(80, 80, 120, 0.04)",
}}
>
<CardHeader className="flex flex-row items-center gap-2 px-3 pb-1 pt-2">
<div
className={cn(
"flex items-center justify-center rounded-full p-1",
meta.iconBg,
)}
>
<meta.icon className="h-5 w-5" />
</div>
<CardTitle className="flex items-center gap-2 text-base font-semibold">
<Badge className={cn("ml-1", meta.badge, "px-2 py-0.5 text-xs")}>
{meta.badgeText}
</Badge>
</CardTitle>
</CardHeader>
<CardContent className="px-3 py-1">
{state === "plan" && (
<div className="flex flex-col items-center gap-2 py-2">
<Loader2 className="mb-1 h-6 w-6 animate-spin text-blue-400" />
<div className="text-center text-sm font-medium text-blue-900">
Analyzing your request...
</div>
<Skeleton className="mt-1 h-3 w-1/2 rounded-full" />
</div>
)}
{state === "generate" && (
<div className="flex flex-col gap-2 py-2">
<div className="flex items-center gap-1">
<Loader2 className="h-4 w-4 animate-spin text-violet-400" />
<span className="text-sm font-medium text-violet-900">
Working on the requirement:
</span>
</div>
<div className="max-h-24 overflow-auto rounded-lg border border-violet-200 bg-violet-50 px-2 py-1 text-xs">
{requirement ? (
<Markdown content={requirement} />
) : (
<span className="italic text-violet-400">
No requirements available yet.
</span>
)}
</div>
</div>
)}
</CardContent>
<div className="px-3 pb-2 pt-1">
<Progress
value={meta.progress}
className={cn(
"h-1 rounded-full bg-gray-200",
state === "plan" && "bg-blue-200",
state === "generate" && "bg-violet-200",
)}
/>
</div>
</Card>
</div>
);
}
export default function Component({ events }) {
const aggregateEvents = () => {
if (!events || events.length === 0) return null;
return events[events.length - 1];
};
const event = aggregateEvents();
return <ArtifactWorkflowCard event={event} />;
}
@@ -0,0 +1,20 @@
import { OpenAI } from "@llamaindex/openai";
import { LlamaIndexServer } from "@llamaindex/server";
import { Settings } from "llamaindex";
import { workflowFactory } from "./src/app/workflow";
Settings.llm = new OpenAI({
model: "gpt-4o-mini",
});
new LlamaIndexServer({
workflow: workflowFactory,
uiConfig: {
starterQuestions: [
"Generate a calculator app",
"Create a simple todo list app",
],
componentsDir: "components",
},
port: 3000,
}).start();
@@ -0,0 +1,337 @@
import { artifactEvent, extractLastArtifact } from "@llamaindex/server";
import { ChatMemoryBuffer, MessageContent, Settings } from "llamaindex";
import {
agentStreamEvent,
createStatefulMiddleware,
createWorkflow,
startAgentEvent,
stopAgentEvent,
workflowEvent,
} from "@llamaindex/workflow";
import { z } from "zod";
export const RequirementSchema = z.object({
next_step: z.enum(["answering", "coding"]),
language: z.string().nullable().optional(),
file_name: z.string().nullable().optional(),
requirement: z.string(),
});
export type Requirement = z.infer<typeof RequirementSchema>;
export const UIEventSchema = z.object({
type: z.literal("ui_event"),
data: z.object({
state: z
.enum(["plan", "generate", "completed"])
.describe(
"The current state of the workflow: 'plan', 'generate', or 'completed'.",
),
requirement: z
.string()
.optional()
.describe(
"An optional requirement creating or updating a code, if applicable.",
),
}),
});
export type UIEvent = z.infer<typeof UIEventSchema>;
const planEvent = workflowEvent<{
userInput: MessageContent;
context?: string | undefined;
}>();
const generateArtifactEvent = workflowEvent<{
requirement: Requirement;
}>();
const synthesizeAnswerEvent = workflowEvent<object>();
const uiEvent = workflowEvent<UIEvent>();
export function workflowFactory(reqBody: unknown) {
const llm = Settings.llm;
const { withState, getContext } = createStatefulMiddleware(() => {
return {
memory: new ChatMemoryBuffer({ llm }),
lastArtifact: extractLastArtifact(reqBody),
};
});
const workflow = withState(createWorkflow());
workflow.handle([startAgentEvent], async ({ data }) => {
const { userInput, chatHistory = [] } = data;
// Prepare chat history
const { state } = getContext();
// Put user input to the memory
if (!userInput) {
throw new Error("Missing user input to start the workflow");
}
state.memory.set(chatHistory);
state.memory.put({ role: "user", content: userInput });
return planEvent.with({
userInput: userInput,
context: state.lastArtifact
? JSON.stringify(state.lastArtifact)
: undefined,
});
});
workflow.handle([planEvent], async ({ data: planData }) => {
const { sendEvent } = getContext();
const { state } = getContext();
sendEvent(
uiEvent.with({
type: "ui_event",
data: {
state: "plan",
},
}),
);
const user_msg = planData.userInput;
const context = planData.context
? `## The context is: \n${planData.context}\n`
: "";
const prompt = `
You are a product analyst responsible for analyzing the user's request and providing the next step for code or document generation.
You are helping user with their code artifact. To update the code, you need to plan a coding step.
Follow these instructions:
1. Carefully analyze the conversation history and the user's request to determine what has been done and what the next step should be.
2. The next step must be one of the following two options:
- "coding": To make the changes to the current code.
- "answering": If you don't need to update the current code or need clarification from the user.
Important: Avoid telling the user to update the code themselves, you are the one who will update the code (by planning a coding step).
3. If the next step is "coding", you may specify the language ("typescript" or "python") and file_name if known, otherwise set them to null.
4. The requirement must be provided clearly what is the user request and what need to be done for the next step in details
as precise and specific as possible, don't be stingy with in the requirement.
5. If the next step is "answering", set language and file_name to null, and the requirement should describe what to answer or explain to the user.
6. Be concise; only return the requirements for the next step.
7. The requirements must be in the following format:
\`\`\`json
{
"next_step": "answering" | "coding",
"language": "typescript" | "python" | null,
"file_name": string | null,
"requirement": string
}
\`\`\`
## Example 1:
User request: Create a calculator app.
You should return:
\`\`\`json
{
"next_step": "coding",
"language": "typescript",
"file_name": "calculator.tsx",
"requirement": "Generate code for a calculator app that has a simple UI with a display and button layout. The display should show the current input and the result. The buttons should include basic operators, numbers, clear, and equals. The calculation should work correctly."
}
\`\`\`
## Example 2:
User request: Explain how the game loop works.
Context: You have already generated the code for a snake game.
You should return:
\`\`\`json
{
"next_step": "answering",
"language": null,
"file_name": null,
"requirement": "The user is asking about the game loop. Explain how the game loop works."
}
\`\`\`
${context}
Now, plan the user's next step for this request:
${user_msg}
`;
const response = await llm.complete({
prompt,
});
// parse the response to Requirement
// 1. use regex to find the json block
const jsonBlock = response.text.match(/```json\s*([\s\S]*?)\s*```/);
if (!jsonBlock) {
throw new Error("No JSON block found in the response.");
}
const requirement = RequirementSchema.parse(JSON.parse(jsonBlock[1]));
state.memory.put({
role: "assistant",
content: `The plan for next step: \n${response.text}`,
});
if (requirement.next_step === "coding") {
return generateArtifactEvent.with({
requirement,
});
} else {
return synthesizeAnswerEvent.with({});
}
});
workflow.handle([generateArtifactEvent], async ({ data: planData }) => {
const { sendEvent } = getContext();
const { state } = getContext();
sendEvent(
uiEvent.with({
type: "ui_event",
data: {
state: "generate",
requirement: planData.requirement.requirement,
},
}),
);
const previousArtifact = state.lastArtifact
? JSON.stringify(state.lastArtifact)
: "There is no previous artifact";
const requirementText = planData.requirement.requirement;
const prompt = `
You are a skilled developer who can help user with coding.
You are given a task to generate or update a code for a given requirement.
## Follow these instructions:
**1. Carefully read the user's requirements.**
If any details are ambiguous or missing, make reasonable assumptions and clearly reflect those in your output.
If the previous code is provided:
+ Carefully analyze the code with the request to make the right changes.
+ Avoid making a lot of changes from the previous code if the request is not to write the code from scratch again.
**2. For code requests:**
- If the user does not specify a framework or language, default to a React component using the Next.js framework.
- For Next.js, use Shadcn UI components, Typescript, @types/node, @types/react, @types/react-dom, PostCSS, and TailwindCSS.
The import pattern should be:
\`\`\`typescript
import { ComponentName } from "@/components/ui/component-name"
import { Markdown } from "@llamaindex/chat-ui"
import { cn } from "@/lib/utils"
\`\`\`
- Ensure the code is idiomatic, production-ready, and includes necessary imports.
- Only generate code relevant to the user's request—do not add extra boilerplate.
**3. Don't be verbose on response**
- No other text or comments only return the code which wrapped by \`\`\`language\`\`\` block.
- If the user's request is to update the code, only return the updated code.
**4. Only the following languages are allowed: "typescript", "python".**
**5. If there is no code to update, return the reason without any code block.**
## Example:
\`\`\`typescript
import React from "react";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
export default function MyComponent() {
return (
<div className="flex flex-col items-center justify-center h-screen">
<Button>Click me</Button>
</div>
);
}
\`\`\`
The previous code is:
{previousArtifact}
Now, i have to generate the code for the following requirement:
{requirement}
`
.replace("{previousArtifact}", previousArtifact)
.replace("{requirement}", requirementText);
const response = await llm.complete({
prompt,
});
// Extract the code from the response
const codeMatch = response.text.match(/```(\w+)([\s\S]*)```/);
if (!codeMatch) {
return synthesizeAnswerEvent.with({});
}
const code = codeMatch[2].trim();
// Put the generated code to the memory
state.memory.put({
role: "assistant",
content: `Updated the code: \n${response.text}`,
});
// To show the Canvas panel for the artifact
sendEvent(
artifactEvent.with({
type: "artifact",
data: {
type: "code",
created_at: Date.now(),
data: {
language: planData.requirement.language || "",
file_name: planData.requirement.file_name || "",
code,
},
},
}),
);
return synthesizeAnswerEvent.with({});
});
workflow.handle([synthesizeAnswerEvent], async () => {
const { sendEvent } = getContext();
const { state } = getContext();
const chatHistory = await state.memory.getMessages();
const messages = [
...chatHistory,
{
role: "system" as const,
content: `
You are a helpful assistant who is responsible for explaining the work to the user.
Based on the conversation history, provide an answer to the user's question.
The user has access to the code so avoid mentioning the whole code again in your response.
`,
},
];
const responseStream = await llm.chat({
messages,
stream: true,
});
sendEvent(
uiEvent.with({
type: "ui_event",
data: {
state: "completed",
},
}),
);
let response = "";
for await (const chunk of responseStream) {
response += chunk.delta;
sendEvent(
agentStreamEvent.with({
delta: chunk.delta,
response: "",
currentAgentName: "assistant",
raw: chunk,
}),
);
}
return stopAgentEvent.with({
result: response,
});
});
return workflow;
}
@@ -19,7 +19,6 @@ export function ChatMessageContent({
<ToolAnnotations />
<ChatMessage.Content.Image />
<DynamicEvents componentDefs={componentDefs} appendError={appendError} />
<ChatMessage.Content.Artifact />
<ChatMessage.Content.Markdown />
<ChatMessage.Content.DocumentFile />
<ChatMessage.Content.Source />
+1 -1
View File
@@ -65,7 +65,7 @@
"@babel/traverse": "^7.27.0",
"@babel/types": "^7.27.0",
"@hookform/resolvers": "^5.0.1",
"@llamaindex/chat-ui": "0.4.9",
"@llamaindex/chat-ui": "0.5.2",
"@radix-ui/react-accordion": "^1.2.3",
"@radix-ui/react-alert-dialog": "^1.1.7",
"@radix-ui/react-aspect-ratio": "^1.1.3",
+1 -1
View File
@@ -41,7 +41,7 @@
"@babel/traverse": "^7.27.0",
"@babel/types": "^7.27.0",
"@hookform/resolvers": "^5.0.1",
"@llamaindex/chat-ui": "0.4.9",
"@llamaindex/chat-ui": "0.5.2",
"@llamaindex/env": "~0.1.30",
"@llamaindex/openai": "~0.4.0",
"@llamaindex/readers": "~3.1.4",
+1
View File
@@ -2,4 +2,5 @@ export * from "./server";
export * from "./types";
export * from "./utils/events";
export { generateEventComponent } from "./utils/gen-ui";
export * from "./utils/inline";
export * from "./utils/prompts";
+24 -21
View File
@@ -1,8 +1,13 @@
import { randomUUID } from "@llamaindex/env";
import { workflowEvent } from "@llamaindex/workflow";
import type { Message } from "ai";
import { MetadataMode, type Metadata, type NodeWithScore } from "llamaindex";
import {
MetadataMode,
type ChatMessage,
type Metadata,
type NodeWithScore,
} from "llamaindex";
import { z } from "zod";
import { getInlineAnnotations } from "./inline";
// Events that appended to stream as annotations
export type SourceEventNode = {
@@ -148,24 +153,22 @@ export const artifactAnnotationSchema = z.object({
data: artifactSchema,
});
export function extractAllArtifacts(messages: Message[]): Artifact[] {
const allArtifacts: Artifact[] = [];
export function extractArtifactsFromMessage(message: ChatMessage): Artifact[] {
const inlineAnnotations = getInlineAnnotations(message);
const artifacts = inlineAnnotations.filter(
(annotation): annotation is z.infer<typeof artifactAnnotationSchema> => {
return artifactAnnotationSchema.safeParse(annotation).success;
},
);
return artifacts.map((artifact) => artifact.data);
}
for (const message of messages) {
const artifacts =
message.annotations
?.filter(
(
annotation,
): annotation is z.infer<typeof artifactAnnotationSchema> =>
artifactAnnotationSchema.safeParse(annotation).success,
)
.map((annotation) => annotation.data as Artifact) ?? [];
allArtifacts.push(...artifacts);
}
return allArtifacts;
export function extractArtifactsFromAllMessages(
messages: ChatMessage[],
): Artifact[] {
return messages
.flatMap((message) => extractArtifactsFromMessage(message))
.sort((a, b) => a.created_at - b.created_at);
}
export function extractLastArtifact(
@@ -187,10 +190,10 @@ export function extractLastArtifact(
requestBody: unknown,
type?: ArtifactType,
): CodeArtifact | DocumentArtifact | Artifact | undefined {
const { messages } = (requestBody as { messages?: Message[] }) ?? {};
const { messages } = (requestBody as { messages?: ChatMessage[] }) ?? {};
if (!messages) return undefined;
const artifacts = extractAllArtifacts(messages);
const artifacts = extractArtifactsFromAllMessages(messages);
if (!artifacts.length) return undefined;
if (type) {
+1
View File
@@ -1,6 +1,7 @@
export * from "./events";
export * from "./file";
export * from "./gen-ui";
export * from "./inline";
export * from "./prompts";
export * from "./request";
export * from "./stream";
+90
View File
@@ -0,0 +1,90 @@
import { agentStreamEvent, type WorkflowEventData } from "@llamaindex/workflow";
import { type ChatMessage } from "llamaindex";
import { z } from "zod";
const INLINE_ANNOTATION_KEY = "annotation"; // the language key to detect inline annotation code in markdown
export const AnnotationSchema = z.object({
type: z.string(),
data: z.any(),
});
export type Annotation = z.infer<typeof AnnotationSchema>;
export function getInlineAnnotations(message: ChatMessage): Annotation[] {
const markdownContent = getMessageMarkdownContent(message);
const inlineAnnotations: Annotation[] = [];
// Regex to match annotation code blocks
// Matches ```annotation followed by content until closing ```
const annotationRegex = new RegExp(
`\`\`\`${INLINE_ANNOTATION_KEY}\\s*\\n([\\s\\S]*?)\\n\`\`\``,
"g",
);
let match;
while ((match = annotationRegex.exec(markdownContent)) !== null) {
const jsonContent = match[1]?.trim();
if (!jsonContent) {
continue;
}
try {
// Parse the JSON content
const parsed = JSON.parse(jsonContent);
// Validate against the annotation schema
const validated = AnnotationSchema.parse(parsed);
// Extract the artifact data
inlineAnnotations.push(validated);
} catch (error) {
// Skip invalid annotations - they might be malformed JSON or invalid schema
console.warn("Failed to parse annotation:", error);
}
}
return inlineAnnotations;
}
/**
* To append inline annotations to the stream, we need to wrap the annotation in a code block with the language key.
* The language key is `annotation` and the code block is wrapped in backticks.
*
* \`\`\`annotation
* \{
* "type": "artifact",
* "data": \{...\}
* \}
* \`\`\`
*/
export function toInlineAnnotation(item: unknown) {
return `\n\`\`\`${INLINE_ANNOTATION_KEY}\n${JSON.stringify(item)}\n\`\`\`\n`;
}
export function toInlineAnnotationEvent(event: WorkflowEventData<unknown>) {
return agentStreamEvent.with({
delta: toInlineAnnotation(event.data),
response: "",
currentAgentName: "assistant",
raw: event.data,
});
}
function getMessageMarkdownContent(message: ChatMessage): string {
let markdownContent = "";
if (typeof message.content === "string") {
markdownContent = message.content;
} else {
message.content.forEach((item) => {
if (item.type === "text") {
markdownContent += item.text;
}
});
}
return markdownContent;
}
+6
View File
@@ -15,12 +15,14 @@ import {
type NodeWithScore,
} from "llamaindex";
import {
artifactEvent,
sourceEvent,
toAgentRunEvent,
toSourceEvent,
type SourceEventNode,
} from "./events";
import { downloadFile } from "./file";
import { toInlineAnnotationEvent } from "./inline";
export async function runWorkflow(
workflow: Workflow,
@@ -74,6 +76,10 @@ function processWorkflowStream(
transformedEvent = toSourceEvent(sourceNodes);
}
}
// Handle artifact events, transform to agentStreamEvent
else if (artifactEvent.include(event)) {
transformedEvent = toInlineAnnotationEvent(event);
}
// Post-process for llama-cloud files
if (sourceEvent.include(transformedEvent)) {
const sourceNodesForDownload = transformedEvent.data.data.nodes; // These are SourceEventNode[]
+299 -19
View File
@@ -181,8 +181,8 @@ importers:
specifier: ^5.0.1
version: 5.0.1(react-hook-form@7.56.1(react@19.1.0))
'@llamaindex/chat-ui':
specifier: 0.4.9
version: 0.4.9(@babel/runtime@7.27.0)(@codemirror/autocomplete@6.18.6)(@codemirror/language@6.11.0)(@codemirror/lint@6.8.5)(@codemirror/search@6.5.10)(@codemirror/state@6.5.2)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.36.7)(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(codemirror@6.0.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
specifier: 0.5.2
version: 0.5.2(@babel/runtime@7.27.0)(@codemirror/autocomplete@6.18.6)(@codemirror/language@6.11.0)(@codemirror/lint@6.8.5)(@codemirror/search@6.5.10)(@codemirror/state@6.5.2)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.36.7)(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(codemirror@6.0.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@llamaindex/env':
specifier: ~0.1.30
version: 0.1.30
@@ -1186,8 +1186,8 @@ packages:
zod:
optional: true
'@llamaindex/chat-ui@0.4.9':
resolution: {integrity: sha512-KEdydC+aJ22VK/TltxIHlMWbWLfh6I0YkyVd1D/CS3FRfLt8l9jfQ/YjY10MiEd8oc1fFfk6ek/FhVWe9Szstg==}
'@llamaindex/chat-ui@0.5.2':
resolution: {integrity: sha512-zIOaf5cuhDtllrcDEDaGNRq2bon8STUYoex4n+zzowR6eirRRYPYoUpRBNBK7fZi4BJhBPmqTS/tkXzjw908Gw==}
peerDependencies:
react: ^18.2.0 || ^19.0.0 || ^19.0.0-rc
@@ -2454,6 +2454,9 @@ packages:
'@types/mdast@3.0.15':
resolution: {integrity: sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==}
'@types/mdast@4.0.4':
resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==}
'@types/mdurl@2.0.0':
resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==}
@@ -4621,6 +4624,9 @@ packages:
mdast-util-from-markdown@1.3.1:
resolution: {integrity: sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==}
mdast-util-from-markdown@2.0.2:
resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==}
mdast-util-gfm-autolink-literal@1.0.3:
resolution: {integrity: sha512-My8KJ57FYEy2W2LyNom4n3E7hKTuQk/0SES0u16tjA9Z3oFkF4RrC/hPAPgjlSpezsOvI8ObcXcElo92wn5IGA==}
@@ -4645,15 +4651,24 @@ packages:
mdast-util-phrasing@3.0.1:
resolution: {integrity: sha512-WmI1gTXUBJo4/ZmSk79Wcb2HcjPJBzM1nlI/OUWA8yk2X9ik3ffNbBGsU+09BFmXaL1IBb9fiuvq6/KMiNycSg==}
mdast-util-phrasing@4.1.0:
resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==}
mdast-util-to-hast@12.3.0:
resolution: {integrity: sha512-pits93r8PhnIoU4Vy9bjW39M2jJ6/tdHyja9rrot9uujkN7UTU9SDnE6WNJz/IGyQk3XHX6yNNtrBH6cQzm8Hw==}
mdast-util-to-markdown@1.5.0:
resolution: {integrity: sha512-bbv7TPv/WC49thZPg3jXuqzuvI45IL2EVAr/KxF0BSdHsU0ceFHOmwQn6evxAh1GaoK/6GQ1wp4R4oW2+LFL/A==}
mdast-util-to-markdown@2.1.2:
resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==}
mdast-util-to-string@3.2.0:
resolution: {integrity: sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==}
mdast-util-to-string@4.0.0:
resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==}
mdurl@2.0.0:
resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==}
@@ -4686,6 +4701,9 @@ packages:
micromark-core-commonmark@1.1.0:
resolution: {integrity: sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==}
micromark-core-commonmark@2.0.3:
resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==}
micromark-extension-gfm-autolink-literal@1.0.5:
resolution: {integrity: sha512-z3wJSLrDf8kRDOh2qBtoTRD53vJ+CWIyo7uyZuxf/JAbNJjiHsOpG1y5wxk8drtv3ETAHutCu6N3thkOOgueWg==}
@@ -4713,63 +4731,123 @@ packages:
micromark-factory-destination@1.1.0:
resolution: {integrity: sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==}
micromark-factory-destination@2.0.1:
resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==}
micromark-factory-label@1.1.0:
resolution: {integrity: sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==}
micromark-factory-label@2.0.1:
resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==}
micromark-factory-space@1.1.0:
resolution: {integrity: sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==}
micromark-factory-space@2.0.1:
resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==}
micromark-factory-title@1.1.0:
resolution: {integrity: sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==}
micromark-factory-title@2.0.1:
resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==}
micromark-factory-whitespace@1.1.0:
resolution: {integrity: sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==}
micromark-factory-whitespace@2.0.1:
resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==}
micromark-util-character@1.2.0:
resolution: {integrity: sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==}
micromark-util-character@2.1.1:
resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==}
micromark-util-chunked@1.1.0:
resolution: {integrity: sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==}
micromark-util-chunked@2.0.1:
resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==}
micromark-util-classify-character@1.1.0:
resolution: {integrity: sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==}
micromark-util-classify-character@2.0.1:
resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==}
micromark-util-combine-extensions@1.1.0:
resolution: {integrity: sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==}
micromark-util-combine-extensions@2.0.1:
resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==}
micromark-util-decode-numeric-character-reference@1.1.0:
resolution: {integrity: sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==}
micromark-util-decode-numeric-character-reference@2.0.2:
resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==}
micromark-util-decode-string@1.1.0:
resolution: {integrity: sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==}
micromark-util-decode-string@2.0.1:
resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==}
micromark-util-encode@1.1.0:
resolution: {integrity: sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==}
micromark-util-encode@2.0.1:
resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==}
micromark-util-html-tag-name@1.2.0:
resolution: {integrity: sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==}
micromark-util-html-tag-name@2.0.1:
resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==}
micromark-util-normalize-identifier@1.1.0:
resolution: {integrity: sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==}
micromark-util-normalize-identifier@2.0.1:
resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==}
micromark-util-resolve-all@1.1.0:
resolution: {integrity: sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==}
micromark-util-resolve-all@2.0.1:
resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==}
micromark-util-sanitize-uri@1.2.0:
resolution: {integrity: sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==}
micromark-util-sanitize-uri@2.0.1:
resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==}
micromark-util-subtokenize@1.1.0:
resolution: {integrity: sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==}
micromark-util-subtokenize@2.1.0:
resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==}
micromark-util-symbol@1.1.0:
resolution: {integrity: sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==}
micromark-util-symbol@2.0.1:
resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==}
micromark-util-types@1.1.0:
resolution: {integrity: sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==}
micromark-util-types@2.0.2:
resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==}
micromark@3.2.0:
resolution: {integrity: sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==}
micromark@4.0.2:
resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==}
micromatch@4.0.8:
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
engines: {node: '>=8.6'}
@@ -5634,14 +5712,17 @@ packages:
remark-parse@10.0.2:
resolution: {integrity: sha512-3ydxgHa/ZQzG8LvC7jTXccARYDcRld3VfcgIIFs7bI6vbRSxJJmzgLEIIoYKyrfhaY+ujuWaf/PJiMZXoiCXgw==}
remark-parse@11.0.0:
resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==}
remark-rehype@10.1.0:
resolution: {integrity: sha512-EFmR5zppdBp0WQeDVZ/b66CWJipB2q2VLNFMabzDSGR66Z2fQii83G5gTBbgGEnEEA0QRussvrFHxk1HWGJskw==}
remark-stringify@10.0.3:
resolution: {integrity: sha512-koyOzCMYoUHudypbj4XpnAKFbkddRMYZHwghnxd7ue5210WzGw6kOBwauJTRUMq16jsovXx8dYNvSSWP89kZ3A==}
remark-stringify@11.0.0:
resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==}
remark@14.0.3:
resolution: {integrity: sha512-bfmJW1dmR2LvaMJuAnE88pZP9DktIFYXazkTfOIKZzi3Knk9lT0roItIA24ydOucI3bV/g/tXBA6hzqq3FV9Ew==}
remark@15.0.1:
resolution: {integrity: sha512-Eht5w30ruCXgFmxVUSlNWQ9iiimq07URKeFS3hNc8cUWy1llX4KDWfyEDZRycMc+znsN9Ux5/tJ/BFdgdOwA3A==}
require-directory@2.1.1:
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
@@ -6265,6 +6346,9 @@ packages:
unified@10.1.2:
resolution: {integrity: sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==}
unified@11.0.5:
resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==}
unist-util-find-after@5.0.0:
resolution: {integrity: sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==}
@@ -7367,7 +7451,7 @@ snapshots:
p-retry: 6.2.1
zod: 3.25.13
'@llamaindex/chat-ui@0.4.9(@babel/runtime@7.27.0)(@codemirror/autocomplete@6.18.6)(@codemirror/language@6.11.0)(@codemirror/lint@6.8.5)(@codemirror/search@6.5.10)(@codemirror/state@6.5.2)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.36.7)(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(codemirror@6.0.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
'@llamaindex/chat-ui@0.5.2(@babel/runtime@7.27.0)(@codemirror/autocomplete@6.18.6)(@codemirror/language@6.11.0)(@codemirror/lint@6.8.5)(@codemirror/search@6.5.10)(@codemirror/state@6.5.2)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.36.7)(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(codemirror@6.0.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
dependencies:
'@codemirror/lang-css': 6.3.1
'@codemirror/lang-html': 6.4.9
@@ -7401,11 +7485,13 @@ snapshots:
react: 19.1.0
react-markdown: 8.0.7(@types/react@19.1.2)(react@19.1.0)
rehype-katex: 7.0.1
remark: 14.0.3
remark: 15.0.1
remark-code-import: 1.2.0
remark-gfm: 3.0.1
remark-math: 5.1.1
remark-parse: 11.0.0
tailwind-merge: 2.6.0
unist-util-visit: 5.0.0
vaul: 0.9.9(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
transitivePeerDependencies:
- '@babel/runtime'
@@ -8713,6 +8799,10 @@ snapshots:
dependencies:
'@types/unist': 2.0.11
'@types/mdast@4.0.4':
dependencies:
'@types/unist': 3.0.3
'@types/mdurl@2.0.0': {}
'@types/ms@2.1.0': {}
@@ -11247,6 +11337,23 @@ snapshots:
transitivePeerDependencies:
- supports-color
mdast-util-from-markdown@2.0.2:
dependencies:
'@types/mdast': 4.0.4
'@types/unist': 3.0.3
decode-named-character-reference: 1.1.0
devlop: 1.1.0
mdast-util-to-string: 4.0.0
micromark: 4.0.2
micromark-util-decode-numeric-character-reference: 2.0.2
micromark-util-decode-string: 2.0.1
micromark-util-normalize-identifier: 2.0.1
micromark-util-symbol: 2.0.1
micromark-util-types: 2.0.2
unist-util-stringify-position: 4.0.0
transitivePeerDependencies:
- supports-color
mdast-util-gfm-autolink-literal@1.0.3:
dependencies:
'@types/mdast': 3.0.15
@@ -11302,6 +11409,11 @@ snapshots:
'@types/mdast': 3.0.15
unist-util-is: 5.2.1
mdast-util-phrasing@4.1.0:
dependencies:
'@types/mdast': 4.0.4
unist-util-is: 6.0.0
mdast-util-to-hast@12.3.0:
dependencies:
'@types/hast': 2.3.10
@@ -11324,10 +11436,26 @@ snapshots:
unist-util-visit: 4.1.2
zwitch: 2.0.4
mdast-util-to-markdown@2.1.2:
dependencies:
'@types/mdast': 4.0.4
'@types/unist': 3.0.3
longest-streak: 3.1.0
mdast-util-phrasing: 4.1.0
mdast-util-to-string: 4.0.0
micromark-util-classify-character: 2.0.1
micromark-util-decode-string: 2.0.1
unist-util-visit: 5.0.0
zwitch: 2.0.4
mdast-util-to-string@3.2.0:
dependencies:
'@types/mdast': 3.0.15
mdast-util-to-string@4.0.0:
dependencies:
'@types/mdast': 4.0.4
mdurl@2.0.0: {}
media-typer@1.1.0: {}
@@ -11363,6 +11491,25 @@ snapshots:
micromark-util-types: 1.1.0
uvu: 0.5.6
micromark-core-commonmark@2.0.3:
dependencies:
decode-named-character-reference: 1.1.0
devlop: 1.1.0
micromark-factory-destination: 2.0.1
micromark-factory-label: 2.0.1
micromark-factory-space: 2.0.1
micromark-factory-title: 2.0.1
micromark-factory-whitespace: 2.0.1
micromark-util-character: 2.1.1
micromark-util-chunked: 2.0.1
micromark-util-classify-character: 2.0.1
micromark-util-html-tag-name: 2.0.1
micromark-util-normalize-identifier: 2.0.1
micromark-util-resolve-all: 2.0.1
micromark-util-subtokenize: 2.1.0
micromark-util-symbol: 2.0.1
micromark-util-types: 2.0.2
micromark-extension-gfm-autolink-literal@1.0.5:
dependencies:
micromark-util-character: 1.2.0
@@ -11437,6 +11584,12 @@ snapshots:
micromark-util-symbol: 1.1.0
micromark-util-types: 1.1.0
micromark-factory-destination@2.0.1:
dependencies:
micromark-util-character: 2.1.1
micromark-util-symbol: 2.0.1
micromark-util-types: 2.0.2
micromark-factory-label@1.1.0:
dependencies:
micromark-util-character: 1.2.0
@@ -11444,11 +11597,23 @@ snapshots:
micromark-util-types: 1.1.0
uvu: 0.5.6
micromark-factory-label@2.0.1:
dependencies:
devlop: 1.1.0
micromark-util-character: 2.1.1
micromark-util-symbol: 2.0.1
micromark-util-types: 2.0.2
micromark-factory-space@1.1.0:
dependencies:
micromark-util-character: 1.2.0
micromark-util-types: 1.1.0
micromark-factory-space@2.0.1:
dependencies:
micromark-util-character: 2.1.1
micromark-util-types: 2.0.2
micromark-factory-title@1.1.0:
dependencies:
micromark-factory-space: 1.1.0
@@ -11456,6 +11621,13 @@ snapshots:
micromark-util-symbol: 1.1.0
micromark-util-types: 1.1.0
micromark-factory-title@2.0.1:
dependencies:
micromark-factory-space: 2.0.1
micromark-util-character: 2.1.1
micromark-util-symbol: 2.0.1
micromark-util-types: 2.0.2
micromark-factory-whitespace@1.1.0:
dependencies:
micromark-factory-space: 1.1.0
@@ -11463,30 +11635,61 @@ snapshots:
micromark-util-symbol: 1.1.0
micromark-util-types: 1.1.0
micromark-factory-whitespace@2.0.1:
dependencies:
micromark-factory-space: 2.0.1
micromark-util-character: 2.1.1
micromark-util-symbol: 2.0.1
micromark-util-types: 2.0.2
micromark-util-character@1.2.0:
dependencies:
micromark-util-symbol: 1.1.0
micromark-util-types: 1.1.0
micromark-util-character@2.1.1:
dependencies:
micromark-util-symbol: 2.0.1
micromark-util-types: 2.0.2
micromark-util-chunked@1.1.0:
dependencies:
micromark-util-symbol: 1.1.0
micromark-util-chunked@2.0.1:
dependencies:
micromark-util-symbol: 2.0.1
micromark-util-classify-character@1.1.0:
dependencies:
micromark-util-character: 1.2.0
micromark-util-symbol: 1.1.0
micromark-util-types: 1.1.0
micromark-util-classify-character@2.0.1:
dependencies:
micromark-util-character: 2.1.1
micromark-util-symbol: 2.0.1
micromark-util-types: 2.0.2
micromark-util-combine-extensions@1.1.0:
dependencies:
micromark-util-chunked: 1.1.0
micromark-util-types: 1.1.0
micromark-util-combine-extensions@2.0.1:
dependencies:
micromark-util-chunked: 2.0.1
micromark-util-types: 2.0.2
micromark-util-decode-numeric-character-reference@1.1.0:
dependencies:
micromark-util-symbol: 1.1.0
micromark-util-decode-numeric-character-reference@2.0.2:
dependencies:
micromark-util-symbol: 2.0.1
micromark-util-decode-string@1.1.0:
dependencies:
decode-named-character-reference: 1.1.0
@@ -11494,24 +11697,49 @@ snapshots:
micromark-util-decode-numeric-character-reference: 1.1.0
micromark-util-symbol: 1.1.0
micromark-util-decode-string@2.0.1:
dependencies:
decode-named-character-reference: 1.1.0
micromark-util-character: 2.1.1
micromark-util-decode-numeric-character-reference: 2.0.2
micromark-util-symbol: 2.0.1
micromark-util-encode@1.1.0: {}
micromark-util-encode@2.0.1: {}
micromark-util-html-tag-name@1.2.0: {}
micromark-util-html-tag-name@2.0.1: {}
micromark-util-normalize-identifier@1.1.0:
dependencies:
micromark-util-symbol: 1.1.0
micromark-util-normalize-identifier@2.0.1:
dependencies:
micromark-util-symbol: 2.0.1
micromark-util-resolve-all@1.1.0:
dependencies:
micromark-util-types: 1.1.0
micromark-util-resolve-all@2.0.1:
dependencies:
micromark-util-types: 2.0.2
micromark-util-sanitize-uri@1.2.0:
dependencies:
micromark-util-character: 1.2.0
micromark-util-encode: 1.1.0
micromark-util-symbol: 1.1.0
micromark-util-sanitize-uri@2.0.1:
dependencies:
micromark-util-character: 2.1.1
micromark-util-encode: 2.0.1
micromark-util-symbol: 2.0.1
micromark-util-subtokenize@1.1.0:
dependencies:
micromark-util-chunked: 1.1.0
@@ -11519,10 +11747,21 @@ snapshots:
micromark-util-types: 1.1.0
uvu: 0.5.6
micromark-util-subtokenize@2.1.0:
dependencies:
devlop: 1.1.0
micromark-util-chunked: 2.0.1
micromark-util-symbol: 2.0.1
micromark-util-types: 2.0.2
micromark-util-symbol@1.1.0: {}
micromark-util-symbol@2.0.1: {}
micromark-util-types@1.1.0: {}
micromark-util-types@2.0.2: {}
micromark@3.2.0:
dependencies:
'@types/debug': 4.1.12
@@ -11545,6 +11784,28 @@ snapshots:
transitivePeerDependencies:
- supports-color
micromark@4.0.2:
dependencies:
'@types/debug': 4.1.12
debug: 4.4.0(supports-color@5.5.0)
decode-named-character-reference: 1.1.0
devlop: 1.1.0
micromark-core-commonmark: 2.0.3
micromark-factory-space: 2.0.1
micromark-util-character: 2.1.1
micromark-util-chunked: 2.0.1
micromark-util-combine-extensions: 2.0.1
micromark-util-decode-numeric-character-reference: 2.0.2
micromark-util-encode: 2.0.1
micromark-util-normalize-identifier: 2.0.1
micromark-util-resolve-all: 2.0.1
micromark-util-sanitize-uri: 2.0.1
micromark-util-subtokenize: 2.1.0
micromark-util-symbol: 2.0.1
micromark-util-types: 2.0.2
transitivePeerDependencies:
- supports-color
micromatch@4.0.8:
dependencies:
braces: 3.0.3
@@ -12401,6 +12662,15 @@ snapshots:
transitivePeerDependencies:
- supports-color
remark-parse@11.0.0:
dependencies:
'@types/mdast': 4.0.4
mdast-util-from-markdown: 2.0.2
micromark-util-types: 2.0.2
unified: 11.0.5
transitivePeerDependencies:
- supports-color
remark-rehype@10.1.0:
dependencies:
'@types/hast': 2.3.10
@@ -12408,18 +12678,18 @@ snapshots:
mdast-util-to-hast: 12.3.0
unified: 10.1.2
remark-stringify@10.0.3:
remark-stringify@11.0.0:
dependencies:
'@types/mdast': 3.0.15
mdast-util-to-markdown: 1.5.0
unified: 10.1.2
'@types/mdast': 4.0.4
mdast-util-to-markdown: 2.1.2
unified: 11.0.5
remark@14.0.3:
remark@15.0.1:
dependencies:
'@types/mdast': 3.0.15
remark-parse: 10.0.2
remark-stringify: 10.0.3
unified: 10.1.2
'@types/mdast': 4.0.4
remark-parse: 11.0.0
remark-stringify: 11.0.0
unified: 11.0.5
transitivePeerDependencies:
- supports-color
@@ -13142,6 +13412,16 @@ snapshots:
trough: 2.2.0
vfile: 5.3.7
unified@11.0.5:
dependencies:
'@types/unist': 3.0.3
bail: 2.0.2
devlop: 1.1.0
extend: 3.0.2
is-plain-obj: 4.1.0
trough: 2.2.0
vfile: 6.0.3
unist-util-find-after@5.0.0:
dependencies:
'@types/unist': 3.0.3
@@ -1,4 +1,7 @@
from llama_index.server.api.callbacks.agent_call_tool import AgentCallTool
from llama_index.server.api.callbacks.artifact_transform import (
InlineAnnotationTransformer,
)
from llama_index.server.api.callbacks.base import EventCallback
from llama_index.server.api.callbacks.llamacloud import LlamaCloudFileDownload
from llama_index.server.api.callbacks.source_nodes import SourceNodesFromToolCall
@@ -12,4 +15,5 @@ __all__ = [
"SuggestNextQuestions",
"LlamaCloudFileDownload",
"AgentCallTool",
"InlineAnnotationTransformer",
]
@@ -0,0 +1,24 @@
import logging
from typing import Any
from llama_index.server.api.callbacks.base import EventCallback
from llama_index.server.models.artifacts import ArtifactEvent
from llama_index.server.utils.inline import to_inline_annotation_event
logger = logging.getLogger("uvicorn")
class InlineAnnotationTransformer(EventCallback):
"""
Transforms an event to AgentStream with inline annotation format.
"""
async def run(self, event: Any) -> Any:
# handle for ArtifactEvent specifically as it's only supported by inline annotation
if isinstance(event, ArtifactEvent):
return to_inline_annotation_event(event)
return event
@classmethod
def from_default(cls, *args: Any, **kwargs: Any) -> "InlineAnnotationTransformer":
return cls()
@@ -18,6 +18,7 @@ from llama_index.core.workflow import (
from llama_index.server.api.callbacks import (
AgentCallTool,
EventCallback,
InlineAnnotationTransformer,
LlamaCloudFileDownload,
SourceNodesFromToolCall,
SuggestNextQuestions,
@@ -72,6 +73,7 @@ def chat_router(
callbacks: list[EventCallback] = [
AgentCallTool(),
InlineAnnotationTransformer(),
SourceNodesFromToolCall(),
LlamaCloudFileDownload(background_tasks),
]
@@ -5,6 +5,7 @@ from typing import Literal, Optional, Union
from llama_index.core.workflow.events import Event
from llama_index.server.models.chat import ChatAPIMessage
from pydantic import BaseModel
from llama_index.server.utils.inline import get_inline_annotations
logger = logging.getLogger(__name__)
@@ -33,10 +34,9 @@ class Artifact(BaseModel):
@classmethod
def from_message(cls, message: ChatAPIMessage) -> Optional["Artifact"]:
if not message.annotations or not isinstance(message.annotations, list):
return None
inline_annotations = get_inline_annotations(message)
for annotation in message.annotations:
for annotation in inline_annotations:
if isinstance(annotation, dict) and annotation.get("type") == "artifact":
try:
artifact = cls.model_validate(annotation.get("data"))
@@ -0,0 +1,81 @@
import json
import re
from typing import Any, List
from pydantic import ValidationError
from llama_index.core.workflow.events import Event
from llama_index.server.models.chat import ChatAPIMessage
from llama_index.core.agent.workflow.workflow_events import AgentStream
INLINE_ANNOTATION_KEY = (
"annotation" # the language key to detect inline annotation code in markdown
)
def get_inline_annotations(message: ChatAPIMessage) -> List[Any]:
"""Extract inline annotations from a chat message."""
markdown_content = message.content
inline_annotations: List[Any] = []
# Regex to match annotation code blocks
# Matches ```annotation followed by content until closing ```
annotation_regex = re.compile(
rf"```{re.escape(INLINE_ANNOTATION_KEY)}\s*\n([\s\S]*?)\n```", re.MULTILINE
)
for match in annotation_regex.finditer(markdown_content):
json_content = match.group(1).strip() if match.group(1) else None
if not json_content:
continue
try:
# Parse the JSON content
parsed = json.loads(json_content)
# Check for required fields in the parsed annotation
if (
not isinstance(parsed, dict)
or "type" not in parsed
or "data" not in parsed
):
continue
# Extract the annotation data
inline_annotations.append(parsed)
except (json.JSONDecodeError, ValidationError) as error:
# Skip invalid annotations - they might be malformed JSON or invalid schema
print(f"Failed to parse annotation: {error}")
return inline_annotations
def to_inline_annotation(item: dict) -> str:
"""
To append inline annotations to the stream, we need to wrap the annotation in a code block with the language key.
The language key is `annotation` and the code block is wrapped in backticks.
```annotation
{
"type": "artifact",
"data": {...}
}
```
"""
return f"\n```{INLINE_ANNOTATION_KEY}\n{json.dumps(item)}\n```\n"
def to_inline_annotation_event(event: Event) -> AgentStream:
"""
Convert an event to an AgentStream with inline annotation format.
"""
event_dict = event.model_dump()
return AgentStream(
delta=to_inline_annotation(event_dict),
response="",
current_agent_name="assistant",
tool_calls=[],
raw=event_dict,
)