pygeai 0.6.0b6__py3-none-any.whl → 0.6.0b10__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 (227) hide show
  1. pygeai/_docs/source/conf.py +78 -6
  2. pygeai/_docs/source/content/api_reference/admin.rst +161 -0
  3. pygeai/_docs/source/content/api_reference/assistant.rst +326 -0
  4. pygeai/_docs/source/content/api_reference/auth.rst +379 -0
  5. pygeai/_docs/source/content/api_reference/embeddings.rst +31 -1
  6. pygeai/_docs/source/content/api_reference/evaluation.rst +590 -0
  7. pygeai/_docs/source/content/api_reference/feedback.rst +237 -0
  8. pygeai/_docs/source/content/api_reference/files.rst +592 -0
  9. pygeai/_docs/source/content/api_reference/gam.rst +401 -0
  10. pygeai/_docs/source/content/api_reference/health.rst +58 -0
  11. pygeai/_docs/source/content/api_reference/project.rst +20 -18
  12. pygeai/_docs/source/content/api_reference/proxy.rst +318 -0
  13. pygeai/_docs/source/content/api_reference/rerank.rst +94 -0
  14. pygeai/_docs/source/content/api_reference/secrets.rst +495 -0
  15. pygeai/_docs/source/content/api_reference/usage_limits.rst +390 -0
  16. pygeai/_docs/source/content/api_reference.rst +13 -1
  17. pygeai/_docs/source/content/debugger.rst +376 -83
  18. pygeai/_docs/source/content/migration.rst +528 -0
  19. pygeai/_docs/source/content/modules.rst +1 -1
  20. pygeai/_docs/source/index.rst +59 -7
  21. pygeai/_docs/source/pygeai.auth.rst +29 -0
  22. pygeai/_docs/source/pygeai.cli.commands.rst +16 -0
  23. pygeai/_docs/source/pygeai.cli.rst +8 -0
  24. pygeai/_docs/source/pygeai.core.utils.rst +16 -0
  25. pygeai/_docs/source/pygeai.rst +1 -0
  26. pygeai/_docs/source/pygeai.tests.auth.rst +21 -0
  27. pygeai/_docs/source/pygeai.tests.cli.commands.rst +16 -0
  28. pygeai/_docs/source/pygeai.tests.cli.rst +16 -0
  29. pygeai/_docs/source/pygeai.tests.core.base.rst +8 -0
  30. pygeai/_docs/source/pygeai.tests.core.embeddings.rst +16 -0
  31. pygeai/_docs/source/pygeai.tests.core.files.rst +8 -0
  32. pygeai/_docs/source/pygeai.tests.core.plugins.rst +21 -0
  33. pygeai/_docs/source/pygeai.tests.core.rst +1 -0
  34. pygeai/_docs/source/pygeai.tests.evaluation.dataset.rst +21 -0
  35. pygeai/_docs/source/pygeai.tests.evaluation.plan.rst +21 -0
  36. pygeai/_docs/source/pygeai.tests.evaluation.result.rst +21 -0
  37. pygeai/_docs/source/pygeai.tests.evaluation.rst +20 -0
  38. pygeai/_docs/source/pygeai.tests.integration.lab.processes.rst +8 -0
  39. pygeai/_docs/source/pygeai.tests.organization.rst +8 -0
  40. pygeai/_docs/source/pygeai.tests.rst +2 -0
  41. pygeai/_docs/source/pygeai.tests.snippets.auth.rst +10 -0
  42. pygeai/_docs/source/pygeai.tests.snippets.chat.rst +40 -0
  43. pygeai/_docs/source/pygeai.tests.snippets.dbg.rst +45 -0
  44. pygeai/_docs/source/pygeai.tests.snippets.embeddings.rst +40 -0
  45. pygeai/_docs/source/pygeai.tests.snippets.evaluation.dataset.rst +197 -0
  46. pygeai/_docs/source/pygeai.tests.snippets.evaluation.plan.rst +133 -0
  47. pygeai/_docs/source/pygeai.tests.snippets.evaluation.result.rst +37 -0
  48. pygeai/_docs/source/pygeai.tests.snippets.evaluation.rst +10 -0
  49. pygeai/_docs/source/pygeai.tests.snippets.organization.rst +40 -0
  50. pygeai/_docs/source/pygeai.tests.snippets.rst +2 -0
  51. pygeai/admin/clients.py +12 -32
  52. pygeai/assistant/clients.py +16 -44
  53. pygeai/assistant/data/clients.py +1 -0
  54. pygeai/assistant/data_analyst/clients.py +6 -13
  55. pygeai/assistant/rag/clients.py +24 -67
  56. pygeai/auth/clients.py +88 -14
  57. pygeai/auth/endpoints.py +4 -0
  58. pygeai/chat/clients.py +192 -25
  59. pygeai/chat/endpoints.py +2 -1
  60. pygeai/cli/commands/auth.py +178 -2
  61. pygeai/cli/commands/chat.py +227 -1
  62. pygeai/cli/commands/embeddings.py +56 -8
  63. pygeai/cli/commands/lab/ai_lab.py +0 -2
  64. pygeai/cli/commands/migrate.py +994 -434
  65. pygeai/cli/commands/organization.py +241 -0
  66. pygeai/cli/error_handler.py +116 -0
  67. pygeai/cli/geai.py +28 -10
  68. pygeai/cli/parsers.py +8 -2
  69. pygeai/core/base/clients.py +4 -1
  70. pygeai/core/common/exceptions.py +11 -10
  71. pygeai/core/embeddings/__init__.py +19 -0
  72. pygeai/core/embeddings/clients.py +20 -9
  73. pygeai/core/embeddings/mappers.py +16 -2
  74. pygeai/core/embeddings/responses.py +9 -2
  75. pygeai/core/feedback/clients.py +4 -8
  76. pygeai/core/files/clients.py +10 -25
  77. pygeai/core/files/managers.py +42 -0
  78. pygeai/core/llm/clients.py +11 -26
  79. pygeai/core/models.py +107 -0
  80. pygeai/core/plugins/clients.py +4 -7
  81. pygeai/core/rerank/clients.py +4 -8
  82. pygeai/core/secrets/clients.py +14 -37
  83. pygeai/core/services/rest.py +1 -1
  84. pygeai/core/utils/parsers.py +32 -0
  85. pygeai/core/utils/validators.py +10 -0
  86. pygeai/dbg/__init__.py +3 -0
  87. pygeai/dbg/debugger.py +565 -70
  88. pygeai/evaluation/clients.py +2 -1
  89. pygeai/evaluation/dataset/clients.py +46 -44
  90. pygeai/evaluation/plan/clients.py +28 -26
  91. pygeai/evaluation/result/clients.py +38 -5
  92. pygeai/gam/clients.py +10 -25
  93. pygeai/health/clients.py +4 -7
  94. pygeai/lab/agents/clients.py +21 -54
  95. pygeai/lab/agents/endpoints.py +2 -0
  96. pygeai/lab/clients.py +1 -0
  97. pygeai/lab/models.py +3 -3
  98. pygeai/lab/processes/clients.py +45 -127
  99. pygeai/lab/strategies/clients.py +11 -25
  100. pygeai/lab/tools/clients.py +23 -67
  101. pygeai/lab/tools/endpoints.py +3 -0
  102. pygeai/migration/__init__.py +31 -0
  103. pygeai/migration/strategies.py +404 -155
  104. pygeai/migration/tools.py +170 -3
  105. pygeai/organization/clients.py +135 -51
  106. pygeai/organization/endpoints.py +6 -1
  107. pygeai/organization/limits/clients.py +32 -91
  108. pygeai/organization/managers.py +157 -1
  109. pygeai/organization/mappers.py +76 -2
  110. pygeai/organization/responses.py +25 -1
  111. pygeai/proxy/clients.py +4 -1
  112. pygeai/tests/admin/test_clients.py +16 -11
  113. pygeai/tests/assistants/rag/test_clients.py +35 -23
  114. pygeai/tests/assistants/test_clients.py +22 -15
  115. pygeai/tests/auth/test_clients.py +191 -7
  116. pygeai/tests/chat/test_clients.py +211 -1
  117. pygeai/tests/cli/commands/test_embeddings.py +32 -9
  118. pygeai/tests/cli/commands/test_evaluation.py +7 -0
  119. pygeai/tests/cli/commands/test_migrate.py +112 -243
  120. pygeai/tests/cli/test_error_handler.py +225 -0
  121. pygeai/tests/cli/test_geai_driver.py +154 -0
  122. pygeai/tests/cli/test_parsers.py +5 -5
  123. pygeai/tests/core/embeddings/test_clients.py +144 -0
  124. pygeai/tests/core/embeddings/test_managers.py +171 -0
  125. pygeai/tests/core/embeddings/test_mappers.py +142 -0
  126. pygeai/tests/core/feedback/test_clients.py +2 -0
  127. pygeai/tests/core/files/test_clients.py +1 -0
  128. pygeai/tests/core/llm/test_clients.py +14 -9
  129. pygeai/tests/core/plugins/test_clients.py +5 -3
  130. pygeai/tests/core/rerank/test_clients.py +1 -0
  131. pygeai/tests/core/secrets/test_clients.py +19 -13
  132. pygeai/tests/dbg/test_debugger.py +453 -75
  133. pygeai/tests/evaluation/dataset/test_clients.py +3 -1
  134. pygeai/tests/evaluation/plan/test_clients.py +4 -2
  135. pygeai/tests/evaluation/result/test_clients.py +7 -5
  136. pygeai/tests/gam/test_clients.py +1 -1
  137. pygeai/tests/health/test_clients.py +1 -0
  138. pygeai/tests/lab/agents/test_clients.py +9 -0
  139. pygeai/tests/lab/processes/test_clients.py +36 -0
  140. pygeai/tests/lab/processes/test_mappers.py +3 -0
  141. pygeai/tests/lab/strategies/test_clients.py +14 -9
  142. pygeai/tests/migration/test_strategies.py +45 -218
  143. pygeai/tests/migration/test_tools.py +133 -9
  144. pygeai/tests/organization/limits/test_clients.py +17 -0
  145. pygeai/tests/organization/test_clients.py +206 -1
  146. pygeai/tests/organization/test_managers.py +122 -1
  147. pygeai/tests/proxy/test_clients.py +2 -0
  148. pygeai/tests/proxy/test_integration.py +1 -0
  149. pygeai/tests/snippets/auth/__init__.py +0 -0
  150. pygeai/tests/snippets/chat/chat_completion_with_reasoning_effort.py +18 -0
  151. pygeai/tests/snippets/chat/get_response.py +15 -0
  152. pygeai/tests/snippets/chat/get_response_streaming.py +20 -0
  153. pygeai/tests/snippets/chat/get_response_with_files.py +16 -0
  154. pygeai/tests/snippets/chat/get_response_with_tools.py +36 -0
  155. pygeai/tests/snippets/dbg/__init__.py +0 -0
  156. pygeai/tests/snippets/dbg/basic_debugging.py +32 -0
  157. pygeai/tests/snippets/dbg/breakpoint_management.py +48 -0
  158. pygeai/tests/snippets/dbg/stack_navigation.py +45 -0
  159. pygeai/tests/snippets/dbg/stepping_example.py +40 -0
  160. pygeai/tests/snippets/embeddings/cache_example.py +31 -0
  161. pygeai/tests/snippets/embeddings/cohere_example.py +41 -0
  162. pygeai/tests/snippets/embeddings/openai_base64_example.py +27 -0
  163. pygeai/tests/snippets/embeddings/openai_example.py +30 -0
  164. pygeai/tests/snippets/embeddings/similarity_example.py +42 -0
  165. pygeai/tests/snippets/evaluation/dataset/__init__.py +0 -0
  166. pygeai/tests/snippets/evaluation/dataset/complete_workflow_example.py +195 -0
  167. pygeai/tests/snippets/evaluation/dataset/create_dataset.py +26 -0
  168. pygeai/tests/snippets/evaluation/dataset/create_dataset_from_file.py +11 -0
  169. pygeai/tests/snippets/evaluation/dataset/create_dataset_row.py +17 -0
  170. pygeai/tests/snippets/evaluation/dataset/create_expected_source.py +18 -0
  171. pygeai/tests/snippets/evaluation/dataset/create_filter_variable.py +19 -0
  172. pygeai/tests/snippets/evaluation/dataset/delete_dataset.py +9 -0
  173. pygeai/tests/snippets/evaluation/dataset/delete_dataset_row.py +10 -0
  174. pygeai/tests/snippets/evaluation/dataset/delete_expected_source.py +15 -0
  175. pygeai/tests/snippets/evaluation/dataset/delete_filter_variable.py +15 -0
  176. pygeai/tests/snippets/evaluation/dataset/get_dataset.py +9 -0
  177. pygeai/tests/snippets/evaluation/dataset/get_dataset_row.py +10 -0
  178. pygeai/tests/snippets/evaluation/dataset/get_expected_source.py +15 -0
  179. pygeai/tests/snippets/evaluation/dataset/get_filter_variable.py +15 -0
  180. pygeai/tests/snippets/evaluation/dataset/list_dataset_rows.py +9 -0
  181. pygeai/tests/snippets/evaluation/dataset/list_datasets.py +6 -0
  182. pygeai/tests/snippets/evaluation/dataset/list_expected_sources.py +10 -0
  183. pygeai/tests/snippets/evaluation/dataset/list_filter_variables.py +10 -0
  184. pygeai/tests/snippets/evaluation/dataset/update_dataset.py +15 -0
  185. pygeai/tests/snippets/evaluation/dataset/update_dataset_row.py +20 -0
  186. pygeai/tests/snippets/evaluation/dataset/update_expected_source.py +18 -0
  187. pygeai/tests/snippets/evaluation/dataset/update_filter_variable.py +19 -0
  188. pygeai/tests/snippets/evaluation/dataset/upload_dataset_rows_file.py +10 -0
  189. pygeai/tests/snippets/evaluation/plan/__init__.py +0 -0
  190. pygeai/tests/snippets/evaluation/plan/add_plan_system_metric.py +13 -0
  191. pygeai/tests/snippets/evaluation/plan/complete_workflow_example.py +136 -0
  192. pygeai/tests/snippets/evaluation/plan/create_evaluation_plan.py +24 -0
  193. pygeai/tests/snippets/evaluation/plan/create_rag_evaluation_plan.py +22 -0
  194. pygeai/tests/snippets/evaluation/plan/delete_evaluation_plan.py +9 -0
  195. pygeai/tests/snippets/evaluation/plan/delete_plan_system_metric.py +13 -0
  196. pygeai/tests/snippets/evaluation/plan/execute_evaluation_plan.py +11 -0
  197. pygeai/tests/snippets/evaluation/plan/get_evaluation_plan.py +9 -0
  198. pygeai/tests/snippets/evaluation/plan/get_plan_system_metric.py +13 -0
  199. pygeai/tests/snippets/evaluation/plan/get_system_metric.py +9 -0
  200. pygeai/tests/snippets/evaluation/plan/list_evaluation_plans.py +7 -0
  201. pygeai/tests/snippets/evaluation/plan/list_plan_system_metrics.py +9 -0
  202. pygeai/tests/snippets/evaluation/plan/list_system_metrics.py +7 -0
  203. pygeai/tests/snippets/evaluation/plan/update_evaluation_plan.py +22 -0
  204. pygeai/tests/snippets/evaluation/plan/update_plan_system_metric.py +14 -0
  205. pygeai/tests/snippets/evaluation/result/__init__.py +0 -0
  206. pygeai/tests/snippets/evaluation/result/complete_workflow_example.py +150 -0
  207. pygeai/tests/snippets/evaluation/result/get_evaluation_result.py +26 -0
  208. pygeai/tests/snippets/evaluation/result/list_evaluation_results.py +17 -0
  209. pygeai/tests/snippets/migrate/__init__.py +45 -0
  210. pygeai/tests/snippets/migrate/agent_migration.py +110 -0
  211. pygeai/tests/snippets/migrate/assistant_migration.py +64 -0
  212. pygeai/tests/snippets/migrate/orchestrator_examples.py +179 -0
  213. pygeai/tests/snippets/migrate/process_migration.py +64 -0
  214. pygeai/tests/snippets/migrate/project_migration.py +42 -0
  215. pygeai/tests/snippets/migrate/tool_migration.py +64 -0
  216. pygeai/tests/snippets/organization/create_project.py +2 -2
  217. pygeai/tests/snippets/organization/get_memberships.py +12 -0
  218. pygeai/tests/snippets/organization/get_organization_members.py +6 -0
  219. pygeai/tests/snippets/organization/get_project_members.py +6 -0
  220. pygeai/tests/snippets/organization/get_project_memberships.py +12 -0
  221. pygeai/tests/snippets/organization/get_project_roles.py +6 -0
  222. {pygeai-0.6.0b6.dist-info → pygeai-0.6.0b10.dist-info}/METADATA +1 -1
  223. {pygeai-0.6.0b6.dist-info → pygeai-0.6.0b10.dist-info}/RECORD +227 -124
  224. {pygeai-0.6.0b6.dist-info → pygeai-0.6.0b10.dist-info}/WHEEL +0 -0
  225. {pygeai-0.6.0b6.dist-info → pygeai-0.6.0b10.dist-info}/entry_points.txt +0 -0
  226. {pygeai-0.6.0b6.dist-info → pygeai-0.6.0b10.dist-info}/licenses/LICENSE +0 -0
  227. {pygeai-0.6.0b6.dist-info → pygeai-0.6.0b10.dist-info}/top_level.txt +0 -0
pygeai/dbg/debugger.py CHANGED
@@ -1,12 +1,47 @@
1
1
  import logging
2
2
  import sys
3
3
  import inspect
4
- from typing import Optional, Any, Callable, Set, Tuple
4
+ import readline
5
+ import pprint
6
+ from types import FrameType
7
+ from typing import Optional, Any, Callable, Set, Dict, List, Tuple
8
+ from dataclasses import dataclass, field
5
9
 
6
10
  from pygeai.cli.geai import main as geai
7
11
  from pygeai.core.utils.console import Console
8
12
 
9
13
 
14
+ @dataclass
15
+ class Breakpoint:
16
+ """Represents a breakpoint with optional conditions."""
17
+ module: Optional[str] = None
18
+ function_name: Optional[str] = None
19
+ condition: Optional[str] = None
20
+ enabled: bool = True
21
+ hit_count: int = 0
22
+
23
+ def __hash__(self):
24
+ return hash((self.module, self.function_name))
25
+
26
+ def __eq__(self, other):
27
+ if not isinstance(other, Breakpoint):
28
+ return False
29
+ return self.module == other.module and self.function_name == other.function_name
30
+
31
+ def matches(self, module: str, func_name: str) -> bool:
32
+ """Check if this breakpoint matches the given module and function."""
33
+ if not self.enabled:
34
+ return False
35
+ module_match = self.module is None or self.module == module
36
+ func_match = self.function_name is None or self.function_name == func_name
37
+ return module_match and func_match
38
+
39
+ def __str__(self):
40
+ status = "enabled" if self.enabled else "disabled"
41
+ cond = f" [if {self.condition}]" if self.condition else ""
42
+ return f"{self.module or '*'}:{self.function_name or '*'} ({status}, hits: {self.hit_count}){cond}"
43
+
44
+
10
45
  class Debugger:
11
46
  """
12
47
  A debugger for the GEAI application to trace and control execution flow.
@@ -15,124 +50,584 @@ class Debugger:
15
50
  and pause execution at specified breakpoints. Breakpoints can be set for specific modules or functions, allowing
16
51
  developers to inspect local variables, execute arbitrary code in the current context, and control program flow
17
52
  through an interactive command interface.
53
+
54
+ Features:
55
+ - Module filtering for performance (only traces pygeai modules by default)
56
+ - Breakpoint management (add, list, remove, enable/disable, conditional)
57
+ - Stack navigation (up/down frames)
58
+ - Stepping (step-into, step-over, step-out)
59
+ - Variable inspection with pretty-printing
60
+ - Source code display
61
+ - Stack trace viewing
62
+ - Readline support for command history
18
63
  """
19
64
 
20
- def __init__(self):
21
- self.setup_logging()
22
- self.breakpoints: Set[Tuple[Optional[str], Optional[str]]] = set()
65
+ def __init__(self, target: Optional[Callable] = None, module_filter: str = "pygeai"):
66
+ """
67
+ Initialize the debugger.
68
+
69
+ Args:
70
+ target: The callable to debug. If None, defaults to pygeai.cli.geai.main
71
+ module_filter: Only trace modules starting with this prefix (for performance)
72
+ """
73
+ self.target = target or geai
74
+ self.module_filter = module_filter
75
+ self._setup_logging()
76
+ self.logger = logging.getLogger('geai.dbg')
77
+
78
+ self.breakpoints: Dict[Tuple[Optional[str], Optional[str]], Breakpoint] = {}
23
79
  self.paused: bool = False
24
- logging.getLogger('geai').info("GEAI debugger started.")
25
- logging.getLogger('geai').info(f"geai module: {geai.__module__}")
80
+ self.current_frame: Optional[FrameType] = None
81
+ self.frame_stack: List[FrameType] = []
82
+ self.current_frame_index: int = 0
83
+
84
+ # Stepping state
85
+ self.step_mode: Optional[str] = None # 'step', 'next', 'return', 'until'
86
+ self.step_frame: Optional[FrameType] = None
87
+ self.step_depth: int = 0
88
+ self.current_depth: int = 0
89
+
90
+ # Setup readline for command history
91
+ self._setup_readline()
92
+
93
+ self.logger.info("GEAI debugger started.")
94
+ self.logger.debug(f"Module filter: {self.module_filter}")
26
95
 
27
- def setup_logging(self):
28
- logger = logging.getLogger('geai')
29
- logger.setLevel(logging.DEBUG)
96
+ def _setup_logging(self):
97
+ """Setup logging configuration, avoiding duplicate handlers."""
98
+ logger = logging.getLogger('geai.dbg')
99
+
100
+ # Only setup if not already configured
101
+ if not logger.handlers:
102
+ logger.setLevel(logging.DEBUG)
103
+ console_handler = logging.StreamHandler()
104
+ console_handler.setLevel(logging.DEBUG)
105
+ formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
106
+ console_handler.setFormatter(formatter)
107
+ logger.addHandler(console_handler)
108
+ logger.propagate = False
30
109
 
31
- console_handler = logging.StreamHandler()
32
- console_handler.setLevel(logging.DEBUG)
110
+ def _setup_readline(self):
111
+ """Setup readline for command history and tab completion."""
112
+ try:
113
+ import os
114
+ histfile = os.path.expanduser("~/.geai_dbg_history")
115
+ try:
116
+ readline.read_history_file(histfile)
117
+ readline.set_history_length(1000)
118
+ except FileNotFoundError:
119
+ pass
120
+
121
+ import atexit
122
+ atexit.register(readline.write_history_file, histfile)
123
+ except Exception as e:
124
+ self.logger.debug(f"Could not setup readline: {e}")
33
125
 
34
- formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
35
- console_handler.setFormatter(formatter)
126
+ def reset(self):
127
+ """Reset debugger state."""
128
+ self.breakpoints.clear()
129
+ self.paused = False
130
+ self.current_frame = None
131
+ self.frame_stack.clear()
132
+ self.current_frame_index = 0
133
+ self.step_mode = None
134
+ self.step_frame = None
135
+ self.step_depth = 0
136
+ self.current_depth = 0
137
+ self.logger.info("Debugger state reset.")
36
138
 
37
- logger.addHandler(console_handler)
139
+ def add_breakpoint(
140
+ self,
141
+ module: Optional[str] = None,
142
+ function_name: Optional[str] = None,
143
+ condition: Optional[str] = None
144
+ ) -> Breakpoint:
145
+ """
146
+ Add a breakpoint by module and/or function name.
147
+
148
+ Args:
149
+ module: Module name to break on (None for any)
150
+ function_name: Function name to break on (None for any)
151
+ condition: Optional condition expression (Python code)
152
+
153
+ Returns:
154
+ The created Breakpoint object
155
+ """
156
+ key = (module, function_name)
157
+ if key in self.breakpoints:
158
+ bp = self.breakpoints[key]
159
+ self.logger.warning(f"Breakpoint already exists: {bp}")
160
+ return bp
161
+
162
+ bp = Breakpoint(module=module, function_name=function_name, condition=condition)
163
+ self.breakpoints[key] = bp
164
+ self.logger.info(f"Breakpoint added: {bp}")
165
+ return bp
38
166
 
39
- def add_breakpoint(self, module: Optional[str] = None, function_name: Optional[str] = None):
40
- """Add a breakpoint by module and/or function name."""
41
- self.breakpoints.add((module, function_name))
42
- logging.getLogger('geai').info(f"Breakpoint added: {module or '*'}:{function_name or '*'}")
167
+ def remove_breakpoint(self, module: Optional[str] = None, function_name: Optional[str] = None) -> bool:
168
+ """Remove a breakpoint."""
169
+ key = (module, function_name)
170
+ if key in self.breakpoints:
171
+ bp = self.breakpoints.pop(key)
172
+ self.logger.info(f"Breakpoint removed: {bp}")
173
+ return True
174
+ self.logger.warning(f"Breakpoint not found: {module or '*'}:{function_name or '*'}")
175
+ return False
43
176
 
44
- def trace_function(self, frame: sys._getframe, event: str, arg: Any) -> Callable:
45
- """Trace function calls to intercept execution."""
46
- if event != 'call': # Only intercept function calls
47
- return self.trace_function
177
+ def list_breakpoints(self) -> List[Breakpoint]:
178
+ """List all breakpoints."""
179
+ return list(self.breakpoints.values())
48
180
 
181
+ def enable_breakpoint(self, module: Optional[str] = None, function_name: Optional[str] = None) -> bool:
182
+ """Enable a breakpoint."""
183
+ key = (module, function_name)
184
+ if key in self.breakpoints:
185
+ self.breakpoints[key].enabled = True
186
+ self.logger.info(f"Breakpoint enabled: {self.breakpoints[key]}")
187
+ return True
188
+ return False
189
+
190
+ def disable_breakpoint(self, module: Optional[str] = None, function_name: Optional[str] = None) -> bool:
191
+ """Disable a breakpoint."""
192
+ key = (module, function_name)
193
+ if key in self.breakpoints:
194
+ self.breakpoints[key].enabled = False
195
+ self.logger.info(f"Breakpoint disabled: {self.breakpoints[key]}")
196
+ return True
197
+ return False
198
+
199
+ def clear_breakpoints(self):
200
+ """Clear all breakpoints."""
201
+ count = len(self.breakpoints)
202
+ self.breakpoints.clear()
203
+ self.logger.info(f"Cleared {count} breakpoint(s).")
204
+
205
+ def _should_trace_module(self, module: Optional[str]) -> bool:
206
+ """Check if we should trace this module based on filter."""
207
+ if not module:
208
+ return False
209
+ return module.startswith(self.module_filter)
210
+
211
+ def _check_condition(self, bp: Breakpoint, frame: FrameType) -> bool:
212
+ """Check if breakpoint condition is met."""
213
+ if not bp.condition:
214
+ return True
215
+
216
+ try:
217
+ # Evaluate condition in frame's context
218
+ result = eval(bp.condition, frame.f_globals, frame.f_locals)
219
+ return bool(result)
220
+ except Exception as e:
221
+ self.logger.error(f"Error evaluating breakpoint condition '{bp.condition}': {e}")
222
+ return False
223
+
224
+ def _build_frame_stack(self, frame: FrameType) -> List[FrameType]:
225
+ """Build a list of frames from current to top of stack."""
226
+ stack = []
227
+ current = frame
228
+ while current is not None:
229
+ stack.append(current)
230
+ current = current.f_back
231
+ return stack
232
+
233
+ def trace_function(self, frame: FrameType, event: str, arg: Any) -> Optional[Callable]:
234
+ """Trace function calls to intercept execution."""
49
235
  module = frame.f_globals.get('__name__')
236
+
237
+ # Performance optimization: only trace filtered modules
238
+ if not self._should_trace_module(module):
239
+ return None
240
+
50
241
  function_name = frame.f_code.co_name
51
-
52
- # Check if the current frame matches a breakpoint
53
- for bp_module, bp_func in self.breakpoints:
54
- module_match = bp_module is None or bp_module == module
55
- func_match = bp_func is None or bp_func == function_name
56
- if module_match and func_match:
57
- logging.getLogger('geai').info(
58
- f"Breakpoint hit at {module}.{function_name}"
59
- )
242
+
243
+ # Handle different event types
244
+ if event == 'call':
245
+ self.current_depth += 1
246
+
247
+ # Check breakpoints
248
+ should_break = False
249
+ for bp in self.breakpoints.values():
250
+ if bp.matches(module, function_name):
251
+ if self._check_condition(bp, frame):
252
+ bp.hit_count += 1
253
+ self.logger.info(f"Breakpoint hit at {module}.{function_name} (hit #{bp.hit_count})")
254
+ should_break = True
255
+ break
256
+
257
+ if should_break:
60
258
  self.paused = True
259
+ self.current_frame = frame
260
+ self.frame_stack = self._build_frame_stack(frame)
261
+ self.current_frame_index = 0
61
262
  self.handle_breakpoint(frame)
62
263
  self.paused = False
264
+
265
+ elif event == 'return':
266
+ self.current_depth -= 1
267
+
268
+ # Handle step-out (return from function)
269
+ if self.step_mode == 'return' and frame == self.step_frame:
270
+ self.step_mode = None
271
+ self.paused = True
272
+ self.current_frame = frame
273
+ self.frame_stack = self._build_frame_stack(frame)
274
+ self.current_frame_index = 0
275
+ self.handle_breakpoint(frame)
276
+ self.paused = False
277
+
278
+ elif event == 'line':
279
+ # Handle step and next
280
+ if self.step_mode == 'step':
281
+ self.step_mode = None
282
+ self.paused = True
283
+ self.current_frame = frame
284
+ self.frame_stack = self._build_frame_stack(frame)
285
+ self.current_frame_index = 0
286
+ self.handle_breakpoint(frame)
287
+ self.paused = False
288
+ elif self.step_mode == 'next' and self.current_depth <= self.step_depth:
289
+ self.step_mode = None
290
+ self.paused = True
291
+ self.current_frame = frame
292
+ self.frame_stack = self._build_frame_stack(frame)
293
+ self.current_frame_index = 0
294
+ self.handle_breakpoint(frame)
295
+ self.paused = False
296
+
63
297
  return self.trace_function
64
298
 
65
- def handle_breakpoint(self, frame: sys._getframe):
66
- """Handle a breakpoint by prompting for interactive commands."""
67
- locals_info = {k: v for k, v in frame.f_locals.items()}
68
- logging.getLogger('geai').info(f"Local variables: {locals_info}")
299
+ def _get_source_lines(self, frame: FrameType, context: int = 5) -> Optional[List[Tuple[int, str]]]:
300
+ """Get source code lines around the current line."""
301
+ try:
302
+ filename = frame.f_code.co_filename
303
+ lineno = frame.f_lineno
304
+
305
+ with open(filename, 'r') as f:
306
+ lines = f.readlines()
307
+
308
+ start = max(0, lineno - context - 1)
309
+ end = min(len(lines), lineno + context)
310
+
311
+ return [(i + 1, lines[i].rstrip()) for i in range(start, end)]
312
+ except Exception as e:
313
+ self.logger.debug(f"Could not get source: {e}")
314
+ return None
315
+
316
+ def _print_source(self, frame: FrameType, context: int = 5):
317
+ """Print source code around current line."""
318
+ lines = self._get_source_lines(frame, context)
319
+ if not lines:
320
+ Console.write_stdout("Source code not available.")
321
+ return
322
+
323
+ lineno = frame.f_lineno
324
+ Console.write_stdout(f"\nSource ({frame.f_code.co_filename}):")
325
+ for num, line in lines:
326
+ marker = "-> " if num == lineno else " "
327
+ Console.write_stdout(f"{marker}{num:4d} {line}")
69
328
 
70
- Console.write_stdout(f"\nPaused at {frame.f_globals.get('__name__')}.{frame.f_code.co_name}")
71
- Console.write_stdout("Enter commands to execute in the current context (type 'continue' to resume, 'quit' to exit, 'help' to display available commands):")
329
+ def _print_stack_trace(self):
330
+ """Print stack trace."""
331
+ Console.write_stdout("\nStack trace (most recent call last):")
332
+ for i, frame in enumerate(reversed(self.frame_stack)):
333
+ marker = ">" if i == len(self.frame_stack) - 1 - self.current_frame_index else " "
334
+ module = frame.f_globals.get('__name__', '?')
335
+ func = frame.f_code.co_name
336
+ lineno = frame.f_lineno
337
+ filename = frame.f_code.co_filename
338
+ Console.write_stdout(f" {marker} #{i:2d} {module}.{func} at {filename}:{lineno}")
339
+
340
+ def _move_frame(self, direction: int) -> bool:
341
+ """Move up or down the frame stack."""
342
+ new_index = self.current_frame_index + direction
343
+ if 0 <= new_index < len(self.frame_stack):
344
+ self.current_frame_index = new_index
345
+ self.current_frame = self.frame_stack[new_index]
346
+ Console.write_stdout(f"Frame #{len(self.frame_stack) - 1 - new_index}: "
347
+ f"{self.current_frame.f_globals.get('__name__', '?')}."
348
+ f"{self.current_frame.f_code.co_name}")
349
+ return True
350
+ else:
351
+ Console.write_stdout(f"Cannot move {direction} (at {'top' if direction < 0 else 'bottom'} of stack)")
352
+ return False
353
+
354
+ def handle_breakpoint(self, frame: FrameType):
355
+ """Handle a breakpoint by prompting for interactive commands."""
356
+ module = frame.f_globals.get('__name__')
357
+ func = frame.f_code.co_name
358
+
359
+ Console.write_stdout(f"\n{'='*60}")
360
+ Console.write_stdout(f"Paused at {module}.{func} (line {frame.f_lineno})")
361
+ self._print_source(frame, context=3)
362
+ Console.write_stdout(f"{'='*60}")
363
+ Console.write_stdout("Type 'h' for help, 'c' to continue")
364
+
72
365
  while True:
73
366
  try:
74
- command = input("(geai-dbg) ")
75
- if command == 'continue' or command == 'c':
367
+ command = input("(geai-dbg) ").strip()
368
+ if not command:
369
+ continue
370
+
371
+ parts = command.split(None, 1)
372
+ cmd = parts[0]
373
+ args = parts[1] if len(parts) > 1 else ""
374
+
375
+ # Control flow commands
376
+ if cmd in ('continue', 'c'):
76
377
  break
77
- elif command == 'quit' or command == "q":
78
- logging.getLogger('geai').info("Debugger terminated by user.")
378
+ elif cmd in ('quit', 'q'):
379
+ self.logger.info("Debugger terminated by user.")
79
380
  sys.exit(0)
80
- elif command == 'run' or command == 'r':
81
- logging.getLogger('geai').info("Running program without further pauses.")
82
- sys.settrace(None) # Disable tracing to skip all breakpoints
381
+ elif cmd in ('run', 'r'):
382
+ self.logger.info("Running program without further pauses.")
383
+ sys.settrace(None)
384
+ break
385
+
386
+ # Stepping commands
387
+ elif cmd in ('step', 's'):
388
+ self.step_mode = 'step'
389
+ self.step_depth = self.current_depth
83
390
  break
84
- elif command == 'breakpoint-module' or command == 'bm':
85
- logging.getLogger('geai').info("Adding breakpoint on module")
391
+ elif cmd in ('next', 'n'):
392
+ self.step_mode = 'next'
393
+ self.step_depth = self.current_depth
394
+ break
395
+ elif cmd in ('return', 'ret'):
396
+ self.step_mode = 'return'
397
+ self.step_frame = frame
398
+ break
399
+
400
+ # Stack navigation
401
+ elif cmd in ('up', 'u'):
402
+ self._move_frame(1)
403
+ elif cmd in ('down', 'd'):
404
+ self._move_frame(-1)
405
+ elif cmd in ('where', 'w', 'bt', 'backtrace'):
406
+ self._print_stack_trace()
407
+
408
+ # Source display
409
+ elif cmd in ('list', 'l'):
410
+ context = int(args) if args.isdigit() else 10
411
+ self._print_source(self.current_frame, context)
412
+
413
+ # Variable inspection
414
+ elif cmd in ('print', 'p'):
415
+ if not args:
416
+ Console.write_stdout("Usage: p <expression>")
417
+ else:
418
+ try:
419
+ result = eval(args, self.current_frame.f_globals, self.current_frame.f_locals)
420
+ Console.write_stdout(repr(result))
421
+ except Exception as e:
422
+ Console.write_stdout(f"Error: {e}")
423
+
424
+ elif cmd in ('pp',):
425
+ if not args:
426
+ Console.write_stdout("Usage: pp <expression>")
427
+ else:
428
+ try:
429
+ result = eval(args, self.current_frame.f_globals, self.current_frame.f_locals)
430
+ pprint.pprint(result)
431
+ except Exception as e:
432
+ Console.write_stdout(f"Error: {e}")
433
+
434
+ elif cmd in ('locals', 'loc'):
435
+ Console.write_stdout("\nLocal variables:")
436
+ pprint.pprint(dict(self.current_frame.f_locals))
437
+
438
+ elif cmd in ('globals', 'glob'):
439
+ Console.write_stdout("\nGlobal variables:")
440
+ # Filter out builtins
441
+ filtered = {k: v for k, v in self.current_frame.f_globals.items()
442
+ if not k.startswith('__')}
443
+ pprint.pprint(filtered)
444
+
445
+ elif cmd in ('args', 'a'):
446
+ Console.write_stdout("\nFunction arguments:")
447
+ arginfo = inspect.getargvalues(self.current_frame)
448
+ for arg in arginfo.args:
449
+ Console.write_stdout(f" {arg} = {repr(self.current_frame.f_locals.get(arg))}")
450
+
451
+ # Breakpoint management
452
+ elif cmd in ('break', 'b'):
453
+ if not args:
454
+ # List breakpoints
455
+ bps = self.list_breakpoints()
456
+ if bps:
457
+ Console.write_stdout("\nBreakpoints:")
458
+ for i, bp in enumerate(bps, 1):
459
+ Console.write_stdout(f" {i}. {bp}")
460
+ else:
461
+ Console.write_stdout("No breakpoints set.")
462
+ else:
463
+ # Parse breakpoint specification: module:function or just function
464
+ if ':' in args:
465
+ mod, func = args.split(':', 1)
466
+ mod = mod.strip() or None
467
+ func = func.strip() or None
468
+ else:
469
+ mod = None
470
+ func = args.strip()
471
+ self.add_breakpoint(module=mod, function_name=func)
472
+
473
+ elif cmd in ('tbreak', 'tb'):
474
+ Console.write_stdout("Temporary breakpoints not yet implemented.")
475
+
476
+ elif cmd in ('clear', 'cl'):
477
+ if args:
478
+ if ':' in args:
479
+ mod, func = args.split(':', 1)
480
+ mod = mod.strip() or None
481
+ func = func.strip() or None
482
+ else:
483
+ mod = None
484
+ func = args.strip()
485
+ self.remove_breakpoint(module=mod, function_name=func)
486
+ else:
487
+ Console.write_stdout("Usage: cl <breakpoint> or 'clearall' to remove all")
488
+
489
+ elif cmd in ('clearall', 'cla'):
490
+ self.clear_breakpoints()
491
+
492
+ elif cmd in ('enable', 'en'):
493
+ if ':' in args:
494
+ mod, func = args.split(':', 1)
495
+ mod = mod.strip() or None
496
+ func = func.strip() or None
497
+ else:
498
+ mod = None
499
+ func = args.strip()
500
+ self.enable_breakpoint(module=mod, function_name=func)
501
+
502
+ elif cmd in ('disable', 'dis'):
503
+ if ':' in args:
504
+ mod, func = args.split(':', 1)
505
+ mod = mod.strip() or None
506
+ func = func.strip() or None
507
+ else:
508
+ mod = None
509
+ func = args.strip()
510
+ self.disable_breakpoint(module=mod, function_name=func)
511
+
512
+ # Legacy commands
513
+ elif cmd in ('breakpoint-module', 'bm'):
514
+ self.logger.info("Adding breakpoint on module")
86
515
  module_name = input("(geai-dbg) Enter module name (or press Enter for any module): ").strip()
87
516
  module_name = module_name if module_name else None
88
517
  self.add_breakpoint(module=module_name)
89
- elif command == 'breakpoint-function' or command == 'bf':
90
- logging.getLogger('geai').info("Adding breakpoint on function name")
518
+
519
+ elif cmd in ('breakpoint-function', 'bf'):
520
+ self.logger.info("Adding breakpoint on function name")
91
521
  function_name = input("(geai-dbg) Enter function name (or press Enter for any function): ").strip()
92
522
  function_name = function_name if function_name else None
93
523
  module_name = input("(geai-dbg) Enter module name (optional, press Enter to skip): ").strip()
94
524
  module_name = module_name if module_name else None
95
525
  self.add_breakpoint(module=module_name, function_name=function_name)
96
- elif command == 'list-modules' or command == 'lm':
97
- logging.getLogger('geai').info("Listing available modules")
98
- modules = [m for m in sys.modules if m.startswith('pygeai')]
99
- Console.write_stdout(f"Available modules: {modules}")
100
- elif command == 'help' or command == 'h':
101
- Console.write_stdout("Available commands:")
102
- Console.write_stdout(" continue, c: Resume execution until next breakpoint")
103
- Console.write_stdout(" quit, q: Exit the debugger")
104
- Console.write_stdout(" run, r: Run program without further pauses")
105
- Console.write_stdout(" breakpoint-module, bm: Add a module breakpoint")
106
- Console.write_stdout(" breakpoint-function, bf: Add a function breakpoint")
107
- Console.write_stdout(" list-modules, lm: List available modules")
108
- Console.write_stdout(" <Python code>: Execute arbitrary Python code in the current context")
526
+
527
+ elif cmd in ('list-modules', 'lm'):
528
+ self.logger.info("Listing available modules")
529
+ modules = [m for m in sys.modules if m.startswith(self.module_filter)]
530
+ Console.write_stdout(f"\nAvailable modules ({len(modules)}):")
531
+ for mod in sorted(modules)[:50]: # Limit output
532
+ Console.write_stdout(f" {mod}")
533
+ if len(modules) > 50:
534
+ Console.write_stdout(f" ... and {len(modules) - 50} more")
535
+
536
+ # Help
537
+ elif cmd in ('help', 'h', '?'):
538
+ self._print_help()
539
+
540
+ # Execute arbitrary Python code
109
541
  else:
110
- logging.getLogger('geai').info(f"Executing interactive command: {command}")
542
+ self.logger.info(f"Executing interactive command: {command}")
111
543
  try:
112
- exec(command, frame.f_globals, frame.f_locals)
544
+ # Try exec first, if it fails try eval and print result
545
+ try:
546
+ exec(command, self.current_frame.f_globals, self.current_frame.f_locals)
547
+ except SyntaxError:
548
+ result = eval(command, self.current_frame.f_globals, self.current_frame.f_locals)
549
+ Console.write_stdout(repr(result))
113
550
  except Exception as e:
114
- logging.getLogger('geai').error(f"Command execution failed: {e}")
551
+ self.logger.error(f"Command execution failed: {e}")
552
+ Console.write_stdout(f"Error: {e}")
553
+
115
554
  except EOFError:
116
- logging.getLogger('geai').info("Debugger terminated by user (EOF).")
555
+ self.logger.info("Debugger terminated by user (EOF).")
117
556
  sys.exit(0)
118
557
  except KeyboardInterrupt:
119
- logging.getLogger('geai').info("Keyboard interrupt received. Continuing execution.")
558
+ self.logger.info("Keyboard interrupt received. Continuing execution.")
120
559
  break
121
560
 
561
+ def _print_help(self):
562
+ """Print help message."""
563
+ help_text = """
564
+ Available commands:
565
+
566
+ Flow Control:
567
+ continue, c Resume execution until next breakpoint
568
+ step, s Step into function calls
569
+ next, n Step over function calls (same level)
570
+ return, ret Continue until current function returns
571
+ run, r Run program to completion (disable tracing)
572
+ quit, q Exit the debugger
573
+
574
+ Stack Navigation:
575
+ where, w, bt Show stack trace
576
+ up, u Move up one stack frame
577
+ down, d Move down one stack frame
578
+
579
+ Source Display:
580
+ list, l [n] Show source code (n lines of context, default 10)
581
+
582
+ Variable Inspection:
583
+ print, p <expr> Evaluate and print expression
584
+ pp <expr> Pretty-print expression
585
+ locals, loc Show local variables
586
+ globals, glob Show global variables
587
+ args, a Show function arguments
588
+
589
+ Breakpoints:
590
+ break, b List all breakpoints
591
+ b <func> Set breakpoint on function
592
+ b <module>:<func> Set breakpoint on module:function
593
+ clear, cl <bp> Remove breakpoint
594
+ clearall, cla Remove all breakpoints
595
+ enable, en <bp> Enable breakpoint
596
+ disable, dis <bp> Disable breakpoint
597
+
598
+ Legacy Commands:
599
+ breakpoint-module, bm Add module breakpoint (interactive)
600
+ breakpoint-function, bf Add function breakpoint (interactive)
601
+ list-modules, lm List available modules
602
+
603
+ Other:
604
+ help, h, ? Show this help
605
+ <Python code> Execute arbitrary Python code
606
+
607
+ Examples:
608
+ p sys.argv Print command-line arguments
609
+ b main Set breakpoint on any 'main' function
610
+ b pygeai.cli:main Set breakpoint on pygeai.cli.main
611
+ pp locals() Pretty-print all local variables
612
+ """
613
+ Console.write_stdout(help_text)
614
+
122
615
  def run(self):
123
- logging.getLogger('geai').info("Setting trace and running geai")
616
+ """Run the target callable under the debugger."""
617
+ self.logger.info("Setting trace and running target")
124
618
  sys.settrace(self.trace_function)
125
619
  try:
126
- geai()
620
+ self.target()
127
621
  except Exception as e:
128
- logging.getLogger('geai').error(f"geai execution failed: {e}")
622
+ self.logger.error(f"Target execution failed: {e}")
129
623
  raise
130
624
  finally:
131
- logging.getLogger('geai').info("Cleaning up trace")
625
+ self.logger.info("Cleaning up trace")
132
626
  sys.settrace(None)
133
627
 
134
628
 
135
629
  def main():
630
+ """Entry point for geai-dbg command."""
136
631
  dbg = Debugger()
137
632
  dbg.add_breakpoint(module='pygeai.cli.geai', function_name='main')
138
633
  dbg.run()