
    Hi>Y                         d dl Z d dlZd dlmZ d dlmZmZmZmZm	Z	 ddl
mZ d dlmZ  ee          ZdZd dlmZ d	efd
Z G d de          ZdS )    N)date)ListDictAnyOptionalTuple   )BaseLLM)setup_loggeru  You are Butler, a personal health assistant for tracking and analyzing health data.

You help the user manage:
- Sleep quality and patterns (from Garmin watch)
- Exercise data: steps, activities, heart rate
- Diet logs: meals, supplements, fasting modes (OMAD, PSMF)
- Diet logs: meals, supplements, fasting modes (OMAD, PSMF)
- Body feelings and symptoms

CRITICAL ANTI-HALLUCINATION RULES:
1. **TOOL-FIRST POLICY**: When user asks about CURRENT or SPECIFIC health data (e.g., "how did I sleep last night?", "what was my steps yesterday?"), you MUST call the appropriate tool FIRST. NEVER answer based on conversation history or memory.
2. **NO DATA INVENTION**: If a tool returns "No data found" or null values, YOU MUST SAY "I cannot find data for this date" or "No data available". Do NOT guess, estimate, or invent numbers.
3. **VISION LIMITATIONS**: When analyzing images, if unclear or unreadable, say "I cannot identify the food in this image". Do NOT guess ingredients you do not see.
4. **EXPLICIT UNCERTAINTY**: If you're unsure whether data exists, call the tool to check. Say "Let me check your data" rather than guessing.

Guidelines:
- Be concise and data-driven
- When user wants to log something (diet, supplement, etc), use the appropriate tool
- Provide actionable insights based ONLY on retrieved data
- Support both English and Chinese

CONVERSATIONAL CONTEXT HANDLING:
- When user gives vague confirmation replies like "好的，可以记录" / "ok, log it" / "yes, record that":
  1. FIRST check the conversation history to see what they were discussing
  2. If they mentioned food/diet in recent messages, extract the details and call `log_diet` with:
     - description: the food items they mentioned
     - meal_type: infer from context (breakfast/lunch/dinner/snack)
     - target_date: today unless specified otherwise
  3. If you cannot extract clear details, politely ask: "请问要记录什么内容？可以告诉我具体吃了什么吗？"
- NEVER return empty responses. If unsure what to do, ask a clarifying question.

WEB SEARCH USAGE:
- You have access to real-time web search via `search_web`.
- USE IT WHEN:
  1. User asks for "latest" research, news, or biohacking trends.
  2. You need to verify external facts (e.g. "benefits of berberine").
  3. User specifically asks to check online.
- DO NOT USE IT WHEN:
  1. User asks for PERSONAL data (steps, sleep). Use `health_read` tools.

Available data sources:
- Garmin health metrics (sleep, steps, heart rate, etc.)
- Manual logs (diet, supplements, fasting, body feelings)
- Real-time Web Search
- Obsidian daily notes (can be synced)

5. **ACTION COMMANDS**: If the user asks to "sync", "update", or "fetch" data, you MUST call the corresponding tool (e.g., `sync_garmin`). Do NOT say "done" without calling the tool. If the tool call fails or you cannot find the tool, report the error.

IMPORTANT: Today's date is {today}. When logging data without a specified date, use today's date.)get_current_time_strreturnc            	         t                               t                                } 	 t          j                            t          j                            t          j                            t          j                            t                                        d          }t          j                            |          rEt          |dd          5 }|
                                }ddd           n# 1 swxY w Y   |  d| S n4# t          $ r'}t                              d|            Y d}~nd}~ww xY w| S )	z<Get system instruction with current date and health profile.)todayz	health.mdrutf-8)encodingNz 

=== USER HEALTH PROTOCOLS ===
zFailed to load health.md: )BUTLER_SYSTEM_INSTRUCTION_BASEformatr   ospathjoindirname__file__existsopenread	Exceptionloggererror)base_prompthealth_md_pathfhealth_profilees        -/root/projects/butler/slack_bot/llm/gemini.pyget_system_instructionr&   @   s_    177>R>T>T7UUK7bgoobgoobgooV^F_F_6`6`&a&acnoo7>>.)) 	WncG<<< *!"* * * * * * * * * * * * * * *!VVnVVV	W  7 7 75!55666666667 s<   B-D
 C7+D
 7C;;D
 >C;?	D
 

D;D66D;c                      e Zd ZdZddee         fdZdefdZ	 	 ddedee	ee
f                  d	eee	ee
f                           d
eee	ee
f                           deeeee	                  f         f
dZd Z	 ddedee	ee
f                  d	eee	ee
f                           d
eee	ee
f                           deeeee	                  f         f
dZdede	ee
f         fdZ	 ddedee	ee
f                  d	eee	ee
f                           d
eee	ee
f                           deeeee	                  f         f
dZdee	ee
f                  defdZdefdZdS )	GeminiLLMzJGemini Implementation via OpenAI-compatible API (for local proxy support).Nsystem_instructionc                    t           j                            d          }t           j                            d          }|s)t                              d           t          d          |r|nt                      | _        	 ddlm	} n)# t          $ r t                              d            w xY w|rd|                                v }|ryd	d
d}t           j                            d          }|r||d<   t                              d            ||| d|          | _        t                              d|            n ||| d          | _        t                              d|            n|dd lm} |                    |           d | _        |                    t           j                            dd          | j                  | _        t                              d           t           j                            dd          | _        t)          |          | _        t                              d| j                    d S )NGEMINI_API_KEYGEMINI_BASE_URLz(GEMINI_API_KEY not found in environment.zGEMINI_API_KEY not foundr   )OpenAIz:openai package not found. Install with: pip install openaizopenrouter.aiz'https://github.com/your-username/butlerzButler Health Assistant)zHTTP-RefererzX-TitleGOOGLE_API_KEYzX-Google-API-Keyz#Using Google API key for OpenRouterz/v1)api_keybase_urldefault_headerszUsing OpenRouter: )r/   r0   zUsing proxy: )r/   GEMINI_MODELzgemini-3-flash)r)   zUsing direct Google APIzInitialized Gemini model: )r   environgetr   r   
ValueErrorr&   r)   openair-   ImportErrorlowerinfoclientgoogle.generativeaigenerativeai	configureGenerativeModelgenai_model
model_namebool	use_proxy)	selfr)   r/   r0   r-   is_openrouterheaders
google_keygenais	            r%   __init__zGeminiLLM.__init__U   s~   *..!122:>>"344 	9LLCDDD7888 9K"h"4"4PfPhPh	%%%%%%% 	 	 	LLUVVV	
  #	3+x~~/?/??M 8 %N8   Z^^,<==
 G2<G./KK EFFF$f# (---$+  
 ;;;<<<<$fW(?O?O?OPPP6H667777 0/////OOGO,,,DK$44
~/?@@#'#:  5    D KK1222*..9IJJhBBBCCCCCs   B	 	&B/r   c                     | j         S N)r@   )rC   s    r%   get_model_namezGeminiLLM.get_model_name   s
        messagecontexttoolsimagesc                    	 | j         r|                     ||||          S |                     ||||          S # t          $ r;}t                              d|            dt          |           dfcY d}~S d}~ww xY w)aX  
        Generate response with optional tool calling and images.

        Args:
            message: Text prompt
            context: Chat history
            tools: Tool definitions
            images: List of dicts {'mime_type': str, 'data': bytes} (for direct API)

        Returns:
            Tuple of (response_text, tool_calls)
        zGemini generation error: z[Gemini Error] N)	rB   _generate_with_openai_generate_with_googler   r   r   strtext_response
tool_calls)rC   rM   rN   rO   rP   r$   s         r%   generate_responsezGeminiLLM.generate_response   s    &	4~ S11'7E6RRR11'7E6RRR 	4 	4 	4LL8Q88999-SVV--t3333333	4s   9 9 
A>0A93A>9A>c                    ddl }ddl}d}d}t          |dz             D ]}	  ||i |c S # t          $ r}	t	          |	          }
d|
v pId|
                                v p3d|
                                v pd	|
v pd
|
v pd|
                                v }|r||k    r|	|d|z  z  |                    dd          z   }t                              d|
 d|dd|dz    d| d	           |	                    |           Y d}	~	d}	~	ww xY wdS )z,Simple retry logic with exponential backoff.r   N      r	   zToken acquisition timeoutlimitbusy500503deadlockzAPI Request failed (z). Retrying in z.2fzs... (Attempt /))
timerandomranger   rT   r8   uniformr   warningsleep)rC   funcargskwargsrb   rc   max_retries
base_delayattemptr$   	error_stris_transientdelays                r%   _retry_with_backoffzGeminiLLM._retry_with_backoff   s   
[1_-- 	" 	"G"tT,V,,,,, " " "FF	 09< 4y0004ioo///4 Y&4 Y&	4
 )//"3"33  $ w+'='=G"a7l3fnnQ6J6JJ   Di   D   DPU   D   D   Dipqrir   D   D  vA   D   D   D  E  E  E

5!!!!!!!!#"	" 	"s   ,
DCDDc           
         t                      }d|dg}|                    |           |rddl}g }|r|                    d|d           |D ]X}	|                    |	d                                       d          }
|	d	         }|                    d
dd| d|
 id           Y|                    d|d           n|r|                    d|d           | j        |d}|r||d<   	 ddl}t          j	        
                    dd                                          dv }|r |j        |t          d          }t                              dt!          |           d           t                              d|
                    d                      t                              dt!          |
                    dg                                 t                              dt!          |
                    dg                                 t          j                            t          j                            t          j                            t          j                            t(                                        d          }t+          |d          5 } |j        ||d t          d!           ddd           n# 1 swxY w Y   t                              d"|             | j        | j        j        j        j        fi |}|j        d         j        }|j        pd}t?          |d#          o|j         }|r|!                                s|st          "                    d$           | j        |d}	  | j        | j        j        j        j        fi |}|j        d         j        }|j        pd}t                              d%t!          |           d           n4# tF          $ r'}t          $                    d&|            Y d}~nd}~ww xY wn# tF          $ r}t          $                    d'|            	 t          j                            t          j                            t          j                            t          j                            t(                                        d(          }t+          |d          5 }ddl} |j        ||d t          )           ddd           n# 1 swxY w Y   t          $                    d*|            n4# tF          $ r'}t          $                    d+|            Y d}~nd}~ww xY wt          |          }t!          |          d,k    rGd-|                                v sd.|                                v r|dd/         d0z   }n|dd,         d1z   }d2| dfcY d}~S d}~ww xY w	 |j        d         j        }|j        pd}t          j	        
                    dd                                          dv }|rt                              d3t!          |                      t                              d4t?          |d#          o|j                     t                              d5|r
|dd,         nd6            t          j                            t          j                            t          j                            t          j                            t(                                        d7          }t+          |d          5 }ddl} |j        |t?          |d#          otK          |j                   t?          |d#          r|j         rt!          |j                   ndt?          |j        d         d8          r|j        d         j&        ndd9|d d:           ddd           n# 1 swxY w Y   t                              d;|            d}t?          |d#          r|j         rg }|j         D ]}|j'        j(        }tS          |t                    s9t          "                    d<tU          |                      t          |          }|                    |j'        j+        | ,                    |          d=           ||fS # tF          $ r;}t          $                    d>|            d?t          |           dfcY d}~S d}~ww xY w)@z1Generate using OpenAI-compatible API (for proxy).system)rolecontentr   Ntext)typerv   datar   	mime_type	image_urlurlzdata:z;base64,)rw   rz   user)modelmessagesrO   DEBUG_GEMINI_PAYLOAD )true1yesF)defaultensure_asciiz[DEBUG] Payload size: z charsz[DEBUG] Model: r}   z[DEBUG] Messages: r~   z[DEBUG] Tools: zdebug_request.jsonwrZ   )indentr   r   z[DEBUG] Full payload saved to rV   z@Empty response with tools - retrying WITHOUT tools as workaroundzRetry without tools: got z!Retry without tools also failed: zOpenAI API Request Failed: zdebug_failed_payload.json)r   r   zDumped failed payload to zFailed to dump payload:    z<htmlz	<!doctyped   z... [HTML Error Truncated]z... [Truncated]z[Gemini API Error] z[DEBUG] Response text length: z![DEBUG] Response has tool_calls: z[DEBUG] Response preview: z(empty)zdebug_response.jsonfinish_reason)rv   has_tool_callstool_calls_countr   )r   r   z"[DEBUG] Response details saved to zTool arguments not a string: nameri   z&Error parsing OpenAI response object: z[Gemini Response Parse Error] )-r&   extendbase64append	b64encodedecoder@   jsonr   r3   r4   r8   dumpsrT   r   r9   lenr   r   r   r   r   dumprq   r:   chatcompletionscreatechoicesrM   ru   hasattrrV   striprf   r   r   rA   r   function	arguments
isinstancerw   r   _safe_parse_json)rC   rM   rN   rO   rP   current_system_instructionr~   r   content_listimgb64_datary   rj   r   
debug_modepayload_preview
debug_filer"   responsemessage_objrU   r   kwargs_no_toolsretry_error	api_errordump_errrn   rV   tcargs_strparse_errors                                  r%   rR   zGeminiLLM._generate_with_openai   s
    &<%=%="%2LMMN    	FMMML G##VW$E$EFFF 
 
!++CK88??HH,	##'DyDD(DD"% %     OOVEEFFFF FG D DEEE _ 
 

  	$#F7O>	;KKK(>CCIIKKOccJ K",$*VSu"U"U"UQS5I5IQQQRRRCfjj.A.ACCDDDRVZZ
B5O5O1P1PRRSSSLc&**Wb2I2I.J.JLLMMM  W\\"'//"'//"'//ZbJcJc:d:d*e*eg{||
*c** TaDIfa3USSSST T T T T T T T T T T T T T TIZIIJJJ 0t/0@0L0S^^W]^^H #*1-5K'/52M$[,??ZKDZN T]0022 T> Tabbb!_ (# #T7t78H8T8[oo_nooH"*"21"5"=K$/$7$=2MKK VC<N<N V V VWWWW  T T TLL!R[!R!RSSSSSSSST  	; 	; 	;LLByBBCCCDW\\"'//"'//"'//ZbJcJc:d:d*e*e  hC  D  D
*c** @aKKKDIfa3????@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ EEEFFFF D D DBBBCCCCCCCCD
 II9~~##ioo////;)//BSBS3S3S!*4C43O!OYY!*4C43D!DY444d:::::::/	;2,	M"*1-5K'/52M (>CCIIKKOccJ OQS=O=OQQRRRUa@b@b@}grg}  A  A  AnP]9ltt9L9Lclnnooo  W\\"'//"'//"'//ZbJcJc:d:d*e*eg|}}
*c** 8aKKKDI -*1+|*L*L*mQUVaVlQmQmKRS^`lKmKm  -Pr}  sI  -PC0F,G,G,G  OPNUV^VfghVikzN{N{  *F)9!)<)J)J  BF	 
 8 8 8 88 8 8 8 8 8 8 8 8 8 8 8 8 8 8 MMMNNN J{L11 k6L 
%0 
 
B!{4H%h44 1'WtH~~'W'WXXX#&x==%% " 0 $ 5 5h ? ?' '    
 !*,, 	M 	M 	MLLO+OOPPPFC4D4DFFLLLLLLL	Ms  ,F6P! "K	=P! 	KP! KB0P! A(O+ *P! +
P5PP! PP! !W,W
BT)T 4T) T	T)T	 T)(W)
U3UWUA2WWWE#c >B_"c "_&&c )_&*Cc 
d0d<ddjson_strc           	         |si S 	 t          j        |          S # t          $ r-}t                              d| d|dd          d           	 t          j                    }|                    |          \  }}t                              d           |cY d}~S # t          $ r Y nw xY w	 |                    d          }|dk    rTt          j                    }|                    ||d                   \  }}t                              d	           |cY d}~S n# t          $ r Y nw xY wt          	                    d
           i cY d}~S d}~ww xY w)z:Parse JSON with fallback for common LLM formatting errors.zJSON parse error: z
. String: Nr   z...z,Successfully recovered JSON using raw_decode{z.Successfully recovered JSON by skipping prefixz7Failed to recover JSON arguments. Returning empty dict.)
r   loadsr   r   rf   JSONDecoder
raw_decoder9   findr   )rC   r   r$   decoderobj_	start_idxs          r%   r   zGeminiLLM._safe_parse_jsons  s    	I	:h''' 	 	 	NNPPPXdsd^PPPQQQ*,, ++H55QJKKK





   $MM#..	??".00G$//0DEEFCKK PQQQJJJJJJ	 #
     LLSTTTIIIIII3	sj    E)EABE
B(%E'B((E,A)DEE
D)&E(D))EEEc                 .   ddl m} g }|D ]1}|d         dk    rdnd}|                    ||d         gd           2d}	|r|                     |          }	| j                            |          }
|g}|rTt                              d	t          |           d
           |D ]&}|                    |d         |d         d           '	 |	r|
	                    ||	ddi          }n|
	                    |          }nK# t          $ r>}t                              dt          |           d|            d| dfcY d}~S d}~ww xY wd}|j        r|j        d         j        j        rk|j        d         j        j        D ]S}t!          |d          rA|j        r:|g }|j        }|                    |j        t'          |j                  d           T	 |j        }n6# t          $ r)}t                              d|            d}Y d}~nd}~ww xY w||fS )z9Generate using Google's generativeai (direct connection).r   Nrt   r|   r}   ru   )rt   parts)historyzAdding z images to requestry   rx   )ry   rx   function_calling_configAUTO)rO   tool_configz(Gemini send_message failed (with images=z): z%[Error] Failed to generate response: function_callr   z2No text in response (likely only function calls): r   )r;   r<   r   _convert_tools_to_geminir?   
start_chatr   r9   r   send_messager   r   rA   
candidatesru   r   r   r   r   dictri   rv   debug)rC   rM   rN   rO   rP   rG   r   msgrt   gemini_toolsr   content_partsr   r   r$   rV   partfcrU   s                      r%   rS   zGeminiLLM._generate_with_google  s    	,+++++  	F 	FC [F2266DNNDC	N3CDDEEEE  	@88??L**7*;; !	 	KKA#f++AAABBB  $$!$[!1K& &    	E <,,!&!:F C -    ,,];; 	E 	E 	ELLXDLLXXUVXXYYY>1>>DDDDDDD	E 
 		8#6q#9#A#G 		 +A.6<  411 d6H !)%'
+B%% " $RW' '   	$MMM 	 	 	LLQaQQRRRMMMMMM	
 j((s6   2C> >
E3E;EEG 
H'HHopenai_toolsc                     ddl m g }|D ]}|                    d          dk    r|d         }|                    j                            |d         |d         j                            j        j        j         fd|d         d	         	                                D             |d                             d
g                                          |rj        
                    |          gS dS )zDConvert OpenAI-style tools to Gemini format (for direct Google API).r   Nrw   r   r   descriptionc                     i | ]]\  }}|j                                                 |                    d d                    |                    dd                    ^S )rw   stringr   r   )rw   r   )protosSchema_convert_typer4   ).0kvrG   rC   s      r%   
<dictcomp>z6GeminiLLM._convert_tools_to_gemini.<locals>.<dictcomp>  sx     ( ( (
 %)Aq	 !"5<#6#6)-););AEE&(<S<S)T)T01mR0H0H $7 $" $"( ( (rL   
parameters
propertiesrequired)rw   r   r   )r   r   r   )function_declarations)r;   r<   r4   r   r   FunctionDeclarationr   TypeOBJECTitemsTool)rC   r   gemini_functionstoolfunc_defrG   s   `    @r%   r   z"GeminiLLM._convert_tools_to_gemini  s>   ++++++  	 	Dxx:--
+ ''L44%f-$,]$;#(<#6#6!&!2!9( ( ( ( (
 -5\,B<,P,V,V,X,X( ( ( &.l%;%?%?
B%O%O $7 
$ 
$ 5    $  	OL%%<L%MMNNtrL   openai_typec                 B   ddl m} |j        j        j        |j        j        j        |j        j        j        |j        j        j        |j        j        j        |j        j        j	        d}|
                    |                                |j        j        j                  S )z#Convert OpenAI type to Gemini type.r   N)r   numberintegerbooleanobjectarray)r;   r<   r   r   STRINGNUMBERINTEGERBOOLEANr   ARRAYr4   r8   )rC   r   rG   type_mappings       r%   r   zGeminiLLM._convert_type  s    ++++++ l'.l'.|(0|(0l'.\&,
 
  1 1 3 3U\5F5MNNNrL   rJ   )NN)__name__
__module____qualname____doc__r   rT   rH   rK   r   r   r   r   rW   rq   rR   r   rS   r   r    rL   r%   r(   r(   R   s       TT;D ;D8C= ;D ;D ;D ;Dz     1515) )) d38n%) T#s(^,-	)
 d38n-.) 
sHT$Z((	)) ) ) )>" " "F 26_) _)_) d38n%_) T#s(^,-	_)
 d38n-._) 
sHT$Z((	)_) _) _) _)B   c3h        N 26E) E)E) d38n%E) T#s(^,-	E)
 d38n-.E) 
sHT$Z((	)E) E) E) E)NT$sCx.5I d    <O O O O O O OrL   r(   )r   r   datetimer   typingr   r   r   r   r   baser
   health.utils.logging_configr   r   r   r   health.utils.time_utilsr   rT   r&   r(   r   rL   r%   <module>r      s   				        3 3 3 3 3 3 3 3 3 3 3 3 3 3       4 4 4 4 4 4	h		0"e f 9 8 8 8 8 8    $tO tO tO tO tO tO tO tO tO tOrL   