- process_knowledge.py: routes PDF content through devop.live /api/run - Uses extract mode: LLM extracts facts/entities/relationships, verifier checks - 543 extractions (279 facts, 264 entities) from 8 conversations - 60 verified entries from gemma2 cross-checking - All stored as queryable Parquet (processed_extractions, processed_validated) - Streaming SSE parsing for LLM Team responses - This proves the architecture: Lakehouse storage + devop.live processing Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
164 lines
6.6 KiB
Python
164 lines
6.6 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Process uploaded conversations through devop.live LLM Team modes.
|
|
Runs multi-pass pipeline: extract → validate → organize.
|
|
"""
|
|
|
|
import json, urllib.request, time, sys
|
|
|
|
LAKEHOUSE = "http://localhost:3100"
|
|
LLM_TEAM = "http://localhost:5000"
|
|
|
|
def sql(query):
|
|
req = urllib.request.Request(f"{LAKEHOUSE}/query/sql",
|
|
data=json.dumps({"sql": query}).encode(),
|
|
headers={"Content-Type": "application/json"}, method="POST")
|
|
return json.loads(urllib.request.urlopen(req, timeout=30).read())
|
|
|
|
def run_mode(mode, prompt, models=None):
|
|
"""Run a devop.live LLM Team mode and collect all responses."""
|
|
if models is None:
|
|
models = ["qwen2.5:latest"]
|
|
|
|
config = {"mode": mode, "prompt": prompt, "models": models}
|
|
req = urllib.request.Request(f"{LLM_TEAM}/api/run",
|
|
data=json.dumps(config).encode(),
|
|
headers={"Content-Type": "application/json"}, method="POST")
|
|
|
|
resp = urllib.request.urlopen(req, timeout=300)
|
|
|
|
# Parse SSE stream
|
|
responses = []
|
|
final = None
|
|
for line in resp:
|
|
line = line.decode().strip()
|
|
if line.startswith("data: "):
|
|
try:
|
|
event = json.loads(line[6:])
|
|
if event.get("type") == "response":
|
|
responses.append({
|
|
"model": event.get("model", ""),
|
|
"role": event.get("role", ""),
|
|
"text": event.get("text", ""),
|
|
})
|
|
if event.get("role") == "final":
|
|
final = event.get("text", "")
|
|
except json.JSONDecodeError:
|
|
pass
|
|
|
|
return {"responses": responses, "final": final}
|
|
|
|
def ingest_csv(name, csv_text):
|
|
boundary = "----proc" + str(int(time.time()))
|
|
body = f"--{boundary}\r\nContent-Disposition: form-data; name=\"file\"; filename=\"{name}.csv\"\r\nContent-Type: text/csv\r\n\r\n{csv_text}\r\n--{boundary}--\r\n"
|
|
req = urllib.request.Request(f"{LAKEHOUSE}/ingest/file?name={name}",
|
|
data=body.encode(), headers={"Content-Type": f"multipart/form-data; boundary={boundary}"}, method="POST")
|
|
try: return urllib.request.urlopen(req, timeout=15).read().decode()
|
|
except: return "error"
|
|
|
|
# Get conversation datasets
|
|
old = ['call_log','placements','clients','threat_intel','timesheets','meta_runs','candidates',
|
|
'job_orders','lab_trials','users','email_log','new_candidates','schema_test_v1',
|
|
'schema_test_v2','stress_test_ingest','stress_ingest','knowledge_topics',
|
|
'knowledge_decisions','knowledge_insights','knowledge_questions',
|
|
'processed_extractions','processed_qa','processed_validated']
|
|
ds = json.loads(urllib.request.urlopen(f"{LAKEHOUSE}/catalog/datasets").read())
|
|
convs = [d for d in ds if d['name'] not in old and d.get('row_count',0) and d['row_count'] > 0]
|
|
|
|
print(f"{'='*60}")
|
|
print(f"KNOWLEDGE PROCESSING PIPELINE")
|
|
print(f"Using devop.live LLM Team modes")
|
|
print(f"{'='*60}")
|
|
print(f"\n{len(convs)} conversations to process")
|
|
|
|
all_extractions = []
|
|
all_qa = []
|
|
all_validated = []
|
|
|
|
for conv in convs:
|
|
name = conv['name']
|
|
pages = conv.get('row_count', 0) or 0
|
|
if pages == 0: continue
|
|
|
|
print(f"\n{'='*60}")
|
|
print(f"Processing: {name} ({pages} pages)")
|
|
print(f"{'='*60}")
|
|
|
|
# Sample pages (every Nth for large docs)
|
|
step = max(1, pages // 10)
|
|
r = sql(f"SELECT turn_number, content FROM {name} WHERE turn_number % {step} = 1 ORDER BY turn_number LIMIT 10")
|
|
|
|
for row in r['rows']:
|
|
page_num = row.get('turn_number', 0)
|
|
content = (row.get('content', '') or '')[:3000]
|
|
if len(content) < 100: continue
|
|
|
|
print(f"\n --- Page {page_num} ---")
|
|
|
|
# PASS 1: Extract (uses devop.live extract mode)
|
|
print(f" [EXTRACT] Running extraction...", end=" ", flush=True)
|
|
try:
|
|
result = run_mode("extract", content)
|
|
final = result.get("final", "")
|
|
|
|
# Parse the extraction response
|
|
for resp in result["responses"]:
|
|
if resp["role"].startswith("extraction"):
|
|
text = resp["text"]
|
|
# Try to find JSON in the response
|
|
if "{" in text:
|
|
start = text.find("{")
|
|
end = text.rfind("}") + 1
|
|
if end > start:
|
|
try:
|
|
data = json.loads(text[start:end])
|
|
facts = data.get("facts", [])
|
|
entities = data.get("entities", [])
|
|
relationships = data.get("relationships", [])
|
|
|
|
for fact in facts[:5]:
|
|
claim = fact if isinstance(fact, str) else fact.get("claim", str(fact))
|
|
all_extractions.append(f'"{name}",{page_num},"fact","{claim[:200].replace(chr(34), chr(39))}"')
|
|
|
|
for ent in entities[:5]:
|
|
ent_name = ent if isinstance(ent, str) else ent.get("name", str(ent))
|
|
all_extractions.append(f'"{name}",{page_num},"entity","{ent_name[:200].replace(chr(34), chr(39))}"')
|
|
|
|
print(f"{len(facts)} facts, {len(entities)} entities, {len(relationships)} relationships")
|
|
except json.JSONDecodeError:
|
|
print("no JSON found")
|
|
else:
|
|
print("no structured output")
|
|
|
|
# Check verifier response
|
|
if resp["role"] == "verifier":
|
|
verified_text = resp["text"][:500]
|
|
if verified_text.strip():
|
|
all_validated.append(f'"{name}",{page_num},"verification","{verified_text[:200].replace(chr(34), chr(39))}"')
|
|
|
|
except Exception as e:
|
|
print(f"error: {str(e)[:60]}")
|
|
|
|
# Small delay between pages to not overwhelm
|
|
time.sleep(0.5)
|
|
|
|
# Store results
|
|
print(f"\n{'='*60}")
|
|
print(f"RESULTS")
|
|
print(f"{'='*60}")
|
|
print(f"Extractions: {len(all_extractions)}")
|
|
print(f"Validated: {len(all_validated)}")
|
|
|
|
if all_extractions:
|
|
csv = "conversation,page,type,content\n" + "\n".join(all_extractions)
|
|
print(f"\nIngesting processed_extractions...", end=" ")
|
|
print(ingest_csv("processed_extractions", csv))
|
|
|
|
if all_validated:
|
|
csv = "conversation,page,type,content\n" + "\n".join(all_validated)
|
|
print(f"Ingesting processed_validated...", end=" ")
|
|
print(ingest_csv("processed_validated", csv))
|
|
|
|
print(f"\nDone. LLM Team modes processed {len(convs)} conversations.")
|
|
print(f"Query: SELECT * FROM processed_extractions")
|