biller-cli 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- biller_cli/__init__.py +0 -0
- biller_cli/ai/__init__.py +397 -0
- biller_cli/ai/ingest.py +394 -0
- biller_cli/commands/down.py +96 -0
- biller_cli/commands/downgrade.py +142 -0
- biller_cli/commands/init.py +210 -0
- biller_cli/commands/up.py +138 -0
- biller_cli/commands/upgrade.py +271 -0
- biller_cli/main.py +34 -0
- biller_cli/templates/biller-user-layer/pom.xml +100 -0
- biller_cli/templates/biller-user-layer/src/main/java/bharat/connect/biller/ApplicationContext.java +13 -0
- biller_cli/templates/biller-user-layer/src/main/java/bharat/connect/biller/config/DatabaseConfig.java +35 -0
- biller_cli/templates/biller-user-layer/src/main/java/bharat/connect/biller/config/WebCorsConfig.java +21 -0
- biller_cli/templates/biller-user-layer/src/main/java/bharat/connect/biller/controller/BbpsRequestController.java +98 -0
- biller_cli/templates/biller-user-layer/src/main/java/bharat/connect/biller/dao/impl/BillFetchDaoImpl.java +135 -0
- biller_cli/templates/biller-user-layer/src/main/java/bharat/connect/biller/dao/impl/BillPaymentDaoImpl.java +100 -0
- biller_cli/templates/biller-user-layer/src/main/java/bharat/connect/biller/provider/BillingProviderConfig.java +34 -0
- biller_cli/templates/biller-user-layer/src/main/java/bharat/connect/biller/provider/BillingProviderProperties.java +92 -0
- biller_cli/templates/biller-user-layer/src/main/java/bharat/connect/biller/provider/impl/CsvBillingProvider.java +257 -0
- biller_cli/templates/biller-user-layer/src/main/java/bharat/connect/biller/provider/impl/ExcelBillingProvider.java +261 -0
- biller_cli/templates/biller-user-layer/src/main/java/bharat/connect/biller/provider/impl/PostgresBillingProvider.java +210 -0
- biller_cli/templates/biller-user-layer/src/main/java/bharat/connect/biller/service/impl/BillFetchServiceImpl.java +131 -0
- biller_cli/templates/biller-user-layer/src/main/java/bharat/connect/biller/service/impl/BillPaymentServiceImpl.java +175 -0
- biller_cli/templates/biller-user-layer/src/main/java/bharat/connect/biller/service/impl/HeartbeatServiceImpl.java +136 -0
- biller_cli/templates/biller-user-layer/src/main/resources/application.properties +88 -0
- biller_cli/templates/pom.xml +33 -0
- biller_cli/utils/preflight.py +151 -0
- biller_cli/utils/secrets.py +37 -0
- biller_cli/utils/version_pin.py +67 -0
- biller_cli-0.1.0.dist-info/METADATA +17 -0
- biller_cli-0.1.0.dist-info/RECORD +33 -0
- biller_cli-0.1.0.dist-info/WHEEL +4 -0
- biller_cli-0.1.0.dist-info/entry_points.txt +2 -0
biller_cli/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
"""
|
|
2
|
+
biller_cli/ai/ingest.py
|
|
3
|
+
|
|
4
|
+
Chunks bbps_biller_integrator_codebase.md and writes embeddings to ChromaDB.
|
|
5
|
+
|
|
6
|
+
Split strategy:
|
|
7
|
+
Pass 1 — split on file boundary markers (^## src/)
|
|
8
|
+
Pass 2 — split oversized chunks (>400 tokens) at method/blank boundaries
|
|
9
|
+
with 50-token overlap
|
|
10
|
+
|
|
11
|
+
Exclusions — dead scaffolding deleted in Phase 0:
|
|
12
|
+
BillerController, UserService, UserServiceImpl,
|
|
13
|
+
UserDao, UserDaoImpl, User.java
|
|
14
|
+
|
|
15
|
+
Run:
|
|
16
|
+
biller-cli ingest --source ~/Desktop/bbps/AIGateway/docs/bbps_biller_integrator_codebase.md
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import re
|
|
22
|
+
import subprocess
|
|
23
|
+
import sys
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from typing import Generator
|
|
26
|
+
|
|
27
|
+
import chromadb
|
|
28
|
+
import tiktoken
|
|
29
|
+
|
|
30
|
+
# ---------------------------------------------------------------------------
|
|
31
|
+
# Constants
|
|
32
|
+
# ---------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
CHROMA_DIR = Path.home() / ".biller-cli" / "chroma"
|
|
35
|
+
COLLECTION_NAME = "biller_codebase"
|
|
36
|
+
EMBED_MODEL = "nomic-embed-text"
|
|
37
|
+
MAX_TOKENS = 400
|
|
38
|
+
OVERLAP_TOKENS = 50
|
|
39
|
+
|
|
40
|
+
# Regex that matches file-level headers only.
|
|
41
|
+
# Matches: ## src/main/java/... and ## src/main/resources/...
|
|
42
|
+
# Does NOT match: ## Java Source Files, ## Resource Files, ## AGENT_REBUILD_GUIDE, etc.
|
|
43
|
+
FILE_HEADER_RE = re.compile(r"^## (src/\S+)", re.MULTILINE)
|
|
44
|
+
|
|
45
|
+
# Dead scaffolding — explicitly deleted in Phase 0.
|
|
46
|
+
# These paths exist in the dump (generated from biller-audit, not the clean tree).
|
|
47
|
+
# They must never be indexed.
|
|
48
|
+
EXCLUDE_PATHS: frozenset[str] = frozenset(
|
|
49
|
+
{
|
|
50
|
+
"src/main/java/bharat/connect/biller/controller/BillerController.java",
|
|
51
|
+
"src/main/java/bharat/connect/biller/service/UserService.java",
|
|
52
|
+
"src/main/java/bharat/connect/biller/service/impl/UserServiceImpl.java",
|
|
53
|
+
"src/main/java/bharat/connect/biller/dao/UserDao.java",
|
|
54
|
+
"src/main/java/bharat/connect/biller/dao/impl/UserDaoImpl.java",
|
|
55
|
+
"src/main/java/bharat/connect/biller/model/User.java",
|
|
56
|
+
}
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# tiktoken encoder — cl100k_base is accurate enough for token budgeting on Java.
|
|
60
|
+
# It is NOT the nomic-embed-text tokeniser, but the counts are close enough for
|
|
61
|
+
# a 400-token ceiling. Do not use len(text.split()) — that undercounts by ~30%.
|
|
62
|
+
_ENC = tiktoken.get_encoding("cl100k_base")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# ---------------------------------------------------------------------------
|
|
66
|
+
# Pre-flight checks
|
|
67
|
+
# ---------------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _check_ollama_model(model: str) -> None:
|
|
71
|
+
"""Exit with an actionable error if the required Ollama model is not pulled."""
|
|
72
|
+
try:
|
|
73
|
+
result = subprocess.run(
|
|
74
|
+
["ollama", "list"], capture_output=True, text=True, timeout=10
|
|
75
|
+
)
|
|
76
|
+
if model not in result.stdout:
|
|
77
|
+
print(
|
|
78
|
+
f"\nError: Ollama model '{model}' is not available.\n"
|
|
79
|
+
f"Pull it with: ollama pull {model}\n"
|
|
80
|
+
)
|
|
81
|
+
sys.exit(1)
|
|
82
|
+
except FileNotFoundError:
|
|
83
|
+
print(
|
|
84
|
+
"\nError: Ollama is not installed or not on PATH.\n"
|
|
85
|
+
"Install from: https://ollama.com\n"
|
|
86
|
+
f"Then run: ollama pull {model}\n"
|
|
87
|
+
)
|
|
88
|
+
sys.exit(1)
|
|
89
|
+
except subprocess.TimeoutExpired:
|
|
90
|
+
print(
|
|
91
|
+
"\nError: Ollama did not respond within 10 seconds.\n"
|
|
92
|
+
"Start it with: ollama serve\n"
|
|
93
|
+
)
|
|
94
|
+
sys.exit(1)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
# ---------------------------------------------------------------------------
|
|
98
|
+
# Parsing — Pass 1
|
|
99
|
+
# ---------------------------------------------------------------------------
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _doc_type(file_path: str) -> str:
|
|
103
|
+
"""Return 'source' for Java files, 'resource' for everything else."""
|
|
104
|
+
return "source" if file_path.endswith(".java") else "resource"
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _parse_file_sections(text: str) -> Generator[tuple[str, str], None, None]:
|
|
108
|
+
"""
|
|
109
|
+
Yield (file_path, content) pairs for every ## src/ section in the dump.
|
|
110
|
+
|
|
111
|
+
Skips sections whose file_path is in EXCLUDE_PATHS.
|
|
112
|
+
Content includes everything between the file header and the next ## src/ header
|
|
113
|
+
(or end of document), minus the header line itself.
|
|
114
|
+
"""
|
|
115
|
+
matches = list(FILE_HEADER_RE.finditer(text))
|
|
116
|
+
for i, match in enumerate(matches):
|
|
117
|
+
file_path = match.group(1)
|
|
118
|
+
|
|
119
|
+
if file_path in EXCLUDE_PATHS:
|
|
120
|
+
continue
|
|
121
|
+
|
|
122
|
+
# Content runs from end of this header line to start of next ## src/ header.
|
|
123
|
+
content_start = match.end()
|
|
124
|
+
content_end = matches[i + 1].start() if i + 1 < len(matches) else len(text)
|
|
125
|
+
content = text[content_start:content_end].strip()
|
|
126
|
+
|
|
127
|
+
if content:
|
|
128
|
+
yield file_path, content
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
# ---------------------------------------------------------------------------
|
|
132
|
+
# Chunking — Pass 2
|
|
133
|
+
# ---------------------------------------------------------------------------
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _token_count(text: str) -> int:
|
|
137
|
+
return len(_ENC.encode(text))
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _split_oversized(content: str, file_path: str) -> list[str]:
|
|
141
|
+
"""
|
|
142
|
+
Split content that exceeds MAX_TOKENS.
|
|
143
|
+
|
|
144
|
+
Split preference order:
|
|
145
|
+
1. Method/block boundaries: a blank line after a closing brace (}\\n\\n)
|
|
146
|
+
2. Any blank line (\\n\\n)
|
|
147
|
+
3. Hard token split as last resort (preserves OVERLAP_TOKENS of context)
|
|
148
|
+
|
|
149
|
+
Returns a list of sub-chunks, each under MAX_TOKENS where possible.
|
|
150
|
+
A single method that exceeds MAX_TOKENS will not be split mid-line — it is
|
|
151
|
+
kept intact and logged as an oversized chunk rather than producing incoherent
|
|
152
|
+
fragments.
|
|
153
|
+
"""
|
|
154
|
+
# Try splitting at method boundaries first, then blank lines.
|
|
155
|
+
for delimiter in (r"\}\n\n", r"\n\n"):
|
|
156
|
+
parts = re.split(delimiter, content)
|
|
157
|
+
if len(parts) > 1:
|
|
158
|
+
return _merge_parts(parts, file_path)
|
|
159
|
+
|
|
160
|
+
# No natural boundary found — hard token split with overlap.
|
|
161
|
+
return _hard_split(content)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _merge_parts(parts: list[str], file_path: str) -> list[str]:
|
|
165
|
+
"""
|
|
166
|
+
Greedily merge split parts into chunks that stay under MAX_TOKENS.
|
|
167
|
+
When a chunk would exceed MAX_TOKENS, close it and start a new one
|
|
168
|
+
seeded with OVERLAP_TOKENS of the previous chunk's tail for context.
|
|
169
|
+
"""
|
|
170
|
+
chunks: list[str] = []
|
|
171
|
+
current = ""
|
|
172
|
+
|
|
173
|
+
for part in parts:
|
|
174
|
+
candidate = (current + "\n\n" + part).strip() if current else part.strip()
|
|
175
|
+
if _token_count(candidate) <= MAX_TOKENS:
|
|
176
|
+
current = candidate
|
|
177
|
+
else:
|
|
178
|
+
if current:
|
|
179
|
+
chunks.append(current)
|
|
180
|
+
# Seed next chunk with overlap from tail of current.
|
|
181
|
+
tail_tokens = _ENC.encode(current)[-OVERLAP_TOKENS:]
|
|
182
|
+
overlap_text = _ENC.decode(tail_tokens)
|
|
183
|
+
current = (overlap_text + "\n\n" + part.strip()).strip()
|
|
184
|
+
else:
|
|
185
|
+
# Single part already exceeds MAX_TOKENS — keep it intact.
|
|
186
|
+
# Splitting a single method mid-line is worse than an oversized chunk.
|
|
187
|
+
chunks.append(part.strip())
|
|
188
|
+
current = ""
|
|
189
|
+
|
|
190
|
+
if current:
|
|
191
|
+
chunks.append(current)
|
|
192
|
+
|
|
193
|
+
return [c for c in chunks if c]
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _hard_split(content: str) -> list[str]:
|
|
197
|
+
"""Token-boundary split for content with no natural delimiters."""
|
|
198
|
+
tokens = _ENC.encode(content)
|
|
199
|
+
chunks: list[str] = []
|
|
200
|
+
step = MAX_TOKENS - OVERLAP_TOKENS
|
|
201
|
+
for start in range(0, len(tokens), step):
|
|
202
|
+
chunk_tokens = tokens[start : start + MAX_TOKENS]
|
|
203
|
+
chunks.append(_ENC.decode(chunk_tokens))
|
|
204
|
+
return chunks
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _chunk_file(file_path: str, content: str) -> list[dict]:
|
|
208
|
+
"""
|
|
209
|
+
Return a list of chunk dicts ready for ChromaDB insertion.
|
|
210
|
+
|
|
211
|
+
Each dict has keys: text, file_path, chunk_index, language, token_count, doc_type
|
|
212
|
+
"""
|
|
213
|
+
token_count = _token_count(content)
|
|
214
|
+
|
|
215
|
+
if token_count <= MAX_TOKENS:
|
|
216
|
+
sub_chunks = [content]
|
|
217
|
+
else:
|
|
218
|
+
sub_chunks = _split_oversized(content, file_path)
|
|
219
|
+
|
|
220
|
+
language = "java" if file_path.endswith(".java") else (
|
|
221
|
+
"xml" if file_path.endswith(".xsd") else (
|
|
222
|
+
"sql" if file_path.endswith(".sql") else "properties"
|
|
223
|
+
)
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
result = []
|
|
227
|
+
for idx, chunk_text in enumerate(sub_chunks):
|
|
228
|
+
result.append(
|
|
229
|
+
{
|
|
230
|
+
"text": chunk_text,
|
|
231
|
+
"file_path": file_path,
|
|
232
|
+
"chunk_index": idx,
|
|
233
|
+
"language": language,
|
|
234
|
+
"token_count": _token_count(chunk_text),
|
|
235
|
+
"doc_type": _doc_type(file_path),
|
|
236
|
+
}
|
|
237
|
+
)
|
|
238
|
+
return result
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
# ---------------------------------------------------------------------------
|
|
242
|
+
# Embedding — via Ollama HTTP API
|
|
243
|
+
# ---------------------------------------------------------------------------
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _embed_batch(texts: list[str]) -> list[list[float]]:
|
|
247
|
+
"""
|
|
248
|
+
Embed a batch of texts using nomic-embed-text via the Ollama Python library.
|
|
249
|
+
Returns a list of embedding vectors in the same order as input texts.
|
|
250
|
+
"""
|
|
251
|
+
import ollama # imported here so the rest of the module is importable without ollama
|
|
252
|
+
|
|
253
|
+
embeddings = []
|
|
254
|
+
for text in texts:
|
|
255
|
+
response = ollama.embeddings(model=EMBED_MODEL, prompt=text)
|
|
256
|
+
embeddings.append(response["embedding"])
|
|
257
|
+
return embeddings
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
# ---------------------------------------------------------------------------
|
|
261
|
+
# ChromaDB write
|
|
262
|
+
# ---------------------------------------------------------------------------
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def _get_collection(client: chromadb.PersistentClient) -> chromadb.Collection:
|
|
266
|
+
"""
|
|
267
|
+
Delete and recreate the collection on every ingest run.
|
|
268
|
+
This prevents duplicate chunk accumulation when re-ingesting
|
|
269
|
+
after a codebase update. No partial-update strategy — full rebuild only.
|
|
270
|
+
"""
|
|
271
|
+
try:
|
|
272
|
+
client.delete_collection(COLLECTION_NAME)
|
|
273
|
+
except Exception:
|
|
274
|
+
pass # Collection did not exist — first run.
|
|
275
|
+
return client.create_collection(
|
|
276
|
+
name=COLLECTION_NAME,
|
|
277
|
+
metadata={"hnsw:space": "cosine"},
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
BATCH_SIZE = 50 # ChromaDB add() performance degrades with very large single batches.
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def _write_to_chroma(
|
|
285
|
+
collection: chromadb.Collection, chunks: list[dict]
|
|
286
|
+
) -> None:
|
|
287
|
+
"""Write all chunks to ChromaDB in batches."""
|
|
288
|
+
total = len(chunks)
|
|
289
|
+
for batch_start in range(0, total, BATCH_SIZE):
|
|
290
|
+
batch = chunks[batch_start : batch_start + BATCH_SIZE]
|
|
291
|
+
texts = [c["text"] for c in batch]
|
|
292
|
+
embeddings = _embed_batch(texts)
|
|
293
|
+
ids = [
|
|
294
|
+
f"{c['file_path']}::chunk_{c['chunk_index']}" for c in batch
|
|
295
|
+
]
|
|
296
|
+
metadatas = [
|
|
297
|
+
{
|
|
298
|
+
"file_path": c["file_path"],
|
|
299
|
+
"chunk_index": c["chunk_index"],
|
|
300
|
+
"language": c["language"],
|
|
301
|
+
"token_count": c["token_count"],
|
|
302
|
+
"doc_type": c["doc_type"],
|
|
303
|
+
}
|
|
304
|
+
for c in batch
|
|
305
|
+
]
|
|
306
|
+
collection.add(
|
|
307
|
+
ids=ids,
|
|
308
|
+
embeddings=embeddings,
|
|
309
|
+
documents=texts,
|
|
310
|
+
metadatas=metadatas,
|
|
311
|
+
)
|
|
312
|
+
done = min(batch_start + BATCH_SIZE, total)
|
|
313
|
+
print(f" Embedded and stored {done}/{total} chunks...", end="\r")
|
|
314
|
+
|
|
315
|
+
print() # newline after the progress line
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
# ---------------------------------------------------------------------------
|
|
319
|
+
# Public entry point
|
|
320
|
+
# ---------------------------------------------------------------------------
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def run_ingest(source_path: Path) -> None:
|
|
324
|
+
"""
|
|
325
|
+
Full ingest pipeline. Called by `biller-cli ingest --source <path>`.
|
|
326
|
+
|
|
327
|
+
Steps:
|
|
328
|
+
1. Pre-flight: verify Ollama + nomic-embed-text available
|
|
329
|
+
2. Read source file
|
|
330
|
+
3. Parse file sections (Pass 1)
|
|
331
|
+
4. Chunk oversized sections (Pass 2)
|
|
332
|
+
5. Write to ChromaDB (delete + recreate collection)
|
|
333
|
+
"""
|
|
334
|
+
# --- 1. Pre-flight ---
|
|
335
|
+
print("Checking Ollama availability...")
|
|
336
|
+
_check_ollama_model(EMBED_MODEL)
|
|
337
|
+
print(f" OK: '{EMBED_MODEL}' is available.\n")
|
|
338
|
+
|
|
339
|
+
# --- 2. Read source ---
|
|
340
|
+
if not source_path.exists():
|
|
341
|
+
print(f"Error: Source file not found: {source_path}")
|
|
342
|
+
sys.exit(1)
|
|
343
|
+
|
|
344
|
+
print(f"Reading: {source_path}")
|
|
345
|
+
text = source_path.read_text(encoding="utf-8")
|
|
346
|
+
print(f" {len(text):,} characters loaded.\n")
|
|
347
|
+
|
|
348
|
+
# --- 3. Parse ---
|
|
349
|
+
print("Parsing file sections...")
|
|
350
|
+
all_chunks: list[dict] = []
|
|
351
|
+
excluded_count = 0
|
|
352
|
+
section_count = 0
|
|
353
|
+
|
|
354
|
+
for file_path, content in _parse_file_sections(text):
|
|
355
|
+
section_count += 1
|
|
356
|
+
file_chunks = _chunk_file(file_path, content)
|
|
357
|
+
all_chunks.extend(file_chunks)
|
|
358
|
+
|
|
359
|
+
# Count excluded sections separately for the summary.
|
|
360
|
+
for match in FILE_HEADER_RE.finditer(text):
|
|
361
|
+
if match.group(1) in EXCLUDE_PATHS:
|
|
362
|
+
excluded_count += 1
|
|
363
|
+
|
|
364
|
+
print(f" {section_count} sections ingested, {excluded_count} excluded (dead scaffolding).")
|
|
365
|
+
print(f" {len(all_chunks)} total chunks after Pass 2 splitting.\n")
|
|
366
|
+
|
|
367
|
+
if not all_chunks:
|
|
368
|
+
print("Error: No chunks produced. Verify the source file format.")
|
|
369
|
+
print("Expected headers matching: ## src/<path>")
|
|
370
|
+
sys.exit(1)
|
|
371
|
+
|
|
372
|
+
# --- 4 & 5. Embed + write ---
|
|
373
|
+
print(f"Initialising ChromaDB at: {CHROMA_DIR}")
|
|
374
|
+
CHROMA_DIR.mkdir(parents=True, exist_ok=True)
|
|
375
|
+
client = chromadb.PersistentClient(path=str(CHROMA_DIR))
|
|
376
|
+
collection = _get_collection(client)
|
|
377
|
+
print(f" Collection '{COLLECTION_NAME}' ready (previous data cleared).\n")
|
|
378
|
+
|
|
379
|
+
print(f"Embedding and storing {len(all_chunks)} chunks...")
|
|
380
|
+
print(" This will take several minutes on first run.\n")
|
|
381
|
+
_write_to_chroma(collection, all_chunks)
|
|
382
|
+
|
|
383
|
+
# --- Summary ---
|
|
384
|
+
final_count = collection.count()
|
|
385
|
+
print(f"\nIngest complete.")
|
|
386
|
+
print(f" Collection : {COLLECTION_NAME}")
|
|
387
|
+
print(f" Location : {CHROMA_DIR}")
|
|
388
|
+
print(f" Chunks : {final_count}")
|
|
389
|
+
print(f" Excluded : {excluded_count} dead scaffolding files\n")
|
|
390
|
+
print("Smoke test:")
|
|
391
|
+
print(' python -c "')
|
|
392
|
+
print(' import chromadb')
|
|
393
|
+
print(f' c = chromadb.PersistentClient(path=\\"{CHROMA_DIR}\\")')
|
|
394
|
+
print(f' col = c.get_collection(\\"{COLLECTION_NAME}\\")')
|
|
395
|
+
print(' r = col.query(query_texts=[\\\"BillFetchService implementation\\\"], n_results=3)')
|
|
396
|
+
print(' [print(m[\\\"file_path\\\"]) for m in r[\\\"metadatas\\\"][0]]')
|
|
397
|
+
print(' "')
|