Skip to content

Commit 148b6c7

Browse files
Initial commit from Create Llama
0 parents  commit 148b6c7

23 files changed

+924
-0
lines changed

.devcontainer/devcontainer.json

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
{
2+
"image": "mcr.microsoft.com/vscode/devcontainers/typescript-node:dev-20-bullseye",
3+
"features": {
4+
"ghcr.io/devcontainers-contrib/features/turborepo-npm:1": {},
5+
"ghcr.io/devcontainers-contrib/features/typescript:2": {},
6+
"ghcr.io/devcontainers/features/python:1": {
7+
"version": "3.11",
8+
"toolsToInstall": [
9+
"flake8",
10+
"black",
11+
"mypy",
12+
"poetry"
13+
]
14+
}
15+
},
16+
"customizations": {
17+
"codespaces": {
18+
"openFiles": [
19+
"README.md"
20+
]
21+
},
22+
"vscode": {
23+
"extensions": [
24+
"ms-vscode.typescript-language-features",
25+
"esbenp.prettier-vscode",
26+
"ms-python.python",
27+
"ms-python.black-formatter",
28+
"ms-python.vscode-flake8",
29+
"ms-python.vscode-pylance"
30+
],
31+
"settings": {
32+
"python.formatting.provider": "black",
33+
"python.languageServer": "Pylance",
34+
"python.analysis.typeCheckingMode": "basic"
35+
}
36+
}
37+
},
38+
"containerEnv": {
39+
"POETRY_VIRTUALENVS_CREATE": "false",
40+
"PYTHONPATH": "${PYTHONPATH}:${workspaceFolder}"
41+
},
42+
"forwardPorts": [
43+
3000,
44+
8000
45+
],
46+
"postCreateCommand": "poetry install"
47+
}

.env

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# The Llama Cloud API key.
2+
# LLAMA_CLOUD_API_KEY=
3+
4+
# The provider for the AI models to use.
5+
MODEL_PROVIDER=openai
6+
7+
# The name of LLM model to use.
8+
MODEL=gpt-4o-mini
9+
10+
# Name of the embedding model to use.
11+
EMBEDDING_MODEL=text-embedding-3-large
12+
13+
# Dimension of the embedding model to use.
14+
EMBEDDING_DIM=1024
15+
16+
# The questions to help users get started (multi-line).
17+
# CONVERSATION_STARTERS=
18+
19+
# The OpenAI API key to use.
20+
OPENAI_API_KEY=sk-proj-7qNCi2Btib0Y7AADy3tgT3BlbkFJ9d7IFV2lk2IfRnvDQbxp
21+
22+
# Temperature for sampling from the model.
23+
# LLM_TEMPERATURE=
24+
25+
# Maximum number of tokens to generate.
26+
# LLM_MAX_TOKENS=
27+
28+
# The number of similar embeddings to return when retrieving documents.
29+
TOP_K=3
30+
31+
# The time in milliseconds to wait for the stream to return a response.
32+
STREAM_TIMEOUT=60000
33+
34+
# FILESERVER_URL_PREFIX is the URL prefix of the server storing the images generated by the interpreter.
35+
FILESERVER_URL_PREFIX=http://localhost:8000/api/files
36+
37+
# The address to start the backend app.
38+
APP_HOST=0.0.0.0
39+
40+
# The port to start the backend app.
41+
APP_PORT=8000
42+
43+
# MESSAGE_QUEUE_PORT=
44+
45+
# CONTROL_PLANE_PORT=
46+
47+
# HUMAN_CONSUMER_PORT=
48+
49+
AGENT_QUERY_ENGINE_PORT=8003
50+
51+
AGENT_QUERY_ENGINE_DESCRIPTION=Query information from the provided data
52+
53+
AGENT_DUMMY_PORT=8004
54+
55+
# The system prompt for the AI model.
56+
SYSTEM_PROMPT=You are a helpful assistant who helps users with their questions.
57+

Dockerfile

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
FROM python:3.11 as build
2+
3+
WORKDIR /app
4+
5+
ENV PYTHONPATH=/app
6+
7+
# Install Poetry
8+
RUN curl -sSL https://install.python-poetry.org | POETRY_HOME=/opt/poetry python && \
9+
cd /usr/local/bin && \
10+
ln -s /opt/poetry/bin/poetry && \
11+
poetry config virtualenvs.create false
12+
13+
# Install Chromium for web loader
14+
# Can disable this if you don't use the web loader to reduce the image size
15+
RUN apt update && apt install -y chromium chromium-driver
16+
17+
# Install dependencies
18+
COPY ./pyproject.toml ./poetry.lock* /app/
19+
RUN poetry install --no-root --no-cache --only main
20+
21+
# ====================================
22+
FROM build as release
23+
24+
COPY . .
25+
26+
CMD ["python", "main.py"]

README.md

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
This is a [LlamaIndex](https://www.llamaindex.ai/) project using [FastAPI](https://fastapi.tiangolo.com/) bootstrapped with [`create-llama`](https://github.com/run-llama/LlamaIndexTS/tree/main/packages/create-llama).
2+
3+
## Getting Started
4+
5+
First, setup the environment with poetry:
6+
7+
> **_Note:_** This step is not needed if you are using the dev-container.
8+
9+
```shell
10+
poetry install
11+
poetry shell
12+
```
13+
14+
Then check the parameters that have been pre-configured in the `.env` file in this directory. (E.g. you might need to configure an `OPENAI_API_KEY` if you're using OpenAI as model provider).
15+
16+
Second, generate the embeddings of the documents in the `./data` directory (if this folder exists - otherwise, skip this step):
17+
18+
```shell
19+
poetry run generate
20+
```
21+
22+
Third, run all the services in one command:
23+
24+
```shell
25+
poetry run python main.py
26+
```
27+
28+
You can monitor and test the agent services with `llama-agents` monitor TUI:
29+
30+
```shell
31+
poetry run llama-agents monitor --control-plane-url http://127.0.0.1:8001
32+
```
33+
34+
## Services:
35+
36+
- Message queue (port 8000): To exchange the message between services
37+
- Control plane (port 8001): A gateway to manage the tasks and services.
38+
- Human consumer (port 8002): To handle result when the task is completed.
39+
- Agent service `query_engine` (port 8003): Agent that can query information from the configured LlamaIndex index.
40+
- Agent service `dummy_agent` (port 8004): A dummy agent that does nothing. Good starting point to add more agents.
41+
42+
The ports listed above are set by default, but you can change them in the `.env` file.
43+
44+
## Learn More
45+
46+
To learn more about LlamaIndex, take a look at the following resources:
47+
48+
- [LlamaIndex Documentation](https://docs.llamaindex.ai) - learn about LlamaIndex.
49+
50+
You can check out [the LlamaIndex GitHub repository](https://github.com/run-llama/llama_index) - your feedback and contributions are welcome!

app/agents/dummy/agent.py

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from llama_agents import AgentService, SimpleMessageQueue
2+
from llama_index.core.agent import FunctionCallingAgentWorker
3+
from llama_index.core.tools import FunctionTool
4+
from llama_index.core.settings import Settings
5+
from app.utils import load_from_env
6+
7+
8+
DEFAULT_DUMMY_AGENT_DESCRIPTION = "I'm a dummy agent which does nothing."
9+
10+
11+
def dummy_function():
12+
"""
13+
This function does nothing.
14+
"""
15+
return ""
16+
17+
18+
def init_dummy_agent(message_queue: SimpleMessageQueue) -> AgentService:
19+
agent = FunctionCallingAgentWorker(
20+
tools=[FunctionTool.from_defaults(fn=dummy_function)],
21+
llm=Settings.llm,
22+
prefix_messages=[],
23+
).as_agent()
24+
25+
return AgentService(
26+
service_name="dummy_agent",
27+
agent=agent,
28+
message_queue=message_queue.client,
29+
description=load_from_env("AGENT_DUMMY_DESCRIPTION", throw_error=False)
30+
or DEFAULT_DUMMY_AGENT_DESCRIPTION,
31+
host=load_from_env("AGENT_DUMMY_HOST", throw_error=False) or "127.0.0.1",
32+
port=int(load_from_env("AGENT_DUMMY_PORT")),
33+
)

app/agents/query_engine/agent.py

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import os
2+
from llama_agents import AgentService, SimpleMessageQueue
3+
from llama_index.core.agent import FunctionCallingAgentWorker
4+
from llama_index.core.tools import QueryEngineTool, ToolMetadata
5+
from llama_index.core.settings import Settings
6+
from app.engine.index import get_index
7+
from app.utils import load_from_env
8+
9+
10+
DEFAULT_QUERY_ENGINE_AGENT_DESCRIPTION = (
11+
"Used to answer the questions using the provided context data."
12+
)
13+
14+
15+
def get_query_engine_tool() -> QueryEngineTool:
16+
"""
17+
Provide an agent worker that can be used to query the index.
18+
"""
19+
index = get_index()
20+
if index is None:
21+
raise ValueError("Index not found. Please create an index first.")
22+
query_engine = index.as_query_engine(similarity_top_k=int(os.getenv("TOP_K", 3)))
23+
return QueryEngineTool(
24+
query_engine=query_engine,
25+
metadata=ToolMetadata(
26+
name="context_data",
27+
description="""
28+
Provide the provided context information.
29+
Use a detailed plain text question as input to the tool.
30+
""",
31+
),
32+
)
33+
34+
35+
def init_query_engine_agent(
36+
message_queue: SimpleMessageQueue,
37+
) -> AgentService:
38+
"""
39+
Initialize the agent service.
40+
"""
41+
agent = FunctionCallingAgentWorker(
42+
tools=[get_query_engine_tool()], llm=Settings.llm, prefix_messages=[]
43+
).as_agent()
44+
return AgentService(
45+
service_name="context_query_agent",
46+
agent=agent,
47+
message_queue=message_queue.client,
48+
description=load_from_env("AGENT_QUERY_ENGINE_DESCRIPTION", throw_error=False)
49+
or DEFAULT_QUERY_ENGINE_AGENT_DESCRIPTION,
50+
host=load_from_env("AGENT_QUERY_ENGINE_HOST", throw_error=False) or "127.0.0.1",
51+
port=int(load_from_env("AGENT_QUERY_ENGINE_PORT")),
52+
)

app/core/control_plane.py

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from llama_index.llms.openai import OpenAI
2+
from llama_agents import AgentOrchestrator, ControlPlaneServer
3+
from app.core.message_queue import message_queue
4+
from app.utils import load_from_env
5+
6+
7+
control_plane_host = (
8+
load_from_env("CONTROL_PLANE_HOST", throw_error=False) or "127.0.0.1"
9+
)
10+
control_plane_port = load_from_env("CONTROL_PLANE_PORT", throw_error=False) or "8001"
11+
12+
13+
# setup control plane
14+
control_plane = ControlPlaneServer(
15+
message_queue=message_queue,
16+
orchestrator=AgentOrchestrator(llm=OpenAI()),
17+
host=control_plane_host,
18+
port=int(control_plane_port) if control_plane_port else None,
19+
)

app/core/message_queue.py

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from llama_agents import SimpleMessageQueue
2+
from app.utils import load_from_env
3+
4+
message_queue_host = (
5+
load_from_env("MESSAGE_QUEUE_HOST", throw_error=False) or "127.0.0.1"
6+
)
7+
message_queue_port = load_from_env("MESSAGE_QUEUE_PORT", throw_error=False) or "8000"
8+
9+
message_queue = SimpleMessageQueue(
10+
host=message_queue_host,
11+
port=int(message_queue_port) if message_queue_port else None,
12+
)

app/core/task_result.py

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import json
2+
from logging import getLogger
3+
from pathlib import Path
4+
from fastapi import FastAPI
5+
from typing import Dict, Optional
6+
from llama_agents import CallableMessageConsumer, QueueMessage
7+
from llama_agents.message_queues.base import BaseMessageQueue
8+
from llama_agents.message_consumers.base import BaseMessageQueueConsumer
9+
from llama_agents.message_consumers.remote import RemoteMessageConsumer
10+
from app.utils import load_from_env
11+
from app.core.message_queue import message_queue
12+
13+
14+
logger = getLogger(__name__)
15+
16+
17+
class TaskResultService:
18+
def __init__(
19+
self,
20+
message_queue: BaseMessageQueue,
21+
name: str = "human",
22+
host: str = "127.0.0.1",
23+
port: Optional[int] = 8002,
24+
) -> None:
25+
self.name = name
26+
self.host = host
27+
self.port = port
28+
29+
self._message_queue = message_queue
30+
31+
# app
32+
self._app = FastAPI()
33+
self._app.add_api_route(
34+
"/", self.home, methods=["GET"], tags=["Human Consumer"]
35+
)
36+
self._app.add_api_route(
37+
"/process_message",
38+
self.process_message,
39+
methods=["POST"],
40+
tags=["Human Consumer"],
41+
)
42+
43+
@property
44+
def message_queue(self) -> BaseMessageQueue:
45+
return self._message_queue
46+
47+
def as_consumer(self, remote: bool = False) -> BaseMessageQueueConsumer:
48+
if remote:
49+
return RemoteMessageConsumer(
50+
url=(
51+
f"http://{self.host}:{self.port}/process_message"
52+
if self.port
53+
else f"http://{self.host}/process_message"
54+
),
55+
message_type=self.name,
56+
)
57+
58+
return CallableMessageConsumer(
59+
message_type=self.name,
60+
handler=self.process_message,
61+
)
62+
63+
async def process_message(self, message: QueueMessage) -> None:
64+
Path("task_results").mkdir(exist_ok=True)
65+
with open("task_results/task_results.json", "+a") as f:
66+
json.dump(message.model_dump(), f)
67+
f.write("\n")
68+
69+
async def home(self) -> Dict[str, str]:
70+
return {"message": "hello, human."}
71+
72+
async def register_to_message_queue(self) -> None:
73+
"""Register to the message queue."""
74+
await self.message_queue.register_consumer(self.as_consumer(remote=True))
75+
76+
77+
human_consumer_host = (
78+
load_from_env("HUMAN_CONSUMER_HOST", throw_error=False) or "127.0.0.1"
79+
)
80+
human_consumer_port = load_from_env("HUMAN_CONSUMER_PORT", throw_error=False) or "8002"
81+
82+
83+
human_consumer_server = TaskResultService(
84+
message_queue=message_queue,
85+
host=human_consumer_host,
86+
port=int(human_consumer_port) if human_consumer_port else None,
87+
name="human",
88+
)

0 commit comments

Comments
 (0)