amd-gaia 0.15.1__py3-none-any.whl → 0.15.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
gaia/agents/chat/agent.py CHANGED
@@ -78,23 +78,6 @@ class ChatAgent(
78
78
  - MCP server integration
79
79
  """
80
80
 
81
- # Define simple tools that can execute without requiring a multi-step plan
82
- SIMPLE_TOOLS = [
83
- "list_indexed_documents",
84
- "rag_status",
85
- "query_documents",
86
- "query_specific_file",
87
- "search_indexed_chunks", # RAG: Search indexed document chunks
88
- "dump_document", # RAG: Export cached extracted text
89
- "search_file_content", # Shared: Grep-like disk search
90
- "search_file", # Shared: Find files by name
91
- "search_directory", # Shared: Find directories by name
92
- "read_file", # Shared: Read any file
93
- "write_file", # Shared: Write any file
94
- "index_directory", # RAG: Index directory
95
- "run_shell_command", # Shell: Execute commands
96
- ]
97
-
98
81
  def __init__(self, config: Optional[ChatAgentConfig] = None):
99
82
  """
100
83
  Initialize Chat Agent.
@@ -109,6 +92,9 @@ class ChatAgent(
109
92
  # Initialize path validator
110
93
  self.path_validator = PathValidator(config.allowed_paths)
111
94
 
95
+ # Store config for access in other methods
96
+ self.config = config
97
+
112
98
  # Now use config for all initialization
113
99
  # Store RAG configuration from config
114
100
  self.rag_documents = config.rag_documents
@@ -148,6 +134,7 @@ class ChatAgent(
148
134
  use_local_llm=not (config.use_claude or config.use_chatgpt),
149
135
  use_llm_chunking=config.use_llm_chunking, # Enable semantic chunking
150
136
  base_url=config.base_url, # Pass base_url to RAG for VLM client
137
+ allowed_paths=config.allowed_paths, # Pass allowed paths to RAG SDK
151
138
  )
152
139
  self.rag = RAGSDK(rag_config)
153
140
  except ImportError as e:
@@ -267,10 +254,7 @@ No documents are currently indexed.
267
254
  """
268
255
 
269
256
  # Add indexed documents section
270
- prompt = (
271
- base_prompt
272
- + indexed_docs_section
273
- + """
257
+ prompt = base_prompt + indexed_docs_section + """
274
258
  **WHEN TO USE TOOLS VS DIRECT ANSWERS:**
275
259
 
276
260
  Use Format 1 (answer) for:
@@ -376,7 +360,6 @@ When user asks to "index my data folder" or similar:
376
360
  2. Show user the matches and ask which one (if multiple)
377
361
  3. Use index_directory on the chosen path
378
362
  4. Report indexing results"""
379
- )
380
363
 
381
364
  return prompt
382
365
 
@@ -473,7 +456,7 @@ When user asks to "index my data folder" or similar:
473
456
  def _is_path_allowed(self, path: str) -> bool:
474
457
  """
475
458
  Check if a path is within allowed directories.
476
- Uses real path resolution to prevent TOCTOU attacks.
459
+ Uses PathValidator for the actual check.
477
460
 
478
461
  Args:
479
462
  path: Path to validate
@@ -481,24 +464,7 @@ When user asks to "index my data folder" or similar:
481
464
  Returns:
482
465
  True if path is allowed, False otherwise
483
466
  """
484
- try:
485
- # Resolve path using os.path.realpath to follow symlinks
486
- # This prevents TOCTOU attacks by resolving at check time
487
- real_path = Path(os.path.realpath(path)).resolve()
488
-
489
- # Check if real path is within any allowed directory
490
- for allowed_path in self.allowed_paths:
491
- try:
492
- # is_relative_to requires Python 3.9+, use alternative for compatibility
493
- real_path.relative_to(allowed_path)
494
- return True
495
- except ValueError:
496
- continue
497
-
498
- return False
499
- except Exception as e:
500
- logger.error(f"Error validating path {path}: {e}")
501
- return False
467
+ return self.path_validator.is_path_allowed(path, prompt_user=False)
502
468
 
503
469
  def _validate_and_open_file(self, file_path: str, mode: str = "r"):
504
470
  """
@@ -647,9 +613,9 @@ When user asks to "index my data folder" or similar:
647
613
  logger.error(f"Failed to index {doc}: {e}")
648
614
 
649
615
  # Update system prompt after indexing to include the new documents
650
- self._update_system_prompt()
616
+ self.update_system_prompt()
651
617
 
652
- def _update_system_prompt(self) -> None:
618
+ def update_system_prompt(self) -> None:
653
619
  """Update the system prompt with current indexed documents."""
654
620
  # Regenerate the system prompt with updated document list
655
621
  self.system_prompt = self._get_system_prompt()
@@ -732,18 +698,26 @@ When user asks to "index my data folder" or similar:
732
698
  )
733
699
  return
734
700
 
701
+ # Resolve to real path for consistent validation
702
+ real_file_path = os.path.realpath(file_path)
703
+
704
+ # Security check
705
+ if not self._is_path_allowed(real_file_path):
706
+ logger.warning(f"Re-indexing skipped: Path not allowed {real_file_path}")
707
+ return
708
+
735
709
  try:
736
- logger.info(f"Reindexing: {file_path}")
710
+ logger.info(f"Reindexing: {real_file_path}")
737
711
  # Use the new reindex_document method which removes old chunks first
738
- result = self.rag.reindex_document(file_path)
712
+ result = self.rag.reindex_document(real_file_path)
739
713
  if result.get("success"):
740
714
  self.indexed_files.add(file_path)
741
- logger.info(f"Successfully reindexed {file_path}")
715
+ logger.info(f"Successfully reindexed {real_file_path}")
742
716
  else:
743
717
  error = result.get("error", "Unknown error")
744
- logger.error(f"Failed to reindex {file_path}: {error}")
718
+ logger.error(f"Failed to reindex {real_file_path}: {error}")
745
719
  except Exception as e:
746
- logger.error(f"Failed to reindex {file_path}: {e}")
720
+ logger.error(f"Failed to reindex {real_file_path}: {e}")
747
721
 
748
722
  def stop_watching(self) -> None:
749
723
  """Stop all file system observers."""
gaia/agents/chat/app.py CHANGED
@@ -370,6 +370,10 @@ def interactive_mode(agent: ChatAgent):
370
370
  f"\n📊 Summary: {success_count}/{len(doc_files)} documents indexed successfully"
371
371
  )
372
372
 
373
+ # Update system prompt to include newly indexed documents
374
+ if success_count > 0:
375
+ agent.update_system_prompt()
376
+
373
377
  else:
374
378
  # Single file
375
379
  if not os.path.exists(arg):
@@ -417,6 +421,9 @@ def interactive_mode(agent: ChatAgent):
417
421
  )
418
422
  print(f"Total Chunks: {result.get('total_chunks', 0)}")
419
423
  print("=" * 60)
424
+
425
+ # Update system prompt to include newly indexed document
426
+ agent.update_system_prompt()
420
427
  else:
421
428
  # Display error
422
429
  print("❌ INDEXING FAILED")
@@ -73,6 +73,7 @@ class RAGToolsMixin:
73
73
  from gaia.agents.base.tools import tool
74
74
 
75
75
  @tool(
76
+ atomic=True,
76
77
  name="query_documents",
77
78
  description="Query indexed documents using RAG to find relevant information. Returns document chunks that the agent should use to answer the user's question.",
78
79
  parameters={
@@ -474,6 +475,7 @@ class RAGToolsMixin:
474
475
  }
475
476
 
476
477
  @tool(
478
+ atomic=True,
477
479
  name="query_specific_file",
478
480
  description="Query a SPECIFIC file by name for targeted, fast retrieval. Use when user mentions a specific file or needs information from one document.",
479
481
  parameters={
@@ -865,6 +867,7 @@ class RAGToolsMixin:
865
867
  }
866
868
 
867
869
  @tool(
870
+ atomic=True,
868
871
  name="search_indexed_chunks",
869
872
  description="Search for exact text patterns within RAG-indexed document chunks. Use for finding specific phrases in indexed documents.",
870
873
  parameters={
@@ -1070,12 +1073,20 @@ class RAGToolsMixin:
1070
1073
  if not os.path.exists(file_path):
1071
1074
  return {"status": "error", "error": f"File not found: {file_path}"}
1072
1075
 
1073
- # Validate path with user confirmation
1074
- if not self.session_manager.validate_path(file_path, operation="index"):
1075
- return {"status": "error", "error": f"Access denied: {file_path}"}
1076
+ # Resolve to real path for consistent validation
1077
+ real_file_path = os.path.realpath(file_path)
1078
+
1079
+ # Validate path with ChatAgent's internal logic (which uses allowed_paths)
1080
+ if hasattr(self, "_is_path_allowed"):
1081
+ if not self._is_path_allowed(real_file_path):
1082
+ return {
1083
+ "status": "error",
1084
+ "error": f"Access denied: {real_file_path} is not in allowed paths",
1085
+ }
1076
1086
 
1077
1087
  # Index the document (now returns dict with stats)
1078
- result = self.rag.index_document(file_path)
1088
+ # Use real_file_path to ensure consistency in RAG index
1089
+ result = self.rag.index_document(real_file_path)
1079
1090
 
1080
1091
  if result.get("success"):
1081
1092
  self.indexed_files.add(file_path)
@@ -1087,8 +1098,8 @@ class RAGToolsMixin:
1087
1098
  self.session_manager.save_session(self.current_session)
1088
1099
 
1089
1100
  # Update system prompt to include the new document
1090
- if hasattr(self, "_update_system_prompt"):
1091
- self._update_system_prompt()
1101
+ if hasattr(self, "update_system_prompt"):
1102
+ self.update_system_prompt()
1092
1103
 
1093
1104
  # Return detailed stats from RAG SDK
1094
1105
  return {
@@ -1123,6 +1134,7 @@ class RAGToolsMixin:
1123
1134
  }
1124
1135
 
1125
1136
  @tool(
1137
+ atomic=True,
1126
1138
  name="list_indexed_documents",
1127
1139
  description="List all currently indexed documents",
1128
1140
  parameters={},
@@ -1147,6 +1159,7 @@ class RAGToolsMixin:
1147
1159
  }
1148
1160
 
1149
1161
  @tool(
1162
+ atomic=True,
1150
1163
  name="rag_status",
1151
1164
  description="Get the status of the RAG system",
1152
1165
  parameters={},
@@ -1511,6 +1524,7 @@ Use the {summary_type} style. Ensure page references from section summaries are
1511
1524
  # This provides shared file search functionality across all agents
1512
1525
 
1513
1526
  @tool(
1527
+ atomic=True,
1514
1528
  name="dump_document",
1515
1529
  description="Export the cached extracted text from an indexed document to a markdown file. Useful for reviewing extracted content or debugging.",
1516
1530
  parameters={
@@ -1620,6 +1634,7 @@ Use the {summary_type} style. Ensure page references from section summaries are
1620
1634
  }
1621
1635
 
1622
1636
  @tool(
1637
+ atomic=True,
1623
1638
  name="index_directory",
1624
1639
  description="Index all supported files in a directory. Supports PDF, TXT, CSV, JSON, and code files.",
1625
1640
  parameters={
@@ -1704,8 +1719,8 @@ Use the {summary_type} style. Ensure page references from section summaries are
1704
1719
  skipped_files.append(str(file_path))
1705
1720
 
1706
1721
  # Update system prompt after indexing directory
1707
- if indexed_files and hasattr(self, "_update_system_prompt"):
1708
- self._update_system_prompt()
1722
+ if indexed_files and hasattr(self, "update_system_prompt"):
1723
+ self.update_system_prompt()
1709
1724
 
1710
1725
  return {
1711
1726
  "status": "success",
@@ -102,6 +102,7 @@ class ShellToolsMixin:
102
102
  from gaia.agents.base.tools import tool
103
103
 
104
104
  @tool(
105
+ atomic=True,
105
106
  name="run_shell_command",
106
107
  description="Execute a shell/terminal command. Useful for listing directories (ls/dir), checking files (cat, stat), finding files (find), text processing (grep, head, tail), and navigation (pwd).",
107
108
  parameters={
@@ -1278,8 +1278,7 @@ def generate_field_display(fields: dict, max_fields: int = 3) -> str:
1278
1278
  # Generate checkbox + title combo for boolean fields (e.g., completed todo)
1279
1279
  if boolean_field and title_field:
1280
1280
  # Render checkbox with title that has strikethrough when boolean is true
1281
- display_fields.append(
1282
- f"""<div className="flex items-center gap-4">
1281
+ display_fields.append(f"""<div className="flex items-center gap-4">
1283
1282
  <div className="relative">
1284
1283
  <input
1285
1284
  type="checkbox"
@@ -1296,8 +1295,7 @@ def generate_field_display(fields: dict, max_fields: int = 3) -> str:
1296
1295
  <h3 className={{`font-semibold text-lg ${{item.{boolean_field} ? "line-through text-slate-500" : "text-slate-100"}}`}}>
1297
1296
  {{item.{title_field}}}
1298
1297
  </h3>
1299
- </div>"""
1300
- )
1298
+ </div>""")
1301
1299
  elif title_field:
1302
1300
  # Just render title without checkbox
1303
1301
  display_fields.append(
@@ -7,6 +7,7 @@ Docker Agent for GAIA.
7
7
  This agent provides an intelligent interface for containerizing applications,
8
8
  generating Dockerfiles, and managing Docker containers through natural language commands.
9
9
  """
10
+
10
11
  import json
11
12
  import logging
12
13
  import subprocess
gaia/agents/emr/agent.py CHANGED
@@ -155,8 +155,7 @@ class MedicalIntakeAgent(Agent, DatabaseMixin, FileWatcherMixin):
155
155
  """
156
156
  try:
157
157
  # Get aggregate stats from patients table
158
- result = self.query(
159
- """
158
+ result = self.query("""
160
159
  SELECT
161
160
  COUNT(*) as total_patients,
162
161
  COALESCE(SUM(processing_time_seconds), 0) as total_processing_time,
@@ -164,8 +163,7 @@ class MedicalIntakeAgent(Agent, DatabaseMixin, FileWatcherMixin):
164
163
  SUM(CASE WHEN is_new_patient = 1 THEN 1 ELSE 0 END) as new_patients,
165
164
  SUM(CASE WHEN is_new_patient = 0 THEN 1 ELSE 0 END) as returning_patients
166
165
  FROM patients
167
- """
168
- )
166
+ """)
169
167
 
170
168
  if result and result[0]:
171
169
  stats = result[0]
@@ -327,7 +325,7 @@ class MedicalIntakeAgent(Agent, DatabaseMixin, FileWatcherMixin):
327
325
  """Get or create VLM client (lazy initialization)."""
328
326
  if self._vlm is None:
329
327
  try:
330
- from gaia.llm.vlm_client import VLMClient
328
+ from gaia.llm import VLMClient
331
329
 
332
330
  self.console.print_model_loading(self._vlm_model)
333
331
  self._vlm = VLMClient(vlm_model=self._vlm_model)
gaia/agents/emr/cli.py CHANGED
@@ -636,7 +636,7 @@ def cmd_test(args):
636
636
 
637
637
  from PIL import Image
638
638
 
639
- from gaia.llm.vlm_client import VLMClient
639
+ from gaia.llm import VLMClient
640
640
 
641
641
  file_path = Path(args.file)
642
642
  if not file_path.exists():
@@ -595,8 +595,7 @@ def create_app(
595
595
  """
596
596
  if search:
597
597
  results = _agent_instance.query(
598
- base_query
599
- + """
598
+ base_query + """
600
599
  WHERE p.first_name LIKE :search OR p.last_name LIKE :search
601
600
  ORDER BY p.created_at DESC
602
601
  LIMIT :limit OFFSET :offset
@@ -605,8 +604,7 @@ def create_app(
605
604
  )
606
605
  else:
607
606
  results = _agent_instance.query(
608
- base_query
609
- + """
607
+ base_query + """
610
608
  ORDER BY p.created_at DESC
611
609
  LIMIT :limit OFFSET :offset
612
610
  """,
gaia/apps/llm/app.py CHANGED
@@ -82,6 +82,10 @@ def main(
82
82
  stream: Whether to stream the response
83
83
  base_url: Base URL for local LLM server (defaults to LEMONADE_BASE_URL env var)
84
84
  """
85
+ from rich.console import Console
86
+
87
+ console = Console()
88
+
85
89
  if not query:
86
90
  raise ValueError("Query is required")
87
91
 
@@ -91,14 +95,21 @@ def main(
91
95
  )
92
96
 
93
97
  if stream:
94
- # Handle streaming response
98
+ # Handle streaming response with Rich formatting
99
+ console.print()
100
+ console.print("[bold cyan]🤖 gaia:[/bold cyan] ", end="")
95
101
  full_response = ""
96
102
  for chunk in response:
97
- print(chunk, end="", flush=True)
98
- full_response += chunk
103
+ if chunk: # Skip None chunks
104
+ print(chunk, end="", flush=True)
105
+ full_response += chunk
99
106
  print() # Add newline
107
+ console.print()
100
108
  return full_response
101
109
  else:
110
+ console.print()
111
+ console.print(f"[bold cyan]🤖 gaia:[/bold cyan] {response}")
112
+ console.print()
102
113
  return response
103
114
 
104
115
 
gaia/chat/app.py CHANGED
@@ -212,8 +212,7 @@ def print_integration_examples():
212
212
  print("INTEGRATION EXAMPLES")
213
213
  print("=" * 60)
214
214
 
215
- print(
216
- """
215
+ print("""
217
216
  Basic Integration:
218
217
  ```python
219
218
  from gaia.chat.sdk import ChatSDK, ChatConfig
@@ -297,8 +296,7 @@ responses = quick_chat_with_memory([
297
296
  "What's my name?"
298
297
  ], assistant_name="Gaia")
299
298
  ```
300
- """
301
- )
299
+ """)
302
300
 
303
301
 
304
302
  async def main():