mirror of
https://github.com/run-llama/create-llama.git
synced 2026-07-19 23:13:36 -04:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6304114ef5 | |||
| 6335de1174 | |||
| b9184ff59a | |||
| cd3fcd0512 | |||
| a47d778602 | |||
| 7f4ac228ee | |||
| 5263bde8e7 | |||
| 4dee65b93d | |||
| c60182a925 | |||
| 0e78ba4603 |
@@ -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
|
||||
|
||||
@@ -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,2 +1,3 @@
|
||||
pnpm format
|
||||
pnpm lint
|
||||
uvx ruff format --check templates/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
@@ -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
@@ -123,7 +123,7 @@ const getAdditionalDependencies = (
|
||||
extras: ["rsa"],
|
||||
});
|
||||
dependencies.push({
|
||||
name: "psycopg2",
|
||||
name: "psycopg2-binary",
|
||||
version: "^2.9.9",
|
||||
});
|
||||
break;
|
||||
|
||||
+1
-1
@@ -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():
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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"]
|
||||
|
||||
+23
-11
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user