Quincy Hsieh commited on
Commit
5f5e6b2
·
1 Parent(s): 731ee37

Change endpoint to APIM endpoint

Browse files
Files changed (2) hide show
  1. config.json +3 -3
  2. ingest_train_data.py +197 -0
config.json CHANGED
@@ -1,10 +1,10 @@
1
  {
2
  "embedding": {
3
- "endpoint_url": "https://<your-resource>.openai.azure.com/openai/deployments/<your-embedding-deployment>/embeddings?api-version=2024-12-01-preview",
4
- "model": "text-embedding-3-small"
5
  },
6
  "llm": {
7
- "endpoint_url": "https://<your-resource>.openai.azure.com/openai/deployments/<your-deployment>/chat/completions?api-version=2024-12-01-preview",
8
  "model": "gpt-5",
9
  "max_tokens": 512,
10
  "temperature": 0.7,
 
1
  {
2
  "embedding": {
3
+ "endpoint_url": "https://hackathon-eiffel-2026-apim.azure-api.net/ai/models/openai/deployments/text-embedding-3/embeddings?api-version=2024-12-01-preview",
4
+ "model": "text-embedding-3"
5
  },
6
  "llm": {
7
+ "endpoint_url": "https://hackathon-eiffel-2026-apim.azure-api.net/ai/models/openai/deployments/gpt-5/chat/completions?api-version=2024-12-01-preview",
8
  "model": "gpt-5",
9
  "max_tokens": 512,
10
  "temperature": 0.7,
ingest_train_data.py ADDED
@@ -0,0 +1,197 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Ingest Training Data Script
3
+ ============================
4
+
5
+ Standalone script to convert all documents in ./train_data into embeddings
6
+ and store them in the ChromaDB vector store.
7
+
8
+ Usage:
9
+ python ingest_train_data.py
10
+
11
+ Requires:
12
+ - AZURE_API_KEY environment variable set
13
+ - data/config.json (or root config.json as fallback) with endpoint URLs
14
+ - Documents (PDF/TXT) in ./train_data/<category>/
15
+ """
16
+
17
+ import os
18
+ import sys
19
+ import json
20
+ import logging
21
+ import time
22
+ from pathlib import Path
23
+
24
+ os.environ.setdefault("ANONYMIZED_TELEMETRY", "False")
25
+
26
+ import requests as http_requests
27
+ import chromadb
28
+ from chromadb.config import Settings
29
+ from langchain_text_splitters import RecursiveCharacterTextSplitter
30
+ from pypdf import PdfReader
31
+
32
+ logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
33
+ logger = logging.getLogger(__name__)
34
+
35
+ # ---------------------------------------------------------------------------
36
+ # Configuration
37
+ # ---------------------------------------------------------------------------
38
+
39
+ PROJECT_ROOT = Path(__file__).parent
40
+ DATA_DIR = Path("/data") if Path("/data").is_dir() else PROJECT_ROOT / "data"
41
+ TRAIN_DATA_DIR = Path("/train_data") if Path("/train_data").is_dir() else PROJECT_ROOT / "train_data"
42
+
43
+ CHROMA_PERSIST_DIR = str(DATA_DIR / "chroma_db")
44
+ COLLECTION_NAME = "rag_documents"
45
+ CHUNK_SIZE = 512
46
+ CHUNK_OVERLAP = 50
47
+ EMBEDDING_BATCH_SIZE = 16
48
+
49
+ _CONFIG_PATH = DATA_DIR / "config.json"
50
+ if not _CONFIG_PATH.exists():
51
+ _CONFIG_PATH = PROJECT_ROOT / "config.json"
52
+ logger.warning(f"No config.json in {DATA_DIR} — using root config.json as fallback.")
53
+
54
+ with open(_CONFIG_PATH, encoding="utf-8") as _f:
55
+ _config = json.load(_f)
56
+
57
+ EMBEDDING_ENDPOINT_URL = _config["embedding"]["endpoint_url"]
58
+ EMBEDDING_MODEL_NAME = _config["embedding"]["model"]
59
+
60
+ AZURE_API_KEY = os.environ.get("AZURE_API_KEY")
61
+ if not AZURE_API_KEY:
62
+ logger.error("AZURE_API_KEY is not set. Export it before running this script.")
63
+ sys.exit(1)
64
+
65
+ # ---------------------------------------------------------------------------
66
+ # Helpers
67
+ # ---------------------------------------------------------------------------
68
+
69
+
70
+ def extract_text_from_pdf(pdf_path: Path) -> str:
71
+ reader = PdfReader(str(pdf_path))
72
+ pages_text = []
73
+ for page_num, page in enumerate(reader.pages, start=1):
74
+ text = page.extract_text()
75
+ if text and text.strip():
76
+ pages_text.append(f"[Page {page_num}]\n{text.strip()}")
77
+ return "\n\n".join(pages_text)
78
+
79
+
80
+ def chunk_text(text: str, source: str) -> list[dict]:
81
+ splitter = RecursiveCharacterTextSplitter(
82
+ chunk_size=CHUNK_SIZE,
83
+ chunk_overlap=CHUNK_OVERLAP,
84
+ separators=["\n\n", "\n", ". ", " ", ""],
85
+ )
86
+ chunks = splitter.split_text(text)
87
+ return [{"text": chunk, "source": source, "chunk_index": i} for i, chunk in enumerate(chunks)]
88
+
89
+
90
+ def generate_embeddings(texts: list[str]) -> list[list[float]]:
91
+ headers = {"api-key": AZURE_API_KEY, "Content-Type": "application/json"}
92
+ payload = {"input": texts, "model": EMBEDDING_MODEL_NAME}
93
+ resp = http_requests.post(EMBEDDING_ENDPOINT_URL, headers=headers, json=payload, timeout=120)
94
+ resp.raise_for_status()
95
+ data = resp.json()
96
+ return [item["embedding"] for item in data["data"]]
97
+
98
+
99
+ def generate_embeddings_batched(texts: list[str]) -> list[list[float]]:
100
+ all_embeddings = []
101
+ for i in range(0, len(texts), EMBEDDING_BATCH_SIZE):
102
+ batch = texts[i:i + EMBEDDING_BATCH_SIZE]
103
+ logger.info(f" Embedding batch {i // EMBEDDING_BATCH_SIZE + 1} ({len(batch)} chunks)...")
104
+ embeddings = generate_embeddings(batch)
105
+ all_embeddings.extend(embeddings)
106
+ time.sleep(0.5) # Rate-limit courtesy
107
+ return all_embeddings
108
+
109
+
110
+ # ---------------------------------------------------------------------------
111
+ # Main
112
+ # ---------------------------------------------------------------------------
113
+
114
+
115
+ def main():
116
+ if not TRAIN_DATA_DIR.exists():
117
+ logger.error(f"Training data directory not found: {TRAIN_DATA_DIR}")
118
+ sys.exit(1)
119
+
120
+ logger.info(f"Training data directory: {TRAIN_DATA_DIR}")
121
+ logger.info(f"ChromaDB path: {CHROMA_PERSIST_DIR}")
122
+ logger.info(f"Embedding model: {EMBEDDING_MODEL_NAME}")
123
+
124
+ chroma_client = chromadb.PersistentClient(
125
+ path=CHROMA_PERSIST_DIR,
126
+ settings=Settings(anonymized_telemetry=False),
127
+ )
128
+ collection = chroma_client.get_or_create_collection(
129
+ name=COLLECTION_NAME,
130
+ metadata={"hnsw:space": "cosine"},
131
+ )
132
+ logger.info(f"ChromaDB collection '{COLLECTION_NAME}' — existing documents: {collection.count()}")
133
+
134
+ # Gather all PDF and TXT files recursively
135
+ files = sorted(
136
+ list(TRAIN_DATA_DIR.rglob("*.pdf")) + list(TRAIN_DATA_DIR.rglob("*.txt"))
137
+ )
138
+ logger.info(f"Found {len(files)} files to process.")
139
+
140
+ total_chunks_added = 0
141
+
142
+ for idx, file_path in enumerate(files, start=1):
143
+ relative = file_path.relative_to(TRAIN_DATA_DIR)
144
+ # Use category/filename as the source identifier
145
+ source = str(relative)
146
+ logger.info(f"[{idx}/{len(files)}] Processing: {source}")
147
+
148
+ try:
149
+ if file_path.suffix.lower() == ".pdf":
150
+ text = extract_text_from_pdf(file_path)
151
+ else:
152
+ text = file_path.read_text(encoding="utf-8")
153
+ except Exception as e:
154
+ logger.warning(f" Skipped (read error): {e}")
155
+ continue
156
+
157
+ if not text.strip():
158
+ logger.warning(f" Skipped (no extractable text)")
159
+ continue
160
+
161
+ chunks = chunk_text(text, source=source)
162
+ if not chunks:
163
+ logger.warning(f" Skipped (no chunks produced)")
164
+ continue
165
+
166
+ logger.info(f" {len(chunks)} chunks extracted")
167
+
168
+ texts = [c["text"] for c in chunks]
169
+ try:
170
+ embeddings = generate_embeddings_batched(texts)
171
+ except http_requests.exceptions.HTTPError as e:
172
+ logger.error(f" Embedding failed: {e}")
173
+ continue
174
+ except Exception as e:
175
+ logger.error(f" Unexpected embedding error: {e}")
176
+ continue
177
+
178
+ existing_count = collection.count()
179
+ ids = [f"doc_{existing_count + i}" for i in range(len(chunks))]
180
+ metadatas = [{"source": c["source"], "chunk_index": c["chunk_index"]} for c in chunks]
181
+
182
+ collection.add(
183
+ ids=ids,
184
+ embeddings=embeddings,
185
+ documents=texts,
186
+ metadatas=metadatas,
187
+ )
188
+ total_chunks_added += len(chunks)
189
+ logger.info(f" Added {len(chunks)} chunks (total in store: {collection.count()})")
190
+
191
+ logger.info("=" * 60)
192
+ logger.info(f"Ingestion complete. Chunks added this run: {total_chunks_added}")
193
+ logger.info(f"Total documents in vector store: {collection.count()}")
194
+
195
+
196
+ if __name__ == "__main__":
197
+ main()