amd-gaia 0.15.0__py3-none-any.whl → 0.15.1__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.
Files changed (181) hide show
  1. {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.1.dist-info}/METADATA +223 -223
  2. amd_gaia-0.15.1.dist-info/RECORD +178 -0
  3. {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.1.dist-info}/entry_points.txt +1 -0
  4. {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.1.dist-info}/licenses/LICENSE.md +20 -20
  5. gaia/__init__.py +29 -29
  6. gaia/agents/__init__.py +19 -19
  7. gaia/agents/base/__init__.py +9 -9
  8. gaia/agents/base/agent.py +2177 -2177
  9. gaia/agents/base/api_agent.py +120 -120
  10. gaia/agents/base/console.py +1841 -1841
  11. gaia/agents/base/errors.py +237 -237
  12. gaia/agents/base/mcp_agent.py +86 -86
  13. gaia/agents/base/tools.py +83 -83
  14. gaia/agents/blender/agent.py +556 -556
  15. gaia/agents/blender/agent_simple.py +133 -135
  16. gaia/agents/blender/app.py +211 -211
  17. gaia/agents/blender/app_simple.py +41 -41
  18. gaia/agents/blender/core/__init__.py +16 -16
  19. gaia/agents/blender/core/materials.py +506 -506
  20. gaia/agents/blender/core/objects.py +316 -316
  21. gaia/agents/blender/core/rendering.py +225 -225
  22. gaia/agents/blender/core/scene.py +220 -220
  23. gaia/agents/blender/core/view.py +146 -146
  24. gaia/agents/chat/__init__.py +9 -9
  25. gaia/agents/chat/agent.py +835 -835
  26. gaia/agents/chat/app.py +1058 -1058
  27. gaia/agents/chat/session.py +508 -508
  28. gaia/agents/chat/tools/__init__.py +15 -15
  29. gaia/agents/chat/tools/file_tools.py +96 -96
  30. gaia/agents/chat/tools/rag_tools.py +1729 -1729
  31. gaia/agents/chat/tools/shell_tools.py +436 -436
  32. gaia/agents/code/__init__.py +7 -7
  33. gaia/agents/code/agent.py +549 -549
  34. gaia/agents/code/cli.py +377 -0
  35. gaia/agents/code/models.py +135 -135
  36. gaia/agents/code/orchestration/__init__.py +24 -24
  37. gaia/agents/code/orchestration/checklist_executor.py +1763 -1763
  38. gaia/agents/code/orchestration/checklist_generator.py +713 -713
  39. gaia/agents/code/orchestration/factories/__init__.py +9 -9
  40. gaia/agents/code/orchestration/factories/base.py +63 -63
  41. gaia/agents/code/orchestration/factories/nextjs_factory.py +118 -118
  42. gaia/agents/code/orchestration/factories/python_factory.py +106 -106
  43. gaia/agents/code/orchestration/orchestrator.py +841 -841
  44. gaia/agents/code/orchestration/project_analyzer.py +391 -391
  45. gaia/agents/code/orchestration/steps/__init__.py +67 -67
  46. gaia/agents/code/orchestration/steps/base.py +188 -188
  47. gaia/agents/code/orchestration/steps/error_handler.py +314 -314
  48. gaia/agents/code/orchestration/steps/nextjs.py +828 -828
  49. gaia/agents/code/orchestration/steps/python.py +307 -307
  50. gaia/agents/code/orchestration/template_catalog.py +469 -469
  51. gaia/agents/code/orchestration/workflows/__init__.py +14 -14
  52. gaia/agents/code/orchestration/workflows/base.py +80 -80
  53. gaia/agents/code/orchestration/workflows/nextjs.py +186 -186
  54. gaia/agents/code/orchestration/workflows/python.py +94 -94
  55. gaia/agents/code/prompts/__init__.py +11 -11
  56. gaia/agents/code/prompts/base_prompt.py +77 -77
  57. gaia/agents/code/prompts/code_patterns.py +2036 -2036
  58. gaia/agents/code/prompts/nextjs_prompt.py +40 -40
  59. gaia/agents/code/prompts/python_prompt.py +109 -109
  60. gaia/agents/code/schema_inference.py +365 -365
  61. gaia/agents/code/system_prompt.py +41 -41
  62. gaia/agents/code/tools/__init__.py +42 -42
  63. gaia/agents/code/tools/cli_tools.py +1138 -1138
  64. gaia/agents/code/tools/code_formatting.py +319 -319
  65. gaia/agents/code/tools/code_tools.py +769 -769
  66. gaia/agents/code/tools/error_fixing.py +1347 -1347
  67. gaia/agents/code/tools/external_tools.py +180 -180
  68. gaia/agents/code/tools/file_io.py +845 -845
  69. gaia/agents/code/tools/prisma_tools.py +190 -190
  70. gaia/agents/code/tools/project_management.py +1016 -1016
  71. gaia/agents/code/tools/testing.py +321 -321
  72. gaia/agents/code/tools/typescript_tools.py +122 -122
  73. gaia/agents/code/tools/validation_parsing.py +461 -461
  74. gaia/agents/code/tools/validation_tools.py +806 -806
  75. gaia/agents/code/tools/web_dev_tools.py +1758 -1758
  76. gaia/agents/code/validators/__init__.py +16 -16
  77. gaia/agents/code/validators/antipattern_checker.py +241 -241
  78. gaia/agents/code/validators/ast_analyzer.py +197 -197
  79. gaia/agents/code/validators/requirements_validator.py +145 -145
  80. gaia/agents/code/validators/syntax_validator.py +171 -171
  81. gaia/agents/docker/__init__.py +7 -7
  82. gaia/agents/docker/agent.py +642 -642
  83. gaia/agents/emr/__init__.py +8 -8
  84. gaia/agents/emr/agent.py +1506 -1506
  85. gaia/agents/emr/cli.py +1322 -1322
  86. gaia/agents/emr/constants.py +475 -475
  87. gaia/agents/emr/dashboard/__init__.py +4 -4
  88. gaia/agents/emr/dashboard/server.py +1974 -1974
  89. gaia/agents/jira/__init__.py +11 -11
  90. gaia/agents/jira/agent.py +894 -894
  91. gaia/agents/jira/jql_templates.py +299 -299
  92. gaia/agents/routing/__init__.py +7 -7
  93. gaia/agents/routing/agent.py +567 -570
  94. gaia/agents/routing/system_prompt.py +75 -75
  95. gaia/agents/summarize/__init__.py +11 -0
  96. gaia/agents/summarize/agent.py +885 -0
  97. gaia/agents/summarize/prompts.py +129 -0
  98. gaia/api/__init__.py +23 -23
  99. gaia/api/agent_registry.py +238 -238
  100. gaia/api/app.py +305 -305
  101. gaia/api/openai_server.py +575 -575
  102. gaia/api/schemas.py +186 -186
  103. gaia/api/sse_handler.py +373 -373
  104. gaia/apps/__init__.py +4 -4
  105. gaia/apps/llm/__init__.py +6 -6
  106. gaia/apps/llm/app.py +173 -169
  107. gaia/apps/summarize/app.py +116 -633
  108. gaia/apps/summarize/html_viewer.py +133 -133
  109. gaia/apps/summarize/pdf_formatter.py +284 -284
  110. gaia/audio/__init__.py +2 -2
  111. gaia/audio/audio_client.py +439 -439
  112. gaia/audio/audio_recorder.py +269 -269
  113. gaia/audio/kokoro_tts.py +599 -599
  114. gaia/audio/whisper_asr.py +432 -432
  115. gaia/chat/__init__.py +16 -16
  116. gaia/chat/app.py +430 -430
  117. gaia/chat/prompts.py +522 -522
  118. gaia/chat/sdk.py +1228 -1225
  119. gaia/cli.py +5481 -5632
  120. gaia/database/__init__.py +10 -10
  121. gaia/database/agent.py +176 -176
  122. gaia/database/mixin.py +290 -290
  123. gaia/database/testing.py +64 -64
  124. gaia/eval/batch_experiment.py +2332 -2332
  125. gaia/eval/claude.py +542 -542
  126. gaia/eval/config.py +37 -37
  127. gaia/eval/email_generator.py +512 -512
  128. gaia/eval/eval.py +3179 -3179
  129. gaia/eval/groundtruth.py +1130 -1130
  130. gaia/eval/transcript_generator.py +582 -582
  131. gaia/eval/webapp/README.md +167 -167
  132. gaia/eval/webapp/package-lock.json +875 -875
  133. gaia/eval/webapp/package.json +20 -20
  134. gaia/eval/webapp/public/app.js +3402 -3402
  135. gaia/eval/webapp/public/index.html +87 -87
  136. gaia/eval/webapp/public/styles.css +3661 -3661
  137. gaia/eval/webapp/server.js +415 -415
  138. gaia/eval/webapp/test-setup.js +72 -72
  139. gaia/llm/__init__.py +9 -2
  140. gaia/llm/base_client.py +60 -0
  141. gaia/llm/exceptions.py +12 -0
  142. gaia/llm/factory.py +70 -0
  143. gaia/llm/lemonade_client.py +3236 -3221
  144. gaia/llm/lemonade_manager.py +294 -294
  145. gaia/llm/providers/__init__.py +9 -0
  146. gaia/llm/providers/claude.py +108 -0
  147. gaia/llm/providers/lemonade.py +120 -0
  148. gaia/llm/providers/openai_provider.py +79 -0
  149. gaia/llm/vlm_client.py +382 -382
  150. gaia/logger.py +189 -189
  151. gaia/mcp/agent_mcp_server.py +245 -245
  152. gaia/mcp/blender_mcp_client.py +138 -138
  153. gaia/mcp/blender_mcp_server.py +648 -648
  154. gaia/mcp/context7_cache.py +332 -332
  155. gaia/mcp/external_services.py +518 -518
  156. gaia/mcp/mcp_bridge.py +811 -550
  157. gaia/mcp/servers/__init__.py +6 -6
  158. gaia/mcp/servers/docker_mcp.py +83 -83
  159. gaia/perf_analysis.py +361 -0
  160. gaia/rag/__init__.py +10 -10
  161. gaia/rag/app.py +293 -293
  162. gaia/rag/demo.py +304 -304
  163. gaia/rag/pdf_utils.py +235 -235
  164. gaia/rag/sdk.py +2194 -2194
  165. gaia/security.py +163 -163
  166. gaia/talk/app.py +289 -289
  167. gaia/talk/sdk.py +538 -538
  168. gaia/testing/__init__.py +87 -87
  169. gaia/testing/assertions.py +330 -330
  170. gaia/testing/fixtures.py +333 -333
  171. gaia/testing/mocks.py +493 -493
  172. gaia/util.py +46 -46
  173. gaia/utils/__init__.py +33 -33
  174. gaia/utils/file_watcher.py +675 -675
  175. gaia/utils/parsing.py +223 -223
  176. gaia/version.py +100 -100
  177. amd_gaia-0.15.0.dist-info/RECORD +0 -168
  178. gaia/agents/code/app.py +0 -266
  179. gaia/llm/llm_client.py +0 -723
  180. {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.1.dist-info}/WHEEL +0 -0
  181. {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.1.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
- # Copyright(C) 2025-2026 Advanced Micro Devices, Inc. All rights reserved.
2
- # SPDX-License-Identifier: MIT
3
-
4
- """
5
- MCP Server Launchers for GAIA Agents
6
- """
1
+ # Copyright(C) 2025-2026 Advanced Micro Devices, Inc. All rights reserved.
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ """
5
+ MCP Server Launchers for GAIA Agents
6
+ """
@@ -1,83 +1,83 @@
1
- # Copyright(C) 2025-2026 Advanced Micro Devices, Inc. All rights reserved.
2
- # SPDX-License-Identifier: MIT
3
-
4
- """
5
- Docker MCP Server Launcher
6
- Starts an MCP server for the Docker agent
7
- """
8
-
9
- from gaia.agents.docker.agent import DockerAgent
10
- from gaia.mcp.agent_mcp_server import MCP_DEFAULT_HOST, MCP_DEFAULT_PORT, AgentMCPServer
11
-
12
-
13
- def start_docker_mcp(
14
- port: int = None,
15
- host: str = None,
16
- verbose: bool = False,
17
- model_id: str = None,
18
- silent_mode: bool = True,
19
- ):
20
- """
21
- Start the Docker MCP server.
22
-
23
- Args:
24
- port: Port to listen on (default: 8080)
25
- host: Host to bind to (default: localhost)
26
- verbose: Enable verbose logging
27
- model_id: LLM model ID to use
28
- silent_mode: Suppress agent console output (default: True for MCP)
29
- """
30
- # Prepare agent parameters
31
- agent_params = {
32
- "silent_mode": silent_mode,
33
- }
34
-
35
- if model_id:
36
- agent_params["model_id"] = model_id
37
-
38
- # Create and start MCP server
39
- server = AgentMCPServer(
40
- agent_class=DockerAgent,
41
- name="GAIA Docker MCP",
42
- port=port or MCP_DEFAULT_PORT,
43
- host=host or MCP_DEFAULT_HOST,
44
- verbose=verbose,
45
- agent_params=agent_params,
46
- )
47
-
48
- server.start()
49
-
50
-
51
- if __name__ == "__main__":
52
- import argparse
53
-
54
- parser = argparse.ArgumentParser(description="GAIA Docker MCP Server")
55
- parser.add_argument(
56
- "--port",
57
- type=int,
58
- default=MCP_DEFAULT_PORT,
59
- help=f"Port to listen on (default: {MCP_DEFAULT_PORT})",
60
- )
61
- parser.add_argument(
62
- "--host",
63
- default=MCP_DEFAULT_HOST,
64
- help=f"Host to bind to (default: {MCP_DEFAULT_HOST})",
65
- )
66
- parser.add_argument(
67
- "--verbose",
68
- action="store_true",
69
- help="Enable verbose logging",
70
- )
71
- parser.add_argument(
72
- "--model-id",
73
- help="LLM model ID to use (default: Qwen3-Coder-30B-A3B-Instruct-GGUF)",
74
- )
75
-
76
- args = parser.parse_args()
77
-
78
- start_docker_mcp(
79
- port=args.port,
80
- host=args.host,
81
- verbose=args.verbose,
82
- model_id=args.model_id,
83
- )
1
+ # Copyright(C) 2025-2026 Advanced Micro Devices, Inc. All rights reserved.
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ """
5
+ Docker MCP Server Launcher
6
+ Starts an MCP server for the Docker agent
7
+ """
8
+
9
+ from gaia.agents.docker.agent import DockerAgent
10
+ from gaia.mcp.agent_mcp_server import MCP_DEFAULT_HOST, MCP_DEFAULT_PORT, AgentMCPServer
11
+
12
+
13
+ def start_docker_mcp(
14
+ port: int = None,
15
+ host: str = None,
16
+ verbose: bool = False,
17
+ model_id: str = None,
18
+ silent_mode: bool = True,
19
+ ):
20
+ """
21
+ Start the Docker MCP server.
22
+
23
+ Args:
24
+ port: Port to listen on (default: 8080)
25
+ host: Host to bind to (default: localhost)
26
+ verbose: Enable verbose logging
27
+ model_id: LLM model ID to use
28
+ silent_mode: Suppress agent console output (default: True for MCP)
29
+ """
30
+ # Prepare agent parameters
31
+ agent_params = {
32
+ "silent_mode": silent_mode,
33
+ }
34
+
35
+ if model_id:
36
+ agent_params["model_id"] = model_id
37
+
38
+ # Create and start MCP server
39
+ server = AgentMCPServer(
40
+ agent_class=DockerAgent,
41
+ name="GAIA Docker MCP",
42
+ port=port or MCP_DEFAULT_PORT,
43
+ host=host or MCP_DEFAULT_HOST,
44
+ verbose=verbose,
45
+ agent_params=agent_params,
46
+ )
47
+
48
+ server.start()
49
+
50
+
51
+ if __name__ == "__main__":
52
+ import argparse
53
+
54
+ parser = argparse.ArgumentParser(description="GAIA Docker MCP Server")
55
+ parser.add_argument(
56
+ "--port",
57
+ type=int,
58
+ default=MCP_DEFAULT_PORT,
59
+ help=f"Port to listen on (default: {MCP_DEFAULT_PORT})",
60
+ )
61
+ parser.add_argument(
62
+ "--host",
63
+ default=MCP_DEFAULT_HOST,
64
+ help=f"Host to bind to (default: {MCP_DEFAULT_HOST})",
65
+ )
66
+ parser.add_argument(
67
+ "--verbose",
68
+ action="store_true",
69
+ help="Enable verbose logging",
70
+ )
71
+ parser.add_argument(
72
+ "--model-id",
73
+ help="LLM model ID to use (default: Qwen3-Coder-30B-A3B-Instruct-GGUF)",
74
+ )
75
+
76
+ args = parser.parse_args()
77
+
78
+ start_docker_mcp(
79
+ port=args.port,
80
+ host=args.host,
81
+ verbose=args.verbose,
82
+ model_id=args.model_id,
83
+ )
gaia/perf_analysis.py ADDED
@@ -0,0 +1,361 @@
1
+ # Copyright(C) 2025-2026 Advanced Micro Devices, Inc. All rights reserved.
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import math
8
+ import re
9
+ import sys
10
+ from dataclasses import dataclass
11
+ from enum import Enum
12
+ from pathlib import Path
13
+ from typing import Any, Callable, Dict, Iterable, List, Sequence, Tuple
14
+
15
+ try:
16
+ import matplotlib.pyplot as plt
17
+ except ImportError: # pragma: no cover - handled at runtime
18
+ plt = None # type: ignore[assignment]
19
+
20
+ PROMPT_MARKER = "new prompt"
21
+ TOKEN_PATTERN = re.compile(r"n_prompt_tokens\s*=\s*(\d+)")
22
+ INPUT_PATTERN = re.compile(r"Input tokens:\s*(\d+)", re.IGNORECASE)
23
+ OUTPUT_PATTERN = re.compile(r"Output tokens:\s*(\d+)", re.IGNORECASE)
24
+ TTFT_PATTERN = re.compile(r"TTFT\s*\(s\):\s*([0-9]*\.?[0-9]+)", re.IGNORECASE)
25
+ TPS_PATTERN = re.compile(r"TPS:\s*([0-9]*\.?[0-9]+)", re.IGNORECASE)
26
+
27
+
28
+ def _require_matplotlib() -> Any:
29
+ if plt is None:
30
+ raise RuntimeError(
31
+ "matplotlib is required for perf-vis. "
32
+ "Install with `pip install matplotlib` or `pip install -e .[dev]`."
33
+ )
34
+ return plt
35
+
36
+
37
+ class Metric(str, Enum):
38
+ PROMPT_TOKENS = "prompt_tokens"
39
+ INPUT_TOKENS = "input_tokens"
40
+ OUTPUT_TOKENS = "output_tokens"
41
+ TTFT = "ttft"
42
+ TPS = "tps"
43
+
44
+
45
+ @dataclass(frozen=True)
46
+ class MetricConfig:
47
+ title: str
48
+ y_label: str
49
+ filename: Path
50
+
51
+
52
+ METRIC_CONFIGS: Dict[Metric, MetricConfig] = {
53
+ Metric.PROMPT_TOKENS: MetricConfig(
54
+ title="Prompt token counts",
55
+ y_label="Prompt token count",
56
+ filename=Path("prompt_token_counts.png"),
57
+ ),
58
+ Metric.INPUT_TOKENS: MetricConfig(
59
+ title="Input token counts",
60
+ y_label="Input token count",
61
+ filename=Path("input_token_counts.png"),
62
+ ),
63
+ Metric.OUTPUT_TOKENS: MetricConfig(
64
+ title="Output token counts",
65
+ y_label="Output token count",
66
+ filename=Path("output_token_counts.png"),
67
+ ),
68
+ Metric.TTFT: MetricConfig(
69
+ title="Time to first token (s)",
70
+ y_label="Seconds to first token",
71
+ filename=Path("ttft_seconds.png"),
72
+ ),
73
+ Metric.TPS: MetricConfig(
74
+ title="Tokens per second",
75
+ y_label="Tokens per second",
76
+ filename=Path("tps.png"),
77
+ ),
78
+ }
79
+
80
+
81
+ def extract_metrics(lines: Iterable[str]) -> Dict[Metric, List[float]]:
82
+ """Collect telemetry values by metric."""
83
+ values: Dict[Metric, List[float]] = {metric: [] for metric in Metric}
84
+ all_lines = list(lines)
85
+ awaiting_prompt_token = False
86
+
87
+ for raw_line in all_lines:
88
+ line = raw_line.strip()
89
+ lower_line = line.lower()
90
+
91
+ if not awaiting_prompt_token and PROMPT_MARKER in lower_line:
92
+ awaiting_prompt_token = True
93
+
94
+ input_match = INPUT_PATTERN.search(line)
95
+ if input_match:
96
+ values[Metric.INPUT_TOKENS].append(float(input_match.group(1)))
97
+
98
+ if awaiting_prompt_token:
99
+ prompt_match = TOKEN_PATTERN.search(line)
100
+ if prompt_match:
101
+ values[Metric.PROMPT_TOKENS].append(float(prompt_match.group(1)))
102
+ awaiting_prompt_token = False
103
+
104
+ output_match = OUTPUT_PATTERN.search(line)
105
+ if output_match:
106
+ values[Metric.OUTPUT_TOKENS].append(float(output_match.group(1)))
107
+
108
+ ttft_match = TTFT_PATTERN.search(line)
109
+ if ttft_match:
110
+ values[Metric.TTFT].append(float(ttft_match.group(1)))
111
+
112
+ tps_match = TPS_PATTERN.search(line)
113
+ if tps_match:
114
+ values[Metric.TPS].append(float(tps_match.group(1)))
115
+
116
+ return values
117
+
118
+
119
+ def build_plot(
120
+ series: Sequence[Tuple[str, List[float]]],
121
+ metric_config: MetricConfig,
122
+ output_path: Path,
123
+ show: bool,
124
+ ) -> None:
125
+ """Create the plot and either save it, display it, or both."""
126
+ plt_mod = _require_matplotlib()
127
+ fig, ax = plt_mod.subplots()
128
+
129
+ for log_name, metric_values in series:
130
+ x_values = range(1, len(metric_values) + 1)
131
+ ax.plot(x_values, metric_values, marker="o", linestyle="-", label=log_name)
132
+
133
+ ax.set_xlabel("LLM call/inference count")
134
+ ax.set_ylabel(metric_config.y_label)
135
+ title = metric_config.title
136
+ if len(series) == 1:
137
+ title = f"{title} - {series[0][0]}"
138
+ ax.set_title(title)
139
+ ax.grid(True, linestyle="--", alpha=0.4)
140
+ if len(series) > 1:
141
+ ax.legend(title="Log file")
142
+ fig.tight_layout()
143
+
144
+ output_path.parent.mkdir(parents=True, exist_ok=True)
145
+ fig.savefig(output_path)
146
+
147
+ if show:
148
+ plt_mod.show()
149
+ else:
150
+ plt_mod.close(fig)
151
+
152
+
153
+ def build_prefill_decode_pies(
154
+ prefill_decode_times: Sequence[Tuple[str, float, float]],
155
+ output_path: Path,
156
+ show: bool,
157
+ ) -> None:
158
+ """Plot per-log prefill vs decode time split as multiple pies."""
159
+ plt_mod = _require_matplotlib()
160
+
161
+ if not prefill_decode_times:
162
+ print(
163
+ "No timing data available to build prefill/decode split.", file=sys.stderr
164
+ )
165
+ return
166
+
167
+ slice_colors = ["#4c78a8", "#f58518"] # Prefill, decode
168
+ pie_outline_colors = plt_mod.get_cmap("tab10").colors
169
+ cols = min(len(prefill_decode_times), 3)
170
+ rows = math.ceil(len(prefill_decode_times) / cols)
171
+
172
+ def autopct_with_seconds(values: Sequence[float]) -> Callable[[float], str]:
173
+ def format_pct(pct: float) -> str:
174
+ seconds = pct / 100 * sum(values)
175
+ return f"{pct:.1f}%\n{seconds:.2f}s"
176
+
177
+ return format_pct
178
+
179
+ fig, axes = plt_mod.subplots(rows, cols, figsize=(4 * cols, 4 * rows))
180
+ axes_flat: List[plt_mod.Axes] = (
181
+ [axes]
182
+ if isinstance(axes, plt_mod.Axes)
183
+ else list(axes.ravel() if hasattr(axes, "ravel") else axes)
184
+ )
185
+
186
+ legend_handles = []
187
+
188
+ for idx, (log_name, prefill_total, decode_total) in enumerate(prefill_decode_times):
189
+ total_time = prefill_total + decode_total
190
+ if total_time <= 0:
191
+ axes_flat[idx].axis("off")
192
+ continue
193
+
194
+ ax = axes_flat[idx]
195
+ sizes = [prefill_total, decode_total]
196
+ outline_color = pie_outline_colors[idx % len(pie_outline_colors)]
197
+
198
+ ax.pie(
199
+ sizes,
200
+ labels=["Prefill", "Decode"],
201
+ colors=slice_colors,
202
+ autopct=autopct_with_seconds(sizes),
203
+ startangle=90,
204
+ wedgeprops={"edgecolor": outline_color, "linewidth": 2},
205
+ )
206
+ ax.axis("equal") # Equal aspect ratio for a true circle.
207
+ ax.set_title(log_name)
208
+ ax.text(
209
+ 0,
210
+ 0,
211
+ f"Total\n{total_time:.2f}s",
212
+ ha="center",
213
+ va="center",
214
+ fontsize=10,
215
+ weight="bold",
216
+ )
217
+
218
+ legend_handles.append(
219
+ plt_mod.Line2D(
220
+ [0],
221
+ [0],
222
+ marker="o",
223
+ color="w",
224
+ markerfacecolor="w",
225
+ markeredgecolor=outline_color,
226
+ markeredgewidth=2,
227
+ markersize=10,
228
+ label=log_name,
229
+ )
230
+ )
231
+
232
+ for ax in axes_flat[len(prefill_decode_times) :]:
233
+ ax.axis("off")
234
+
235
+ fig.legend(
236
+ handles=legend_handles,
237
+ loc="upper center",
238
+ ncol=min(len(legend_handles), cols),
239
+ title="Log file",
240
+ )
241
+ fig.suptitle("Prefill vs decode time split", y=0.98)
242
+ fig.tight_layout(rect=(0, 0, 1, 0.92))
243
+
244
+ output_path.parent.mkdir(parents=True, exist_ok=True)
245
+ fig.savefig(output_path)
246
+
247
+ if show:
248
+ plt_mod.show()
249
+ else:
250
+ plt_mod.close(fig)
251
+
252
+
253
+ def parse_cli(argv: Sequence[str] | None = None) -> argparse.Namespace:
254
+ parser = argparse.ArgumentParser(
255
+ description=(
256
+ "Plot telemetry values (prompt/input/output tokens, TTFT, TPS) "
257
+ "recorded in one or more llama.cpp server logs."
258
+ )
259
+ )
260
+ parser.add_argument(
261
+ "log_paths",
262
+ type=Path,
263
+ nargs="+",
264
+ help="One or more paths to llama.cpp server log files.",
265
+ )
266
+ parser.add_argument(
267
+ "--show",
268
+ action="store_true",
269
+ help="Display the plot window in addition to saving the image.",
270
+ )
271
+ return parser.parse_args(argv)
272
+
273
+
274
+ def run_perf_visualization(log_paths: Sequence[Path], show: bool = False) -> int:
275
+ """Process log files and generate performance plots."""
276
+ try:
277
+ _ = _require_matplotlib()
278
+ except RuntimeError as exc:
279
+ print(f"error: {exc}", file=sys.stderr)
280
+ return 1
281
+
282
+ metrics = list(Metric)
283
+ series_by_metric: Dict[Metric, List[Tuple[str, List[float]]]] = {
284
+ metric: [] for metric in metrics
285
+ }
286
+ prefill_decode_times: List[Tuple[str, float, float]] = []
287
+
288
+ for log_path in log_paths:
289
+ if not log_path.is_file():
290
+ print(f"error: log file not found: {log_path}", file=sys.stderr)
291
+ return 1
292
+
293
+ try:
294
+ with log_path.open("r", encoding="utf-8", errors="replace") as fh:
295
+ metric_values = extract_metrics(fh)
296
+ except OSError as exc:
297
+ print(f"error: failed to read log file {log_path}: {exc}", file=sys.stderr)
298
+ return 1
299
+
300
+ for metric in metrics:
301
+ values = metric_values.get(metric, [])
302
+ if not values:
303
+ print(
304
+ f"No {METRIC_CONFIGS[metric].title.lower()} were found in the log: "
305
+ f"{log_path}",
306
+ file=sys.stderr,
307
+ )
308
+ return 1
309
+
310
+ log_name = log_path.name or str(log_path)
311
+ series_by_metric[metric].append((log_name, values))
312
+
313
+ prefill_total_time = sum(metric_values[Metric.TTFT])
314
+ decode_total_time = sum(
315
+ output_tokens / tps
316
+ for output_tokens, tps in zip(
317
+ metric_values[Metric.OUTPUT_TOKENS], metric_values[Metric.TPS]
318
+ )
319
+ if tps > 0
320
+ )
321
+ prefill_decode_times.append(
322
+ (log_path.name or str(log_path), prefill_total_time, decode_total_time)
323
+ )
324
+
325
+ for metric in metrics:
326
+ build_plot(
327
+ series_by_metric[metric],
328
+ metric_config=METRIC_CONFIGS[metric],
329
+ output_path=METRIC_CONFIGS[metric].filename,
330
+ show=show,
331
+ )
332
+
333
+ total_points = sum(len(counts) for _, counts in series_by_metric[metric])
334
+ print(
335
+ f"Saved {METRIC_CONFIGS[metric].title.lower()} plot with "
336
+ f"{total_points} entries from {len(series_by_metric[metric])} log(s) "
337
+ f"to {METRIC_CONFIGS[metric].filename}"
338
+ )
339
+
340
+ prefill_decode_path = Path("prefill_decode_split.png")
341
+ build_prefill_decode_pies(
342
+ prefill_decode_times=prefill_decode_times,
343
+ output_path=prefill_decode_path,
344
+ show=show,
345
+ )
346
+ if prefill_decode_times:
347
+ print(
348
+ f"Saved prefill vs decode time split pies for "
349
+ f"{len(prefill_decode_times)} log(s) to {prefill_decode_path}"
350
+ )
351
+
352
+ return 0
353
+
354
+
355
+ def main(argv: Sequence[str] | None = None) -> int:
356
+ args = parse_cli(argv)
357
+ return run_perf_visualization(args.log_paths, show=args.show)
358
+
359
+
360
+ if __name__ == "__main__":
361
+ raise SystemExit(main())
gaia/rag/__init__.py CHANGED
@@ -1,10 +1,10 @@
1
- #!/usr/bin/env python3
2
- # Copyright(C) 2024-2025 Advanced Micro Devices, Inc. All rights reserved.
3
- # SPDX-License-Identifier: MIT
4
-
5
- """GAIA RAG (Retrieval-Augmented Generation) Module"""
6
-
7
- from .app import main as rag_main
8
- from .sdk import RAGSDK, RAGConfig, quick_rag
9
-
10
- __all__ = ["RAGConfig", "RAGSDK", "quick_rag", "rag_main"]
1
+ #!/usr/bin/env python3
2
+ # Copyright(C) 2024-2025 Advanced Micro Devices, Inc. All rights reserved.
3
+ # SPDX-License-Identifier: MIT
4
+
5
+ """GAIA RAG (Retrieval-Augmented Generation) Module"""
6
+
7
+ from .app import main as rag_main
8
+ from .sdk import RAGSDK, RAGConfig, quick_rag
9
+
10
+ __all__ = ["RAGConfig", "RAGSDK", "quick_rag", "rag_main"]