Add upsert endpoint
This commit is contained in:
parent
faa2ecbfda
commit
4f58e0f0b9
|
|
@ -5,4 +5,38 @@ services:
|
||||||
build: .
|
build: .
|
||||||
volumes:
|
volumes:
|
||||||
- ..:/workspace:cached
|
- ..:/workspace:cached
|
||||||
|
ports:
|
||||||
|
- "8000:8000"
|
||||||
|
environment:
|
||||||
|
- QDRANT_URL=qdrant:6334
|
||||||
|
- TEI_BASE_URL=http://text-embeddings-inference
|
||||||
|
- TEI_RERANK_BASE_URL=http://text-embeddings-inference-rerank
|
||||||
command: sleep infinity
|
command: sleep infinity
|
||||||
|
|
||||||
|
qdrant:
|
||||||
|
image: qdrant/qdrant:v1.7.4
|
||||||
|
volumes:
|
||||||
|
- ../qdrant_storage:/qdrant/storage:z
|
||||||
|
ports:
|
||||||
|
- "6333:6333"
|
||||||
|
- "6334:6334"
|
||||||
|
|
||||||
|
text-embeddings-inference:
|
||||||
|
image: ghcr.io/huggingface/text-embeddings-inference:cpu-0.6
|
||||||
|
volumes:
|
||||||
|
- "../tei_data:/data"
|
||||||
|
ports:
|
||||||
|
- "8001:80"
|
||||||
|
environment:
|
||||||
|
- MODEL_ID=${TEI_MODEL_ID:-BAAI/bge-large-en-v1.5}
|
||||||
|
- REVISION=${TEI_MODEL_REVISION}
|
||||||
|
|
||||||
|
text-embeddings-inference-rerank:
|
||||||
|
image: ghcr.io/huggingface/text-embeddings-inference:cpu-0.6
|
||||||
|
volumes:
|
||||||
|
- "../tei_data:/data"
|
||||||
|
ports:
|
||||||
|
- "8002:80"
|
||||||
|
environment:
|
||||||
|
- MODEL_ID=${TEI_RERANK_MODEL_ID:-BAAI/bge-reranker-large}
|
||||||
|
- REVISION=${TEI_RERANK_MODEL_REVISION}
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,8 @@
|
||||||
"eamodio.gitlens",
|
"eamodio.gitlens",
|
||||||
"yzhang.markdown-all-in-one",
|
"yzhang.markdown-all-in-one",
|
||||||
"DavidAnson.vscode-markdownlint",
|
"DavidAnson.vscode-markdownlint",
|
||||||
"ninoseki.vscode-pylens"
|
"ninoseki.vscode-pylens",
|
||||||
|
"ms-azuretools.vscode-docker"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
from langchain.text_splitter import MarkdownHeaderTextSplitter
|
||||||
|
from langchain_core.runnables import Runnable, chain
|
||||||
|
|
||||||
|
|
||||||
|
def get_markdown_header_text_splitter_chain(
|
||||||
|
markdown_header_text_splitter: MarkdownHeaderTextSplitter,
|
||||||
|
) -> Runnable[str, list[str]]:
|
||||||
|
if not markdown_header_text_splitter.strip_headers:
|
||||||
|
raise ValueError("`strip_headers` must be True") # noqa: TRY003
|
||||||
|
|
||||||
|
@chain
|
||||||
|
def markdown_header_text_splitter_chain(text: str) -> list[str]:
|
||||||
|
documents = markdown_header_text_splitter.split_text(text)
|
||||||
|
# Add all parent headers to the page content
|
||||||
|
return [
|
||||||
|
"\n...\n".join(
|
||||||
|
f"{header_key} {document.metadata[header_key]}"
|
||||||
|
for _, header_key in markdown_header_text_splitter.headers_to_split_on
|
||||||
|
)
|
||||||
|
+ f"\n{document.page_content}"
|
||||||
|
for document in documents
|
||||||
|
]
|
||||||
|
|
||||||
|
return markdown_header_text_splitter_chain
|
||||||
|
|
||||||
|
|
||||||
|
markdown_3_headers_text_splitter_chain = get_markdown_header_text_splitter_chain(
|
||||||
|
MarkdownHeaderTextSplitter(
|
||||||
|
headers_to_split_on=[
|
||||||
|
("#", "#"),
|
||||||
|
("##", "##"),
|
||||||
|
("###", "###"),
|
||||||
|
],
|
||||||
|
strip_headers=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
from langchain.text_splitter import RecursiveCharacterTextSplitter, TextSplitter
|
||||||
|
from langchain_core.runnables import Runnable, chain
|
||||||
|
|
||||||
|
|
||||||
|
def get_text_splitter_chain(
|
||||||
|
text_splitter: TextSplitter,
|
||||||
|
) -> Runnable[str, list[str]]:
|
||||||
|
@chain
|
||||||
|
def text_splitter_chain(text: str) -> list[str]:
|
||||||
|
return text_splitter.split_text(text)
|
||||||
|
|
||||||
|
return text_splitter_chain
|
||||||
|
|
||||||
|
|
||||||
|
recursive_character_text_splitter_chain = get_text_splitter_chain(
|
||||||
|
RecursiveCharacterTextSplitter()
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
from fastapi import Depends
|
||||||
|
|
||||||
|
from llm_qa.embeddings.tei import TeiEmbeddings
|
||||||
|
from llm_qa.settings import Settings
|
||||||
|
|
||||||
|
|
||||||
|
def settings() -> Settings:
|
||||||
|
return Settings()
|
||||||
|
|
||||||
|
|
||||||
|
def tei_embeddings(settings: Annotated[Settings, Depends(settings)]) -> TeiEmbeddings:
|
||||||
|
return TeiEmbeddings(
|
||||||
|
base_url=settings.tei_base_url,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def tei_rerank_embeddings(
|
||||||
|
settings: Annotated[Settings, Depends(settings)],
|
||||||
|
) -> TeiEmbeddings:
|
||||||
|
return TeiEmbeddings(
|
||||||
|
base_url=settings.tei_rerank_base_url,
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
from abc import ABC
|
||||||
|
|
||||||
|
from langchain.embeddings.base import Embeddings
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class PydanticEmbeddings(Embeddings, BaseModel, ABC):
|
||||||
|
pass
|
||||||
|
|
@ -0,0 +1,105 @@
|
||||||
|
from typing import override
|
||||||
|
from urllib.parse import urljoin
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
from llm_qa.embeddings.base import PydanticEmbeddings
|
||||||
|
from llm_qa.models.tei import (
|
||||||
|
EmbedRequest,
|
||||||
|
EmbedResponseAdapter,
|
||||||
|
ErrorResponse,
|
||||||
|
RerankRequest,
|
||||||
|
RerankResponse,
|
||||||
|
RerankResponseAdapter,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TeiEmbeddings(PydanticEmbeddings):
|
||||||
|
base_url: str
|
||||||
|
embed_endpoint: str = "/embed"
|
||||||
|
rerank_endpoint: str = "/rerank"
|
||||||
|
document_prefix: str = "passage: "
|
||||||
|
query_prefix: str = "query: "
|
||||||
|
_client: httpx.Client = httpx.Client()
|
||||||
|
_async_client: httpx.AsyncClient = httpx.AsyncClient()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def embed_url(self) -> str:
|
||||||
|
return urljoin(self.base_url, self.embed_endpoint)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def rerank_url(self) -> str:
|
||||||
|
return urljoin(self.base_url, self.rerank_endpoint)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _handle_status(response: httpx.Response) -> None:
|
||||||
|
try:
|
||||||
|
response.raise_for_status()
|
||||||
|
except httpx.HTTPStatusError as e:
|
||||||
|
match e.response.status_code:
|
||||||
|
case 413 | 422 | 424 | 429:
|
||||||
|
try:
|
||||||
|
error_response = ErrorResponse.model_validate_json(
|
||||||
|
e.response.content
|
||||||
|
)
|
||||||
|
note = f"Error: {error_response.error}, Error Type: {error_response.error_type}"
|
||||||
|
except ValueError:
|
||||||
|
note = e.response.text
|
||||||
|
e.add_note(note)
|
||||||
|
raise
|
||||||
|
|
||||||
|
def _embed(self, text: str | list[str]) -> list[list[float]]:
|
||||||
|
"""Embed text."""
|
||||||
|
response = self._client.post(
|
||||||
|
url=self.embed_url,
|
||||||
|
json=EmbedRequest(inputs=text).model_dump(),
|
||||||
|
)
|
||||||
|
self._handle_status(response)
|
||||||
|
return EmbedResponseAdapter.validate_json(response.content)
|
||||||
|
|
||||||
|
async def _aembed(self, text: str | list[str]) -> list[list[float]]:
|
||||||
|
"""Asynchronously embed text."""
|
||||||
|
response = await self._async_client.post(
|
||||||
|
url=self.embed_url,
|
||||||
|
json=EmbedRequest(inputs=text).model_dump(),
|
||||||
|
)
|
||||||
|
self._handle_status(response)
|
||||||
|
return EmbedResponseAdapter.validate_json(response.content)
|
||||||
|
|
||||||
|
@override
|
||||||
|
def embed_documents(self, texts: list[str]) -> list[list[float]]:
|
||||||
|
"""Embed search docs."""
|
||||||
|
return self._embed([self.document_prefix + text for text in texts])
|
||||||
|
|
||||||
|
@override
|
||||||
|
def embed_query(self, text: str) -> list[float]:
|
||||||
|
"""Embed query text."""
|
||||||
|
return self._embed(self.document_prefix + text)[0]
|
||||||
|
|
||||||
|
@override
|
||||||
|
async def aembed_documents(self, texts: list[str]) -> list[list[float]]:
|
||||||
|
"""Asynchronous Embed search docs."""
|
||||||
|
return await self._aembed([self.document_prefix + text for text in texts])
|
||||||
|
|
||||||
|
@override
|
||||||
|
async def aembed_query(self, text: str) -> list[float]:
|
||||||
|
"""Asynchronous Embed query text."""
|
||||||
|
return (await self._aembed(self.document_prefix + text))[0]
|
||||||
|
|
||||||
|
def rerank(self, query: str, texts: list[str]) -> list[RerankResponse]:
|
||||||
|
"""Rerank texts."""
|
||||||
|
response = self._client.post(
|
||||||
|
url=self.rerank_url,
|
||||||
|
json=RerankRequest(query=query, texts=texts).model_dump(),
|
||||||
|
)
|
||||||
|
self._handle_status(response)
|
||||||
|
return RerankResponseAdapter.validate_json(response.content)
|
||||||
|
|
||||||
|
async def arerank(self, query: str, texts: list[str]) -> list[RerankResponse]:
|
||||||
|
"""Asynchronously rerank texts."""
|
||||||
|
response = await self._async_client.post(
|
||||||
|
url=self.rerank_url,
|
||||||
|
json=RerankRequest(query=query, texts=texts).model_dump(),
|
||||||
|
)
|
||||||
|
self._handle_status(response)
|
||||||
|
return RerankResponseAdapter.validate_json(response.content)
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
from enum import StrEnum
|
||||||
|
|
||||||
|
from pydantic import BaseModel, Field, TypeAdapter
|
||||||
|
|
||||||
|
|
||||||
|
class EmbedRequest(BaseModel):
|
||||||
|
inputs: str | list[str]
|
||||||
|
normalize: bool = True
|
||||||
|
truncate: bool = False
|
||||||
|
|
||||||
|
|
||||||
|
EmbedResponseAdapter = TypeAdapter(list[list[float]])
|
||||||
|
|
||||||
|
|
||||||
|
class RerankRequest(BaseModel):
|
||||||
|
query: str
|
||||||
|
texts: list[str]
|
||||||
|
raw_scores: bool = False
|
||||||
|
return_text: bool = False
|
||||||
|
truncate: bool = False
|
||||||
|
|
||||||
|
|
||||||
|
class RerankResponse(BaseModel):
|
||||||
|
index: int = Field(..., ge=0)
|
||||||
|
score: float
|
||||||
|
text: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
RerankResponseAdapter = TypeAdapter(list[RerankResponse])
|
||||||
|
|
||||||
|
|
||||||
|
class ErrorType(StrEnum):
|
||||||
|
Unhealthy = "Unhealthy"
|
||||||
|
Backend = "Backend"
|
||||||
|
Overloaded = "Overloaded"
|
||||||
|
Validation = "Validation"
|
||||||
|
Tokenizer = "Tokenizer"
|
||||||
|
|
||||||
|
|
||||||
|
class ErrorResponse(BaseModel):
|
||||||
|
error: str
|
||||||
|
error_type: ErrorType
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
from enum import StrEnum
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class TextType(StrEnum):
|
||||||
|
PLAIN_TEXT = "PLAIN_TEXT"
|
||||||
|
MARKDOWN = "MARKDOWN"
|
||||||
|
|
||||||
|
|
||||||
|
class UpsertTextRequest(BaseModel):
|
||||||
|
text: str
|
||||||
|
type: TextType
|
||||||
|
collection: str
|
||||||
|
|
||||||
|
|
||||||
|
class UpsertTextResponse(BaseModel):
|
||||||
|
num_documents: int
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
from llm_qa.routers import upsert
|
||||||
|
|
||||||
|
router = APIRouter(prefix="/api/v1", tags=["v1"])
|
||||||
|
router.include_router(upsert.router)
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Depends
|
||||||
|
|
||||||
|
from llm_qa.dependencies import settings, tei_embeddings
|
||||||
|
from llm_qa.embeddings.base import PydanticEmbeddings
|
||||||
|
from llm_qa.models.upsert import UpsertTextRequest, UpsertTextResponse
|
||||||
|
from llm_qa.services.upsert import upsert_text as upsert_text_service
|
||||||
|
from llm_qa.settings import Settings
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/upsert-text")
|
||||||
|
async def upsert_text(
|
||||||
|
upsert_request: UpsertTextRequest,
|
||||||
|
settings: Annotated[Settings, Depends(settings)],
|
||||||
|
embeddings: Annotated[PydanticEmbeddings, Depends(tei_embeddings)],
|
||||||
|
) -> UpsertTextResponse:
|
||||||
|
num_documents = await upsert_text_service(
|
||||||
|
text=upsert_request.text,
|
||||||
|
text_type=upsert_request.type,
|
||||||
|
collection=upsert_request.collection,
|
||||||
|
embeddings=embeddings,
|
||||||
|
qdrant_url=settings.qdrant_url,
|
||||||
|
)
|
||||||
|
return UpsertTextResponse(num_documents=num_documents)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/upsert-file")
|
||||||
|
async def upsert_file() -> None:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from langchain.schema.document import Document
|
||||||
|
from langchain_core.embeddings import Embeddings
|
||||||
|
|
||||||
|
from llm_qa.chains.text_splitters.markdown_header_text_splitter import (
|
||||||
|
markdown_3_headers_text_splitter_chain,
|
||||||
|
)
|
||||||
|
from llm_qa.chains.text_splitters.text_splitter import (
|
||||||
|
recursive_character_text_splitter_chain,
|
||||||
|
)
|
||||||
|
from llm_qa.models.upsert import TextType
|
||||||
|
from llm_qa.vectorstores.qdrant import upsert_documents
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
async def upsert_text(
|
||||||
|
text: str,
|
||||||
|
text_type: TextType,
|
||||||
|
collection: str,
|
||||||
|
embeddings: Embeddings,
|
||||||
|
qdrant_url: str,
|
||||||
|
) -> int:
|
||||||
|
match text_type:
|
||||||
|
case TextType.PLAIN_TEXT:
|
||||||
|
text_splitter_chain = recursive_character_text_splitter_chain
|
||||||
|
case TextType.MARKDOWN:
|
||||||
|
text_splitter_chain = markdown_3_headers_text_splitter_chain
|
||||||
|
case _:
|
||||||
|
raise ValueError(f"Unknown text type: `{text_type}`") # noqa: TRY003
|
||||||
|
|
||||||
|
text_chunks = await text_splitter_chain.ainvoke(text)
|
||||||
|
|
||||||
|
documents = [Document(page_content=chunk) for chunk in text_chunks]
|
||||||
|
|
||||||
|
await upsert_documents(
|
||||||
|
documents=documents,
|
||||||
|
embeddings=embeddings,
|
||||||
|
qdrant_url=qdrant_url,
|
||||||
|
collection=collection,
|
||||||
|
)
|
||||||
|
return len(documents)
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||||
|
|
||||||
|
|
||||||
|
class Settings(BaseSettings):
|
||||||
|
model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8")
|
||||||
|
|
||||||
|
qdrant_url: str
|
||||||
|
tei_base_url: str
|
||||||
|
tei_rerank_base_url: str
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from langchain.docstore.document import Document
|
||||||
|
from langchain.embeddings.base import Embeddings
|
||||||
|
from langchain.vectorstores.qdrant import Qdrant
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
async def upsert_documents(
|
||||||
|
documents: list[Document], embeddings: Embeddings, qdrant_url: str, collection: str
|
||||||
|
) -> None:
|
||||||
|
logger.info(
|
||||||
|
"Upserting %d documents to Qdrant collection `%s`", len(documents), collection
|
||||||
|
)
|
||||||
|
await Qdrant.afrom_documents(
|
||||||
|
documents=documents,
|
||||||
|
embedding=embeddings,
|
||||||
|
url=qdrant_url,
|
||||||
|
prefer_grpc=True,
|
||||||
|
collection_name=collection,
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
from fastapi import FastAPI
|
||||||
|
|
||||||
|
from llm_qa.routers import api_v1
|
||||||
|
|
||||||
|
app = FastAPI(title="LLM QA")
|
||||||
|
app.include_router(api_v1.router)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import uvicorn
|
||||||
|
|
||||||
|
uvicorn.run("llm_qa.web:app", host="0.0.0.0", port=8000, reload=True) # noqa: S104
|
||||||
|
|
@ -287,6 +287,20 @@ files = [
|
||||||
{file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"},
|
{file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "click"
|
||||||
|
version = "8.1.7"
|
||||||
|
description = "Composable command line interface toolkit"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"},
|
||||||
|
{file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
colorama = {version = "*", markers = "platform_system == \"Windows\""}
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "colorama"
|
name = "colorama"
|
||||||
version = "0.4.6"
|
version = "0.4.6"
|
||||||
|
|
@ -338,6 +352,25 @@ files = [
|
||||||
[package.extras]
|
[package.extras]
|
||||||
tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich"]
|
tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fastapi"
|
||||||
|
version = "0.109.2"
|
||||||
|
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "fastapi-0.109.2-py3-none-any.whl", hash = "sha256:2c9bab24667293b501cad8dd388c05240c850b58ec5876ee3283c47d6e1e3a4d"},
|
||||||
|
{file = "fastapi-0.109.2.tar.gz", hash = "sha256:f3817eac96fe4f65a2ebb4baa000f394e55f5fccdaf7f75250804bc58f354f73"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0"
|
||||||
|
starlette = ">=0.36.3,<0.37.0"
|
||||||
|
typing-extensions = ">=4.8.0"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
all = ["email-validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.7)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "frozenlist"
|
name = "frozenlist"
|
||||||
version = "1.4.1"
|
version = "1.4.1"
|
||||||
|
|
@ -495,6 +528,62 @@ files = [
|
||||||
docs = ["Sphinx", "furo"]
|
docs = ["Sphinx", "furo"]
|
||||||
test = ["objgraph", "psutil"]
|
test = ["objgraph", "psutil"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "h11"
|
||||||
|
version = "0.14.0"
|
||||||
|
description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"},
|
||||||
|
{file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "httpcore"
|
||||||
|
version = "1.0.2"
|
||||||
|
description = "A minimal low-level HTTP client."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "httpcore-1.0.2-py3-none-any.whl", hash = "sha256:096cc05bca73b8e459a1fc3dcf585148f63e534eae4339559c9b8a8d6399acc7"},
|
||||||
|
{file = "httpcore-1.0.2.tar.gz", hash = "sha256:9fc092e4799b26174648e54b74ed5f683132a464e95643b226e00c2ed2fa6535"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
certifi = "*"
|
||||||
|
h11 = ">=0.13,<0.15"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
asyncio = ["anyio (>=4.0,<5.0)"]
|
||||||
|
http2 = ["h2 (>=3,<5)"]
|
||||||
|
socks = ["socksio (==1.*)"]
|
||||||
|
trio = ["trio (>=0.22.0,<0.23.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "httpx"
|
||||||
|
version = "0.26.0"
|
||||||
|
description = "The next generation HTTP client."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"},
|
||||||
|
{file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
anyio = "*"
|
||||||
|
certifi = "*"
|
||||||
|
httpcore = "==1.*"
|
||||||
|
idna = "*"
|
||||||
|
sniffio = "*"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
brotli = ["brotli", "brotlicffi"]
|
||||||
|
cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"]
|
||||||
|
http2 = ["h2 (>=3,<5)"]
|
||||||
|
socks = ["socksio (==1.*)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "idna"
|
name = "idna"
|
||||||
version = "3.6"
|
version = "3.6"
|
||||||
|
|
@ -1113,6 +1202,21 @@ files = [
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
|
typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pydantic-settings"
|
||||||
|
version = "2.1.0"
|
||||||
|
description = "Settings management using Pydantic"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "pydantic_settings-2.1.0-py3-none-any.whl", hash = "sha256:7621c0cb5d90d1140d2f0ef557bdf03573aac7035948109adf2574770b77605a"},
|
||||||
|
{file = "pydantic_settings-2.1.0.tar.gz", hash = "sha256:26b1492e0a24755626ac5e6d715e9077ab7ad4fb5f19a8b7ed7011d52f36141c"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
pydantic = ">=2.3.0"
|
||||||
|
python-dotenv = ">=0.21.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pygments"
|
name = "pygments"
|
||||||
version = "2.17.2"
|
version = "2.17.2"
|
||||||
|
|
@ -1128,6 +1232,20 @@ files = [
|
||||||
plugins = ["importlib-metadata"]
|
plugins = ["importlib-metadata"]
|
||||||
windows-terminal = ["colorama (>=0.4.6)"]
|
windows-terminal = ["colorama (>=0.4.6)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "python-dotenv"
|
||||||
|
version = "1.0.1"
|
||||||
|
description = "Read key-value pairs from a .env file and set them as environment variables"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"},
|
||||||
|
{file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
cli = ["click (>=5.0)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyyaml"
|
name = "pyyaml"
|
||||||
version = "6.0.1"
|
version = "6.0.1"
|
||||||
|
|
@ -1363,6 +1481,23 @@ pure-eval = "*"
|
||||||
[package.extras]
|
[package.extras]
|
||||||
tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"]
|
tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "starlette"
|
||||||
|
version = "0.36.3"
|
||||||
|
description = "The little ASGI library that shines."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "starlette-0.36.3-py3-none-any.whl", hash = "sha256:13d429aa93a61dc40bf503e8c801db1f1bca3dc706b10ef2434a36123568f044"},
|
||||||
|
{file = "starlette-0.36.3.tar.gz", hash = "sha256:90a671733cfb35771d8cc605e0b679d23b992f8dcfad48cc60b38cb29aeb7080"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
anyio = ">=3.4.0,<5"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7)", "pyyaml"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tenacity"
|
name = "tenacity"
|
||||||
version = "8.2.3"
|
version = "8.2.3"
|
||||||
|
|
@ -1435,6 +1570,24 @@ h2 = ["h2 (>=4,<5)"]
|
||||||
socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
|
socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
|
||||||
zstd = ["zstandard (>=0.18.0)"]
|
zstd = ["zstandard (>=0.18.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "uvicorn"
|
||||||
|
version = "0.27.1"
|
||||||
|
description = "The lightning-fast ASGI server."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "uvicorn-0.27.1-py3-none-any.whl", hash = "sha256:5c89da2f3895767472a35556e539fd59f7edbe9b1e9c0e1c99eebeadc61838e4"},
|
||||||
|
{file = "uvicorn-0.27.1.tar.gz", hash = "sha256:3d9a267296243532db80c83a959a3400502165ade2c1338dea4e67915fd4745a"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
click = ">=7.0"
|
||||||
|
h11 = ">=0.8"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wcwidth"
|
name = "wcwidth"
|
||||||
version = "0.2.13"
|
version = "0.2.13"
|
||||||
|
|
@ -1552,4 +1705,4 @@ multidict = ">=4.0"
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.12"
|
python-versions = "^3.12"
|
||||||
content-hash = "97e8b5f3e3e62c576dc50012212e077c00fd9fe18b6273a8929bf95d24ba168f"
|
content-hash = "7ffd6635973f31fbcbc5e962102b8cf12920f83e383dc0bb73732522db897a6f"
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,10 @@ readme = "README.md"
|
||||||
python = "^3.12"
|
python = "^3.12"
|
||||||
langchain-community = "^0.0.19"
|
langchain-community = "^0.0.19"
|
||||||
langchain = "^0.1.6"
|
langchain = "^0.1.6"
|
||||||
|
fastapi = "^0.109.2"
|
||||||
|
uvicorn = "^0.27.1"
|
||||||
|
httpx = "^0.26.0"
|
||||||
|
pydantic-settings = "^2.1.0"
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
ruff = "0.2.1"
|
ruff = "0.2.1"
|
||||||
|
|
@ -18,3 +22,52 @@ ipython = "^8.21.0"
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry-core"]
|
requires = ["poetry-core"]
|
||||||
build-backend = "poetry.core.masonry.api"
|
build-backend = "poetry.core.masonry.api"
|
||||||
|
|
||||||
|
|
||||||
|
[tool.mypy]
|
||||||
|
python_version = "3.12"
|
||||||
|
plugins = ["pydantic.mypy"]
|
||||||
|
modules = ["llm_qa"]
|
||||||
|
strict = true
|
||||||
|
|
||||||
|
[tool.ruff]
|
||||||
|
target-version = "py312"
|
||||||
|
preview = true
|
||||||
|
include = ["llm_qa/**/*.py", "pyproject.toml"]
|
||||||
|
lint.select = [
|
||||||
|
"F",
|
||||||
|
"I",
|
||||||
|
"N",
|
||||||
|
"UP",
|
||||||
|
"YTT",
|
||||||
|
"ANN",
|
||||||
|
"ASYNC",
|
||||||
|
"S",
|
||||||
|
"B",
|
||||||
|
"C4",
|
||||||
|
"DTZ",
|
||||||
|
"FA",
|
||||||
|
"ISC",
|
||||||
|
"ICN",
|
||||||
|
"G",
|
||||||
|
"INP",
|
||||||
|
"PIE",
|
||||||
|
"PT",
|
||||||
|
"RSE",
|
||||||
|
"RET",
|
||||||
|
"SLF",
|
||||||
|
"SIM",
|
||||||
|
"TID",
|
||||||
|
"TCH",
|
||||||
|
"PTH",
|
||||||
|
"ERA",
|
||||||
|
"PGH",
|
||||||
|
"PL",
|
||||||
|
"TRY",
|
||||||
|
"FLY",
|
||||||
|
"PERF",
|
||||||
|
"FURB",
|
||||||
|
"LOG",
|
||||||
|
"RUF",
|
||||||
|
]
|
||||||
|
lint.ignore = ["E501", "ANN101", "PLR0913", "PLR0917", "ISC001"]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue