Skip to content

Commit 6bd76fb

Browse files
feat: Add template for structured extraction (#185)
1 parent a553d50 commit 6bd76fb

File tree

13 files changed

+237
-63
lines changed

13 files changed

+237
-63
lines changed

.changeset/proud-seals-yell.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"create-llama": patch
3+
---
4+
5+
Add template for structured extraction

helpers/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,11 @@ export const installTemplate = async (
163163
// This is a backend, so we need to copy the test data and create the env file.
164164

165165
// Copy the environment file to the target directory.
166-
if (props.template === "streaming" || props.template === "multiagent") {
166+
if (
167+
props.template === "streaming" ||
168+
props.template === "multiagent" ||
169+
props.template === "extractor"
170+
) {
167171
await createBackendEnvFile(props.root, {
168172
modelConfig: props.modelConfig,
169173
llamaCloudKey: props.llamaCloudKey,

helpers/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export type ModelConfig = {
1818
isConfigured(): boolean;
1919
};
2020
export type TemplateType =
21+
| "extractor"
2122
| "streaming"
2223
| "community"
2324
| "llamapack"

questions.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,7 @@ export const askQuestions = async (
342342
title: "Multi-agent app (using llama-agents)",
343343
value: "multiagent",
344344
},
345+
{ title: "Structured Extractor", value: "extractor" },
345346
{
346347
title: `Community template from ${styledRepo}`,
347348
value: "community",
@@ -405,7 +406,7 @@ export const askQuestions = async (
405406
return; // early return - no further questions needed for llamapack projects
406407
}
407408

408-
if (program.template === "multiagent") {
409+
if (program.template === "multiagent" || program.template === "extractor") {
409410
// TODO: multi-agents currently only supports FastAPI
410411
program.framework = preferences.framework = "fastapi";
411412
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
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) featuring [structured extraction](https://docs.llamaindex.ai/en/stable/examples/structured_outputs/structured_outputs/?h=structured+output).
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 the API in one command:
23+
24+
```shell
25+
poetry run python main.py
26+
```
27+
28+
The example provides the `/api/extractor/query` API endpoint.
29+
30+
This query endpoint returns structured data in the format of the [Output](./app/api/routers/output.py) class. Modify this class to change the output format.
31+
32+
You can test the endpoint with the following curl request:
33+
34+
```shell
35+
curl --location 'localhost:8000/api/extractor/query' \
36+
--header 'Content-Type: application/json' \
37+
--data '{ "query": "What is the maximum weight for a parcel?" }'
38+
```
39+
40+
Which will return a response that the RAG pipeline is confident about the answer.
41+
42+
Try
43+
44+
```shell
45+
curl --location 'localhost:8000/api/extractor/query' \
46+
--header 'Content-Type: application/json' \
47+
--data '{ "query": "What is the weather today?" }'
48+
```
49+
50+
To retrieve a response with low confidence since the question is not related to the provided document in the `./data` directory.
51+
52+
You can start editing the API endpoint by modifying [`extractor.py`](./app/api/routers/extractor.py). The endpoints auto-update as you save the file.
53+
54+
Open [http://localhost:8000/docs](http://localhost:8000/docs) with your browser to see the Swagger UI of the API.
55+
56+
The API allows CORS for all origins to simplify development. You can change this behavior by setting the `ENVIRONMENT` environment variable to `prod`:
57+
58+
```
59+
ENVIRONMENT=prod python main.py
60+
```
61+
62+
## Learn More
63+
64+
To learn more about LlamaIndex, take a look at the following resources:
65+
66+
- [LlamaIndex Documentation](https://docs.llamaindex.ai) - learn about LlamaIndex.
67+
68+
You can check out [the LlamaIndex GitHub repository](https://github.com/run-llama/llama_index) - your feedback and contributions are welcome!

templates/types/extractor/fastapi/app/__init__.py

Whitespace-only changes.

templates/types/extractor/fastapi/app/api/__init__.py

Whitespace-only changes.

templates/types/extractor/fastapi/app/api/routers/__init__.py

Whitespace-only changes.
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import logging
2+
import os
3+
4+
from fastapi import APIRouter, HTTPException
5+
from llama_index.core.settings import Settings
6+
from pydantic import BaseModel
7+
8+
from app.api.routers.output import Output
9+
from app.engine.index import get_index
10+
11+
extractor_router = r = APIRouter()
12+
13+
logger = logging.getLogger("uvicorn")
14+
15+
16+
class RequestData(BaseModel):
17+
query: str
18+
19+
class Config:
20+
json_schema_extra = {
21+
"examples": [
22+
{"query": "What's the maximum weight for a parcel?"},
23+
],
24+
}
25+
26+
27+
@r.post("/query")
28+
async def query_request(
29+
data: RequestData,
30+
):
31+
# Create a query engine using that returns responses in the format of the Output class
32+
query_engine = get_query_engine(Output)
33+
34+
response = await query_engine.aquery(data.query)
35+
36+
output_data = response.response.dict()
37+
return Output(**output_data)
38+
39+
40+
def get_query_engine(output_cls: BaseModel):
41+
top_k = os.getenv("TOP_K", 3)
42+
43+
index = get_index()
44+
if index is None:
45+
raise HTTPException(
46+
status_code=500,
47+
detail=str(
48+
"StorageContext is empty - call 'poetry run generate' to generate the storage first"
49+
),
50+
)
51+
52+
sllm = Settings.llm.as_structured_llm(output_cls)
53+
54+
return index.as_query_engine(
55+
similarity_top_k=int(top_k),
56+
llm=sllm,
57+
response_mode="tree_summarize",
58+
)
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import logging
2+
from llama_index.core.schema import BaseModel, Field
3+
from typing import List
4+
5+
logger = logging.getLogger("uvicorn")
6+
7+
8+
class Output(BaseModel):
9+
response: str = Field(..., description="The answer to the question.")
10+
page_numbers: List[int] = Field(
11+
...,
12+
description="The page numbers of the sources used to answer this question. Do not include a page number if the context is irrelevant.",
13+
)
14+
confidence: float = Field(
15+
...,
16+
ge=0,
17+
le=1,
18+
description="Confidence value between 0-1 of the correctness of the result.",
19+
)
20+
confidence_explanation: str = Field(
21+
..., description="Explanation for the confidence score"
22+
)
23+
24+
class Config:
25+
json_schema_extra = {
26+
"example": {
27+
"response": "This is an example answer.",
28+
"page_numbers": [1, 2, 3],
29+
"confidence": 0.85,
30+
"confidence_explanation": "This is an explanation for the confidence score.",
31+
}
32+
}

0 commit comments

Comments
 (0)