File size: 5,113 Bytes
b36ae5b bdcb7a6 b36ae5b cfc74e7 b36ae5b cfc74e7 b36ae5b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 | import json
import sys
import os
import concurrent.futures
from pathlib import Path
from openai import OpenAI
from faster_whisper import WhisperModel
from triggers import TRIGGER_PROMPTS
AMD_ENDPOINT = os.environ.get("AMD_ENDPOINT", "http://134.199.198.41:8000/v1")
MODEL_NAME = os.environ.get("MODEL_NAME", "Qwen/Qwen3-14B")
client = OpenAI(base_url=AMD_ENDPOINT, api_key="not-required")
def transcribe(audio_path: str) -> list[dict]:
model = WhisperModel("base", device="cpu", compute_type="int8")
segments, _ = model.transcribe(audio_path, beam_size=3, language="en")
results = []
for seg in segments:
text = seg.text.strip()
if len(text) > 10:
results.append({
"start": round(seg.start, 2),
"end": round(seg.end, 2),
"text": text,
})
return results
def merge_segments(segments: list[dict], max_chars: int = 300) -> list[dict]:
"""Merge short segments into chunks to reduce LLM calls."""
merged = []
current = None
for seg in segments:
if current is None:
current = {**seg}
elif len(current["text"]) + len(seg["text"]) < max_chars:
current["text"] += " " + seg["text"]
current["end"] = seg["end"]
else:
merged.append(current)
current = {**seg}
if current:
merged.append(current)
return merged
def run_trigger(trigger_name: str, prompt_template: str, segment: dict, profile: dict) -> dict | None:
prompt = prompt_template.format(
name=profile.get("name", ""),
situation=profile.get("situation", ""),
knowledge_level=profile.get("knowledge_level", "intermediate"),
concerns=", ".join(profile.get("concerns", [])),
segment_text=segment["text"],
)
try:
response = client.chat.completions.create(
model=MODEL_NAME,
messages=[{"role": "user", "content": prompt}],
max_tokens=500,
temperature=0.3,
extra_body={"chat_template_kwargs": {"enable_thinking": False}},
)
content = response.choices[0].message.content.strip()
if "</think>" in content:
content = content.split("</think>")[-1].strip()
if content.startswith("```"):
content = content.split("\n", 1)[1].rsplit("```", 1)[0].strip()
result = json.loads(content)
if result.get("triggered"):
return {
"trigger": trigger_name,
"timestamp": segment["end"],
"end": segment["end"],
"segment_text": segment["text"],
"data": result,
}
except (json.JSONDecodeError, Exception) as e:
print(f" [{trigger_name}] parse error on segment @{segment['start']}s: {e}", file=sys.stderr)
return None
def process_segment_triggers(args: tuple) -> list[dict]:
"""Process one trigger on one segment (for parallel execution)."""
trigger_name, template, segment, profile = args
result = run_trigger(trigger_name, template, segment, profile)
return [result] if result else []
def run_pipeline(audio_path: str, profile: dict, max_workers: int = 8) -> dict:
print(f"[1/3] Transcribing: {audio_path}")
segments = transcribe(audio_path)
print(f" → {len(segments)} raw segments")
chunks = merge_segments(segments)
print(f" → merged into {len(chunks)} chunks")
print(f"[2/3] Running triggers ({len(chunks)} chunks × 4 triggers = {len(chunks)*4} calls, {max_workers} workers)...")
all_tasks = []
for chunk in chunks:
for trigger_name, template in TRIGGER_PROMPTS.items():
all_tasks.append((trigger_name, template, chunk, profile))
all_cards = []
with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
futures = [executor.submit(process_segment_triggers, task) for task in all_tasks]
for future in concurrent.futures.as_completed(futures):
all_cards.extend(future.result())
all_cards.sort(key=lambda c: c["timestamp"])
print(f"[3/3] Done. {len(all_cards)} cards generated.")
return {
"profile": profile["name"],
"audio_file": audio_path,
"total_segments": len(segments),
"total_chunks": len(chunks),
"total_cards": len(all_cards),
"cards": all_cards,
"transcript": segments,
}
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python pipeline.py <audio_file> [situation]")
sys.exit(1)
audio_file = sys.argv[1]
situation = sys.argv[2] if len(sys.argv) > 2 else "General professional context"
profile = {
"name": "User",
"situation": situation,
"knowledge_level": "intermediate",
"concerns": [situation],
}
result = run_pipeline(audio_file, profile)
output_path = Path(audio_file).stem + f"_{profile_name}_cards.json"
with open(output_path, "w") as f:
json.dump(result, f, indent=2)
print(f"Output saved to: {output_path}")
|