From 60e170e777ccc372b4874b4d1e00808281530d84 Mon Sep 17 00:00:00 2001 From: charles Date: Mon, 25 May 2026 22:50:09 -0700 Subject: [PATCH] Temp --- .env | 5 +-- .gitignore | 1 + src/llm/__pycache__/models.cpython-314.pyc | Bin 4035 -> 4316 bytes src/llm/__pycache__/processor.cpython-314.pyc | Bin 4989 -> 5661 bytes src/llm/models.py | 13 +++++-- src/llm/processor.py | 34 +++++++++++------- .../__pycache__/orchestrator.cpython-314.pyc | Bin 7275 -> 7517 bytes src/pipeline/orchestrator.py | 9 +++-- src/stt/__pycache__/listener.cpython-314.pyc | Bin 4333 -> 4387 bytes .../__pycache__/transcriber.cpython-314.pyc | Bin 2944 -> 2991 bytes src/stt/listener.py | 3 ++ src/stt/transcriber.py | 4 +-- src/ui/__pycache__/cli.cpython-314.pyc | Bin 2673 -> 2760 bytes src/ui/cli.py | 14 +++++--- 14 files changed, 56 insertions(+), 27 deletions(-) diff --git a/.env b/.env index eb21030..2a76d88 100644 --- a/.env +++ b/.env @@ -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 diff --git a/.gitignore b/.gitignore index d4f588e..86eb1c0 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ artifacts/ +__pycache__ diff --git a/src/llm/__pycache__/models.cpython-314.pyc b/src/llm/__pycache__/models.cpython-314.pyc index caf94d138b49acc3245a2752d5bd86d0db34d01f..2dab23409bed3ac2aeabc45fbeb78ce50eb55c2f 100644 GIT binary patch delta 685 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 LrxfleqUiWHY$G73 diff --git a/src/llm/models.py b/src/llm/models.py index d11ee0f..72256f8 100644 --- a/src/llm/models.py +++ b/src/llm/models.py @@ -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 diff --git a/src/llm/processor.py b/src/llm/processor.py index 2036508..df69d56 100644 --- a/src/llm/processor.py +++ b/src/llm/processor.py @@ -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 diff --git a/src/pipeline/__pycache__/orchestrator.cpython-314.pyc b/src/pipeline/__pycache__/orchestrator.cpython-314.pyc index deb8141a776389e046dffcaf75d8535483e5909e..94c2e6cedbf08f669dd92eed82905a3ac6f36a75 100644 GIT binary patch delta 916 zcmZWoOHUI~82#>@&UD(&6etX(Pev$)R}Iz&S_CaJm1x0{7+hH1Gzc0>21LXMsENkJ zmDj{XH~s)!7(zm#Awek{HyAJ&68!^eEKmbW@6a^Vco*k8=X~efnM^W2j(rPStE{3K z=s6gWhGgljb>3v*seAw+YSYxGFMh+2=6s#a$5fX5y+$ep@7DB2PHy zQ3ahl0nmm^!3YY9h(?HnWQaO5VGjCB0o*XcL^Bqs9&*8w@8T~q0tE2hLp=1J5#}Tl zQOP|a-`H@38AKIk@={Q$J&{O@d?~J7jcGwy)#81z(b2)-0eLJoF&303o3hs=CDfG2 z<&n4+qbt5zK7FN!w$@R_WOY`Kl!6HxZrq~}M=wq>SSn0%;3_8Ht+K!MOTqb+0Qt`6{-^#|%!LoyYMcBD8Ai$w;VyAiD|dL*3(oRlX76t)-=qDkk4i24b~F1jpXo=NO43NuWi zfu-9ehNaFDZ@^)Z%$w{pKXWc)6d=vE3Ur)kwno%GGi&=~;DYq^vtYNyahUH`aks-4V1_W delta 686 zcmYL^O-vI(7>2*uKU&ysTe=(iv%6iOMq2x;BG`%wv=>FK#dwi}ij64;+7yhW(TIA` zvtJBXPex-*H1$Y~*kU{x5-=JQQ%+vIl%K?UFmV<_m}K7XnRn*>X69$|_n>r0vb6&p z#h@}3mETJ5tsOjFJ_HcAJ}}sDTqOT6;he&!?XO7^!$Pmf9VKQ0zz>&k-Kj9yE}Rph z=oGtS{QjN~Ce+rf+VGmFB_x0x(+h1d)}ZsW0Tb@MQv%%R1yG^HPV8Bb z_2K0?h29t zoOF$}jngO1JO^Bt)aqx~OHLT1M)?HZc4vhY#UF0GYEiiIt?5ejms@9qQPcELvx_a3 z7BJwOckD*YoS7((;yd4Ye~e=55zNCMM&ykqP}x)!{=jBbqjC~^)J1X@8)|@DK~6hk OKgSSb3rsC5H2NQ7(whbV diff --git a/src/pipeline/orchestrator.py b/src/pipeline/orchestrator.py index 17919ec..b5f54a2 100644 --- a/src/pipeline/orchestrator.py +++ b/src/pipeline/orchestrator.py @@ -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}") diff --git a/src/stt/__pycache__/listener.cpython-314.pyc b/src/stt/__pycache__/listener.cpython-314.pyc index 30fb0aa9504001757e9c0620142ad2d848160f55..006facc5beb3cb64f66dc14ee3090178cfc8d32f 100644 GIT binary patch delta 327 zcmaE>xLAo-n~#@^0SF9@M6-%E^13lI+HDSD=3!)dSeHJuN4(q$D+OvJu-s#v7Brur1~gm74B5 z(RZ@{SF3B&r z#hRRxUz}RR2NWq10}-Y`VkKh{KaeVB1QKdR0w7^qAaP5`u{0$!-zT#eXm@In9>{=z z&69b7-m;&(fX|3AYVu{iM4%m}{NVzcAYCRftwqk0=kVvUO0vp*GMmgN;Kpb;IaEN2 d(QR^tfC^*i!r=CAC3nHV)Df9497G6tG^OW3hA zB{SbAv$!NRFSSUoxFoTt#9(s*_a{b1qshU%T0pXnS6adZsCFe|5kHVBW&{#yMFK#w z$Y%0JUU5d>%_n(*PO_VPjn9ZNd@?70B2ZU0f4G1KNDauEA{!9lH2D&LE~^-;+$V#{ nE&^_h29vu5lo(wnuM<#V44Hgcz>zF+*f&4` diff --git a/src/stt/__pycache__/transcriber.cpython-314.pyc b/src/stt/__pycache__/transcriber.cpython-314.pyc index 16fe8f74fbf2c7e87555148c9ac0d9b99042a571..8936e12cd92512e1c439adb6decc42bf1b83d6aa 100644 GIT binary patch delta 323 zcmZn=UoXz9&Bx2d00f3cqFK2cd23i1J0^Fr8Z!kjPcCGWk|3080Ep7XSbN delta 291 zcmZ24-XPAa&Bx2d00gttM6<#+^4732=1lHlHI^!13S$Uh0?D9)0M^N$Sv4nrVilQe zz$V1S0hgWZ&Llf|J{!m6LN@8iflR_cwzxbGOqz`$MTLPuhM|Bxh7TqdAUJs;tMKF( zY~MLKZ?Pp7msA#{PX5TA!zepBjzgMJdU6$q0^^d&vpJL}ALnS*(FE%2uyZHKl7H{{_rBlz{{E4< zKb)9W5>dc4b7uJ3{hh>dW@~3^S{;QCK;sw0dC8aNWq(H>8bLx_h|eed#3-x?ML0`U zxbTl#$_Fl+No$fOzryrsUtLj`iot2_L|ThvpIZB=0nMXqvM-BjF%+_WA=0Hj9oG{7 zr#CPTC9_~9EoKmtpII5!Qm?)X(8T2Ou%bR2uf|LO{mSXb)a7EC3Hh4o z`MAd{M{m}fj&InXl({f-eoZHO!#BtkU-yl7+@>DfWj>DaDB}S5!|x$D%bu1rHVZD~ zf(BdUmZ2EzvKO*)KOF!OnrNTLLKUEPGWd$ST4)UJ=uRIC@H@PTFb<=^EIUyeWa7g6 z0@A&;3>pl|>oUvCl0?gU^({>18b))2+2m?M6#dZTB&dnAoXM0~nx5r2?qZn_CEZ)M z8>SN`R}H^T+O}=lt95!yXuKhZRGB4-z~|3zgnUak?J$L0<(%a-bjzEoP%4Y2-irv2 zVH5V%V|(h<59-veTE4~aXQyvxI@yl39zIPinXX}(wlU`Uj_VrD%3@gv1%KV$Fi6N7jWvf*8A&39sY}NChmKB~3pO@| zv|Vf(8NwTnj@=kZ4=j7;RN~bTHsL4=LUjA$*2M?vOou=7C(jARZj=)e`?C6F?DN>p zo56*SoI8@AnvK8kBR}!vNP>9b2pA#plN|4Ixc}{?@WrolRR!)Tv1*>Xmt(5=J8Oc( zJV)J!@p@giZHEp_y-tcqoxHOxx(rhYo=B%=BqRv!DG@0|D9}L563NkXo@WvSuVADN^@XzeG1FXYdv$9lyHMXVQglHeEs8Kz>fKg5JiSEzWTQL(I6 zrR6jY%X`MX6ucf8P4{{qrABSD;~9c1fRW$f@u(#==<4>d6WMbrRnKWuE5T*3ly|n<1*Ii7r`VU$MBY*@ z?IaQ$0BW?1<{bM;KAg935tc?M(Cio%G;>5tS}9JHMprm|WCZJ0`oHiRmo;9%4cW>R z!O9lFJLI9~D*0ByI`ry$0PDPkS!fyM=M|K|Li5A7lKVqQUaX*4S#cxE9-u8<<%kTR zJo6B1Ni}v$GT9wjZzM<60bBx08aElD8UEeW$FUdZCDb%T^X#5I@seCYVp7F?on#W+uTzTNYw>)D;48+s0P zVwqzsmnh_7MP!W=k>@yJMIt&oqrCMo$#LS)7~F|IW^`H510D5^hJ8lto6;>j#=Nujh9j!d8Ew$ zb&!Pg+yO{}FtLZH`*?a6&pfV=*sGtLy5mJJDXQ$VWGO!!O-fwqxmDV831;6Xr#qsO7%!8JUYAcXR^JV&8~8)P z7~8hr?((MY04WQhVNv*XuF&(GH^^%o6AKl6&doqZ2>l9~eJDPG^zU$?4;S{~^gbMA aZ>DCmS+wv>1}Qs`xHni}*HUYW$oUUMk@Ir^ diff --git a/src/ui/cli.py b/src/ui/cli.py index 7fcb6c1..d1075a9 100644 --- a/src/ui/cli.py +++ b/src/ui/cli.py @@ -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() - orchestrator = PipelineOrchestrator(loop=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.")