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.
- {amd_gaia-0.15.1.dist-info → amd_gaia-0.15.2.dist-info}/METADATA +1 -2
- {amd_gaia-0.15.1.dist-info → amd_gaia-0.15.2.dist-info}/RECORD +35 -31
- {amd_gaia-0.15.1.dist-info → amd_gaia-0.15.2.dist-info}/WHEEL +1 -1
- gaia/agents/base/agent.py +45 -90
- gaia/agents/base/api_agent.py +0 -1
- gaia/agents/base/console.py +126 -0
- gaia/agents/base/tools.py +7 -2
- gaia/agents/blender/__init__.py +7 -0
- gaia/agents/blender/agent.py +7 -10
- gaia/agents/blender/core/view.py +2 -2
- gaia/agents/chat/agent.py +22 -48
- gaia/agents/chat/app.py +7 -0
- gaia/agents/chat/tools/rag_tools.py +23 -8
- gaia/agents/chat/tools/shell_tools.py +1 -0
- gaia/agents/code/prompts/code_patterns.py +2 -4
- gaia/agents/docker/agent.py +1 -0
- gaia/agents/emr/agent.py +3 -5
- gaia/agents/emr/cli.py +1 -1
- gaia/agents/emr/dashboard/server.py +2 -4
- gaia/apps/llm/app.py +14 -3
- gaia/chat/app.py +2 -4
- gaia/cli.py +511 -333
- gaia/installer/__init__.py +23 -0
- gaia/installer/init_command.py +1275 -0
- gaia/installer/lemonade_installer.py +619 -0
- gaia/llm/__init__.py +2 -1
- gaia/llm/lemonade_client.py +284 -99
- gaia/llm/providers/lemonade.py +12 -14
- gaia/rag/sdk.py +1 -1
- gaia/security.py +24 -4
- gaia/talk/app.py +2 -4
- gaia/version.py +2 -2
- {amd_gaia-0.15.1.dist-info → amd_gaia-0.15.2.dist-info}/entry_points.txt +0 -0
- {amd_gaia-0.15.1.dist-info → amd_gaia-0.15.2.dist-info}/licenses/LICENSE.md +0 -0
- {amd_gaia-0.15.1.dist-info → amd_gaia-0.15.2.dist-info}/top_level.txt +0 -0
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
|
|
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
|
-
|
|
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.
|
|
616
|
+
self.update_system_prompt()
|
|
651
617
|
|
|
652
|
-
def
|
|
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: {
|
|
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(
|
|
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 {
|
|
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 {
|
|
718
|
+
logger.error(f"Failed to reindex {real_file_path}: {error}")
|
|
745
719
|
except Exception as e:
|
|
746
|
-
logger.error(f"Failed to reindex {
|
|
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
|
-
#
|
|
1074
|
-
|
|
1075
|
-
|
|
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
|
-
|
|
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, "
|
|
1091
|
-
self.
|
|
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, "
|
|
1708
|
-
self.
|
|
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(
|
gaia/agents/docker/agent.py
CHANGED
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
|
|
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
|
@@ -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
|
-
|
|
98
|
-
|
|
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():
|