Temp
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
# D&D Helpers Configuration
|
||||
OPENAI_API_KEY=your_api_key_here
|
||||
LLM_MODEL=gpt-4o
|
||||
OPENAI_API_KEY=no-key-required
|
||||
OPENAI_BASE_URL=https://vllm.tipsy.codes/v1
|
||||
LLM_MODEL=Intel/gemma-4-31B-it-int4-AutoRound
|
||||
WHISPER_MODEL=base
|
||||
AUDIO_DEVICE_ID=None
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
artifacts/
|
||||
__pycache__
|
||||
|
||||
Binary file not shown.
Binary file not shown.
+10
-3
@@ -46,11 +46,18 @@ class CharacterStateUpdate(BaseModel):
|
||||
|
||||
class ExtractionResult(BaseModel):
|
||||
lore_updates: List[LoreUpdate] = Field(
|
||||
default_factory=list, description="List of discovered lore facts"
|
||||
default_factory=list, description="List of discovered lore facts", alias="lore"
|
||||
)
|
||||
character_updates: List[CharacterStateUpdate] = Field(
|
||||
default_factory=list, description="List of character state changes"
|
||||
default_factory=list,
|
||||
description="List of character state changes",
|
||||
alias="character_state",
|
||||
)
|
||||
significant_events: List[str] = Field(
|
||||
default_factory=list, description="List of significant plot points or events"
|
||||
default_factory=list,
|
||||
description="List of significant plot points or events",
|
||||
alias="events",
|
||||
)
|
||||
|
||||
class Config:
|
||||
populate_by_name = True
|
||||
|
||||
+22
-12
@@ -13,20 +13,20 @@ class LLMProcessor:
|
||||
self,
|
||||
api_key: Optional[str] = None,
|
||||
base_url: Optional[str] = None,
|
||||
model: str = "gpt-4o",
|
||||
model: Optional[str] = None,
|
||||
):
|
||||
"""
|
||||
Initializes the LLMProcessor.
|
||||
|
||||
:param api_key: OpenAI API key. If None, it looks for OPENAI_API_KEY in environment variables.
|
||||
:param base_url: OpenAI-compatible base URL (e.g., for vLLM).
|
||||
:param model: The model to use for processing.
|
||||
:param model: The model to use for processing. If None, it looks for LLM_MODEL in environment variables.
|
||||
"""
|
||||
self.client = OpenAI(
|
||||
api_key=api_key or os.environ.get("OPENAI_API_KEY"),
|
||||
base_url=base_url or os.environ.get("OPENAI_BASE_URL"),
|
||||
)
|
||||
self.model = model
|
||||
self.model = model or os.environ.get("LLM_MODEL", "gpt-4o")
|
||||
|
||||
def _call_llm(
|
||||
self,
|
||||
@@ -45,6 +45,7 @@ class LLMProcessor:
|
||||
{"role": "user", "content": user_prompt},
|
||||
],
|
||||
response_format=response_format,
|
||||
extra_body={"include_reasoning": False},
|
||||
)
|
||||
return response.choices[0].message.content
|
||||
except Exception as e:
|
||||
@@ -55,27 +56,36 @@ class LLMProcessor:
|
||||
"""
|
||||
Stage 1: Raw Transcript -> Filtered Text.
|
||||
"""
|
||||
return self._call_llm(NOISE_FILTER_SYSTEM_PROMPT, text)
|
||||
result = self._call_llm(NOISE_FILTER_SYSTEM_PROMPT, text)
|
||||
print(f"LLM Processor (Filter): {text} -> {result}")
|
||||
return result
|
||||
|
||||
def extract_structured_data(self, filtered_text: str) -> ExtractionResult:
|
||||
"""
|
||||
Stage 2: Filtered Text -> Structured Data.
|
||||
"""
|
||||
# We use OpenAI's structured output (JSON mode/tool calling) via Pydantic's response_format.
|
||||
# For models that support it, we can pass the Pydantic model directly.
|
||||
# If we are using an older model or vLLM, we might need to manually parse the JSON.
|
||||
|
||||
# Using the newer 'beta.chat.completions.parse' for Pydantic support
|
||||
print(f"LLM Processor (Extract): Calling extraction for: {filtered_text}")
|
||||
try:
|
||||
completion = self.client.beta.chat.completions.parse(
|
||||
# Using standard chat.completions.create with JSON mode for better compatibility with vLLM
|
||||
response = self.client.chat.completions.create(
|
||||
model=self.model,
|
||||
messages=[
|
||||
{"role": "system", "content": EXTRACTION_SYSTEM_PROMPT},
|
||||
{"role": "user", "content": filtered_text},
|
||||
],
|
||||
response_format=ExtractionResult,
|
||||
response_format={"type": "json_object"},
|
||||
extra_body={"include_reasoning": False},
|
||||
)
|
||||
return completion.choices[0].message.parsed
|
||||
|
||||
import json
|
||||
|
||||
content = response.choices[0].message.content
|
||||
print(f"LLM Processor (Extract): Raw JSON response: {content}")
|
||||
data = json.loads(content)
|
||||
|
||||
# Map the JSON data to the Pydantic model
|
||||
return ExtractionResult(**data)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Extraction Error: {e}")
|
||||
# Return an empty ExtractionResult if parsing fails
|
||||
|
||||
Binary file not shown.
@@ -59,9 +59,10 @@ class PipelineOrchestrator:
|
||||
# Get raw text from transcript queue
|
||||
raw_text = await self.transcript_queue.get()
|
||||
|
||||
logger.info(f"Processing text: {raw_text}")
|
||||
logger.info(f"LLM Worker: Processing text: {raw_text}")
|
||||
|
||||
# Process via LLM (Filter -> Extract)
|
||||
# Note: this is currently a synchronous call, which blocks the loop.
|
||||
result = self.processor.process_pipeline(raw_text)
|
||||
|
||||
if (
|
||||
@@ -69,10 +70,12 @@ class PipelineOrchestrator:
|
||||
or result.character_updates
|
||||
or result.significant_events
|
||||
):
|
||||
logger.info("Proposal generated. Putting into proposal queue.")
|
||||
logger.info(
|
||||
f"LLM Worker: Proposal generated. Putting into proposal queue. (Lore: {len(result.lore_updates)}, Char: {len(result.character_updates)})"
|
||||
)
|
||||
await self.proposal_queue.put(result)
|
||||
else:
|
||||
logger.info("No relevant game data extracted.")
|
||||
logger.info("LLM Worker: No relevant game data extracted.")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"LLM Worker error: {e}")
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -41,6 +41,9 @@ class AudioListener:
|
||||
target_samples = int(self.sample_rate * self.chunk_duration)
|
||||
chunk = chunk[:target_samples]
|
||||
|
||||
# Flatten to 1D array (samples,) as expected by faster-whisper
|
||||
chunk = chunk.flatten()
|
||||
|
||||
# Use call_soon_threadsafe to put the chunk into the asyncio queue from the callback thread
|
||||
self.loop.call_soon_threadsafe(self.audio_queue.put_nowait, chunk)
|
||||
self._buffer = []
|
||||
|
||||
@@ -46,8 +46,8 @@ class Transcriber:
|
||||
return ""
|
||||
|
||||
try:
|
||||
# faster-whisper expects audio in float32
|
||||
audio_data = audio_chunk.astype("float32")
|
||||
# faster-whisper expects audio in float32 and 1D array
|
||||
audio_data = audio_chunk.astype("float32").flatten()
|
||||
|
||||
# Transcribe the audio
|
||||
segments, info = self.model.transcribe(audio_data, beam_size=5)
|
||||
|
||||
Binary file not shown.
+8
-4
@@ -2,11 +2,14 @@ import asyncio
|
||||
from typing import List
|
||||
|
||||
import typer
|
||||
from dotenv import load_dotenv
|
||||
|
||||
from src.llm.models import CharacterStateUpdate, ExtractionResult, LoreUpdate
|
||||
from src.pipeline.orchestrator import PipelineOrchestrator
|
||||
from src.ui.tui import ConfirmationApp
|
||||
|
||||
load_dotenv()
|
||||
|
||||
app = typer.Typer(help="D&D Helpers CLI")
|
||||
|
||||
|
||||
@@ -17,14 +20,15 @@ def run():
|
||||
"""
|
||||
typer.echo("Starting D&D Helpers pipeline...")
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
async def main():
|
||||
loop = asyncio.get_running_loop()
|
||||
orchestrator = PipelineOrchestrator(loop=loop)
|
||||
await orchestrator.run()
|
||||
|
||||
try:
|
||||
loop.run_until_complete(orchestrator.run())
|
||||
asyncio.run(main())
|
||||
except KeyboardInterrupt:
|
||||
orchestrator.stop()
|
||||
loop.run_until_complete(asyncio.sleep(0)) # Give it a moment to cleanup
|
||||
pass
|
||||
|
||||
typer.echo("Pipeline stopped.")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user