-
Notifications
You must be signed in to change notification settings - Fork 183
Add support for artifact in llama-index-server #580
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 1 commit
Commits
Show all changes
28 commits
Select commit
Hold shift + click to select a range
be4550f
support artifact
leehuwuj ba29391
migrate poetry to uv
leehuwuj 629a179
fix ci
leehuwuj d787ecf
update ci
leehuwuj 630a76d
Refactor artifact generation tools by introducing separate CodeGenera…
leehuwuj 96d846f
enhance code
leehuwuj 11fa3bc
remove previous content from tool input
leehuwuj f85e913
fix test
leehuwuj 03ee8c3
bump chat ui
leehuwuj 2b27ebd
revert changes
leehuwuj 66636e5
remove dead code
leehuwuj ef56e1d
Add artifact workflows for code and document generation
leehuwuj 322f43d
remove app_writer workflow
leehuwuj 2c3ac6d
Refactor artifact workflow classes and UI event handling
leehuwuj f975b79
Use uv to release package
leehuwuj 4fdfca5
Refactor artifact workflows and UI components
leehuwuj 1ba5a26
move code
leehuwuj fbed55a
Merge remote-tracking branch 'origin/main' into lee/add-artifact
leehuwuj 9e42cf3
Merge remote-tracking branch 'origin/main' into lee/add-artifact
leehuwuj 872f238
sort artifact
leehuwuj e09de60
fix mypy
leehuwuj 11ae75d
fix adding custom route does not work
leehuwuj 51f70a9
fix mypy
leehuwuj 830b3e5
revert create-llama change
leehuwuj 484cb9c
disable e2e test for python package change
leehuwuj 88af75c
fix missing set memory
leehuwuj 6104c3d
remove include last artifact in the code
leehuwuj 2f17cdc
Add ArtifactEvent model and update workflows to use it
leehuwuj File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
4 changes: 4 additions & 0 deletions
4
llama-index-server/llama_index/server/tools/artifact/__init__.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
from llama_index.server.tools.artifact.code_generator import CodeGenerator | ||
from llama_index.server.tools.artifact.document_generator import DocumentGenerator | ||
|
||
__all__ = ["CodeGenerator", "DocumentGenerator"] |
145 changes: 145 additions & 0 deletions
145
llama-index-server/llama_index/server/tools/artifact/code_generator.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
import logging | ||
import re | ||
import time | ||
from typing import List, Optional | ||
|
||
from llama_index.core.llms import LLM | ||
from llama_index.core.llms.llm import ChatMessage, MessageRole | ||
from llama_index.core.settings import Settings | ||
from llama_index.core.tools.function_tool import FunctionTool | ||
from llama_index.server.api.models import Artifact, ArtifactType, CodeArtifactData | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
CODE_GENERATION_PROMPT = """ | ||
You are a highly skilled content creator and software engineer. | ||
Your task is to generate code to resolve 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. | ||
If the previous code is provided, carefully analyze the code with the request to make the right changes. | ||
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: | ||
``` | ||
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. | ||
Example: | ||
```typescript | ||
import React from "react"; | ||
|
||
export default function MyComponent() { | ||
return <div>Hello World</div>; | ||
} | ||
``` | ||
""" | ||
|
||
|
||
class CodeGenerator: | ||
def __init__( | ||
self, | ||
llm: Optional[LLM] = None, | ||
last_artifact: Optional[Artifact] = None, | ||
) -> None: | ||
if llm is None: | ||
if Settings.llm is None: | ||
raise ValueError( | ||
"Missing llm. Please provide a valid LLM or set the LLM using Settings.llm." | ||
) | ||
llm = Settings.llm | ||
self.llm = llm | ||
self.last_artifact = last_artifact | ||
|
||
def prepare_chat_messages( | ||
self, requirement: str, language: str, previous_code: Optional[str] = None | ||
) -> List[ChatMessage]: | ||
user_messages: List[ChatMessage] = [] | ||
user_messages.append(ChatMessage(role=MessageRole.USER, content=requirement)) | ||
if previous_code: | ||
user_messages.append( | ||
ChatMessage( | ||
role=MessageRole.USER, | ||
content=f"```{language}\n{previous_code}\n```", | ||
) | ||
) | ||
else: | ||
user_messages.append( | ||
ChatMessage( | ||
role=MessageRole.USER, | ||
content=f"Write code in {language}. Wrap the code in ```{language}``` block.", | ||
) | ||
) | ||
return user_messages | ||
|
||
async def generate_code( | ||
self, | ||
file_name: str, | ||
language: str, | ||
requirement: str, | ||
previous_code: Optional[str] = None, | ||
leehuwuj marked this conversation as resolved.
Show resolved
Hide resolved
|
||
) -> Artifact: | ||
""" | ||
Generate code based on the provided requirement. | ||
|
||
Args: | ||
file_name (str): The name of the file to generate. | ||
language (str): The language of the code to generate (Only "typescript" and "python" is supported now) | ||
requirement (str): Provide a detailed requirement for the code to be generated/updated. | ||
old_content (Optional[str]): Existing code content to be modified or referenced. Defaults to None. | ||
|
||
Returns: | ||
Artifact: A dictionary containing the generated artifact details | ||
(type, data). | ||
""" | ||
user_messages = self.prepare_chat_messages(requirement, language, previous_code) | ||
|
||
messages: List[ChatMessage] = [ | ||
ChatMessage(role=MessageRole.SYSTEM, content=CODE_GENERATION_PROMPT), | ||
*user_messages, | ||
] | ||
|
||
try: | ||
response = await self.llm.achat(messages) | ||
raw_content = response.message.content | ||
if not raw_content: | ||
raise ValueError( | ||
"Empty response. Try with a clearer requirement or provide previous code." | ||
) | ||
|
||
# Extract code from code block in raw content | ||
code_block = re.search(r"```(.*?)\n(.*?)```", raw_content, re.DOTALL) | ||
if not code_block: | ||
raise ValueError("Couldn't parse code from the response.") | ||
code = code_block.group(2).strip() | ||
return Artifact( | ||
created_at=int(time.time()), | ||
type=ArtifactType.CODE, | ||
data=CodeArtifactData( | ||
file_name=file_name, | ||
code=code, | ||
language=language, | ||
), | ||
) | ||
except Exception as e: | ||
raise ValueError(f"Couldn't generate code. {e}") | ||
|
||
def to_tool(self) -> FunctionTool: | ||
""" | ||
Converts the CodeGenerator instance into a FunctionTool. | ||
|
||
Returns: | ||
FunctionTool: A tool that can be used by agents. | ||
""" | ||
return FunctionTool.from_defaults( | ||
self.generate_code, | ||
name="artifact_code_generator", | ||
description="Generate/update code based on a requirement.", | ||
) |
129 changes: 129 additions & 0 deletions
129
llama-index-server/llama_index/server/tools/artifact/document_generator.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
import logging | ||
import re | ||
import time | ||
from typing import List, Literal, Optional | ||
|
||
from llama_index.core.llms import LLM | ||
from llama_index.core.llms.llm import ChatMessage, MessageRole | ||
from llama_index.core.settings import Settings | ||
from llama_index.core.tools.function_tool import FunctionTool | ||
from llama_index.server.api.models import Artifact, ArtifactType, DocumentArtifactData | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
DOCUMENT_GENERATION_PROMPT = """ | ||
You are a highly skilled writer and content creator. | ||
Your task is to generate documents 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. | ||
If previous content is provided, carefully analyze it with the request to make the right changes. | ||
2. For document creation: | ||
- Create well-structured documents with clear headings, paragraphs, and formatting. | ||
- Use concise and professional language. | ||
- Ensure content is accurate, well-researched, and relevant to the topic. | ||
- Organize information logically with a proper introduction, body, and conclusion when appropriate. | ||
- Add citations or references when necessary. | ||
3. For document editing: | ||
- Maintain the original document's structure unless requested otherwise. | ||
- Improve clarity, flow, and grammar while preserving the original message. | ||
- Remove redundancies and strengthen weak points. | ||
4. Answer in appropriate format which wrapped by ```<format>``` block with a file name for the content, no other text or comments. | ||
Example: | ||
```markdown | ||
# Title | ||
|
||
Content | ||
``` | ||
""" | ||
|
||
|
||
class DocumentGenerator: | ||
def __init__( | ||
self, | ||
llm: Optional[LLM] = None, | ||
last_artifact: Optional[Artifact] = None, | ||
) -> None: | ||
if llm is None: | ||
if Settings.llm is None: | ||
raise ValueError( | ||
"Missing llm. Please provide a valid LLM or set the LLM using Settings.llm." | ||
) | ||
llm = Settings.llm | ||
self.llm = llm | ||
self.last_artifact = last_artifact | ||
|
||
def prepare_chat_messages( | ||
self, requirement: str, previous_content: Optional[str] = None | ||
) -> List[ChatMessage]: | ||
user_messages: List[ChatMessage] = [] | ||
user_messages.append(ChatMessage(role=MessageRole.USER, content=requirement)) | ||
if previous_content: | ||
user_messages.append( | ||
ChatMessage(role=MessageRole.USER, content=previous_content) | ||
) | ||
return user_messages | ||
|
||
async def generate_document( | ||
self, | ||
file_name: str, | ||
document_format: Literal["markdown", "html"], | ||
requirement: str, | ||
previous_content: Optional[str] = None, | ||
) -> Artifact: | ||
""" | ||
Generate document content based on the provided requirement. | ||
|
||
Args: | ||
file_name (str): The name of the file to generate. | ||
document_format (str): The format of the document to generate. (Only "markdown" and "html" are supported now) | ||
requirement (str): A detailed requirement for the document to be generated/updated. | ||
previous_content (Optional[str]): Existing document content to be modified or referenced. Defaults to None. | ||
|
||
Returns: | ||
Artifact: The generated document. | ||
""" | ||
user_messages = self.prepare_chat_messages(requirement, previous_content) | ||
|
||
messages: List[ChatMessage] = [ | ||
ChatMessage(role=MessageRole.SYSTEM, content=DOCUMENT_GENERATION_PROMPT), | ||
*user_messages, | ||
] | ||
|
||
try: | ||
response = await self.llm.achat(messages) | ||
raw_content = response.message.content | ||
if not raw_content: | ||
raise ValueError( | ||
"Empty response. Try with a clearer requirement or provide previous content." | ||
) | ||
# Extract content from the response | ||
content = re.search(r"```(.*?)\n(.*?)```", raw_content, re.DOTALL) | ||
if not content: | ||
raise ValueError("Couldn't parse content from the response.") | ||
return Artifact( | ||
created_at=int(time.time()), | ||
type=ArtifactType.DOCUMENT, | ||
data=DocumentArtifactData( | ||
title=file_name, | ||
content=content.group(2).strip(), | ||
type=document_format, | ||
), | ||
) | ||
except Exception as e: | ||
raise ValueError(f"Couldn't generate document. {e}") | ||
|
||
def to_tool(self) -> FunctionTool: | ||
""" | ||
Converts the DocumentGenerator instance into a FunctionTool. | ||
|
||
Returns: | ||
FunctionTool: A tool that can be used by agents. | ||
""" | ||
return FunctionTool.from_defaults( | ||
self.generate_document, | ||
name="artifact_document_generator", | ||
description="Generate/update documents based on a requirement.", | ||
) |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.