Temp
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
# D&D Helpers Configuration
|
# D&D Helpers Configuration
|
||||||
OPENAI_API_KEY=your_api_key_here
|
OPENAI_API_KEY=no-key-required
|
||||||
LLM_MODEL=gpt-4o
|
OPENAI_BASE_URL=https://vllm.tipsy.codes/v1
|
||||||
|
LLM_MODEL=Intel/gemma-4-31B-it-int4-AutoRound
|
||||||
WHISPER_MODEL=base
|
WHISPER_MODEL=base
|
||||||
AUDIO_DEVICE_ID=None
|
AUDIO_DEVICE_ID=None
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
artifacts/
|
artifacts/
|
||||||
|
__pycache__
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
+10
-3
@@ -46,11 +46,18 @@ class CharacterStateUpdate(BaseModel):
|
|||||||
|
|
||||||
class ExtractionResult(BaseModel):
|
class ExtractionResult(BaseModel):
|
||||||
lore_updates: List[LoreUpdate] = Field(
|
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(
|
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(
|
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,
|
self,
|
||||||
api_key: Optional[str] = None,
|
api_key: Optional[str] = None,
|
||||||
base_url: Optional[str] = None,
|
base_url: Optional[str] = None,
|
||||||
model: str = "gpt-4o",
|
model: Optional[str] = None,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Initializes the LLMProcessor.
|
Initializes the LLMProcessor.
|
||||||
|
|
||||||
:param api_key: OpenAI API key. If None, it looks for OPENAI_API_KEY in environment variables.
|
: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 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(
|
self.client = OpenAI(
|
||||||
api_key=api_key or os.environ.get("OPENAI_API_KEY"),
|
api_key=api_key or os.environ.get("OPENAI_API_KEY"),
|
||||||
base_url=base_url or os.environ.get("OPENAI_BASE_URL"),
|
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(
|
def _call_llm(
|
||||||
self,
|
self,
|
||||||
@@ -45,6 +45,7 @@ class LLMProcessor:
|
|||||||
{"role": "user", "content": user_prompt},
|
{"role": "user", "content": user_prompt},
|
||||||
],
|
],
|
||||||
response_format=response_format,
|
response_format=response_format,
|
||||||
|
extra_body={"include_reasoning": False},
|
||||||
)
|
)
|
||||||
return response.choices[0].message.content
|
return response.choices[0].message.content
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -55,27 +56,36 @@ class LLMProcessor:
|
|||||||
"""
|
"""
|
||||||
Stage 1: Raw Transcript -> Filtered Text.
|
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:
|
def extract_structured_data(self, filtered_text: str) -> ExtractionResult:
|
||||||
"""
|
"""
|
||||||
Stage 2: Filtered Text -> Structured Data.
|
Stage 2: Filtered Text -> Structured Data.
|
||||||
"""
|
"""
|
||||||
# We use OpenAI's structured output (JSON mode/tool calling) via Pydantic's response_format.
|
print(f"LLM Processor (Extract): Calling extraction for: {filtered_text}")
|
||||||
# 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
|
|
||||||
try:
|
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,
|
model=self.model,
|
||||||
messages=[
|
messages=[
|
||||||
{"role": "system", "content": EXTRACTION_SYSTEM_PROMPT},
|
{"role": "system", "content": EXTRACTION_SYSTEM_PROMPT},
|
||||||
{"role": "user", "content": filtered_text},
|
{"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:
|
except Exception as e:
|
||||||
print(f"Extraction Error: {e}")
|
print(f"Extraction Error: {e}")
|
||||||
# Return an empty ExtractionResult if parsing fails
|
# Return an empty ExtractionResult if parsing fails
|
||||||
|
|||||||
Binary file not shown.
@@ -59,9 +59,10 @@ class PipelineOrchestrator:
|
|||||||
# Get raw text from transcript queue
|
# Get raw text from transcript queue
|
||||||
raw_text = await self.transcript_queue.get()
|
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)
|
# Process via LLM (Filter -> Extract)
|
||||||
|
# Note: this is currently a synchronous call, which blocks the loop.
|
||||||
result = self.processor.process_pipeline(raw_text)
|
result = self.processor.process_pipeline(raw_text)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -69,10 +70,12 @@ class PipelineOrchestrator:
|
|||||||
or result.character_updates
|
or result.character_updates
|
||||||
or result.significant_events
|
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)
|
await self.proposal_queue.put(result)
|
||||||
else:
|
else:
|
||||||
logger.info("No relevant game data extracted.")
|
logger.info("LLM Worker: No relevant game data extracted.")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"LLM Worker error: {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)
|
target_samples = int(self.sample_rate * self.chunk_duration)
|
||||||
chunk = chunk[:target_samples]
|
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
|
# 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.loop.call_soon_threadsafe(self.audio_queue.put_nowait, chunk)
|
||||||
self._buffer = []
|
self._buffer = []
|
||||||
|
|||||||
@@ -46,8 +46,8 @@ class Transcriber:
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# faster-whisper expects audio in float32
|
# faster-whisper expects audio in float32 and 1D array
|
||||||
audio_data = audio_chunk.astype("float32")
|
audio_data = audio_chunk.astype("float32").flatten()
|
||||||
|
|
||||||
# Transcribe the audio
|
# Transcribe the audio
|
||||||
segments, info = self.model.transcribe(audio_data, beam_size=5)
|
segments, info = self.model.transcribe(audio_data, beam_size=5)
|
||||||
|
|||||||
Binary file not shown.
+9
-5
@@ -2,11 +2,14 @@ import asyncio
|
|||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
import typer
|
import typer
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
from src.llm.models import CharacterStateUpdate, ExtractionResult, LoreUpdate
|
from src.llm.models import CharacterStateUpdate, ExtractionResult, LoreUpdate
|
||||||
from src.pipeline.orchestrator import PipelineOrchestrator
|
from src.pipeline.orchestrator import PipelineOrchestrator
|
||||||
from src.ui.tui import ConfirmationApp
|
from src.ui.tui import ConfirmationApp
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
app = typer.Typer(help="D&D Helpers CLI")
|
app = typer.Typer(help="D&D Helpers CLI")
|
||||||
|
|
||||||
|
|
||||||
@@ -17,14 +20,15 @@ def run():
|
|||||||
"""
|
"""
|
||||||
typer.echo("Starting D&D Helpers pipeline...")
|
typer.echo("Starting D&D Helpers pipeline...")
|
||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
async def main():
|
||||||
orchestrator = PipelineOrchestrator(loop=loop)
|
loop = asyncio.get_running_loop()
|
||||||
|
orchestrator = PipelineOrchestrator(loop=loop)
|
||||||
|
await orchestrator.run()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
loop.run_until_complete(orchestrator.run())
|
asyncio.run(main())
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
orchestrator.stop()
|
pass
|
||||||
loop.run_until_complete(asyncio.sleep(0)) # Give it a moment to cleanup
|
|
||||||
|
|
||||||
typer.echo("Pipeline stopped.")
|
typer.echo("Pipeline stopped.")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user