bstraehle commited on
Commit
911c6dc
·
verified ·
1 Parent(s): 6571608

Upload 3 files

Browse files
Files changed (1) hide show
  1. agents/tools/ai_tools.py +354 -271
agents/tools/ai_tools.py CHANGED
@@ -17,6 +17,7 @@ from agents.models.llms import (
17
  LLM_IMAGE_TO_FEN,
18
  LLM_ALGEBRAIC_NOTATION,
19
  LLM_FINAL_ANSWER,
 
20
 
21
  THINKING_LEVEL_WEB_SEARCH,
22
  THINKING_LEVEL_MEDIA_ANALYSIS,
@@ -47,44 +48,57 @@ class AITools():
47
  def _get_client():
48
  return genai.Client(api_key=os.environ["GEMINI_API_KEY"])
49
 
 
 
 
 
50
  def _media_analysis_tool(tool_name: str, model: str, question: str, file_path: str) -> str:
51
  print("")
52
  print(f"🛠️ AITools: {tool_name}: question={question}, file_path={file_path}")
53
 
54
- try:
55
- client = AITools._get_client()
56
-
57
- file = client.files.upload(file=file_path)
58
-
59
- while True:
60
- media_file = client.files.get(name=file.name)
61
- if media_file.state == "ACTIVE":
62
- break
63
- elif media_file.state == "FAILED":
64
- raise RuntimeError("Media file processing failed")
65
- time.sleep(1)
66
-
67
- response = client.models.generate_content(
68
- model=model,
69
- contents=[file, question],
70
- config=types.GenerateContentConfig(
71
- # gemini-3-pro-preview daily rate limit
72
- #thinking_config=types.ThinkingConfig(
73
- # thinking_level=THINKING_LEVEL_MEDIA_ANALYSIS
74
- #)
 
 
 
 
 
75
  )
76
- )
77
 
78
- result = response.text
79
-
80
- print(f"🛠️ AITools: {tool_name}: model={model}")
81
- #print(f"🛠️ AITools: {tool_name}: thinking_level={THINKING_LEVEL_MEDIA_ANALYSIS}")
82
- print(f"🛠️ AITools: {tool_name}: result={result}")
83
-
84
- return result
85
- except Exception as e:
86
- print(f"⚠️ AITools: {tool_name}: exception={str(e)}")
87
- raise RuntimeError(f"Processing failed: {str(e)}")
 
 
 
 
 
88
 
89
  def _extract_execution_result(response):
90
  for part in response.candidates[0].content.parts:
@@ -109,31 +123,39 @@ class AITools():
109
  print("")
110
  print(f"🛠️ AITools: web_search_tool: question={question}")
111
 
112
- try:
113
- client = AITools._get_client()
114
-
115
- response = client.models.generate_content(
116
- model=LLM_WEB_SEARCH,
117
- contents=question,
118
- config=types.GenerateContentConfig(
119
- tools=[types.Tool(google_search=types.GoogleSearch())],
120
- # gemini-3-pro-preview daily rate limit
121
- #thinking_config=types.ThinkingConfig(
122
- # thinking_level=THINKING_LEVEL_WEB_SEARCH
123
- #)
 
 
 
 
124
  )
125
- )
126
 
127
- result = response.text
128
-
129
- print(f"🛠️ AITools: web_search_tool: model={LLM_WEB_SEARCH}")
130
- #print(f"🛠️ AITools: web_search_tool: thinking_level={THINKING_LEVEL_WEB_SEARCH}")
131
- print(f"🛠️ AITools: web_search_tool: result={result}")
132
-
133
- return result
134
- except Exception as e:
135
- print(f"⚠️ AITools: web_search_tool: exception={str(e)}")
136
- raise RuntimeError(f"Processing failed: {str(e)}")
 
 
 
 
 
137
 
138
  @tool("Web Browser Tool")
139
  def web_browser_tool(question: str, url: str) -> str:
@@ -249,31 +271,40 @@ class AITools():
249
  print("")
250
  print(f"🛠️ AITools: youtube_analysis_tool: question={question}, url={url}")
251
 
252
- try:
253
- client = AITools._get_client()
254
-
255
- result = client.models.generate_content(
256
- model=LLM_YOUTUBE_ANALYSIS,
257
- contents=types.Content(
258
- parts=[types.Part(file_data=types.FileData(file_uri=url)),
259
- types.Part(text=question)]
260
- ),
261
- config=types.GenerateContentConfig(
262
- # gemini-3-pro-preview daily rate limit
263
- #thinking_config=types.ThinkingConfig(
264
- # thinking_level=THINKING_LEVEL_YOUTUBE_ANALYSIS
265
- #)
 
 
 
 
 
266
  )
267
- )
268
 
269
- print(f"🛠️ AITools: youtube_analysis_tool: model={LLM_YOUTUBE_ANALYSIS}")
270
- #print(f"🛠️ AITools: youtube_analysis_tool: thinking_level={THINKING_LEVEL_YOUTUBE_ANALYSIS}")
271
- print(f"🛠️ AITools: youtube_analysis_tool: result={result}")
 
272
 
273
- return result
274
- except Exception as e:
275
- print(f"⚠️ AITools: youtube_analysis_tool: exception={str(e)}")
276
- raise RuntimeError(f"Processing failed: {str(e)}")
 
 
 
 
277
 
278
  @tool("Document Analysis Tool")
279
  def document_analysis_tool(question: str, file_path: str) -> str:
@@ -292,44 +323,53 @@ class AITools():
292
  print("")
293
  print(f"🛠️ AITools: document_analysis_tool: question={question}, file_path={file_path}")
294
 
295
- try:
296
- client = AITools._get_client()
297
-
298
- contents = []
299
-
300
- if is_ext(file_path, ".docx"):
301
- text_data = read_docx_text(file_path)
302
- contents = [f"{question}\n{text_data}"]
303
- print(f"🛠️ Text data:\n{text_data}")
304
- elif is_ext(file_path, ".pptx"):
305
- text_data = read_pptx_text(file_path)
306
- contents = [f"{question}\n{text_data}"]
307
- print(f"🛠️ Text data:\n{text_data}")
308
- else:
309
- file = client.files.upload(file=file_path)
310
- contents = [file, question]
311
-
312
- response = client.models.generate_content(
313
- model=LLM_DOCUMENT_ANALYSIS,
314
- contents=contents,
315
- config=types.GenerateContentConfig(
316
- # gemini-3-pro-preview daily rate limit
317
- #thinking_config=types.ThinkingConfig(
318
- # thinking_level=THINKING_LEVEL_DOCUMENT_ANALYSIS
319
- #)
 
 
 
 
 
320
  )
321
- )
322
-
323
- result = response.text
324
-
325
- print(f"🛠️ AITools: document_analysis_tool: model={LLM_DOCUMENT_ANALYSIS}")
326
- #print(f"🛠️ AITools: document_analysis_tool: thinking_level={THINKING_LEVEL_DOCUMENT_ANALYSIS}")
327
- print(f"🛠️ AITools: document_analysis_tool: result={result}")
328
-
329
- return result
330
- except Exception as e:
331
- print(f"⚠️ AITools: document_analysis_tool: exception={str(e)}")
332
- raise RuntimeError(f"Processing failed: {str(e)}")
 
 
 
 
333
 
334
  @tool("Code Generation and Execution Tool")
335
  def code_generation_and_execution_tool(question: str, json_data: str) -> str:
@@ -347,31 +387,39 @@ class AITools():
347
  print("")
348
  print(f"🛠️ AITools: code_generation_and_execution_tool: question={question}, json_data={json_data}")
349
 
350
- try:
351
- client = AITools._get_client()
352
-
353
- response = client.models.generate_content(
354
- model=LLM_CODE_GENERATION,
355
- contents=[f"{question}\n{json_data}"],
356
- config=types.GenerateContentConfig(
357
- tools=[types.Tool(code_execution=types.ToolCodeExecution)],
358
- # gemini-3-pro-preview daily rate limit
359
- #thinking_config=types.ThinkingConfig(
360
- # thinking_level=THINKING_LEVEL_CODE_GENERATION
361
- #)
362
- ),
363
- )
364
-
365
- result = AITools._extract_execution_result(response)
366
-
367
- print(f"🛠️ AITools: code_generation_and_execution_tool: model={LLM_CODE_GENERATION}")
368
- #print(f"🛠️ AITools: code_generation_and_execution_tool: thinking_level={THINKING_LEVEL_CODE_GENERATION}")
369
- print(f"🛠️ AITools: code_generation_and_execution_tool: result={result}")
370
-
371
- return result
372
- except Exception as e:
373
- print(f"⚠️ AITools: code_generation_and_execution_tool: exception={str(e)}")
374
- raise RuntimeError(f"Processing failed: {str(e)}")
 
 
 
 
 
 
 
 
375
 
376
  @tool("Code Execution Tool")
377
  def code_execution_tool(question: str, file_path: str) -> str:
@@ -390,33 +438,41 @@ class AITools():
390
  print("")
391
  print(f"🛠️ AITools: code_execution_tool: question={question}, file_path={file_path}")
392
 
393
- try:
394
- client = AITools._get_client()
395
-
396
- file = client.files.upload(file=file_path)
397
-
398
- response = client.models.generate_content(
399
- model=LLM_CODE_EXECUTION,
400
- contents=[file, question],
401
- config=types.GenerateContentConfig(
402
- tools=[types.Tool(code_execution=types.ToolCodeExecution)],
403
- # gemini-3-pro-preview daily rate limit
404
- #thinking_config=types.ThinkingConfig(
405
- # thinking_level=THINKING_LEVEL_CODE_EXECUTION
406
- #)
407
- ),
408
- )
409
-
410
- result = AITools._extract_execution_result(response)
411
-
412
- print(f"🛠️ AITools: code_execution_tool: model={LLM_CODE_EXECUTION}")
413
- #print(f"🛠️ AITools: code_execution_tool: thinking_level={THINKING_LEVEL_CODE_EXECUTION}")
414
- print(f"🛠️ AITools: code_execution_tool: result={result}")
415
-
416
- return result
417
- except Exception as e:
418
- print(f"⚠️ AITools: code_execution_tool: exception={str(e)}")
419
- raise RuntimeError(f"Processing failed: {str(e)}")
 
 
 
 
 
 
 
 
420
 
421
  @tool("Image to FEN Tool")
422
  def img_to_fen_tool(question: str, file_path: str, active_color: str) -> str:
@@ -436,55 +492,64 @@ class AITools():
436
  print("")
437
  print(f"🛠️ AITools: img_to_fen_tool: question={question}, file_path={file_path}, active_color={active_color}")
438
 
439
- try:
440
- client = AITools._get_client()
441
-
442
- with open(file_path, "rb") as f:
443
- img_bytes = f.read()
444
- img_b64 = base64.b64encode(img_bytes).decode("ascii")
445
-
446
- prompt = PROMPT_IMG_TO_FEN.format(question=question, active_color=active_color)
447
-
448
- content = types.Content(
449
- parts=[
450
- types.Part(text=prompt),
451
- types.Part(
452
- inline_data=types.Blob(
453
- mime_type="image/png",
454
- data=base64.b64decode(img_b64),
 
 
 
455
  )
 
 
 
 
 
 
 
 
456
  )
457
- ]
458
- )
459
-
460
- response = client.models.generate_content(
461
- model=LLM_IMAGE_TO_FEN,
462
- contents=[content],
463
- config=types.GenerateContentConfig(
464
- # gemini-3-pro-preview daily rate limit
465
- #thinking_config=types.ThinkingConfig(
466
- # thinking_level=THINKING_LEVEL_IMAGE_TO_FEN
467
- #)
468
  )
469
- )
470
 
471
- result = None
472
 
473
- for part in response.parts:
474
- if part.text is not None:
475
- result = part.text
476
- break
477
 
478
- board = chess.Board(result) # FEN validation
479
 
480
- print(f"🛠️ AITools: img_to_fen_tool: model={LLM_IMAGE_TO_FEN}")
481
- #print(f"🛠️ AITools: img_to_fen_tool: thinking_level={THINKING_LEVEL_IMAGE_TO_FEN}")
482
- print(f"🛠️ AITools: img_to_fen_tool: result={result}")
 
483
 
484
- return result
485
- except Exception as e:
486
- print(f"⚠️ AITools: img_to_fen_tool: exception={str(e)}")
487
- raise RuntimeError(f"Processing failed: {str(e)}")
 
 
 
 
488
 
489
  @tool("Algebraic Notation Tool")
490
  def algebraic_notation_tool(question: str, file_path: str, position_evaluation: str) -> str:
@@ -504,53 +569,62 @@ class AITools():
504
  print("")
505
  print(f"🛠️ AITools: algebraic_notation_tool: question={question}, file_path={file_path}, position_evaluation={position_evaluation}")
506
 
507
- try:
508
- client = AITools._get_client()
509
-
510
- with open(file_path, "rb") as f:
511
- img_bytes = f.read()
512
- img_b64 = base64.b64encode(img_bytes).decode("ascii")
513
-
514
- prompt = PROMPT_ALGEBRAIC_NOTATION.format(question=question, position_evaluation=position_evaluation)
515
-
516
- content = types.Content(
517
- parts=[
518
- types.Part(text=prompt),
519
- types.Part(
520
- inline_data=types.Blob(
521
- mime_type="image/png",
522
- data=base64.b64decode(img_b64),
 
 
 
523
  )
 
 
 
 
 
 
 
 
524
  )
525
- ]
526
- )
527
-
528
- response = client.models.generate_content(
529
- model=LLM_ALGEBRAIC_NOTATION,
530
- contents=[content],
531
- config=types.GenerateContentConfig(
532
- # gemini-3-pro-preview daily rate limit
533
- #thinking_config=types.ThinkingConfig(
534
- # thinking_level=THINKING_LEVEL_ALGEBRAIC_NOTATION
535
- #)
536
  )
537
- )
538
 
539
- result = None
540
-
541
- for part in response.parts:
542
- if part.text is not None:
543
- result = part.text
544
- break
545
 
546
- print(f"🛠️ AITools: algebraic_notation_tool: model={LLM_ALGEBRAIC_NOTATION}")
547
- #print(f"🛠️ AITools: algebraic_notation_tool: thinking_level={THINKING_LEVEL_ALGEBRAIC_NOTATION}")
548
- print(f"🛠️ AITools: algebraic_notation_tool: result={result}")
 
549
 
550
- return result
551
- except Exception as e:
552
- print(f"⚠️ AITools: algebraic_notation_tool: exception={str(e)}")
553
- raise RuntimeError(f"Processing failed: {str(e)}")
 
 
 
 
554
 
555
  def final_answer_tool(question: str, answer: str) -> str:
556
  """Given a question and initial answer, get the final answer.
@@ -568,29 +642,38 @@ class AITools():
568
  print("")
569
  print(f"🛠️ AITools: final_answer_tool: question={question}, answer={answer}")
570
 
571
- try:
572
- client = AITools._get_client()
573
 
574
- prompt = PROMPT_FINAL_ANSWER.format(question=question, answer=answer)
575
-
576
- response = client.models.generate_content(
577
- model=LLM_FINAL_ANSWER,
578
- contents=[prompt],
579
- config=types.GenerateContentConfig(
580
- # gemini-3-pro-preview daily rate limit
581
- #thinking_config=types.ThinkingConfig(
582
- # thinking_level=THINKING_LEVEL_FINAL_ANSWER
583
- #)
 
 
 
 
 
584
  )
585
- )
586
-
587
- result = response.text.strip()
588
 
589
- print(f"🛠️ AITools: final_answer_tool: model={LLM_FINAL_ANSWER}")
590
- #print(f"🛠️ AITools: final_answer_tool: thinking_level={THINKING_LEVEL_FINAL_ANSWER}")
591
- print(f"🛠️ AITools: final_answer_tool: result={result}")
592
-
593
- return result
594
- except Exception as e:
595
- print(f"⚠️ AITools: final_answer_tool: exception={str(e)}")
596
- raise RuntimeError(f"Processing failed: {str(e)}")
 
 
 
 
 
 
17
  LLM_IMAGE_TO_FEN,
18
  LLM_ALGEBRAIC_NOTATION,
19
  LLM_FINAL_ANSWER,
20
+ LLM_FALLBACK,
21
 
22
  THINKING_LEVEL_WEB_SEARCH,
23
  THINKING_LEVEL_MEDIA_ANALYSIS,
 
48
  def _get_client():
49
  return genai.Client(api_key=os.environ["GEMINI_API_KEY"])
50
 
51
+ def _is_rate_limit_error(exception):
52
+ error_str = str(exception)
53
+ return "429" in error_str and "RESOURCE_EXHAUSTED" in error_str
54
+
55
  def _media_analysis_tool(tool_name: str, model: str, question: str, file_path: str) -> str:
56
  print("")
57
  print(f"🛠️ AITools: {tool_name}: question={question}, file_path={file_path}")
58
 
59
+ client = AITools._get_client()
60
+ current_model = model
61
+
62
+ for attempt in range(2):
63
+ try:
64
+ file = client.files.upload(file=file_path)
65
+
66
+ while True:
67
+ media_file = client.files.get(name=file.name)
68
+ if media_file.state == "ACTIVE":
69
+ break
70
+ elif media_file.state == "FAILED":
71
+ raise RuntimeError("Media file processing failed")
72
+ time.sleep(1)
73
+
74
+ config_params = {}
75
+
76
+ if current_model != LLM_FALLBACK:
77
+ config_params["thinking_config"] = types.ThinkingConfig(
78
+ thinking_level=THINKING_LEVEL_MEDIA_ANALYSIS
79
+ )
80
+
81
+ response = client.models.generate_content(
82
+ model=current_model,
83
+ contents=[file, question],
84
+ config=types.GenerateContentConfig(**config_params)
85
  )
 
86
 
87
+ result = response.text
88
+
89
+ print(f"🛠️ AITools: {tool_name}: model={current_model}")
90
+ if current_model != LLM_FALLBACK:
91
+ print(f"🛠️ AITools: {tool_name}: thinking_level={THINKING_LEVEL_MEDIA_ANALYSIS}")
92
+ print(f"🛠️ AITools: {tool_name}: result={result}")
93
+
94
+ return result
95
+ except Exception as e:
96
+ if attempt == 0 and AITools._is_rate_limit_error(e):
97
+ print(f"⚠️ AITools: {tool_name}: Rate limit hit with {current_model}, falling back to {LLM_FALLBACK}")
98
+ current_model = LLM_FALLBACK
99
+ continue
100
+ print(f"⚠️ AITools: {tool_name}: exception={str(e)}")
101
+ raise RuntimeError(f"Processing failed: {str(e)}")
102
 
103
  def _extract_execution_result(response):
104
  for part in response.candidates[0].content.parts:
 
123
  print("")
124
  print(f"🛠️ AITools: web_search_tool: question={question}")
125
 
126
+ client = AITools._get_client()
127
+ model = LLM_WEB_SEARCH
128
+
129
+ for attempt in range(2):
130
+ try:
131
+ config_params = {"tools": [types.Tool(google_search=types.GoogleSearch())]}
132
+
133
+ if model != LLM_FALLBACK:
134
+ config_params["thinking_config"] = types.ThinkingConfig(
135
+ thinking_level=THINKING_LEVEL_WEB_SEARCH
136
+ )
137
+
138
+ response = client.models.generate_content(
139
+ model=model,
140
+ contents=question,
141
+ config=types.GenerateContentConfig(**config_params)
142
  )
 
143
 
144
+ result = response.text
145
+
146
+ print(f"🛠️ AITools: web_search_tool: model={model}")
147
+ if model != LLM_FALLBACK:
148
+ print(f"🛠️ AITools: web_search_tool: thinking_level={THINKING_LEVEL_WEB_SEARCH}")
149
+ print(f"🛠️ AITools: web_search_tool: result={result}")
150
+
151
+ return result
152
+ except Exception as e:
153
+ if attempt == 0 and AITools._is_rate_limit_error(e):
154
+ print(f"⚠️ AITools: web_search_tool: Rate limit hit with {model}, falling back to {LLM_FALLBACK}")
155
+ model = LLM_FALLBACK
156
+ continue
157
+ print(f"⚠️ AITools: web_search_tool: exception={str(e)}")
158
+ raise RuntimeError(f"Processing failed: {str(e)}")
159
 
160
  @tool("Web Browser Tool")
161
  def web_browser_tool(question: str, url: str) -> str:
 
271
  print("")
272
  print(f"🛠️ AITools: youtube_analysis_tool: question={question}, url={url}")
273
 
274
+ client = AITools._get_client()
275
+ model = LLM_YOUTUBE_ANALYSIS
276
+
277
+ for attempt in range(2):
278
+ try:
279
+ config_params = {}
280
+
281
+ if model != LLM_FALLBACK:
282
+ config_params["thinking_config"] = types.ThinkingConfig(
283
+ thinking_level=THINKING_LEVEL_YOUTUBE_ANALYSIS
284
+ )
285
+
286
+ result = client.models.generate_content(
287
+ model=model,
288
+ contents=types.Content(
289
+ parts=[types.Part(file_data=types.FileData(file_uri=url)),
290
+ types.Part(text=question)]
291
+ ),
292
+ config=types.GenerateContentConfig(**config_params)
293
  )
 
294
 
295
+ print(f"🛠️ AITools: youtube_analysis_tool: model={model}")
296
+ if model != LLM_FALLBACK:
297
+ print(f"🛠️ AITools: youtube_analysis_tool: thinking_level={THINKING_LEVEL_YOUTUBE_ANALYSIS}")
298
+ print(f"🛠️ AITools: youtube_analysis_tool: result={result}")
299
 
300
+ return result
301
+ except Exception as e:
302
+ if attempt == 0 and AITools._is_rate_limit_error(e):
303
+ print(f"⚠️ AITools: youtube_analysis_tool: Rate limit hit with {model}, falling back to {LLM_FALLBACK}")
304
+ model = LLM_FALLBACK
305
+ continue
306
+ print(f"⚠️ AITools: youtube_analysis_tool: exception={str(e)}")
307
+ raise RuntimeError(f"Processing failed: {str(e)}")
308
 
309
  @tool("Document Analysis Tool")
310
  def document_analysis_tool(question: str, file_path: str) -> str:
 
323
  print("")
324
  print(f"🛠️ AITools: document_analysis_tool: question={question}, file_path={file_path}")
325
 
326
+ client = AITools._get_client()
327
+ model = LLM_DOCUMENT_ANALYSIS
328
+
329
+ for attempt in range(2):
330
+ try:
331
+ contents = []
332
+
333
+ if is_ext(file_path, ".docx"):
334
+ text_data = read_docx_text(file_path)
335
+ contents = [f"{question}\n{text_data}"]
336
+ print(f"🛠️ Text data:\n{text_data}")
337
+ elif is_ext(file_path, ".pptx"):
338
+ text_data = read_pptx_text(file_path)
339
+ contents = [f"{question}\n{text_data}"]
340
+ print(f"🛠️ Text data:\n{text_data}")
341
+ else:
342
+ file = client.files.upload(file=file_path)
343
+ contents = [file, question]
344
+
345
+ config_params = {}
346
+
347
+ if model != LLM_FALLBACK:
348
+ config_params["thinking_config"] = types.ThinkingConfig(
349
+ thinking_level=THINKING_LEVEL_DOCUMENT_ANALYSIS
350
+ )
351
+
352
+ response = client.models.generate_content(
353
+ model=model,
354
+ contents=contents,
355
+ config=types.GenerateContentConfig(**config_params)
356
  )
357
+
358
+ result = response.text
359
+
360
+ print(f"🛠️ AITools: document_analysis_tool: model={model}")
361
+ if model != LLM_FALLBACK:
362
+ print(f"🛠️ AITools: document_analysis_tool: thinking_level={THINKING_LEVEL_DOCUMENT_ANALYSIS}")
363
+ print(f"🛠️ AITools: document_analysis_tool: result={result}")
364
+
365
+ return result
366
+ except Exception as e:
367
+ if attempt == 0 and AITools._is_rate_limit_error(e):
368
+ print(f"⚠️ AITools: document_analysis_tool: Rate limit hit with {model}, falling back to {LLM_FALLBACK}")
369
+ model = LLM_FALLBACK
370
+ continue
371
+ print(f"⚠️ AITools: document_analysis_tool: exception={str(e)}")
372
+ raise RuntimeError(f"Processing failed: {str(e)}")
373
 
374
  @tool("Code Generation and Execution Tool")
375
  def code_generation_and_execution_tool(question: str, json_data: str) -> str:
 
387
  print("")
388
  print(f"🛠️ AITools: code_generation_and_execution_tool: question={question}, json_data={json_data}")
389
 
390
+ client = AITools._get_client()
391
+ model = LLM_CODE_GENERATION
392
+
393
+ for attempt in range(2):
394
+ try:
395
+ config_params = {"tools": [types.Tool(code_execution=types.ToolCodeExecution)]}
396
+
397
+ if model != LLM_FALLBACK:
398
+ config_params["thinking_config"] = types.ThinkingConfig(
399
+ thinking_level=THINKING_LEVEL_CODE_GENERATION
400
+ )
401
+
402
+ response = client.models.generate_content(
403
+ model=model,
404
+ contents=[f"{question}\n{json_data}"],
405
+ config=types.GenerateContentConfig(**config_params),
406
+ )
407
+
408
+ result = AITools._extract_execution_result(response)
409
+
410
+ print(f"🛠️ AITools: code_generation_and_execution_tool: model={model}")
411
+ if model != LLM_FALLBACK:
412
+ print(f"🛠️ AITools: code_generation_and_execution_tool: thinking_level={THINKING_LEVEL_CODE_GENERATION}")
413
+ print(f"🛠️ AITools: code_generation_and_execution_tool: result={result}")
414
+
415
+ return result
416
+ except Exception as e:
417
+ if attempt == 0 and AITools._is_rate_limit_error(e):
418
+ print(f"⚠️ AITools: code_generation_and_execution_tool: Rate limit hit with {model}, falling back to {LLM_FALLBACK}")
419
+ model = LLM_FALLBACK
420
+ continue
421
+ print(f"⚠️ AITools: code_generation_and_execution_tool: exception={str(e)}")
422
+ raise RuntimeError(f"Processing failed: {str(e)}")
423
 
424
  @tool("Code Execution Tool")
425
  def code_execution_tool(question: str, file_path: str) -> str:
 
438
  print("")
439
  print(f"🛠️ AITools: code_execution_tool: question={question}, file_path={file_path}")
440
 
441
+ client = AITools._get_client()
442
+ model = LLM_CODE_EXECUTION
443
+
444
+ for attempt in range(2):
445
+ try:
446
+ file = client.files.upload(file=file_path)
447
+
448
+ config_params = {"tools": [types.Tool(code_execution=types.ToolCodeExecution)]}
449
+
450
+ if model != LLM_FALLBACK:
451
+ config_params["thinking_config"] = types.ThinkingConfig(
452
+ thinking_level=THINKING_LEVEL_CODE_EXECUTION
453
+ )
454
+
455
+ response = client.models.generate_content(
456
+ model=model,
457
+ contents=[file, question],
458
+ config=types.GenerateContentConfig(**config_params),
459
+ )
460
+
461
+ result = AITools._extract_execution_result(response)
462
+
463
+ print(f"🛠️ AITools: code_execution_tool: model={model}")
464
+ if model != LLM_FALLBACK:
465
+ print(f"🛠️ AITools: code_execution_tool: thinking_level={THINKING_LEVEL_CODE_EXECUTION}")
466
+ print(f"🛠️ AITools: code_execution_tool: result={result}")
467
+
468
+ return result
469
+ except Exception as e:
470
+ if attempt == 0 and AITools._is_rate_limit_error(e):
471
+ print(f"⚠️ AITools: code_execution_tool: Rate limit hit with {model}, falling back to {LLM_FALLBACK}")
472
+ model = LLM_FALLBACK
473
+ continue
474
+ print(f"⚠️ AITools: code_execution_tool: exception={str(e)}")
475
+ raise RuntimeError(f"Processing failed: {str(e)}")
476
 
477
  @tool("Image to FEN Tool")
478
  def img_to_fen_tool(question: str, file_path: str, active_color: str) -> str:
 
492
  print("")
493
  print(f"🛠️ AITools: img_to_fen_tool: question={question}, file_path={file_path}, active_color={active_color}")
494
 
495
+ client = AITools._get_client()
496
+ model = LLM_IMAGE_TO_FEN
497
+
498
+ for attempt in range(2):
499
+ try:
500
+ with open(file_path, "rb") as f:
501
+ img_bytes = f.read()
502
+ img_b64 = base64.b64encode(img_bytes).decode("ascii")
503
+
504
+ prompt = PROMPT_IMG_TO_FEN.format(question=question, active_color=active_color)
505
+
506
+ content = types.Content(
507
+ parts=[
508
+ types.Part(text=prompt),
509
+ types.Part(
510
+ inline_data=types.Blob(
511
+ mime_type="image/png",
512
+ data=base64.b64decode(img_b64),
513
+ )
514
  )
515
+ ]
516
+ )
517
+
518
+ config_params = {}
519
+
520
+ if model != LLM_FALLBACK:
521
+ config_params["thinking_config"] = types.ThinkingConfig(
522
+ thinking_level=THINKING_LEVEL_IMAGE_TO_FEN
523
  )
524
+
525
+ response = client.models.generate_content(
526
+ model=model,
527
+ contents=[content],
528
+ config=types.GenerateContentConfig(**config_params)
 
 
 
 
 
 
529
  )
 
530
 
531
+ result = None
532
 
533
+ for part in response.parts:
534
+ if part.text is not None:
535
+ result = part.text
536
+ break
537
 
538
+ board = chess.Board(result) # FEN validation
539
 
540
+ print(f"🛠️ AITools: img_to_fen_tool: model={model}")
541
+ if model != LLM_FALLBACK:
542
+ print(f"🛠️ AITools: img_to_fen_tool: thinking_level={THINKING_LEVEL_IMAGE_TO_FEN}")
543
+ print(f"🛠️ AITools: img_to_fen_tool: result={result}")
544
 
545
+ return result
546
+ except Exception as e:
547
+ if attempt == 0 and AITools._is_rate_limit_error(e):
548
+ print(f"⚠️ AITools: img_to_fen_tool: Rate limit hit with {model}, falling back to {LLM_FALLBACK}")
549
+ model = LLM_FALLBACK
550
+ continue
551
+ print(f"⚠️ AITools: img_to_fen_tool: exception={str(e)}")
552
+ raise RuntimeError(f"Processing failed: {str(e)}")
553
 
554
  @tool("Algebraic Notation Tool")
555
  def algebraic_notation_tool(question: str, file_path: str, position_evaluation: str) -> str:
 
569
  print("")
570
  print(f"🛠️ AITools: algebraic_notation_tool: question={question}, file_path={file_path}, position_evaluation={position_evaluation}")
571
 
572
+ client = AITools._get_client()
573
+ model = LLM_ALGEBRAIC_NOTATION
574
+
575
+ for attempt in range(2):
576
+ try:
577
+ with open(file_path, "rb") as f:
578
+ img_bytes = f.read()
579
+ img_b64 = base64.b64encode(img_bytes).decode("ascii")
580
+
581
+ prompt = PROMPT_ALGEBRAIC_NOTATION.format(question=question, position_evaluation=position_evaluation)
582
+
583
+ content = types.Content(
584
+ parts=[
585
+ types.Part(text=prompt),
586
+ types.Part(
587
+ inline_data=types.Blob(
588
+ mime_type="image/png",
589
+ data=base64.b64decode(img_b64),
590
+ )
591
  )
592
+ ]
593
+ )
594
+
595
+ config_params = {}
596
+
597
+ if model != LLM_FALLBACK:
598
+ config_params["thinking_config"] = types.ThinkingConfig(
599
+ thinking_level=THINKING_LEVEL_ALGEBRAIC_NOTATION
600
  )
601
+
602
+ response = client.models.generate_content(
603
+ model=model,
604
+ contents=[content],
605
+ config=types.GenerateContentConfig(**config_params)
 
 
 
 
 
 
606
  )
 
607
 
608
+ result = None
609
+
610
+ for part in response.parts:
611
+ if part.text is not None:
612
+ result = part.text
613
+ break
614
 
615
+ print(f"🛠️ AITools: algebraic_notation_tool: model={model}")
616
+ if model != LLM_FALLBACK:
617
+ print(f"🛠️ AITools: algebraic_notation_tool: thinking_level={THINKING_LEVEL_ALGEBRAIC_NOTATION}")
618
+ print(f"🛠️ AITools: algebraic_notation_tool: result={result}")
619
 
620
+ return result
621
+ except Exception as e:
622
+ if attempt == 0 and AITools._is_rate_limit_error(e):
623
+ print(f"⚠️ AITools: algebraic_notation_tool: Rate limit hit with {model}, falling back to {LLM_FALLBACK}")
624
+ model = LLM_FALLBACK
625
+ continue
626
+ print(f"⚠️ AITools: algebraic_notation_tool: exception={str(e)}")
627
+ raise RuntimeError(f"Processing failed: {str(e)}")
628
 
629
  def final_answer_tool(question: str, answer: str) -> str:
630
  """Given a question and initial answer, get the final answer.
 
642
  print("")
643
  print(f"🛠️ AITools: final_answer_tool: question={question}, answer={answer}")
644
 
645
+ client = AITools._get_client()
646
+ model = LLM_FINAL_ANSWER
647
 
648
+ for attempt in range(2):
649
+ try:
650
+ prompt = PROMPT_FINAL_ANSWER.format(question=question, answer=answer)
651
+
652
+ config_params = {}
653
+
654
+ if model != LLM_FALLBACK:
655
+ config_params["thinking_config"] = types.ThinkingConfig(
656
+ thinking_level=THINKING_LEVEL_FINAL_ANSWER
657
+ )
658
+
659
+ response = client.models.generate_content(
660
+ model=model,
661
+ contents=[prompt],
662
+ config=types.GenerateContentConfig(**config_params)
663
  )
664
+
665
+ result = response.text.strip()
 
666
 
667
+ print(f"🛠️ AITools: final_answer_tool: model={model}")
668
+ if model != LLM_FALLBACK:
669
+ print(f"🛠️ AITools: final_answer_tool: thinking_level={THINKING_LEVEL_FINAL_ANSWER}")
670
+ print(f"🛠️ AITools: final_answer_tool: result={result}")
671
+
672
+ return result
673
+ except Exception as e:
674
+ if attempt == 0 and AITools._is_rate_limit_error(e):
675
+ print(f"⚠️ AITools: final_answer_tool: Rate limit hit with {model}, falling back to {LLM_FALLBACK}")
676
+ model = LLM_FALLBACK
677
+ continue
678
+ print(f"⚠️ AITools: final_answer_tool: exception={str(e)}")
679
+ raise RuntimeError(f"Processing failed: {str(e)}")