Build a Q&A Bot over Your Company Docs with Pinecone + Claude (Python, SDK v10) A developer published a walkthrough for building a command-line retrieval-augmented generation Q&A bot over internal company documents using Pinecone's Python SDK v10.3.0 and Anthropic's anthropic 2.4.0 with the claude-sonnet-5-5 model. The pipeline chunks documents, embeds and stores them in a Pinecone index via hosted llama-text-embed-v2, retrieves and reranks results with bge-reranker-v2-m3, then prompts Claude to answer only from retrieved chunks with numbered citations, and it maps deprecated v5–v7 SDK calls to their v10 equivalents. AI disclosure: This article was written with the help of AI and reviewed by me. Every API call in it was checked against the SDK source of pinecone==10.3.0 and anthropic==2.4.0 , and prices and limits were taken from the official pages linked inline as of November 2026 . Treat the code as a starting point: run it against your own keys before relying on it. Once a company has enough internal docs, the same problems show up: new hires can't find the leave policy, nobody remembers which version of the expense rules is current, and wiki search only works if you guess the exact words the author used "PTO" won't find the page that says "annual leave" . Retrieval-augmented generation RAG is the usual fix: split documents into chunks, embed them into a vector database, retrieve the most relevant chunks for each question, and have an LLM answer only from those chunks, with citations. This walkthrough builds a command-line Q&A bot with Pinecone managed vector database with hosted embedding and rerank models and Claude Anthropic's LLM, for answer generation : Versions used latest on PyPI in November 2026 : | Component | Version | |---|---| | Python | 3.10+ Pinecone SDK v10 supports 3.10–3.14 | | pinecone Pinecone Python SDK | 10.3.0, API version 2026-07 | | anthropic Anthropic Python SDK | 2.4.0 | | Claude model | claude-sonnet-5-5 | The Pinecone SDK changed a lot in v10. Many tutorials still use the v5–v7 style create index for model , upsert records , ServerlessSpec , which may fail or emit deprecation warnings. The pitfalls section maps old calls to new ones. ┌──────────── Offline ingest ─────────────────┐ .md/.txt ── chunk ── documents.upsert ── Pinecone index │ llama-text-embed-v2 │ embeds on write └─────────────────────────────────────────────┘ ┌──────────── Online ask ─────────────────────┐ question ── documents.search embed scoring, recall 12 ── inference.rerank bge-reranker-v2-m3, keep 4 ── build prompt ── Claude Messages API ── answer with n citations └─────────────────────────────────────────────┘ Embedding, storage and reranking all happen on Pinecone's side, so you don't install PyTorch or host an embedding model. The trade-off: your text is processed by Pinecone's hosted models more on that under data residency . Create an account at pinecone.io https://www.pinecone.io/ , then create a key on the API Keys page of the console https://app.pinecone.io https://app.pinecone.io . The free Starter plan is enough for everything here, but note its limits as of November 2026, see Pricing https://www.pinecone.io/pricing/ and Rate limits https://docs.pinecone.io/reference/api/database-limits/rate-limits.md : us-east-1 bge-reranker-v2-m3 : Create a key in the Claude Console https://platform.claude.com https://platform.claude.com . Check Anthropic's supported countries list https://www.anthropic.com/supported-countries for your region first. requirements.txt : pinecone==10.3.0 anthropic==2.4.0 python -m venv .venv source .venv/bin/activate pip install -r requirements.txt The package is pinecone , not the old pinecone-client . | Variable | Required | Notes | |---|---|---| | PINECONE API KEY | yes | From the Pinecone console | | ANTHROPIC API KEY | yes | From the Claude Console; anthropic.Anthropic reads it by default | | PINECONE INDEX | no | Index name, default company-docs | | PINECONE NAMESPACE | no | Namespace, default default-tenant | | USE RERANK | no | 1 to rerank default , 0 to skip and save Starter rerank quota | export PINECONE API KEY="your-pinecone-key" export ANTHROPIC API KEY="your-anthropic-key" Don't hard-code keys or commit them to Git. Chunking is the first thing that makes or breaks RAG quality. Chunks that are too large mix several topics, so the embedding gets "averaged out" and you waste prompt tokens. Chunks that are too small lose context and the model gets half a sentence. A simple strategy that works for Markdown or plain-text policy docs: php def split text text: str, size: int = 500, overlap: int = 80 - list str : paragraphs = p.strip for p in text.split "\n\n" if p.strip chunks, buf = , "" for p in paragraphs: if len p size: if buf: chunks.append buf buf = "" step = size - overlap chunks.extend p i:i + size for i in range 0, len p , step continue if len buf + len p + 1 <= size: buf = f"{buf}\n{p}" if buf else p else: chunks.append buf buf = buf -overlap: + "\n" + p if overlap else p if buf: chunks.append buf return chunks This counts characters, not tokens, to avoid depending on a tokenizer. 500 characters of English is roughly 100–150 tokens, well under both limits that matter here: llama-text-embed-v2 : max 2048 tokens per input, truncated by default beyond that For finer strategies by sentence, by token, by heading , see Pinecone's Chunking Strategies https://www.pinecone.io/learn/chunking-strategies/ . Each chunk carries metadata: source relative path , category top-level folder such as hr/ or finance/ and chunk index . category is used for filtering, source for citations and for cleaning up on re-ingest. Anthropic doesn't offer an embedding model Embeddings https://platform.claude.com/docs/en/build-with-claude/embeddings , so you need one from somewhere else. Three common options: | Option | How | Pros | Cons | |---|---|---|---| | A. Pinecone integrated embedding used here | Declare embed on a text field in the index schema; write and query plain text | Least code, no dimension mismatches | Limited to Pinecone-hosted models; text is processed by those models | | B. Pinecone Inference, called separately | pc.inference.embed ... returns vectors you write to a dense vector field | You own the vectors and can reuse them | You must keep the model and dimension consistent between write and query | | C. Third-party or self-hosted model | Voyage AI one of the providers Anthropic's docs point to , another cloud API, or an open-source model | Any model, can be fully private | Another service to run; you align dimensions and normalization yourself | Pinecone-hosted dense embedding models docs as of November 2026 : | Model | Dimensions | Max input | Notes | |---|---|---|---| | llama-text-embed-v2 | 1024 default / 2048 / 768 / 512 / 384 | 2048 tokens | Model page lists 26 supported languages | | multilingual-e5-large | 1024 | 507 tokens | Multilingual; good for short queries + medium passages | | text-embedding-3-small / -large | up to 1536 / 3072, configurable | 8192 tokens | OpenAI models processed via Microsoft Azure; document indexes only | | gemini-embedding-2 | 128–3072 | 8192 tokens | Google model; not available on Starter | On the Standard plan, llama-text-embed-v2 is listed at $0.16 per million tokens and multilingual-e5-large at $0.08 Pricing https://www.pinecone.io/pricing/ . This walkthrough uses llama-text-embed-v2 : multilingual, a roomier input limit than e5, and available on Starter. If your docs are mostly short Q&A pairs, try multilingual-e5-large : change the model name in the schema and rebuild the index. In SDK v10 the recommended index type is the document index . You declare the scoring fields with SchemaBuilder ; every other field you write is stored as metadata and indexed for filtering automatically. Adding an embed config to a string field makes Pinecone embed that field's text on write, and embed the query text at search time Integrated embedding https://docs.pinecone.io/guides/search/integrated-embedding.md . python import os from pinecone import Pinecone, SchemaBuilder pc = Pinecone api key=os.environ "PINECONE API KEY" schema = SchemaBuilder .add string field name="chunk text", embed={"model": "llama-text-embed-v2", "dimension": 1024}, .build if not pc.indexes.exists name="company-docs" : pc.indexes.create name="company-docs", schema=schema, deployment={"deployment type": "managed", "cloud": "aws", "region": "us-east-1"}, Notes: deployment type: "managed" means serverless. spec=ServerlessSpec ... is deprecated the SDK source says it's translated into deployment . If you omit pc.indexes.create timeout=-1 to return immediately. source or category in it returns a 400 at creation. embed field also stores the original text and enables full-text search by default store text defaults to true , so search results can return chunk text . Set store text=False if you don't want the raw text stored in Pinecone, but then you'll have to look it up by ID in your own storage. index = pc.index name="company-docs" documents = { " id": "3be9998a6b0d7b83 0", ASCII only "chunk text": "Employees get 5 days of paid annual leave after one year of service...", "source": "hr/annual-leave.md", metadata, filterable automatically "category": "hr", "chunk index": 0, }, ... for i in range 0, len documents , 96 : index.documents.upsert namespace="default-tenant", documents=documents i:i + 96 Key points: id must be 1–512 ASCII characters. usage.embed total tokens in the response tells you how many embedding tokens the write used, which helps reconcile costs. Re-ingesting: if a file shrinks and produces fewer chunks, the old extra chunks linger. The script calls documents.delete filter=... by source before writing, to clear a file's old chunks. resp = index.documents.search namespace="default-tenant", top k=12, score by= {"type": "embed", "fields": "chunk text" , "query": "How much annual leave do I get?"} , include fields= "chunk text", "source", "chunk index" , filter={"category": {"$eq": "hr"}}, optional metadata filter for m in resp.matches: print m.id, m.score, m.get "source" include fields you only get score . This is a classic beginner trap: the search "works" but returns no text. " " returns every field. score by ; it can't be combined with a BM25 text clause. For hybrid search, the docs suggest running both searches and merging with RRF Vector recall uses a bi-encoder: question and documents are embedded separately and compared by distance. It's fast but coarse. A rerank model cross-encoder scores the question and each document together, which is more accurate but slower. The usual pattern is to over-recall with vectors 12 here and rerank down to the best few 4 here for the prompt. ranked = pc.inference.rerank model="bge-reranker-v2-m3", query="How much annual leave do I get?", documents= {"id": m.id, "chunk text": m.get "chunk text", "" } for m in resp.matches , rank fields= "chunk text" , top n=4, return documents=False, parameters={"truncate": "END"}, for r in ranked.data: print r.index, r.score r.index is the position in the documents you passed in Model choice: Pinecone currently hosts three rerank models: bge-reranker-v2-m3 multilingual, available on Starter , cohere-rerank-4-fast Standard and above, processed via Azure, possibly outside the US , and pinecone-rerank-v0 deprecated; don't use it in new projects . bge-reranker-v2-m3 defaults to truncate: NONE , which errors on over-long input, so END is set explicitly. Older tutorials often do recall + rerank in one call via index.search ..., rerank={...} . That's for legacy integrated indexes created with create index for model , using the Records API . With document indexes, rerank is a separate pc.inference.rerank call. Wrap each retrieved chunk in numbered XML tags so the model has anchors to cite: SYSTEM PROMPT = """You are an internal knowledge-base assistant. Answer employees' questions using ONLY the material inside