Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
45a2e54
chore: define learning steps
Sep 5, 2025
0448a6f
chore: update LN steps
Oct 20, 2025
b7c0ff9
Merge branch 'langchain-ai:master' into master
thongvmdev Oct 25, 2025
92f6953
WIP: 1025
thongvmdev Oct 25, 2025
8d009ac
feat: update docker config
thongvmdev Oct 30, 2025
1d34486
feat: migrate to using nomic-embed-text embedding model
thongvmdev Nov 1, 2025
0030874
chore: update ingest docs
thongvmdev Nov 3, 2025
2a91b51
done ingest learning
thongvmdev Nov 5, 2025
ce9b700
Merge branch 'master' into learn/ingestion
thongvmdev Nov 5, 2025
7d1e06a
finish learning graph flow
thongvmdev Nov 8, 2025
5d282ca
learn: finish phase 3 Frontend Architecture
thongvmdev Nov 13, 2025
74c0546
feat: update evals logic
thongvmdev Nov 20, 2025
edaaafd
feat: update test file
thongvmdev Nov 21, 2025
4be30b1
chore: update yml file
thongvmdev Nov 21, 2025
d606eaa
update yml
thongvmdev Nov 21, 2025
56303fd
chore: update eval yml
thongvmdev Nov 21, 2025
caafd47
Update script
thongvmdev Nov 21, 2025
acdd2cd
update eval yml
thongvmdev Nov 21, 2025
0550cd0
ignore evaluate_retrieval_recall test
thongvmdev Nov 21, 2025
f7fa3cf
feat: update model split logic
thongvmdev Nov 21, 2025
6842126
chore: update LN steps
thongvmdev Nov 22, 2025
270cfd0
chore: re-structure docs file
thongvmdev Nov 22, 2025
bebb3cf
chore: add blog evals
thongvmdev Nov 22, 2025
4b4701b
feat: init migration
thongvmdev Nov 24, 2025
c3a729f
refactor ingest pipeline
thongvmdev Nov 24, 2025
2705beb
feat: update
thongvmdev Nov 25, 2025
4ccdf66
feat: add ollama api key
thongvmdev Nov 25, 2025
dbdaca9
fix issues
thongvmdev Nov 25, 2025
817afb4
chore: remove log
thongvmdev Nov 25, 2025
72a7ffb
chore: add log table fomart
thongvmdev Nov 26, 2025
83a8741
update judgeModel
thongvmdev Nov 26, 2025
810c5f0
update model
thongvmdev Nov 26, 2025
46a6b05
update test e2e
thongvmdev Nov 26, 2025
0b01c34
feat: update to consume eval module from langsmith
thongvmdev Nov 26, 2025
f8c34a7
chore: remove log
thongvmdev Nov 26, 2025
fc87be9
update eval js yml
thongvmdev Nov 26, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions .cursor/plans/migrate-to-local-weaviate-1df91472.plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<!-- 1df91472-d73d-47a7-8bf6-a4f0f3c34bca 761d6e0b-bf93-4fb8-acbc-c61e0c924bfb -->
# Migrate to Local Weaviate with Transformers

## Overview

Replace OpenAI embeddings and Weaviate Cloud with a local Weaviate Docker instance using the `text2vec-transformers` module (`sentence-transformers/multi-qa-MiniLM-L6-cos-v1`) for cost-free embeddings.

## Key Changes

### 1. Update Docker Infrastructure

**File: `docker-compose.yml`**

Add Weaviate service with text2vec-transformers module:

- Weaviate core service on port 8080
- text2vec-transformers service with `sentence-transformers-multi-qa-MiniLM-L6-cos-v1` model
- CPU-only configuration (ENABLE_CUDA: 0)
- Persistent volume for data storage

### 2. Update Weaviate Connection Logic

**File: `backend/ingest.py`**

Changes:

- Replace `weaviate.connect_to_weaviate_cloud()` with `weaviate.connect_to_local()`
- Remove `WEAVIATE_API_KEY` usage (local instance doesn't need auth)
- Configure WeaviateVectorStore to use Weaviate's built-in vectorizer instead of external embeddings
- The `text2vec-transformers` module will handle vectorization automatically

**File: `backend/retrieval.py`**

Changes:

- Update `make_weaviate_retriever()` to connect to local instance
- Modify `make_text_encoder()` to support `weaviate/text2vec-transformers` option
- Handle embedding model configuration for local vs cloud scenarios

### 3. Update Embedding Configuration

**File: `backend/embeddings.py`**

Update `get_embeddings_model()` to return `None` or a placeholder when using Weaviate's built-in vectorizer, since Weaviate handles embeddings internally.

**File: `backend/configuration.py`**

Update default `embedding_model` from `"openai/text-embedding-3-small"` to `"weaviate/text2vec-transformers"` to reflect the new local setup.

### 4. Environment Variables

Update `.env` file:

- Change `WEAVIATE_URL` from cloud URL to `http://localhost:8080`
- `WEAVIATE_API_KEY` can be removed or left empty (not needed for local)

### 5. Optional: Update Index Names

**File: `backend/constants.py`**

Consider renaming index names to reflect the new embedding model (e.g., replace `OpenAI_text_embedding_3_small` suffix with `transformers_multi_qa_MiniLM`).

## Technical Notes

- Weaviate's text2vec-transformers module vectorizes text automatically at ingestion and query time
- No external API calls for embeddings = zero cost
- CPU-only transformer inference will be slower than GPU but functional
- First run will download the transformer model (~80MB)
- Data persists in Docker volume between restarts

### To-dos

- [ ] Add Weaviate and text2vec-transformers services to docker-compose.yml with proper configuration
- [ ] Modify backend/ingest.py to connect to local Weaviate and remove external embedding dependency
- [ ] Modify backend/retrieval.py to support local Weaviate connection and built-in vectorizer
- [ ] Update backend/embeddings.py and backend/configuration.py for Weaviate built-in vectorizer
- [ ] Start Docker services and test ingestion with local Weaviate to verify embeddings work correctly
57 changes: 57 additions & 0 deletions .github/workflows/eval-js.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: Eval JS

on:
workflow_dispatch:

concurrency:
group: eval-js-${{ github.ref }}
cancel-in-progress: true

jobs:
run_eval:
runs-on: ubuntu-latest
environment: evals
steps:
- uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"

- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10.23.0
run_install: false

- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV

- name: Setup pnpm cache
uses: actions/cache@v4
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('backend-js/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-

- name: Install dependencies
working-directory: backend-js
run: pnpm install --frozen-lockfile

- name: Run E2E tests
working-directory: backend-js
env:
LANGSMITH_API_KEY: ${{ secrets.LANGSMITH_API_KEY }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
OLLAMA_API_KEY: ${{ secrets.OLLAMA_API_KEY }}
OLLAMA_BASE_URL: ${{ vars.OLLAMA_BASE_URL }}
WEAVIATE_URL: ${{ vars.WEAVIATE_URL }}
WEAVIATE_GRPC_URL: ${{ vars.WEAVIATE_GRPC_URL }}
WEAVIATE_API_KEY: ${{ secrets.WEAVIATE_API_KEY }}
RECORD_MANAGER_DB_URL: ${{ secrets.RECORD_MANAGER_DB_URL }}
run: pnpm test:e2e
10 changes: 7 additions & 3 deletions .github/workflows/eval.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ concurrency:
jobs:
run_eval:
runs-on: ubuntu-latest
environment: Evaluation
environment: evals
steps:
- uses: actions/checkout@v4

Expand Down Expand Up @@ -48,7 +48,11 @@ jobs:
env:
LANGSMITH_API_KEY: ${{ secrets.LANGSMITH_API_KEY }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
WEAVIATE_URL: ${{ secrets.WEAVIATE_URL }}
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
OLLAMA_API_KEY: ${{ secrets.OLLAMA_API_KEY }}
OLLAMA_BASE_URL: ${{ vars.OLLAMA_BASE_URL }}
WEAVIATE_URL: ${{ vars.WEAVIATE_URL }}
WEAVIATE_GRPC_URL: ${{ vars.WEAVIATE_GRPC_URL }}
WEAVIATE_API_KEY: ${{ secrets.WEAVIATE_API_KEY }}
RECORD_MANAGER_DB_URL: ${{ secrets.RECORD_MANAGER_DB_URL }}
run: uv run pytest backend/tests/evals
152 changes: 115 additions & 37 deletions _scripts/clear_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,52 +2,130 @@

import logging
import os
from dotenv import load_dotenv

# Load environment variables from .env file
load_dotenv()

import weaviate
from langchain.embeddings import OpenAIEmbeddings
from langchain.indexes import SQLRecordManager, index
from langchain.vectorstores import Weaviate
from langchain_weaviate import WeaviateVectorStore
from backend.embeddings import get_embeddings_model
from backend.constants import (
OLLAMA_BASE_EMBEDDING_DOCS_URL,
OLLAMA_BASE_URL,
WEAVIATE_GENERAL_GUIDES_AND_TUTORIALS_INDEX_NAME,
)
from backend.utils import get_weaviate_client

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

WEAVIATE_URL = os.environ["WEAVIATE_URL"]
WEAVIATE_API_KEY = os.environ["WEAVIATE_API_KEY"]
RECORD_MANAGER_DB_URL = os.environ.get(
"RECORD_MANAGER_DB_URL",
)

RECORD_MANAGER_DB_URL = os.environ["RECORD_MANAGER_DB_URL"]
WEAVIATE_DOCS_INDEX_NAME = "LangChain_Combined_Docs_OpenAI_text_embedding_3_small"
WEAVIATE_URL = os.environ.get("WEAVIATE_URL")
WEAVIATE_GRPC_URL = os.environ.get("WEAVIATE_GRPC_URL")

WEAVIATE_API_KEY = os.environ.get("WEAVIATE_API_KEY")


def clear():
client = weaviate.Client(
url=WEAVIATE_URL,
auth_client_secret=weaviate.AuthApiKey(api_key=WEAVIATE_API_KEY),
)
vectorstore = Weaviate(
client=client,
index_name=WEAVIATE_DOCS_INDEX_NAME,
text_key="text",
embedding=OpenAIEmbeddings(),
by_text=False,
attributes=["source", "title"],
)

record_manager = SQLRecordManager(
f"weaviate/{WEAVIATE_DOCS_INDEX_NAME}", db_url=RECORD_MANAGER_DB_URL
)
record_manager.create_schema()

indexing_stats = index(
[],
record_manager,
vectorstore,
cleanup="full",
source_id_key="source",
)

logger.info("Indexing stats: ", indexing_stats)
logger.info(
"LangChain now has this many vectors: ",
client.query.aggregate(WEAVIATE_DOCS_INDEX_NAME).with_meta_count().do(),
)
embedding = get_embeddings_model(base_url=OLLAMA_BASE_URL)

with get_weaviate_client(
weaviate_url=WEAVIATE_URL,
weaviate_grpc_url=WEAVIATE_GRPC_URL,
weaviate_api_key=WEAVIATE_API_KEY,
) as weaviate_client:
collection_name = WEAVIATE_GENERAL_GUIDES_AND_TUTORIALS_INDEX_NAME

# First, directly delete all documents from Weaviate collection
# This ensures we delete everything, not just what's tracked in record manager
try:
collection = weaviate_client.collections.get(collection_name)

# Get count before deletion
initial_count = collection.aggregate.over_all().total_count
logger.info(
f"Found {initial_count} documents in collection before deletion"
)

if initial_count > 0:
# Fetch all object UUIDs and delete them individually
# This is the most reliable way to delete all documents
import weaviate.classes.query as wq

deleted_count = 0
batch_size = 100

while True:
# Fetch a batch of objects (only get UUIDs, not full data)
objects = collection.query.fetch_objects(limit=batch_size)

if not objects.objects:
break

# Delete each object individually
for obj in objects.objects:
try:
collection.data.delete_by_id(obj.uuid)
deleted_count += 1
except Exception as e:
logger.warning(f"Failed to delete object {obj.uuid}: {e}")

logger.info(
f"Deleted batch of {len(objects.objects)} documents (total: {deleted_count})"
)

# If we got fewer objects than batch_size, we're done
if len(objects.objects) < batch_size:
break

logger.info(
f"Successfully deleted {deleted_count} documents directly from Weaviate collection: {collection_name}"
)
else:
logger.info("Collection is already empty")

except Exception as e:
logger.warning(f"Could not delete directly from collection: {e}")
logger.info("Falling back to record manager cleanup...")

vectorstore = WeaviateVectorStore(
client=weaviate_client,
index_name=collection_name,
text_key="text",
embedding=embedding,
attributes=["source", "title"],
)

record_manager = SQLRecordManager(
f"weaviate/{collection_name}",
db_url=RECORD_MANAGER_DB_URL,
)

record_manager.create_schema()

# Also clean up record manager to keep it in sync
indexing_stats = index(
[],
record_manager,
vectorstore,
cleanup="full",
source_id_key="source",
)

logger.info(f"Indexing stats: {indexing_stats}")
num_vecs = (
weaviate_client.collections.get(collection_name)
.aggregate.over_all()
.total_count
)
logger.info(
f"General Guides and Tutorials now has this many vectors: {num_vecs}"
)


if __name__ == "__main__":
Expand Down
7 changes: 3 additions & 4 deletions _scripts/evaluate_chains.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"openai": "gpt-3.5-turbo-1106",
"anthropic": "claude-2",
}
WEAVIATE_DOCS_INDEX_NAME = "LangChain_Combined_Docs_OpenAI_text_embedding_3_small"
WEAVIATE_DOCS_INDEX_NAME = "LangChain_Combined_Docs_transformers_multi_qa_MiniLM"


def create_chain(
Expand Down Expand Up @@ -112,12 +112,11 @@ def create_chain(


def _get_retriever():
WEAVIATE_URL = os.environ["WEAVIATE_URL"]
WEAVIATE_API_KEY = os.environ["WEAVIATE_API_KEY"]
WEAVIATE_URL = os.environ.get("WEAVIATE_URL", "http://localhost:8080")

# Note: This uses the old Weaviate client API v3. Consider upgrading to v4.
client = weaviate.Client(
url=WEAVIATE_URL,
auth_client_secret=weaviate.AuthApiKey(api_key=WEAVIATE_API_KEY),
)
weaviate_client = Weaviate(
client=client,
Expand Down
7 changes: 3 additions & 4 deletions _scripts/evaluate_chains_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,14 @@
from langsmith.evaluation.evaluator import EvaluationResult
from langsmith.schemas import Example, Run

WEAVIATE_URL = os.environ["WEAVIATE_URL"]
WEAVIATE_API_KEY = os.environ["WEAVIATE_API_KEY"]
WEAVIATE_DOCS_INDEX_NAME = "LangChain_Combined_Docs_OpenAI_text_embedding_3_small"
WEAVIATE_URL = os.environ.get("WEAVIATE_URL", "http://localhost:8080")
WEAVIATE_DOCS_INDEX_NAME = "LangChain_Combined_Docs_transformers_multi_qa_MiniLM"


def search(inp: str, callbacks=None) -> list:
# Note: This uses the old Weaviate client API v3. Consider upgrading to v4.
client = weaviate.Client(
url=WEAVIATE_URL,
auth_client_secret=weaviate.AuthApiKey(api_key=WEAVIATE_API_KEY),
)
weaviate_client = Weaviate(
client=client,
Expand Down
7 changes: 3 additions & 4 deletions _scripts/evaluate_chains_improved_chain.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"openai": "gpt-3.5-turbo-1106",
"anthropic": "claude-2",
}
WEAVIATE_DOCS_INDEX_NAME = "LangChain_Combined_Docs_OpenAI_text_embedding_3_small"
WEAVIATE_DOCS_INDEX_NAME = "LangChain_Combined_Docs_transformers_multi_qa_MiniLM"


def search(search_queries, retriever: BaseRetriever):
Expand Down Expand Up @@ -148,12 +148,11 @@ def create_chain(


def _get_retriever():
WEAVIATE_URL = os.environ["WEAVIATE_URL"]
WEAVIATE_API_KEY = os.environ["WEAVIATE_API_KEY"]
WEAVIATE_URL = os.environ.get("WEAVIATE_URL", "http://localhost:8080")

# Note: This uses the old Weaviate client API v3. Consider upgrading to v4.
client = weaviate.Client(
url=WEAVIATE_URL,
auth_client_secret=weaviate.AuthApiKey(api_key=WEAVIATE_API_KEY),
)
weaviate_client = Weaviate(
client=client,
Expand Down
Loading