| from typing import Optional |
| from pinecone import Pinecone, ServerlessSpec |
| from src.models import Chunk |
| from src.config import PINECONE_API_KEY, PINECONE_INDEX_NAME |
| from src.embedder import embedding_dimension |
|
|
|
|
| _pc: Optional[Pinecone] = None |
| _index = None |
|
|
|
|
| def _get_pc() -> Pinecone: |
| global _pc |
| if _pc is None: |
| _pc = Pinecone(api_key=PINECONE_API_KEY) |
| return _pc |
|
|
|
|
| def _ensure_index(): |
| global _index |
| if _index is not None: |
| return _index |
|
|
| pc = _get_pc() |
| dim = embedding_dimension() |
|
|
| existing = [i.name for i in pc.list_indexes()] |
| if PINECONE_INDEX_NAME not in existing: |
| pc.create_index( |
| name=PINECONE_INDEX_NAME, |
| dimension=dim, |
| metric="cosine", |
| spec=ServerlessSpec(cloud="aws", region="us-east-1"), |
| ) |
|
|
| _index = pc.Index(PINECONE_INDEX_NAME) |
| return _index |
|
|
|
|
| def upsert_embeddings(chunks: list[Chunk], vectors: list[list[float]]): |
| index = _ensure_index() |
| records = [] |
| for chunk, vec in zip(chunks, vectors): |
| records.append({ |
| "id": chunk.id, |
| "values": vec, |
| "metadata": {"text": chunk.text[:1000], "source": chunk.source}, |
| }) |
| batch_size = 100 |
| for i in range(0, len(records), batch_size): |
| index.upsert(records[i:i + batch_size]) |
|
|
|
|
| def semantic_search(query_vector: list[float], top_k: int = 5) -> list[dict]: |
| index = _ensure_index() |
| result = index.query(vector=query_vector, top_k=top_k, include_metadata=True) |
| return [{"id": m.id, "score": m.score, "text": m.metadata.get("text", "")} |
| for m in result.matches] |
|
|
|
|
| def close(): |
| global _pc, _index |
| _index = None |
| _pc = None |
|
|