Compare commits

...

10 Commits

Author SHA1 Message Date
github-actions[bot] 6304114ef5 Release 0.2.17 (#357)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-10-09 16:31:50 +07:00
Marcus Schiesser 6335de1174 docs: changeset 2024-10-09 16:18:11 +07:00
Huu Le b9184ff59a fix: (FastAPI) Using LlamaCloud parameters does not use the configured value in the environment. (#358) 2024-10-09 16:13:35 +07:00
Thuc Pham cd3fcd0512 bump: use latest LITS (#343) 2024-10-09 13:40:04 +07:00
github-actions[bot] a47d778602 Release 0.2.16 (#349)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-10-08 17:28:40 +07:00
Marcus Schiesser 7f4ac228ee Don't need to run generate script for LlamaCloud (#352) 2024-10-08 16:56:12 +07:00
Marcus Schiesser 5263bde8e7 feat: Use selected LlamaCloud index in multi-agent template (#350) 2024-10-08 16:54:14 +07:00
Huu Le 4dee65b93d add astral's uv tool to github action (#351) 2024-10-08 16:19:20 +07:00
Huu Le c60182a925 Add mypy checker (#346) 2024-10-08 15:17:38 +07:00
Marcus Schiesser 0e78ba4603 fix: .env not loaded on poetry run generate (#348)
--------
Co-authored-by: leehuwuj <leehuwuj@gmail.com>
2024-10-08 13:41:37 +07:00
46 changed files with 512 additions and 250 deletions
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
python-version: ["3.11"]
os: [macos-latest, windows-latest, ubuntu-22.04]
frameworks: ["fastapi"]
datasources: ["--no-files", "--example-file"]
datasources: ["--no-files", "--example-file", "--llamacloud"]
defaults:
run:
shell: bash
+3
View File
@@ -17,6 +17,9 @@ jobs:
- uses: pnpm/action-setup@v3
- name: Install uv
uses: astral-sh/setup-uv@v3
- name: Setup Node.js
uses: actions/setup-node@v4
with:
+1
View File
@@ -1,2 +1,3 @@
pnpm format
pnpm lint
uvx ruff format --check templates/
+16
View File
@@ -1,5 +1,21 @@
# create-llama
## 0.2.17
### Patch Changes
- cd3fcd0: bump: use LlamaIndexTS 0.6.18
- 6335de1: Fix using LlamaCloud selector does not use the configured values in the environment (Python)
## 0.2.16
### Patch Changes
- 0e78ba4: Fix: programmatically ensure index for LlamaCloud
- 0e78ba4: Fix .env not loaded on poetry run generate
- 7f4ac22: Don't need to run generate script for LlamaCloud
- 5263bde: Use selected LlamaCloud index in multi-agent template
## 0.2.15
### Patch Changes
+149 -93
View File
@@ -15,6 +15,8 @@ const dataSource: string = process.env.DATASOURCE
? process.env.DATASOURCE
: "--example-file";
// TODO: add support for other templates
if (
dataSource === "--example-file" // XXX: this test provides its own data source - only trigger it on one data source (usually the CI matrix will trigger multiple data sources)
) {
@@ -45,14 +47,86 @@ if (
const observabilityOptions = ["llamatrace", "traceloop"];
// Run separate tests for each observability option to reduce CI runtime
test.describe("Test resolve python dependencies with observability", () => {
// Testing with streaming template, vectorDb: none, tools: none, and dataSource: --example-file
for (const observability of observabilityOptions) {
test(`observability: ${observability}`, async () => {
const cwd = await createTestDir();
test.describe("Mypy check", () => {
test.describe.configure({ retries: 0 });
await createAndCheckLlamaProject({
// Test vector databases
for (const vectorDb of vectorDbs) {
test(`Mypy check for vectorDB: ${vectorDb}`, async () => {
const cwd = await createTestDir();
const { pyprojectPath } = await createAndCheckLlamaProject({
options: {
cwd,
templateType: "streaming",
templateFramework,
dataSource: "--example-file",
vectorDb,
tools: "none",
port: 3000,
externalPort: 8000,
postInstallAction: "none",
templateUI: undefined,
appType: "--no-frontend",
llamaCloudProjectName: undefined,
llamaCloudIndexName: undefined,
observability: undefined,
},
});
const pyprojectContent = fs.readFileSync(pyprojectPath, "utf-8");
if (vectorDb !== "none") {
if (vectorDb === "pg") {
expect(pyprojectContent).toContain(
"llama-index-vector-stores-postgres",
);
} else {
expect(pyprojectContent).toContain(
`llama-index-vector-stores-${vectorDb}`,
);
}
}
});
}
// Test tools
for (const tool of toolOptions) {
test(`Mypy check for tool: ${tool}`, async () => {
const cwd = await createTestDir();
const { pyprojectPath } = await createAndCheckLlamaProject({
options: {
cwd,
templateType: "streaming",
templateFramework,
dataSource: "--example-file",
vectorDb: "none",
tools: tool,
port: 3000,
externalPort: 8000,
postInstallAction: "none",
templateUI: undefined,
appType: "--no-frontend",
llamaCloudProjectName: undefined,
llamaCloudIndexName: undefined,
observability: undefined,
},
});
const pyprojectContent = fs.readFileSync(pyprojectPath, "utf-8");
if (tool === "wikipedia.WikipediaToolSpec") {
expect(pyprojectContent).toContain("wikipedia");
}
if (tool === "google.GoogleSearchToolSpec") {
expect(pyprojectContent).toContain("google");
}
});
}
// Test data sources
for (const dataSource of dataSources) {
const dataSourceType = dataSource.split(" ")[0];
test(`Mypy check for data source: ${dataSourceType}`, async () => {
const cwd = await createTestDir();
const { pyprojectPath } = await createAndCheckLlamaProject({
options: {
cwd,
templateType: "streaming",
@@ -60,89 +134,53 @@ if (
dataSource,
vectorDb: "none",
tools: "none",
port: 3000, // port, not used
externalPort: 8000, // externalPort, not used
postInstallAction: "none", // postInstallAction
templateUI: undefined, // ui
appType: "--no-frontend", // appType
llamaCloudProjectName: undefined, // llamaCloudProjectName
llamaCloudIndexName: undefined, // llamaCloudIndexName
port: 3000,
externalPort: 8000,
postInstallAction: "none",
templateUI: undefined,
appType: "--no-frontend",
llamaCloudProjectName: undefined,
llamaCloudIndexName: undefined,
observability: undefined,
},
});
const pyprojectContent = fs.readFileSync(pyprojectPath, "utf-8");
if (dataSource.includes("--web-source")) {
expect(pyprojectContent).toContain("llama-index-readers-web");
}
if (dataSource.includes("--db-source")) {
expect(pyprojectContent).toContain("llama-index-readers-database");
}
});
}
// Test observability options
for (const observability of observabilityOptions) {
test(`Mypy check for observability: ${observability}`, async () => {
const cwd = await createTestDir();
const { pyprojectPath } = await createAndCheckLlamaProject({
options: {
cwd,
templateType: "streaming",
templateFramework,
dataSource: "--example-file",
vectorDb: "none",
tools: "none",
port: 3000,
externalPort: 8000,
postInstallAction: "none",
templateUI: undefined,
appType: "--no-frontend",
llamaCloudProjectName: undefined,
llamaCloudIndexName: undefined,
observability,
},
});
});
}
});
test.describe("Test resolve python dependencies", () => {
for (const vectorDb of vectorDbs) {
for (const tool of toolOptions) {
for (const dataSource of dataSources) {
const dataSourceType = dataSource.split(" ")[0];
const toolDescription = tool === "none" ? "no tools" : tool;
const optionDescription = `vectorDb: ${vectorDb}, ${toolDescription}, dataSource: ${dataSourceType}`;
test(`options: ${optionDescription}`, async () => {
const cwd = await createTestDir();
const { pyprojectPath, projectPath } =
await createAndCheckLlamaProject({
options: {
cwd,
templateType: "streaming",
templateFramework,
dataSource,
vectorDb,
tools: tool,
port: 3000, // port, not used
externalPort: 8000, // externalPort, not used
postInstallAction: "none", // postInstallAction
templateUI: undefined, // ui
appType: "--no-frontend", // appType
llamaCloudProjectName: undefined, // llamaCloudProjectName
llamaCloudIndexName: undefined, // llamaCloudIndexName
observability: undefined, // observability
},
});
// Additional checks for specific dependencies
// Verify that specific dependencies are in pyproject.toml
const pyprojectContent = fs.readFileSync(pyprojectPath, "utf-8");
if (vectorDb !== "none") {
if (vectorDb === "pg") {
expect(pyprojectContent).toContain(
"llama-index-vector-stores-postgres",
);
} else {
expect(pyprojectContent).toContain(
`llama-index-vector-stores-${vectorDb}`,
);
}
}
if (tool !== "none") {
if (tool === "wikipedia.WikipediaToolSpec") {
expect(pyprojectContent).toContain("wikipedia");
}
if (tool === "google.GoogleSearchToolSpec") {
expect(pyprojectContent).toContain("google");
}
}
// Check for data source specific dependencies
if (dataSource.includes("--web-source")) {
expect(pyprojectContent).toContain("llama-index-readers-web");
}
if (dataSource.includes("--db-source")) {
expect(pyprojectContent).toContain(
"llama-index-readers-database ",
);
}
});
}
}
}
});
}
async function createAndCheckLlamaProject({
@@ -161,21 +199,39 @@ async function createAndCheckLlamaProject({
const pyprojectPath = path.join(projectPath, "pyproject.toml");
expect(fs.existsSync(pyprojectPath)).toBeTruthy();
// Run poetry lock
const env = {
...process.env,
POETRY_VIRTUALENVS_IN_PROJECT: "true",
};
// Run poetry install
try {
const { stdout, stderr } = await execAsync(
"poetry config virtualenvs.in-project true && poetry lock --no-update",
{ cwd: projectPath },
const { stdout: installStdout, stderr: installStderr } = await execAsync(
"poetry install",
{ cwd: projectPath, env },
);
console.log("poetry lock stdout:", stdout);
console.error("poetry lock stderr:", stderr);
console.log("poetry install stdout:", installStdout);
console.error("poetry install stderr:", installStderr);
} catch (error) {
console.error("Error running poetry lock:", error);
console.error("Error running poetry install:", error);
throw error;
}
// Check if poetry.lock file was created
expect(fs.existsSync(path.join(projectPath, "poetry.lock"))).toBeTruthy();
// Run poetry run mypy
try {
const { stdout: mypyStdout, stderr: mypyStderr } = await execAsync(
"poetry run mypy .",
{ cwd: projectPath, env },
);
console.log("poetry run mypy stdout:", mypyStdout);
console.error("poetry run mypy stderr:", mypyStderr);
} catch (error) {
console.error("Error running mypy:", error);
throw error;
}
// If we reach this point without throwing an error, the test passes
expect(true).toBeTruthy();
return { pyprojectPath, projectPath };
}
+1 -1
View File
@@ -65,7 +65,7 @@ const getVectorDBEnvs = (
{
name: "PG_CONNECTION_STRING",
description:
"For generating a connection URI, see https://docs.timescale.com/use-timescale/latest/services/create-a-service\nThe PostgreSQL connection string.",
"For generating a connection URI, see https://supabase.com/vector\nThe PostgreSQL connection string.",
},
];
+1 -1
View File
@@ -123,7 +123,7 @@ const getAdditionalDependencies = (
extras: ["rsa"],
});
dependencies.push({
name: "psycopg2",
name: "psycopg2-binary",
version: "^2.9.9",
});
break;
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "create-llama",
"version": "0.2.15",
"version": "0.2.17",
"description": "Create LlamaIndex-powered apps with one command",
"keywords": [
"rag",
@@ -1,17 +1,19 @@
import os
from typing import List
from app.engine.index import IndexConfig, get_index
from app.engine.tools import ToolFactory
from llama_index.core.agent import AgentRunner
from llama_index.core.callbacks import CallbackManager
from llama_index.core.settings import Settings
from llama_index.core.tools import BaseTool
from llama_index.core.tools.query_engine import QueryEngineTool
def get_chat_engine(filters=None, params=None, event_handlers=None, **kwargs):
system_prompt = os.getenv("SYSTEM_PROMPT")
top_k = int(os.getenv("TOP_K", 0))
tools = []
tools: List[BaseTool] = []
callback_manager = CallbackManager(handlers=event_handlers or [])
# Add query tool if index exists
@@ -25,7 +27,8 @@ def get_chat_engine(filters=None, params=None, event_handlers=None, **kwargs):
tools.append(query_engine_tool)
# Add additional tools
tools += ToolFactory.from_env()
configured_tools: List[BaseTool] = ToolFactory.from_env()
tools.extend(configured_tools)
return AgentRunner.from_llm(
llm=Settings.llm,
@@ -1,7 +1,8 @@
import importlib
import os
from typing import Dict, List, Union
import yaml
import yaml # type: ignore
from llama_index.core.tools.function_tool import FunctionTool
from llama_index.core.tools.tool_spec.base import BaseToolSpec
@@ -17,7 +18,8 @@ class ToolFactory:
ToolType.LOCAL: "app.engine.tools",
}
def load_tools(tool_type: str, tool_name: str, config: dict) -> list[FunctionTool]:
@staticmethod
def load_tools(tool_type: str, tool_name: str, config: dict) -> List[FunctionTool]:
source_package = ToolFactory.TOOL_SOURCE_PACKAGE_MAP[tool_type]
try:
if "ToolSpec" in tool_name:
@@ -43,24 +45,32 @@ class ToolFactory:
@staticmethod
def from_env(
map_result: bool = False,
) -> list[FunctionTool] | dict[str, FunctionTool]:
) -> Union[Dict[str, List[FunctionTool]], List[FunctionTool]]:
"""
Load tools from the configured file.
Params:
- use_map: if True, return map of tool name and the tool itself
Args:
map_result: If True, return a map of tool names to their corresponding tools.
Returns:
A dictionary of tool names to lists of FunctionTools if map_result is True,
otherwise a list of FunctionTools.
"""
if map_result:
tools = {}
else:
tools = []
tools: Union[Dict[str, List[FunctionTool]], List[FunctionTool]] = (
{} if map_result else []
)
if os.path.exists("config/tools.yaml"):
with open("config/tools.yaml", "r") as f:
tool_configs = yaml.safe_load(f)
for tool_type, config_entries in tool_configs.items():
for tool_name, config in config_entries.items():
tool = ToolFactory.load_tools(tool_type, tool_name, config)
loaded_tools = ToolFactory.load_tools(
tool_type, tool_name, config
)
if map_result:
tools[tool_name] = tool
tools[tool_name] = loaded_tools # type: ignore
else:
tools.extend(tool)
tools.extend(loaded_tools) # type: ignore
return tools
@@ -87,7 +87,7 @@ class CodeGeneratorTool:
ChatMessage(role="user", content=user_message),
]
try:
sllm = Settings.llm.as_structured_llm(output_cls=CodeArtifact)
sllm = Settings.llm.as_structured_llm(output_cls=CodeArtifact) # type: ignore
response = sllm.chat(messages)
data: CodeArtifact = response.raw
return data.model_dump()
@@ -21,14 +21,15 @@ def duckduckgo_search(
"Please install it by running: `poetry add duckduckgo_search` or `pip install duckduckgo_search`"
)
params = {
"keywords": query,
"region": region,
"max_results": max_results,
}
results = []
with DDGS() as ddg:
results = list(ddg.text(**params))
results = list(
ddg.text(
keywords=query,
region=region,
max_results=max_results,
)
)
return results
@@ -51,13 +52,14 @@ def duckduckgo_image_search(
"duckduckgo_search package is required to use this function."
"Please install it by running: `poetry add duckduckgo_search` or `pip install duckduckgo_search`"
)
params = {
"keywords": query,
"region": region,
"max_results": max_results,
}
with DDGS() as ddg:
results = list(ddg.images(**params))
results = list(
ddg.images(
keywords=query,
region=region,
max_results=max_results,
)
)
return results
@@ -43,6 +43,6 @@ def get_chat_engine(filters=None, params=None, event_handlers=None, **kwargs):
memory=memory,
system_prompt=system_prompt,
retriever=retriever,
node_postprocessors=node_postprocessors,
node_postprocessors=node_postprocessors, # type: ignore
callback_manager=callback_manager,
)
@@ -1,6 +1,6 @@
import {
BaseChatEngine,
BaseToolWithCall,
ChatEngine,
OpenAIAgent,
QueryEngineTool,
} from "llamaindex";
@@ -45,7 +45,7 @@ export async function createChatEngine(documentIds?: string[], params?: any) {
const agent = new OpenAIAgent({
tools,
systemPrompt: process.env.SYSTEM_PROMPT,
}) as unknown as ChatEngine;
}) as unknown as BaseChatEngine;
return agent;
}
@@ -1,20 +1,22 @@
import logging
from typing import Any, Dict, List
import yaml
import yaml # type: ignore
from app.engine.loaders.db import DBLoaderConfig, get_db_documents
from app.engine.loaders.file import FileLoaderConfig, get_file_documents
from app.engine.loaders.web import WebLoaderConfig, get_web_documents
from llama_index.core import Document
logger = logging.getLogger(__name__)
def load_configs():
def load_configs() -> Dict[str, Any]:
with open("config/loaders.yaml") as f:
configs = yaml.safe_load(f)
return configs
def get_documents():
def get_documents() -> List[Document]:
documents = []
config = load_configs()
for loader_type, loader_config in config.items():
+8 -1
View File
@@ -1,5 +1,6 @@
import logging
from typing import List
from pydantic import BaseModel
logger = logging.getLogger(__name__)
@@ -11,7 +12,13 @@ class DBLoaderConfig(BaseModel):
def get_db_documents(configs: list[DBLoaderConfig]):
from llama_index.readers.database import DatabaseReader
try:
from llama_index.readers.database import DatabaseReader
except ImportError:
logger.error(
"Failed to import DatabaseReader. Make sure llama_index is installed."
)
raise
docs = []
for entry in configs:
+4 -2
View File
@@ -1,3 +1,5 @@
from typing import List, Optional
from pydantic import BaseModel, Field
@@ -8,8 +10,8 @@ class CrawlUrl(BaseModel):
class WebLoaderConfig(BaseModel):
driver_arguments: list[str] = Field(default=None)
urls: list[CrawlUrl]
driver_arguments: Optional[List[str]] = Field(default_factory=list)
urls: List[CrawlUrl]
def get_web_documents(config: WebLoaderConfig):
@@ -5,7 +5,7 @@ from app.api.routers.models import (
ChatData,
)
from app.api.routers.vercel_response import VercelStreamResponse
from app.engine import get_chat_engine
from app.engine.engine import get_chat_engine
from fastapi import APIRouter, BackgroundTasks, HTTPException, Request, status
chat_router = r = APIRouter()
@@ -28,8 +28,8 @@ async def chat(
# but agent workflow does not support them yet
# ignore chat params and use all documents for now
# TODO: generate filters based on doc_ids
# TODO: use chat params
engine = get_chat_engine(chat_history=messages)
params = data.data or {}
engine = get_chat_engine(chat_history=messages, params=params)
event_handler = engine.run(input=last_message_content, streaming=True)
return VercelStreamResponse(
@@ -18,11 +18,11 @@ def get_chat_engine(
agent_type = os.getenv("EXAMPLE_TYPE", "").lower()
match agent_type:
case "choreography":
agent = create_choreography(chat_history)
agent = create_choreography(chat_history, **kwargs)
case "orchestrator":
agent = create_orchestrator(chat_history)
agent = create_orchestrator(chat_history, **kwargs)
case _:
agent = create_workflow(chat_history)
agent = create_workflow(chat_history, **kwargs)
logger.info(f"Using agent pattern: {agent_type}")
@@ -8,8 +8,8 @@ from app.examples.researcher import create_researcher
from llama_index.core.chat_engine.types import ChatMessage
def create_choreography(chat_history: Optional[List[ChatMessage]] = None):
researcher = create_researcher(chat_history)
def create_choreography(chat_history: Optional[List[ChatMessage]] = None, **kwargs):
researcher = create_researcher(chat_history, **kwargs)
publisher = create_publisher(chat_history)
reviewer = FunctionCallingAgent(
name="reviewer",
@@ -21,12 +21,14 @@ def create_choreography(chat_history: Optional[List[ChatMessage]] = None):
name="writer",
agents=[researcher, reviewer, publisher],
description="expert in writing blog posts, needs researched information and images to write a blog post",
system_prompt=dedent("""
system_prompt=dedent(
"""
You are an expert in writing blog posts. You are given a task to write a blog post. Before starting to write the post, consult the researcher agent to get the information you need. Don't make up any information yourself.
After creating a draft for the post, send it to the reviewer agent to receive feedback and make sure to incorporate the feedback from the reviewer.
You can consult the reviewer and researcher a maximum of two times. Your output should contain only the blog post.
Finally, always request the publisher to create a document (PDF, HTML) and publish the blog post.
"""),
"""
),
# TODO: add chat_history support to AgentCallingAgent
# chat_history=chat_history,
)
@@ -8,28 +8,32 @@ from app.examples.researcher import create_researcher
from llama_index.core.chat_engine.types import ChatMessage
def create_orchestrator(chat_history: Optional[List[ChatMessage]] = None):
researcher = create_researcher(chat_history)
def create_orchestrator(chat_history: Optional[List[ChatMessage]] = None, **kwargs):
researcher = create_researcher(chat_history, **kwargs)
writer = FunctionCallingAgent(
name="writer",
description="expert in writing blog posts, need information and images to write a post",
system_prompt=dedent("""
system_prompt=dedent(
"""
You are an expert in writing blog posts.
You are given a task to write a blog post. Do not make up any information yourself.
If you don't have the necessary information to write a blog post, reply "I need information about the topic to write the blog post".
If you need to use images, reply "I need images about the topic to write the blog post". Do not use any dummy images made up by you.
If you have all the information needed, write the blog post.
"""),
"""
),
chat_history=chat_history,
)
reviewer = FunctionCallingAgent(
name="reviewer",
description="expert in reviewing blog posts, needs a written blog post to review",
system_prompt=dedent("""
system_prompt=dedent(
"""
You are an expert in reviewing blog posts. You are given a task to review a blog post. Review the post and fix any issues found yourself. You must output a final blog post.
A post must include at least one valid image. If not, reply "I need images about the topic to write the blog post". An image URL starting with "example" or "your website" is not valid.
Especially check for logical inconsistencies and proofread the post for grammar and spelling errors.
"""),
"""
),
chat_history=chat_history,
)
publisher = create_publisher(chat_history)
@@ -3,17 +3,19 @@ from textwrap import dedent
from typing import List
from app.agents.single import FunctionCallingAgent
from app.engine.index import get_index
from app.engine.index import IndexConfig, get_index
from app.engine.tools import ToolFactory
from llama_index.core.chat_engine.types import ChatMessage
from llama_index.core.tools import QueryEngineTool, ToolMetadata
def _create_query_engine_tool() -> QueryEngineTool:
def _create_query_engine_tool(params=None) -> QueryEngineTool:
"""
Provide an agent worker that can be used to query the index.
"""
index = get_index()
# Add query tool if index exists
index_config = IndexConfig(**(params or {}))
index = get_index(index_config)
if index is None:
return None
top_k = int(os.getenv("TOP_K", 0))
@@ -31,13 +33,13 @@ def _create_query_engine_tool() -> QueryEngineTool:
)
def _get_research_tools() -> QueryEngineTool:
def _get_research_tools(**kwargs) -> QueryEngineTool:
"""
Researcher take responsibility for retrieving information.
Try init wikipedia or duckduckgo tool if available.
"""
tools = []
query_engine_tool = _create_query_engine_tool()
query_engine_tool = _create_query_engine_tool(**kwargs)
if query_engine_tool is not None:
tools.append(query_engine_tool)
researcher_tool_names = ["duckduckgo", "wikipedia.WikipediaToolSpec"]
@@ -48,16 +50,17 @@ def _get_research_tools() -> QueryEngineTool:
return tools
def create_researcher(chat_history: List[ChatMessage]):
def create_researcher(chat_history: List[ChatMessage], **kwargs):
"""
Researcher is an agent that take responsibility for using tools to complete a given task.
"""
tools = _get_research_tools()
tools = _get_research_tools(**kwargs)
return FunctionCallingAgent(
name="researcher",
tools=tools,
description="expert in retrieving any unknown content or searching for images from the internet",
system_prompt=dedent("""
system_prompt=dedent(
"""
You are a researcher agent. You are given a research task.
If the conversation already includes the information and there is no new request for additional information from the user, you should return the appropriate content to the writer.
@@ -77,6 +80,7 @@ def create_researcher(chat_history: List[ChatMessage]):
If you use the tools but don't find any related information, please return "I didn't find any new information for {the topic}." along with the content you found. Don't try to make up information yourself.
If the request doesn't need any new information because it was in the conversation history, please return "The task doesn't need any new information. Please reuse the existing content in the conversation history."
"""),
"""
),
chat_history=chat_history,
)
@@ -17,9 +17,10 @@ from llama_index.core.workflow import (
)
def create_workflow(chat_history: Optional[List[ChatMessage]] = None):
def create_workflow(chat_history: Optional[List[ChatMessage]] = None, **kwargs):
researcher = create_researcher(
chat_history=chat_history,
**kwargs,
)
publisher = create_publisher(
chat_history=chat_history,
@@ -127,7 +128,8 @@ class BlogPostWorkflow(Workflow):
self, input: str, chat_history: List[ChatMessage]
) -> str:
prompt_template = PromptTemplate(
dedent("""
dedent(
"""
You are an expert in decision-making, helping people write and publish blog posts.
If the user is asking for a file or to publish content, respond with 'publish'.
If the user requests to write or update a blog post, respond with 'not_publish'.
@@ -140,7 +142,8 @@ class BlogPostWorkflow(Workflow):
Given the chat history and the new user request, decide whether to publish based on existing information.
Decision (respond with either 'not_publish' or 'publish'):
""")
"""
)
)
chat_history_str = "\n".join(
+12 -4
View File
@@ -1,7 +1,11 @@
from llama_index.embeddings.openai import OpenAIEmbedding
from llama_index.core.settings import Settings
from typing import Dict
import logging
import os
from typing import Dict
from llama_index.core.settings import Settings
from llama_index.embeddings.openai import OpenAIEmbedding
logger = logging.getLogger(__name__)
DEFAULT_MODEL = "gpt-3.5-turbo"
DEFAULT_EMBEDDING_MODEL = "text-embedding-3-large"
@@ -50,7 +54,11 @@ def embedding_config_from_env() -> Dict:
def init_llmhub():
from llama_index.llms.openai_like import OpenAILike
try:
from llama_index.llms.openai_like import OpenAILike
except ImportError:
logger.error("Failed to import OpenAILike. Make sure llama_index is installed.")
raise
llm_configs = llm_config_from_env()
embedding_configs = embedding_config_from_env()
@@ -33,8 +33,13 @@ def init_settings():
def init_ollama():
from llama_index.embeddings.ollama import OllamaEmbedding
from llama_index.llms.ollama.base import DEFAULT_REQUEST_TIMEOUT, Ollama
try:
from llama_index.embeddings.ollama import OllamaEmbedding
from llama_index.llms.ollama.base import DEFAULT_REQUEST_TIMEOUT, Ollama
except ImportError:
raise ImportError(
"Ollama support is not installed. Please install it with `poetry add llama-index-llms-ollama` and `poetry add llama-index-embeddings-ollama`"
)
base_url = os.getenv("OLLAMA_BASE_URL") or "http://127.0.0.1:11434"
request_timeout = float(
@@ -55,25 +60,29 @@ def init_openai():
from llama_index.llms.openai import OpenAI
max_tokens = os.getenv("LLM_MAX_TOKENS")
config = {
"model": os.getenv("MODEL"),
"temperature": float(os.getenv("LLM_TEMPERATURE", DEFAULT_TEMPERATURE)),
"max_tokens": int(max_tokens) if max_tokens is not None else None,
}
Settings.llm = OpenAI(**config)
Settings.llm = OpenAI(
model=os.getenv("MODEL", "gpt-4o-mini"),
temperature=float(os.getenv("LLM_TEMPERATURE", DEFAULT_TEMPERATURE)),
max_tokens=int(max_tokens) if max_tokens is not None else None,
)
dimensions = os.getenv("EMBEDDING_DIM")
config = {
"model": os.getenv("EMBEDDING_MODEL"),
"dimensions": int(dimensions) if dimensions is not None else None,
}
Settings.embed_model = OpenAIEmbedding(**config)
Settings.embed_model = OpenAIEmbedding(
model=os.getenv("EMBEDDING_MODEL", "text-embedding-3-small"),
dimensions=int(dimensions) if dimensions is not None else None,
)
def init_azure_openai():
from llama_index.core.constants import DEFAULT_TEMPERATURE
from llama_index.embeddings.azure_openai import AzureOpenAIEmbedding
from llama_index.llms.azure_openai import AzureOpenAI
try:
from llama_index.embeddings.azure_openai import AzureOpenAIEmbedding
from llama_index.llms.azure_openai import AzureOpenAI
except ImportError:
raise ImportError(
"Azure OpenAI support is not installed. Please install it with `poetry add llama-index-llms-azure-openai` and `poetry add llama-index-embeddings-azure-openai`"
)
llm_deployment = os.environ["AZURE_OPENAI_LLM_DEPLOYMENT"]
embedding_deployment = os.environ["AZURE_OPENAI_EMBEDDING_DEPLOYMENT"]
@@ -105,26 +114,37 @@ def init_azure_openai():
def init_fastembed():
"""
Use Qdrant Fastembed as the local embedding provider.
"""
from llama_index.embeddings.fastembed import FastEmbedEmbedding
try:
from llama_index.embeddings.fastembed import FastEmbedEmbedding
except ImportError:
raise ImportError(
"FastEmbed support is not installed. Please install it with `poetry add llama-index-embeddings-fastembed`"
)
embed_model_map: Dict[str, str] = {
# Small and multilingual
"all-MiniLM-L6-v2": "sentence-transformers/all-MiniLM-L6-v2",
# Large and multilingual
"paraphrase-multilingual-mpnet-base-v2": "sentence-transformers/paraphrase-multilingual-mpnet-base-v2", # noqa: E501
"paraphrase-multilingual-mpnet-base-v2": "sentence-transformers/paraphrase-multilingual-mpnet-base-v2",
}
embedding_model = os.getenv("EMBEDDING_MODEL")
if embedding_model is None:
raise ValueError("EMBEDDING_MODEL environment variable is not set")
# This will download the model automatically if it is not already downloaded
Settings.embed_model = FastEmbedEmbedding(
model_name=embed_model_map[os.getenv("EMBEDDING_MODEL")]
model_name=embed_model_map[embedding_model]
)
def init_groq():
from llama_index.llms.groq import Groq
try:
from llama_index.llms.groq import Groq
except ImportError:
raise ImportError(
"Groq support is not installed. Please install it with `poetry add llama-index-llms-groq`"
)
Settings.llm = Groq(model=os.getenv("MODEL"))
# Groq does not provide embeddings, so we use FastEmbed instead
@@ -132,7 +152,12 @@ def init_groq():
def init_anthropic():
from llama_index.llms.anthropic import Anthropic
try:
from llama_index.llms.anthropic import Anthropic
except ImportError:
raise ImportError(
"Anthropic support is not installed. Please install it with `poetry add llama-index-llms-anthropic`"
)
model_map: Dict[str, str] = {
"claude-3-opus": "claude-3-opus-20240229",
@@ -148,8 +173,13 @@ def init_anthropic():
def init_gemini():
from llama_index.embeddings.gemini import GeminiEmbedding
from llama_index.llms.gemini import Gemini
try:
from llama_index.embeddings.gemini import GeminiEmbedding
from llama_index.llms.gemini import Gemini
except ImportError:
raise ImportError(
"Gemini support is not installed. Please install it with `poetry add llama-index-llms-gemini` and `poetry add llama-index-embeddings-gemini`"
)
model_name = f"models/{os.getenv('MODEL')}"
embed_model_name = f"models/{os.getenv('EMBEDDING_MODEL')}"
@@ -15,6 +15,6 @@ def get_vector_store():
token=token,
api_endpoint=endpoint,
collection_name=collection,
embedding_dimension=int(os.getenv("EMBEDDING_DIM")),
embedding_dimension=int(os.getenv("EMBEDDING_DIM", 768)),
)
return store
@@ -1,4 +1,5 @@
import os
from llama_index.vector_stores.chroma import ChromaVectorStore
@@ -18,7 +19,7 @@ def get_vector_store():
)
store = ChromaVectorStore.from_params(
host=os.getenv("CHROMA_HOST"),
port=int(os.getenv("CHROMA_PORT")),
port=os.getenv("CHROMA_PORT", "8001"),
collection_name=collection_name,
)
return store
@@ -1,10 +1,17 @@
# flake8: noqa: E402
import os
from dotenv import load_dotenv
from app.engine.index import get_index
load_dotenv()
from llama_cloud import PipelineType
from app.settings import init_settings
from llama_index.core.settings import Settings
from app.engine.index import get_client, get_index
import logging
from llama_index.core.readers import SimpleDirectoryReader
from app.engine.service import LLamaCloudFileService
@@ -13,10 +20,49 @@ logging.basicConfig(level=logging.INFO)
logger = logging.getLogger()
def ensure_index(index):
project_id = index._get_project_id()
client = get_client()
pipelines = client.pipelines.search_pipelines(
project_id=project_id,
pipeline_name=index.name,
pipeline_type=PipelineType.MANAGED.value,
)
if len(pipelines) == 0:
from llama_index.embeddings.openai import OpenAIEmbedding
if not isinstance(Settings.embed_model, OpenAIEmbedding):
raise ValueError(
"Creating a new pipeline with a non-OpenAI embedding model is not supported."
)
client.pipelines.upsert_pipeline(
project_id=project_id,
request={
"name": index.name,
"embedding_config": {
"type": "OPENAI_EMBEDDING",
"component": {
"api_key": os.getenv("OPENAI_API_KEY"), # editable
"model_name": os.getenv("EMBEDDING_MODEL"),
},
},
"transform_config": {
"mode": "auto",
"config": {
"chunk_size": Settings.chunk_size, # editable
"chunk_overlap": Settings.chunk_overlap, # editable
},
},
},
)
def generate_datasource():
init_settings()
logger.info("Generate index for the provided data")
index = get_index()
ensure_index(index)
project_id = index._get_project_id()
pipeline_id = index._get_pipeline_id()
@@ -7,7 +7,7 @@ from llama_index.core.ingestion.api_utils import (
get_client as llama_cloud_get_client,
)
from llama_index.indices.managed.llama_cloud import LlamaCloudIndex
from pydantic import BaseModel, Field, validator
from pydantic import BaseModel, Field, field_validator
logger = logging.getLogger("uvicorn")
@@ -15,31 +15,39 @@ logger = logging.getLogger("uvicorn")
class LlamaCloudConfig(BaseModel):
# Private attributes
api_key: str = Field(
default=os.getenv("LLAMA_CLOUD_API_KEY"),
exclude=True, # Exclude from the model representation
)
base_url: Optional[str] = Field(
default=os.getenv("LLAMA_CLOUD_BASE_URL"),
exclude=True,
)
organization_id: Optional[str] = Field(
default=os.getenv("LLAMA_CLOUD_ORGANIZATION_ID"),
exclude=True,
)
# Configuration attributes, can be set by the user
pipeline: str = Field(
description="The name of the pipeline to use",
default=os.getenv("LLAMA_CLOUD_INDEX_NAME"),
)
project: str = Field(
description="The name of the LlamaCloud project",
default=os.getenv("LLAMA_CLOUD_PROJECT_NAME"),
)
def __init__(self, **kwargs):
if "api_key" not in kwargs:
kwargs["api_key"] = os.getenv("LLAMA_CLOUD_API_KEY")
if "base_url" not in kwargs:
kwargs["base_url"] = os.getenv("LLAMA_CLOUD_BASE_URL")
if "organization_id" not in kwargs:
kwargs["organization_id"] = os.getenv("LLAMA_CLOUD_ORGANIZATION_ID")
if "pipeline" not in kwargs:
kwargs["pipeline"] = os.getenv("LLAMA_CLOUD_INDEX_NAME")
if "project" not in kwargs:
kwargs["project"] = os.getenv("LLAMA_CLOUD_PROJECT_NAME")
super().__init__(**kwargs)
# Validate and throw error if the env variables are not set before starting the app
@validator("pipeline", "project", "api_key", pre=True, always=True)
@field_validator("pipeline", "project", "api_key", mode="before")
@classmethod
def validate_env_vars(cls, value):
def validate_fields(cls, value):
if value is None:
raise ValueError(
"Please set LLAMA_CLOUD_INDEX_NAME, LLAMA_CLOUD_PROJECT_NAME and LLAMA_CLOUD_API_KEY"
@@ -56,7 +64,7 @@ class LlamaCloudConfig(BaseModel):
class IndexConfig(BaseModel):
llama_cloud_pipeline_config: LlamaCloudConfig = Field(
default=LlamaCloudConfig(),
default_factory=lambda: LlamaCloudConfig(),
alias="llamaCloudPipeline",
)
callback_manager: Optional[CallbackManager] = Field(
@@ -1,4 +1,5 @@
import os
from llama_index.vector_stores.milvus import MilvusVectorStore
@@ -15,6 +16,6 @@ def get_vector_store():
user=os.getenv("MILVUS_USERNAME"),
password=os.getenv("MILVUS_PASSWORD"),
collection_name=collection,
dim=int(os.getenv("EMBEDDING_DIM")),
dim=int(os.getenv("EMBEDDING_DIM", 768)),
)
return store
@@ -3,7 +3,7 @@ import os
from datetime import timedelta
from typing import Optional
from cachetools import TTLCache, cached
from cachetools import TTLCache, cached # type: ignore
from llama_index.core.callbacks import CallbackManager
from llama_index.core.indices import load_index_from_storage
from llama_index.core.storage import StorageContext
@@ -1,6 +1,6 @@
import { MetadataFilter, MetadataFilters } from "llamaindex";
import { CloudRetrieveParams, MetadataFilter } from "llamaindex";
export function generateFilters(documentIds: string[]): MetadataFilters {
export function generateFilters(documentIds: string[]) {
// public documents don't have the "private" field or it's set to "false"
const publicDocumentsFilter: MetadataFilter = {
key: "private",
@@ -8,7 +8,10 @@ export function generateFilters(documentIds: string[]): MetadataFilters {
};
// if no documentIds are provided, only retrieve information from public documents
if (!documentIds.length) return { filters: [publicDocumentsFilter] };
if (!documentIds.length)
return {
filters: [publicDocumentsFilter],
} as CloudRetrieveParams["filters"];
const privateDocumentsFilter: MetadataFilter = {
key: "file_id", // Note: LLamaCloud uses "file_id" to reference private document ids as "doc_id" is a restricted field in LlamaCloud
@@ -20,5 +23,5 @@ export function generateFilters(documentIds: string[]): MetadataFilters {
return {
filters: [publicDocumentsFilter, privateDocumentsFilter],
condition: "or",
};
} as CloudRetrieveParams["filters"];
}
@@ -18,7 +18,9 @@ async function loadAndIndex() {
// create postgres vector store
const vectorStore = new PGVectorStore({
connectionString: process.env.PG_CONNECTION_STRING,
clientConfig: {
connectionString: process.env.PG_CONNECTION_STRING,
},
schemaName: PGVECTOR_SCHEMA,
tableName: PGVECTOR_TABLE,
});
@@ -9,7 +9,9 @@ import {
export async function getDataSource(params?: any) {
checkRequiredEnvVars();
const pgvs = new PGVectorStore({
connectionString: process.env.PG_CONNECTION_STRING,
clientConfig: {
connectionString: process.env.PG_CONNECTION_STRING,
},
schemaName: PGVECTOR_SCHEMA,
tableName: PGVECTOR_TABLE,
});
@@ -6,7 +6,7 @@ load_dotenv()
import logging
import os
from llama_index.core.ingestion import IngestionPipeline
from llama_index.core.ingestion import DocstoreStrategy, IngestionPipeline
from llama_index.core.node_parser import SentenceSplitter
from llama_index.core.settings import Settings
from llama_index.core.storage import StorageContext
@@ -41,7 +41,7 @@ def run_pipeline(docstore, vector_store, documents):
Settings.embed_model,
],
docstore=docstore,
docstore_strategy="upserts_and_delete",
docstore_strategy=DocstoreStrategy.UPSERTS_AND_DELETE, # type: ignore
vector_store=vector_store,
)
@@ -16,7 +16,7 @@ class IndexConfig(BaseModel):
)
def get_index(config: IndexConfig = None):
def get_index(config: Optional[IndexConfig] = None) -> VectorStoreIndex:
if config is None:
config = IndexConfig()
logger.info("Connecting vector store...")
@@ -21,7 +21,7 @@
"dotenv": "^16.3.1",
"duck-duck-scrape": "^2.2.5",
"express": "^4.18.2",
"llamaindex": "0.6.2",
"llamaindex": "0.6.18",
"pdf2json": "3.0.5",
"ajv": "^8.12.0",
"@e2b/code-interpreter": "0.0.9-beta.3",
@@ -13,7 +13,7 @@ from app.api.routers.models import (
SourceNodes,
)
from app.api.routers.vercel_response import VercelStreamResponse
from app.engine import get_chat_engine
from app.engine.engine import get_chat_engine
from app.engine.query_filter import generate_filters
chat_router = r = APIRouter()
@@ -23,7 +23,7 @@ async def chat_config() -> ChatConfig:
try:
from app.engine.service import LLamaCloudFileService
logger.info("LlamaCloud is configured. Adding /config/llamacloud route.")
print("LlamaCloud is configured. Adding /config/llamacloud route.")
@r.get("/llamacloud")
async def chat_llama_cloud_config():
@@ -42,7 +42,5 @@ try:
}
except ImportError:
logger.debug(
"LlamaCloud is not configured. Skipping adding /config/llamacloud route."
)
print("LlamaCloud is not configured. Skipping adding /config/llamacloud route.")
pass
@@ -1,13 +1,13 @@
import json
import asyncio
import json
import logging
from typing import AsyncGenerator, Dict, Any, List, Optional
from typing import Any, AsyncGenerator, Dict, List, Optional
from llama_index.core.callbacks.base import BaseCallbackHandler
from llama_index.core.callbacks.schema import CBEventType
from llama_index.core.tools.types import ToolOutput
from pydantic import BaseModel
logger = logging.getLogger(__name__)
@@ -31,15 +31,20 @@ class CallbackEvent(BaseModel):
return None
def get_tool_message(self) -> dict | None:
if self.payload is None:
return None
func_call_args = self.payload.get("function_call")
if func_call_args is not None and "tool" in self.payload:
tool = self.payload.get("tool")
if tool is None:
return None
return {
"type": "events",
"data": {
"title": f"Calling tool: {tool.name} with inputs: {func_call_args}",
},
}
return None
def _is_output_serializable(self, output: Any) -> bool:
try:
@@ -49,6 +54,8 @@ class CallbackEvent(BaseModel):
return False
def get_agent_tool_response(self) -> dict | None:
if self.payload is None:
return None
response = self.payload.get("response")
if response is not None:
sources = response.sources
@@ -74,6 +81,7 @@ class CallbackEvent(BaseModel):
},
},
}
return None
def to_response(self):
try:
@@ -114,11 +122,13 @@ class EventCallbackHandler(BaseCallbackHandler):
event_type: CBEventType,
payload: Optional[Dict[str, Any]] = None,
event_id: str = "",
parent_id: str = "",
**kwargs: Any,
) -> str:
event = CallbackEvent(event_id=event_id, event_type=event_type, payload=payload)
if event.to_response() is not None:
self._aqueue.put_nowait(event)
return event_id
def on_event_end(
self,
@@ -1,6 +1,6 @@
import logging
import os
from typing import Any, Dict, List, Literal, Optional
from typing import Any, Dict, List, Literal, Optional, Union
from llama_index.core.llms import ChatMessage, MessageRole
from llama_index.core.schema import NodeWithScore
@@ -62,15 +62,23 @@ class ArtifactAnnotation(BaseModel):
class Annotation(BaseModel):
type: str
data: AnnotationFileData | List[str] | AgentAnnotation | ArtifactAnnotation
data: Union[AnnotationFileData, List[str], AgentAnnotation, ArtifactAnnotation]
def to_content(self) -> str | None:
def to_content(self) -> Optional[str]:
if self.type == "document_file":
# We only support generating context content for CSV files for now
csv_files = [file for file in self.data.files if file.filetype == "csv"]
if len(csv_files) > 0:
return "Use data from following CSV raw content\n" + "\n".join(
[f"```csv\n{csv_file.content.value}\n```" for csv_file in csv_files]
if isinstance(self.data, AnnotationFileData):
# We only support generating context content for CSV files for now
csv_files = [file for file in self.data.files if file.filetype == "csv"]
if len(csv_files) > 0:
return "Use data from following CSV raw content\n" + "\n".join(
[
f"```csv\n{csv_file.content.value}\n```"
for csv_file in csv_files
]
)
else:
logger.warning(
f"Unexpected data type for document_file annotation: {type(self.data)}"
)
else:
logger.warning(
@@ -213,6 +221,7 @@ class ChatData(BaseModel):
for annotation in message.annotations:
if (
annotation.type == "document_file"
and isinstance(annotation.data, AnnotationFileData)
and annotation.data.files is not None
):
for fi in annotation.data.files:
@@ -242,7 +251,7 @@ class SourceNodes(BaseModel):
)
@classmethod
def get_url_from_metadata(cls, metadata: Dict[str, Any]) -> str:
def get_url_from_metadata(cls, metadata: Dict[str, Any]) -> Optional[str]:
url_prefix = os.getenv("FILESERVER_URL_PREFIX")
if not url_prefix:
logger.warning(
@@ -1 +0,0 @@
from .engine import get_chat_engine as get_chat_engine
@@ -6,15 +6,16 @@ load_dotenv()
import logging
import os
from app.engine.loaders import get_documents
from app.engine.vectordb import get_vector_store
from app.settings import init_settings
from llama_index.core.ingestion import IngestionPipeline
from llama_index.core.ingestion import DocstoreStrategy, IngestionPipeline
from llama_index.core.node_parser import SentenceSplitter
from llama_index.core.settings import Settings
from llama_index.core.storage import StorageContext
from llama_index.core.storage.docstore import SimpleDocumentStore
from app.engine.loaders import get_documents
from app.engine.vectordb import get_vector_store
from app.settings import init_settings
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger()
@@ -40,7 +41,7 @@ def run_pipeline(docstore, vector_store, documents):
Settings.embed_model,
],
docstore=docstore,
docstore_strategy="upserts_and_delete",
docstore_strategy=DocstoreStrategy.UPSERTS_AND_DELETE, # type: ignore
vector_store=vector_store,
)
@@ -17,6 +17,23 @@ aiostream = "^0.5.2"
cachetools = "^5.3.3"
llama-index = "0.11.6"
[tool.poetry.group.dev.dependencies]
mypy = "^1.8.0"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
[tool.mypy]
python_version = "3.11"
plugins = "pydantic.mypy"
exclude = [ "tests", "venv", ".venv", "output", "config" ]
check_untyped_defs = true
warn_unused_ignores = false
show_error_codes = true
namespace_packages = true
ignore_missing_imports = true
follow_imports = "silent"
implicit_optional = true
strict_optional = false
disable_error_code = ["return-value", "import-untyped", "assignment"]
@@ -45,7 +45,7 @@ export function LlamaCloudSelector({
setRequestData,
onSelect,
defaultPipeline,
shouldCheckValid = true,
shouldCheckValid = false,
}: LlamaCloudSelectorProps) {
const { backend } = useClientConfig();
const [config, setConfig] = useState<LlamaCloudConfig>();
@@ -95,7 +95,8 @@ export function LlamaCloudSelector({
</div>
);
}
if (!isValid(config) && shouldCheckValid) {
if (shouldCheckValid && !isValid(config.projects, config.pipeline)) {
return (
<p className="text-red-500">
Invalid LlamaCloud configuration. Check console logs.
@@ -107,7 +108,11 @@ export function LlamaCloudSelector({
return (
<Select
onValueChange={handlePipelineSelect}
defaultValue={JSON.stringify(pipeline)}
defaultValue={
isValid(projects, pipeline, false)
? JSON.stringify(pipeline)
: undefined
}
>
<SelectTrigger className="w-[200px]">
<SelectValue placeholder="Select a pipeline" />
@@ -137,26 +142,33 @@ export function LlamaCloudSelector({
);
}
function isValid(config: LlamaCloudConfig): boolean {
const { projects, pipeline } = config;
function isValid(
projects: LLamaCloudProject[] | undefined,
pipeline: PipelineConfig | undefined,
logErrors: boolean = true,
): boolean {
if (!projects?.length) return false;
if (!pipeline) return false;
const matchedProject = projects.find(
(project: LLamaCloudProject) => project.name === pipeline.project,
);
if (!matchedProject) {
console.error(
`LlamaCloud project ${pipeline.project} not found. Check LLAMA_CLOUD_PROJECT_NAME variable`,
);
if (logErrors) {
console.error(
`LlamaCloud project ${pipeline.project} not found. Check LLAMA_CLOUD_PROJECT_NAME variable`,
);
}
return false;
}
const pipelineExists = matchedProject.pipelines.some(
(p) => p.name === pipeline.pipeline,
);
if (!pipelineExists) {
console.error(
`LlamaCloud pipeline ${pipeline.pipeline} not found. Check LLAMA_CLOUD_INDEX_NAME variable`,
);
if (logErrors) {
console.error(
`LlamaCloud pipeline ${pipeline.pipeline} not found. Check LLAMA_CLOUD_INDEX_NAME variable`,
);
}
return false;
}
return true;
@@ -27,7 +27,7 @@
"duck-duck-scrape": "^2.2.5",
"formdata-node": "^6.0.3",
"got": "^14.4.1",
"llamaindex": "0.6.2",
"llamaindex": "0.6.18",
"lucide-react": "^0.294.0",
"next": "^14.2.4",
"react": "^18.2.0",