Compare commits

..

3 Commits

Author SHA1 Message Date
github-actions[bot] 109ec63779 Release @llamaindex/server@0.1.6 (#1886)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: marcusschiesser <17126+marcusschiesser@users.noreply.github.com>
2025-04-24 19:40:11 +07:00
Thuc Pham 82d4b46fe4 feat: re-add supports for artifacts (#1869) 2025-04-24 19:28:15 +07:00
Logan f8c2d0b8ad Cleanup remaining workflows docs (#1881) 2025-04-23 16:15:49 -07:00
29 changed files with 812 additions and 184 deletions
+1
View File
@@ -7,3 +7,4 @@ dist/
.source/
# prttier doesn't support mdx3 we are using
*.mdx
packages/server/server/
+8
View File
@@ -1,5 +1,13 @@
# examples
## 0.3.12
### Patch Changes
- Updated dependencies [82d4b46]
- @llamaindex/server@0.1.6
- @llamaindex/tools@0.0.7
## 0.3.11
### Patch Changes
+17
View File
@@ -0,0 +1,17 @@
# Run LlamaIndex Server with simple steps
1. Setup environment variables
```bash
export OPENAI_API_KEY=<your-openai-api-key>
```
2. Run the server
```bash
npx tsx llamaindex-server/simple-workflow/index.ts
```
3. Open the app at `http://localhost:4000` and start chatting with the agent
![Screenshot](./screenshot.png)
Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

@@ -0,0 +1,19 @@
import { OpenAI } from "@llamaindex/openai";
import { LlamaIndexServer } from "@llamaindex/server";
import { weather } from "@llamaindex/tools";
import "dotenv/config";
import { agent } from "llamaindex";
const weatherAgent = agent({
tools: [weather()],
llm: new OpenAI({ model: "gpt-4o-mini" }),
});
new LlamaIndexServer({
workflow: () => weatherAgent,
uiConfig: {
appTitle: "Weather Agent",
starterQuestions: ["Ho Chi Minh city weather", "New York weather"],
},
port: 4000,
}).start();
+3 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@llamaindex/examples",
"version": "0.3.11",
"version": "0.3.12",
"private": true,
"scripts": {
"lint": "eslint .",
@@ -50,8 +50,9 @@
"@llamaindex/together": "^0.0.12",
"@llamaindex/jinaai": "^0.0.12",
"@llamaindex/perplexity": "^0.0.9",
"@llamaindex/server": "^0.1.6",
"@llamaindex/supabase": "^0.1.1",
"@llamaindex/tools": "^0.0.6",
"@llamaindex/tools": "^0.0.7",
"@notionhq/client": "^2.2.15",
"@pinecone-database/pinecone": "^4.0.0",
"@llamaindex/assemblyai": "^0.1.1",
+6
View File
@@ -1,5 +1,11 @@
# @llamaindex/server
## 0.1.6
### Patch Changes
- 82d4b46: feat: re-add supports for artifacts
## 0.1.5
### Patch Changes
@@ -1,149 +0,0 @@
"use client";
import { ChatSection as ChatSectionUI } from "@llamaindex/chat-ui";
import "@llamaindex/chat-ui/styles/markdown.css";
import "@llamaindex/chat-ui/styles/pdf.css";
import { useChat } from "ai/react";
import { Sparkles, Star } from "lucide-react";
import { useEffect, useMemo, useState } from "react";
import { RenderingErrors } from "./rendering-errors";
import { Button } from "./ui/button";
import CustomChatInput from "./ui/chat/chat-input";
import CustomChatMessages from "./ui/chat/chat-messages";
import { fetchComponentDefinitions } from "./ui/chat/custom/events/loader";
import { ComponentDef } from "./ui/chat/custom/events/types";
import { getConfig } from "./ui/lib/utils";
export default function ChatSection() {
const [componentDefs, setComponentDefs] = useState<ComponentDef[]>([]);
const [errors, setErrors] = useState<string[]>([]); // contain all errors when compiling with Babel and runtime
const appendError = (error: string) => {
setErrors((prev) => [...prev, error]);
};
const uniqueErrors = useMemo(() => {
return Array.from(new Set(errors));
}, [errors]);
// fetch component definitions and use Babel to tranform JSX code to JS code
// this is triggered only once when the page is initialised
useEffect(() => {
fetchComponentDefinitions().then(({ components, errors }) => {
setComponentDefs(components);
if (errors.length > 0) {
setErrors((prev) => [...prev, ...errors]);
}
});
}, []);
const handler = useChat({
api: getConfig("CHAT_API"),
onError: (error: unknown) => {
if (!(error instanceof Error)) throw error;
let errorMessage: string;
try {
errorMessage = JSON.parse(error.message).detail;
} catch (e) {
errorMessage = error.message;
}
alert(errorMessage);
},
experimental_throttle: 100,
});
return (
<>
<div className="grid h-screen w-screen grid-cols-4 gap-4 overflow-hidden">
<div className="col-span-1">
<div className="flex flex-col gap-8 p-2 pl-4">
<div className="flex items-center gap-2">
<Sparkles className="size-4" />
<h1 className="font-semibold">{getConfig("APP_TITLE")}</h1>
</div>
<RenderingErrors
uniqueErrors={uniqueErrors}
clearErrors={() => setErrors([])}
/>
</div>
</div>
<div className="col-span-2 h-full min-h-0">
<ChatSectionUI handler={handler} className="p-0">
<CustomChatMessages
componentDefs={componentDefs}
appendError={appendError}
/>
<CustomChatInput />
</ChatSectionUI>
</div>
<div className="col-span-1">
<LlamaIndexLinks />
</div>
</div>
<TailwindCDNInjection />
</>
);
}
function LlamaIndexLinks() {
return (
<div className="flex items-center justify-end gap-4 p-2 pr-4">
<div className="flex items-center gap-2">
<a
href="https://www.llamaindex.ai/"
target="_blank"
rel="noopener noreferrer"
className="text-sm text-gray-600 hover:text-gray-800 dark:text-gray-400 dark:hover:text-gray-200"
>
Built by LlamaIndex
</a>
<img
className="h-[24px] w-[24px] rounded-sm"
src="/llama.png"
alt="Llama Logo"
/>
</div>
<a
href="https://github.com/run-llama/LlamaIndexTS"
target="_blank"
rel="noopener noreferrer"
>
<Button variant="outline" size="sm">
<Star className="mr-2 size-4" />
Star on GitHub
</Button>
</a>
</div>
);
}
/**
* The default border color has changed to `currentColor` in Tailwind CSS v4,
* so adding these compatibility styles to make sure everything still
* looks the same as it did with Tailwind CSS v3.
*/
const tailwindConfig = `
@import "tailwindcss";
@layer base {
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--color-gray-200, currentColor);
}
}
`;
function TailwindCDNInjection() {
if (!getConfig("COMPONENTS_API")) return null;
return (
<>
<script
async
src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"
></script>
<style type="text/tailwindcss">{tailwindConfig}</style>
</>
);
}
@@ -0,0 +1,24 @@
"use client";
import { ChatCanvas, useChatCanvas } from "@llamaindex/chat-ui";
import { ResizableHandle, ResizablePanel } from "../../resizable";
import { CodeArtifactRenderer } from "./preview";
export function ChatCanvasPanel() {
const { displayedArtifact, isCanvasOpen } = useChatCanvas();
if (!displayedArtifact || !isCanvasOpen) return null;
return (
<>
<ResizableHandle withHandle />
<ResizablePanel defaultSize={60} minSize={50}>
<ChatCanvas className="w-full">
<ChatCanvas.CodeArtifact
tabs={{ preview: <CodeArtifactRenderer /> }}
/>
<ChatCanvas.DocumentArtifact />
</ChatCanvas>
</ResizablePanel>
</>
);
}
@@ -0,0 +1,155 @@
"use client";
import { CodeArtifact, useChatCanvas } from "@llamaindex/chat-ui";
import { Loader2, WandSparkles } from "lucide-react";
import React, { FunctionComponent, useEffect, useState } from "react";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "../../accordion";
import { buttonVariants } from "../../button";
import { cn } from "../../lib/utils";
import { DynamicComponentErrorBoundary } from "../custom/events/error-boundary";
import { parseComponent } from "../custom/events/loader";
const SUPPORTED_FRONTEND_PREVIEW = [
"js",
"ts",
"jsx",
"tsx",
"javascript",
"typescript",
];
export function CodeArtifactRenderer() {
const { displayedArtifact } = useChatCanvas();
if (displayedArtifact?.type !== "code") return null;
const codeArtifact = displayedArtifact as CodeArtifact;
if (!SUPPORTED_FRONTEND_PREVIEW.includes(codeArtifact.data.language)) {
return (
<div className="flex h-full items-center justify-center gap-2">
<p className="text-sm text-gray-500">
Preview is not supported for this language
</p>
</div>
);
}
return <CodeArtifactRendererComp artifact={codeArtifact} />;
}
function CodeArtifactRendererComp({ artifact }: { artifact: CodeArtifact }) {
const { appendErrors } = useChatCanvas();
const [isRendering, setIsRendering] = useState(true);
const [component, setComponent] = useState<FunctionComponent | null>(null);
const {
data: { code, file_name },
} = artifact;
useEffect(() => {
const renderComponent = async () => {
setIsRendering(true);
const { component: parsedComponent, error } = await parseComponent(
code,
file_name,
);
if (error) {
setComponent(null);
appendErrors(artifact, [error]);
} else {
setComponent(() => parsedComponent);
}
setIsRendering(false);
};
renderComponent();
}, [artifact]);
if (isRendering) {
return (
<div className="flex h-full items-center justify-center gap-2">
<Loader2 className="size-4 animate-spin" />
<p className="text-sm text-gray-500">Rendering Artifact...</p>
</div>
);
}
if (!component) {
return <CodeErrors artifact={artifact} />;
}
return (
<DynamicComponentErrorBoundary
onError={(error) => appendErrors(artifact, [error])}
>
{React.createElement(component)}
</DynamicComponentErrorBoundary>
);
}
function CodeErrors({ artifact }: { artifact: CodeArtifact }) {
const { getCodeErrors, fixCodeErrors } = useChatCanvas();
const uniqueErrors = getCodeErrors(artifact);
if (uniqueErrors.length === 0) return null;
return (
<div className="flex flex-col gap-10 px-10 pt-10">
<p className="text-center text-sm text-gray-500">
Error when rendering code, please check the details and try fixing them.
</p>
<Accordion
type="single"
defaultValue="errors"
collapsible
className="w-full rounded-xl border border-gray-100 bg-white shadow-md"
>
<AccordionItem value="errors" className="border-none px-4">
<AccordionTrigger className="py-2 hover:no-underline">
<div className="flex flex-1 items-center justify-between">
<div className="flex items-center gap-2">
<span className="text-muted-foreground font-bold">
Rendering errors
</span>
<span className="inline-flex h-5 w-5 items-center justify-center rounded-full bg-yellow-500 text-xs text-white">
{uniqueErrors.length}
</span>
</div>
<div className="flex items-center gap-2">
<div
className={cn(
buttonVariants({ variant: "default", size: "sm" }),
"mr-2 h-8 cursor-pointer bg-gradient-to-r from-blue-500 to-purple-500 text-white hover:from-blue-600 hover:to-purple-600",
)}
onClick={(e) => {
e.stopPropagation();
fixCodeErrors(artifact);
}}
>
<WandSparkles className="mr-2 h-4 w-4" />
<span>Fix errors</span>
</div>
</div>
</div>
</AccordionTrigger>
<AccordionContent className="pb-4">
<div className="space-y-2">
{uniqueErrors.map((error, index) => (
<p key={index} className="text-muted-foreground text-sm">
{error}
</p>
))}
</div>
</AccordionContent>
</AccordionItem>
</Accordion>
</div>
);
}
@@ -0,0 +1,55 @@
"use client";
import { Sparkles, Star } from "lucide-react";
import { Button } from "../button";
import { getConfig } from "../lib/utils";
export function ChatHeader() {
return (
<div className="flex items-center justify-between px-4 pt-2">
<ChatAppTitle />
<LlamaIndexLinks />
</div>
);
}
function ChatAppTitle() {
return (
<div className="flex items-center gap-2">
<Sparkles className="size-4" />
<h1 className="font-semibold">{getConfig("APP_TITLE")}</h1>
</div>
);
}
function LlamaIndexLinks() {
return (
<div className="flex items-center justify-end gap-4">
<div className="flex items-center gap-2">
<a
href="https://www.llamaindex.ai/"
target="_blank"
rel="noopener noreferrer"
className="text-sm text-gray-600 hover:text-gray-800 dark:text-gray-400 dark:hover:text-gray-200"
>
Built by LlamaIndex
</a>
<img
className="h-[24px] w-[24px] rounded-sm"
src="/llama.png"
alt="Llama Logo"
/>
</div>
<a
href="https://github.com/run-llama/LlamaIndexTS"
target="_blank"
rel="noopener noreferrer"
>
<Button variant="outline" size="sm">
<Star className="mr-2 size-4" />
Star on GitHub
</Button>
</a>
</div>
);
}
@@ -0,0 +1,32 @@
"use client";
/**
* The default border color has changed to `currentColor` in Tailwind CSS v4,
* so adding these compatibility styles to make sure everything still
* looks the same as it did with Tailwind CSS v3.
*/
const tailwindConfig = `
@import "tailwindcss";
@layer base {
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--color-gray-200, currentColor);
}
}
`;
export function ChatInjection() {
return (
<>
<script
async
src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"
></script>
<style type="text/tailwindcss">{tailwindConfig}</style>
</>
);
}
@@ -19,6 +19,7 @@ export function ChatMessageContent({
<ToolAnnotations />
<ChatMessage.Content.Image />
<DynamicEvents componentDefs={componentDefs} appendError={appendError} />
<ChatMessage.Content.Artifact />
<ChatMessage.Content.Markdown />
<ChatMessage.Content.DocumentFile />
<ChatMessage.Content.Source />
@@ -0,0 +1,89 @@
"use client";
import { ChatSection as ChatUI } from "@llamaindex/chat-ui";
import { useChat } from "ai/react";
import { useEffect, useMemo, useState } from "react";
import { getConfig } from "../lib/utils";
import { ResizablePanel, ResizablePanelGroup } from "../resizable";
import { ChatCanvasPanel } from "./canvas/panel";
import { ChatHeader } from "./chat-header";
import { ChatInjection } from "./chat-injection";
import CustomChatInput from "./chat-input";
import CustomChatMessages from "./chat-messages";
import { DynamicEventsErrors } from "./custom/events/dynamic-events-errors";
import { fetchComponentDefinitions } from "./custom/events/loader";
import { ComponentDef } from "./custom/events/types";
export default function ChatSection() {
const handler = useChat({
api: getConfig("CHAT_API"),
onError: (error: unknown) => {
if (!(error instanceof Error)) throw error;
let errorMessage: string;
try {
errorMessage = JSON.parse(error.message).detail;
} catch (e) {
errorMessage = error.message;
}
alert(errorMessage);
},
experimental_throttle: 100,
});
return (
<>
<div className="flex h-screen w-screen flex-col overflow-hidden">
<ChatHeader />
<ChatUI
handler={handler}
className="flex min-h-0 flex-1 flex-row justify-center gap-4 px-4 py-0"
>
<ResizablePanelGroup direction="horizontal">
<ChatSectionPanel />
<ChatCanvasPanel />
</ResizablePanelGroup>
</ChatUI>
</div>
<ChatInjection />
</>
);
}
function ChatSectionPanel() {
const [componentDefs, setComponentDefs] = useState<ComponentDef[]>([]);
const [dynamicEventsErrors, setDynamicEventsErrors] = useState<string[]>([]); // contain all errors when rendering dynamic events from componentDir
const appendError = (error: string) => {
setDynamicEventsErrors((prev) => [...prev, error]);
};
const uniqueErrors = useMemo(() => {
return Array.from(new Set(dynamicEventsErrors));
}, [dynamicEventsErrors]);
// fetch component definitions and use Babel to tranform JSX code to JS code
// this is triggered only once when the page is initialised
useEffect(() => {
fetchComponentDefinitions().then(({ components, errors }) => {
setComponentDefs(components);
if (errors.length > 0) {
setDynamicEventsErrors((prev) => [...prev, ...errors]);
}
});
}, []);
return (
<ResizablePanel defaultSize={40} minSize={30} className="max-w-1/2 mx-auto">
<div className="flex h-full min-w-0 flex-1 flex-col gap-4">
<DynamicEventsErrors
errors={uniqueErrors}
clearErrors={() => setDynamicEventsErrors([])}
/>
<CustomChatMessages
componentDefs={componentDefs}
appendError={appendError}
/>
<CustomChatInput />
</div>
</ResizablePanel>
);
}
@@ -8,18 +8,18 @@ import {
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "./ui/accordion";
import { buttonVariants } from "./ui/button";
import { cn } from "./ui/lib/utils";
} from "../../../accordion";
import { buttonVariants } from "../../../button";
import { cn } from "../../../lib/utils";
export function RenderingErrors({
uniqueErrors,
export function DynamicEventsErrors({
errors,
clearErrors,
}: {
uniqueErrors: string[];
errors: string[];
clearErrors: () => void;
}) {
if (uniqueErrors.length === 0) return null;
if (errors.length === 0) return null;
return (
<Accordion
type="single"
@@ -32,10 +32,10 @@ export function RenderingErrors({
<div className="flex flex-1 items-center justify-between">
<div className="flex items-center gap-2">
<span className="text-muted-foreground font-bold">
Rendering errors
Errors when rendering dynamic events from components directory
</span>
<span className="inline-flex h-5 w-5 items-center justify-center rounded-full bg-yellow-500 text-xs text-white">
{uniqueErrors.length}
{errors.length}
</span>
</div>
<div
@@ -52,7 +52,7 @@ export function RenderingErrors({
</AccordionTrigger>
<AccordionContent className="pb-4">
<div className="space-y-2">
{uniqueErrors.map((error, index) => (
{errors.map((error, index) => (
<p key={index} className="text-muted-foreground text-sm">
{error}
</p>
@@ -122,7 +122,7 @@ export async function parseImports(code: string) {
const importPromises = imports.map(async ({ name, source }) => {
if (!(source in SOURCE_MAP)) {
throw new Error(
`Fail to import ${name} from ${source}. Reason: Module not found. \nCurrently we only support importing UI components from Shadcn components, widgets from "llamaindex/chat-ui/widgets" and icons from "lucide-react". See https://ts.llamaindex.ai/docs/llamaindex/modules/ui/llamaindex-server for more information.`,
`Fail to import ${name} from ${source}. Reason: Module not found. \nCurrently we only support importing UI components from Shadcn components, widgets from "llamaindex/chat-ui/widgets" and icons from "lucide-react"`,
);
}
try {
@@ -2,9 +2,10 @@
"use client";
import * as Babel from "@babel/standalone";
import { FunctionComponent } from "react";
import { getConfig } from "../../../lib/utils";
import { parseImports } from "./import";
import { ComponentDef, EventRenderComponent } from "./types";
import { ComponentDef } from "./types";
export type SourceComponentDef = {
type: string;
@@ -17,11 +18,10 @@ export async function fetchComponentDefinitions(): Promise<{
errors: string[];
}> {
const endpoint = getConfig("COMPONENTS_API");
if (!endpoint)
return {
components: [],
errors: ["/api/components endpoint is not defined in config"],
};
if (!endpoint) {
console.warn("/api/components endpoint is not defined in config");
return { components: [], errors: [] };
}
const response = await fetch(endpoint);
const components = (await response.json()) as SourceComponentDef[];
@@ -59,10 +59,10 @@ export async function fetchComponentDefinitions(): Promise<{
}
// create React component from code
async function parseComponent(
export async function parseComponent(
code: string,
filename: string,
): Promise<{ component: EventRenderComponent | null; error?: string }> {
): Promise<{ component: FunctionComponent<any> | null; error?: string }> {
try {
const [transpiledCode, resolvedImports] = await Promise.all([
transpileCode(code, filename),
@@ -119,7 +119,7 @@ async function createComponentFromCode(
transpiledCode: string,
importMap: Record<string, any>,
componentName: string | null = "Component",
): Promise<EventRenderComponent | null> {
): Promise<FunctionComponent<any> | null> {
const argNames = Object.keys(importMap); // e.g., ["React", "Button", "Badge"]
const argValues = Object.values(importMap); // list of corresponding modules
@@ -5,5 +5,3 @@ export type ComponentDef = {
type: string; // eg. deep_research_event
comp: FunctionComponent<{ events: JSONValue[] }>;
};
export type EventRenderComponent = FunctionComponent<{ events: JSONValue[] }>;
+3
View File
@@ -1,5 +1,8 @@
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "@llamaindex/chat-ui/styles/markdown.css";
import "@llamaindex/chat-ui/styles/pdf.css";
import "./globals.css";
const inter = Inter({ subsets: ["latin"] });
+1 -1
View File
@@ -3,7 +3,7 @@
import { Loader2 } from "lucide-react";
import dynamic from "next/dynamic";
const ChatSection = dynamic(() => import("./components/chat-section"), {
const ChatSection = dynamic(() => import("./components/ui/chat/chat-section"), {
ssr: false,
loading: () => (
<div className="flex h-full w-full items-center justify-center">
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "@llamaindex/server",
"description": "LlamaIndex Server",
"version": "0.1.5",
"version": "0.1.6",
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
@@ -62,7 +62,7 @@
"@babel/types": "^7.27.0",
"@hookform/resolvers": "^5.0.1",
"@llama-flow/core": "^0.3.4",
"@llamaindex/chat-ui": "0.4.0",
"@llamaindex/chat-ui": "0.4.1",
"@llamaindex/env": "workspace:*",
"@radix-ui/react-accordion": "^1.2.3",
"@radix-ui/react-alert-dialog": "^1.1.7",
+127
View File
@@ -1,10 +1,12 @@
import { randomUUID } from "@llamaindex/env";
import type { Message } from "ai";
import {
MetadataMode,
WorkflowEvent,
type Metadata,
type NodeWithScore,
} from "llamaindex";
import { z } from "zod";
// Events that appended to stream as annotations
export type SourceEventNode = {
@@ -88,3 +90,128 @@ export function toAgentRunEvent(input: {
},
});
}
export type ArtifactType = "code" | "document";
export type Artifact<T = unknown> = {
created_at: number;
type: ArtifactType;
data: T;
};
export type CodeArtifactData = {
file_name: string;
code: string;
language: string;
};
export type DocumentArtifactData = {
title: string;
content: string;
type: string; // markdown, html,...
};
export type CodeArtifact = Artifact<CodeArtifactData> & {
type: "code";
};
export type DocumentArtifact = Artifact<DocumentArtifactData> & {
type: "document";
};
export class ArtifactEvent extends WorkflowEvent<{
type: "artifact";
data: Artifact;
}> {}
export const codeArtifactSchema = z.object({
type: z.literal("code"),
data: z.object({
file_name: z.string(),
code: z.string(),
language: z.string(),
}),
created_at: z.number(),
});
export const documentArtifactSchema = z.object({
type: z.literal("document"),
data: z.object({
title: z.string(),
content: z.string(),
type: z.string(),
}),
created_at: z.number(),
});
export const artifactSchema = z.union([
codeArtifactSchema,
documentArtifactSchema,
]);
export const artifactAnnotationSchema = z.object({
type: z.literal("artifact"),
data: artifactSchema,
});
export function extractAllArtifacts(messages: Message[]): Artifact[] {
const allArtifacts: Artifact[] = [];
for (const message of messages) {
const artifacts =
message.annotations
?.filter(
(annotation) =>
artifactAnnotationSchema.safeParse(annotation).success,
)
.map((artifact) => artifact as Artifact) ?? [];
allArtifacts.push(...artifacts);
}
return allArtifacts;
}
export function extractLastArtifact(
requestBody: unknown,
type: "code",
): CodeArtifact | undefined;
export function extractLastArtifact(
requestBody: unknown,
type: "document",
): DocumentArtifact | undefined;
export function extractLastArtifact(
requestBody: unknown,
type?: ArtifactType,
): Artifact | undefined;
export function extractLastArtifact(
requestBody: unknown,
type?: ArtifactType,
): CodeArtifact | DocumentArtifact | Artifact | undefined {
const { messages } = (requestBody as { messages?: Message[] }) ?? {};
if (!messages) return undefined;
const artifacts = extractAllArtifacts(messages);
if (!artifacts.length) return undefined;
if (type) {
const lastArtifact = artifacts
.reverse()
.find((artifact) => artifact.type === type);
if (!lastArtifact) return undefined;
if (type === "code") {
return lastArtifact as CodeArtifact;
}
if (type === "document") {
return lastArtifact as DocumentArtifact;
}
}
return artifacts[artifacts.length - 1];
}
+4 -1
View File
@@ -29,7 +29,10 @@ export const handleChat = async (
const stream = await runWorkflow(workflow, {
userInput: lastMessage.content,
chatHistory: messages.slice(0, -1) as ChatMessage[],
chatHistory: messages.slice(0, -1).map((message) => ({
content: message.content,
role: message.role as ChatMessage["role"],
})),
});
pipeStreamToResponse(res, stream);
+6
View File
@@ -1,5 +1,11 @@
# @llamaindex/tools
## 0.0.7
### Patch Changes
- 82d4b46: feat: re-add supports for artifacts
## 0.0.6
### Patch Changes
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@llamaindex/tools",
"description": "LlamaIndex Tools",
"version": "0.0.6",
"version": "0.0.7",
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
+2
View File
@@ -1,4 +1,6 @@
export * from "./tools/code-artifact-generator";
export * from "./tools/code-generator";
export * from "./tools/document-artifact-generator";
export * from "./tools/document-generator";
export * from "./tools/duckduckgo";
export * from "./tools/form-filling";
@@ -0,0 +1,112 @@
import { Settings, type JSONValue } from "@llamaindex/core/global";
import type { ChatMessage, LLM } from "@llamaindex/core/llms";
import { tool } from "@llamaindex/core/tools";
import { z } from "zod";
const CODE_ARTIFACT_GENERATION_PROMPT = `You are a highly skilled software engineer. Your task is to generate a code artifact based on the user's request.
Follow these instructions exactly:
1. Carefully read the user's requirements. If any details are ambiguous or missing, make reasonable assumptions and clearly reflect those in your output.
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.
- Please don't use inline styles or any external libraries.
- For Shadcn ui components, you can import them like this: import { Button } from "@/components/ui/button"
- 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. Return ONLY valid, parseable JSON in the following format. Do not include any explanations, markdown formatting, or code blocks around the JSON.
{
"type": "code",
"data": {
"file_name": "filename.ext",
"code": "your code here",
"language": "programming language"
}
}
4. Your entire response must be valid JSON matching this format exactly. Do not include any explanations, markdown formatting, or code blocks around the JSON.
---
EXAMPLE
{
"type": "code",
"data": {
"file_name": "MyComponent.tsx",
"code": "import React from 'react';\nexport default function MyComponent() { return <div>Hello World</div>; }",
"language": "typescript"
}
}`;
type CodeArtifact = {
created_at: number;
type: "code";
data: {
file_name: string;
code: string;
language: string;
};
};
export type CodeArtifactGeneratorToolOutput = CodeArtifact;
export const codeArtifactGenerator = ({
llm,
lastArtifact,
}: {
llm?: LLM;
lastArtifact?: CodeArtifact;
}) => {
return tool({
name: "code_artifact_generator",
description: "Generate a code artifact based on the input.",
parameters: z.object({
requirement: z
.string()
.describe("The description of the code artifact you want to build."),
}),
execute: async ({ requirement }) => {
const artifact = await generateCodeArtifact(
requirement,
lastArtifact,
llm,
);
return artifact as JSONValue;
},
});
};
async function generateCodeArtifact(
requirement: string,
lastArtifact?: CodeArtifact,
llm?: LLM,
): Promise<CodeArtifact | null> {
const userMessage = `
Generate a code artifact: ${requirement}
${lastArtifact ? `The existing content is: \n\`\`\`${lastArtifact.data.code}\`\`\`` : ""}`;
const messages: ChatMessage[] = [
{ role: "system", content: CODE_ARTIFACT_GENERATION_PROMPT },
{ role: "user", content: userMessage },
];
try {
const response = await (llm ?? Settings.llm).chat({ messages });
const content = response.message.content.toString();
const jsonContent = content
.replace(/^```json\s*|\s*```$/g, "")
.replace(/^`+|`+$/g, "")
.trim();
const parsedResponse = JSON.parse(jsonContent);
const artifact: CodeArtifact = {
type: "code",
data: parsedResponse.data,
created_at: Date.now(),
};
return artifact;
} catch (error) {
console.error("Failed to generate code artifact", error);
return null;
}
}
@@ -0,0 +1,112 @@
import { Settings, type JSONValue } from "@llamaindex/core/global";
import type { ChatMessage, LLM } from "@llamaindex/core/llms";
import { tool } from "@llamaindex/core/tools";
import { z } from "zod";
const DOCUMENT_ARTIFACT_GENERATION_PROMPT = `You are a highly skilled content creator. Your task is to generate a document artifact based on the user's request.
Follow these instructions exactly:
1. Carefully read the user's requirements. If any details are ambiguous or missing, make reasonable assumptions and clearly reflect those in your output.
2. For document requests:
- Always generate Markdown (.md) documents.
- Use clear structure: headings, subheadings, lists, and tables for comparisons.
- Ensure content is concise, well-organized, and directly addresses the user's needs.
3. Return ONLY valid, parseable JSON in the following format. Do not include any explanations, markdown formatting, or code blocks around the JSON.
{
"type": "document",
"data": {
"title": "Document Title",
"content": "Markdown content here",
"type": "markdown"
}
}
4. Your entire response must be valid JSON matching this format exactly. Do not include any explanations, markdown formatting, or code blocks around the JSON.
---
EXAMPLE
{
"type": "document",
"data": {
"title": "Quick Start Guide",
"content": "# Quick Start\n\nFollow these steps to begin...",
"type": "markdown"
}
}`;
type DocumentArtifact = {
created_at: number;
type: "document";
data: {
title: string;
content: string;
type: string;
};
};
export type DocumentArtifactGeneratorToolOutput = DocumentArtifact;
export const documentArtifactGenerator = ({
llm,
lastArtifact,
}: {
llm?: LLM;
lastArtifact?: DocumentArtifact;
}) => {
return tool({
name: "document_artifact_generator",
description: "Generate a document artifact based on the input.",
parameters: z.object({
requirement: z
.string()
.describe(
"The description of the document artifact you want to build.",
),
}),
execute: async ({ requirement }) => {
const artifact = await generateDocumentArtifact(
requirement,
lastArtifact,
llm,
);
return artifact as JSONValue;
},
});
};
async function generateDocumentArtifact(
requirement: string,
lastArtifact?: DocumentArtifact,
llm?: LLM,
): Promise<DocumentArtifact | null> {
const userMessage = `
Generate a document artifact: ${requirement}
${lastArtifact ? `The existing content is: \n\`\`\`${lastArtifact.data.content}\`\`\`` : ""}
`;
const messages: ChatMessage[] = [
{ role: "system", content: DOCUMENT_ARTIFACT_GENERATION_PROMPT },
{ role: "user", content: userMessage },
];
try {
const response = await (llm ?? Settings.llm).chat({ messages });
const content = response.message.content.toString();
const jsonContent = content
.replace(/^```json\s*|\s*```$/g, "")
.replace(/^`+|`+$/g, "")
.trim();
const parsedResponse = JSON.parse(jsonContent);
const artifact: DocumentArtifact = {
type: "document",
data: parsedResponse.data,
created_at: Date.now(),
};
return artifact;
} catch (error) {
console.error("Failed to generate document artifact", error);
return null;
}
}
+12 -6
View File
@@ -713,6 +713,9 @@ importers:
'@llamaindex/replicate':
specifier: ^0.0.44
version: link:../packages/providers/replicate
'@llamaindex/server':
specifier: ^0.1.6
version: link:../packages/server
'@llamaindex/supabase':
specifier: ^0.1.1
version: link:../packages/providers/storage/supabase
@@ -720,7 +723,7 @@ importers:
specifier: ^0.0.12
version: link:../packages/providers/together
'@llamaindex/tools':
specifier: ^0.0.6
specifier: ^0.0.7
version: link:../packages/tools
'@llamaindex/upstash':
specifier: ^0.0.16
@@ -1701,8 +1704,8 @@ importers:
specifier: ^0.3.4
version: 0.3.4(@modelcontextprotocol/sdk@1.9.0)(hono@4.7.7)(next@15.3.0(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(p-retry@6.2.1)(zod@3.24.2)
'@llamaindex/chat-ui':
specifier: 0.4.0
version: 0.4.0(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
specifier: 0.4.1
version: 0.4.1(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@llamaindex/env':
specifier: workspace:*
version: link:../env
@@ -3866,8 +3869,8 @@ packages:
peerDependencies:
react: ^18.2.0 || ^19.0.0 || ^19.0.0-rc
'@llamaindex/chat-ui@0.4.0':
resolution: {integrity: sha512-u9jOUuyKPDFnJsorfH8oIE0UVO+zlabbD8lgTFbN37XUdYFFG4rteEYwUWc/n4/h/GjnFcTfxpiyWmiwTKzicw==}
'@llamaindex/chat-ui@0.4.1':
resolution: {integrity: sha512-lRPsnpPe2mBJJVlKEpIKSmr5ofCGVRP7U21CfSCFaFVA/V81BaZZ/gyt2G8+vzXmTWGqRYJGBNHcsGVPx0G7Mw==}
peerDependencies:
react: ^18.2.0 || ^19.0.0 || ^19.0.0-rc
@@ -3904,6 +3907,7 @@ packages:
'@mixedbread-ai/sdk@2.2.11':
resolution: {integrity: sha512-NJiY6BVPR+s/DTzUPQS1Pv418trOmII/8hftmIqxXlYaKbIrgJimQfwCW9M6Y21YPcMA8zTQGYZHm4IWlMjIQw==}
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
'@modelcontextprotocol/sdk@0.5.0':
resolution: {integrity: sha512-RXgulUX6ewvxjAG0kOpLMEdXXWkzWgaoCGaA2CwNW7cQCIphjpJhjpHSiaPdVCnisjRF/0Cm9KWHUuIoeiAblQ==}
@@ -15989,12 +15993,14 @@ snapshots:
- react-dom
- supports-color
'@llamaindex/chat-ui@0.4.0(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
'@llamaindex/chat-ui@0.4.1(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
dependencies:
'@llamaindex/pdf-viewer': 1.3.0(@types/react@19.0.10)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@radix-ui/react-accordion': 1.2.4(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@radix-ui/react-collapsible': 1.1.4(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@radix-ui/react-hover-card': 1.1.7(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@radix-ui/react-icons': 1.3.2(react@19.1.0)
'@radix-ui/react-popover': 1.1.7(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@radix-ui/react-progress': 1.1.3(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@radix-ui/react-select': 2.1.7(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@radix-ui/react-slot': 1.2.0(@types/react@19.0.10)(react@19.1.0)