quantalogic 0.2.16__py3-none-any.whl → 0.2.18__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.
quantalogic/__init__.py CHANGED
@@ -11,10 +11,11 @@ warnings.filterwarnings(
11
11
 
12
12
 
13
13
  from .agent import Agent # noqa: E402
14
+ from .console_print_events import console_print_events # noqa: E402
15
+ from .console_print_token import console_print_token # noqa: E402
14
16
  from .event_emitter import EventEmitter # noqa: E402
15
17
  from .memory import AgentMemory, VariableMemory # noqa: E402
16
- from .print_event import console_print_events # noqa: E402
17
18
 
18
19
  """QuantaLogic package for AI-powered generative models."""
19
20
 
20
- __all__ = ["Agent", "EventEmitter", "AgentMemory", "VariableMemory", "console_print_events"]
21
+ __all__ = ["Agent", "EventEmitter", "AgentMemory", "VariableMemory", "console_print_events","console_print_token"]
quantalogic/agent.py CHANGED
@@ -8,7 +8,7 @@ from loguru import logger
8
8
  from pydantic import BaseModel, ConfigDict
9
9
 
10
10
  from quantalogic.event_emitter import EventEmitter
11
- from quantalogic.generative_model import GenerativeModel
11
+ from quantalogic.generative_model import GenerativeModel, ResponseStats, TokenUsage
12
12
  from quantalogic.memory import AgentMemory, Message, VariableMemory
13
13
  from quantalogic.prompts import system_prompt
14
14
  from quantalogic.tool_manager import ToolManager
@@ -71,6 +71,8 @@ class Agent(BaseModel):
71
71
  max_output_tokens: int = DEFAULT_MAX_OUTPUT_TOKENS
72
72
  max_iterations: int = 30
73
73
  system_prompt: str = ""
74
+ compact_every_n_iterations: int | None = None # Add this to the class attributes
75
+ max_tokens_working_memory: int | None = None # Add max_tokens_working_memory attribute
74
76
 
75
77
  def __init__(
76
78
  self,
@@ -81,10 +83,15 @@ class Agent(BaseModel):
81
83
  task_to_solve: str = "",
82
84
  specific_expertise: str = "General AI assistant with coding and problem-solving capabilities",
83
85
  get_environment: Callable[[], str] = get_environment,
86
+ compact_every_n_iterations: int | None = None, # New parameter
87
+ max_tokens_working_memory: int | None = None, # New parameter to set max working memory tokens
84
88
  ):
85
89
  """Initialize the agent with model, memory, tools, and configurations."""
86
90
  try:
87
91
  logger.debug("Initializing agent...")
92
+ # Create event emitter first
93
+ event_emitter = EventEmitter()
94
+
88
95
  # Add TaskCompleteTool to the tools list if not already present
89
96
  if TaskCompleteTool() not in tools:
90
97
  tools.append(TaskCompleteTool())
@@ -108,7 +115,7 @@ class Agent(BaseModel):
108
115
 
109
116
  logger.debug("Base class init started ...")
110
117
  super().__init__(
111
- model=GenerativeModel(model=model_name),
118
+ model=GenerativeModel(model=model_name, event_emitter=event_emitter),
112
119
  memory=memory,
113
120
  variable_store=VariableMemory(),
114
121
  tools=tool_manager,
@@ -116,19 +123,30 @@ class Agent(BaseModel):
116
123
  ask_for_user_validation=ask_for_user_validation,
117
124
  task_to_solve=task_to_solve,
118
125
  specific_expertise=specific_expertise,
126
+ event_emitter=event_emitter,
119
127
  )
128
+
129
+ # Set the new compact_every_n_iterations parameter
130
+ self.compact_every_n_iterations = compact_every_n_iterations or self.max_iterations
131
+ logger.debug(f"Memory will be compacted every {self.compact_every_n_iterations} iterations")
132
+
133
+ # Set the max_tokens_working_memory parameter
134
+ self.max_tokens_working_memory = max_tokens_working_memory
135
+ logger.debug(f"Max tokens for working memory set to: {self.max_tokens_working_memory}")
136
+
120
137
  logger.debug("Agent initialized successfully.")
121
138
  except Exception as e:
122
139
  logger.error(f"Failed to initialize agent: {str(e)}")
123
140
  raise
124
141
 
125
- def solve_task(self, task: str, max_iterations: int = 30) -> str:
142
+ def solve_task(self, task: str, max_iterations: int = 30, streaming: bool = False) -> str:
126
143
  """Solve the given task using the ReAct framework.
127
144
 
128
145
  Args:
129
146
  task (str): The task description.
130
147
  max_iterations (int, optional): Maximum number of iterations to attempt solving the task.
131
148
  Defaults to 30 to prevent infinite loops and ensure timely task completion.
149
+ streaming (bool, optional): Whether to use streaming mode for generating responses.
132
150
 
133
151
  Returns:
134
152
  str: The final response after task completion.
@@ -172,11 +190,34 @@ class Agent(BaseModel):
172
190
 
173
191
  self._compact_memory_if_needed(current_prompt)
174
192
 
175
- result = self.model.generate_with_history(messages_history=self.memory.memory, prompt=current_prompt)
193
+ if streaming:
194
+ # For streaming, collect the response chunks
195
+ content = ""
196
+ for chunk in self.model.generate_with_history(
197
+ messages_history=self.memory.memory, prompt=current_prompt, streaming=True
198
+ ):
199
+ content += chunk
200
+
201
+ # Create a response object similar to non-streaming mode
202
+ result = ResponseStats(
203
+ response=content,
204
+ usage=TokenUsage(
205
+ prompt_tokens=0, # We don't have token counts in streaming mode
206
+ completion_tokens=0,
207
+ total_tokens=0,
208
+ ),
209
+ model=self.model.model,
210
+ finish_reason="stop",
211
+ )
212
+ else:
213
+ result = self.model.generate_with_history(
214
+ messages_history=self.memory.memory, prompt=current_prompt, streaming=False
215
+ )
176
216
 
177
217
  content = result.response
178
- token_usage = result.usage
179
- self.total_tokens = token_usage.total_tokens
218
+ if not streaming: # Only update tokens for non-streaming mode
219
+ token_usage = result.usage
220
+ self.total_tokens = token_usage.total_tokens
180
221
 
181
222
  # Emit event: Task Think End
182
223
  self._emit_event(
@@ -187,7 +228,7 @@ class Agent(BaseModel):
187
228
  )
188
229
 
189
230
  # Process the assistant's response
190
- result = self._observe_response(result.response, iteration=self.current_iteration)
231
+ result = self._observe_response(content, iteration=self.current_iteration)
191
232
 
192
233
  current_prompt = result.next_prompt
193
234
 
@@ -237,9 +278,31 @@ class Agent(BaseModel):
237
278
  self.total_tokens = self.model.token_counter_with_history(message_history, prompt)
238
279
 
239
280
  def _compact_memory_if_needed(self, current_prompt: str = ""):
240
- """Compacts the memory if it exceeds the maximum occupancy."""
281
+ """Compacts the memory if it exceeds the maximum occupancy or token limit."""
241
282
  ratio_occupied = self._calculate_context_occupancy()
242
- if ratio_occupied >= MAX_OCCUPANCY:
283
+
284
+ # Compact memory if any of these conditions are met:
285
+ # 1. Memory occupancy exceeds MAX_OCCUPANCY, or
286
+ # 2. Current iteration is a multiple of compact_every_n_iterations, or
287
+ # 3. Working memory exceeds max_tokens_working_memory (if set)
288
+ should_compact_by_occupancy = ratio_occupied >= MAX_OCCUPANCY
289
+ should_compact_by_iteration = (
290
+ self.compact_every_n_iterations is not None and
291
+ self.current_iteration > 0 and
292
+ self.current_iteration % self.compact_every_n_iterations == 0
293
+ )
294
+ should_compact_by_token_limit = (
295
+ self.max_tokens_working_memory is not None and
296
+ self.total_tokens > self.max_tokens_working_memory
297
+ )
298
+
299
+ if should_compact_by_occupancy or should_compact_by_iteration or should_compact_by_token_limit:
300
+ if should_compact_by_occupancy:
301
+ logger.debug(f"Memory compaction triggered: Occupancy {ratio_occupied}% exceeds {MAX_OCCUPANCY}%")
302
+
303
+ if should_compact_by_iteration:
304
+ logger.debug(f"Memory compaction triggered: Iteration {self.current_iteration} is a multiple of {self.compact_every_n_iterations}")
305
+
243
306
  self._emit_event("memory_full")
244
307
  self.memory.compact()
245
308
  self.total_tokens = self.model.token_counter_with_history(self.memory.memory, current_prompt)
@@ -292,9 +355,10 @@ class Agent(BaseModel):
292
355
  is_repeated_call = self._is_repeated_tool_call(tool_name, arguments_with_values)
293
356
 
294
357
  if is_repeated_call:
295
- return self._handle_repeated_tool_call(tool_name, arguments_with_values)
358
+ executed_tool, response = self._handle_repeated_tool_call(tool_name, arguments_with_values)
359
+ else:
360
+ executed_tool, response = self._execute_tool(tool_name, tool, arguments_with_values)
296
361
 
297
- executed_tool, response = self._execute_tool(tool_name, tool, arguments_with_values)
298
362
  if not executed_tool:
299
363
  return self._handle_tool_execution_failure(response)
300
364
 
@@ -427,34 +491,26 @@ class Agent(BaseModel):
427
491
 
428
492
  # Format the response message
429
493
  formatted_response = (
430
- "\n"
431
- f"--- Observations for iteration {iteration} / max {self.max_iterations} ---\n"
432
- "\n"
433
- f"\n --- Tool execution result stored in variable ${variable_name}$ --- \n"
434
- "\n"
435
- f"<{variable_name}>\n{response_display}\n</{variable_name}>\n" + "\n"
436
- "\n"
437
- f"--- Tools --- \n"
438
- "\n"
439
- f"{self._get_tools_names_prompt()}"
440
- "\n"
441
- f"--- Variables --- \n"
442
- "\n"
443
- f"{self._get_variable_prompt()}\n"
444
- "\n"
445
- "You must analyze this answer and evaluate what to do next to solve the task.\n"
446
- "If the step failed, take a step back and rethink your approach.\n"
447
- "\n"
448
- "--- Task to solve summary ---\n"
449
- "\n"
450
- f"{self.task_to_solve_summary}"
451
- "\n"
494
+ f"\n--- Observations for iteration {iteration} / max {self.max_iterations} ---\n"
495
+ f"\n--- Tool execution result in ${variable_name}$ ---\n"
496
+ f"<{variable_name}>\n{response_display}\n</{variable_name}>\n\n"
497
+ f"--- Tools ---\n{self._get_tools_names_prompt()}\n"
498
+ f"--- Variables ---\n{self._get_variable_prompt()}\n"
499
+ "Analyze this response to determine the next steps. If the step failed, reconsider your approach.\n"
500
+ f"--- Task to solve summary ---\n{self.task_to_solve_summary}\n"
452
501
  "--- Format ---\n"
453
- "\n"
454
- "You MUST respond with exactly two XML blocks formatted in markdown:\n"
455
- "\n"
456
- " - One <thinking> block detailing your analysis,\n"
457
- " - One <tool_name> block specifying the chosen tool and its arguments, as outlined in the system prompt.\n"
502
+ "Respond only with two XML blocks in markdown as specified in system prompt.\n"
503
+ "No extra comments must be added.\n"
504
+ "```xml\n"
505
+ "<thinking>\n"
506
+ "...\n"
507
+ "</thinking>\n"
508
+ "```\n"
509
+ "```xml\n"
510
+ "< ...tool_name... >\n"
511
+ "...\n"
512
+ "</ ...tool_name... >\n"
513
+ "```"
458
514
  )
459
515
 
460
516
  return formatted_response
@@ -5,9 +5,11 @@
5
5
  # Local application imports
6
6
  from quantalogic.agent import Agent
7
7
  from quantalogic.coding_agent import create_coding_agent
8
+ from quantalogic.console_print_token import console_print_token
8
9
  from quantalogic.tools import (
9
10
  AgentTool,
10
11
  DownloadHttpFileTool,
12
+ DuckDuckGoSearchTool,
11
13
  EditWholeContentTool,
12
14
  ExecuteBashCommandTool,
13
15
  InputQuestionTool,
@@ -23,20 +25,28 @@ from quantalogic.tools import (
23
25
  RipgrepTool,
24
26
  SearchDefinitionNames,
25
27
  TaskCompleteTool,
26
- WriteFileTool,
27
- DuckDuckGoSearchTool,
28
28
  WikipediaSearchTool,
29
+ WriteFileTool,
29
30
  )
30
31
 
31
32
  MODEL_NAME = "deepseek/deepseek-chat"
32
33
 
33
34
 
34
- def create_agent(model_name: str, vision_model_name: str | None) -> Agent:
35
+ def create_agent(
36
+ model_name: str,
37
+ vision_model_name: str | None,
38
+ no_stream: bool = False,
39
+ compact_every_n_iteration: int | None = None,
40
+ max_tokens_working_memory: int | None = None
41
+ ) -> Agent:
35
42
  """Create an agent with the specified model and tools.
36
43
 
37
44
  Args:
38
45
  model_name (str): Name of the model to use
39
46
  vision_model_name (str | None): Name of the vision model to use
47
+ no_stream (bool, optional): If True, the agent will not stream results.
48
+ compact_every_n_iteration (int | None, optional): Frequency of memory compaction.
49
+ max_tokens_working_memory (int | None, optional): Maximum tokens for working memory.
40
50
 
41
51
  Returns:
42
52
  Agent: An agent with the specified model and tools
@@ -54,25 +64,36 @@ def create_agent(model_name: str, vision_model_name: str | None) -> Agent:
54
64
  RipgrepTool(),
55
65
  SearchDefinitionNames(),
56
66
  MarkitdownTool(),
57
- LLMTool(model_name=model_name),
67
+ LLMTool(model_name=model_name, on_token=console_print_token if not no_stream else None),
58
68
  DownloadHttpFileTool(),
59
69
  ]
60
70
 
61
71
  if vision_model_name:
62
- tools.append(LLMVisionTool(model_name=vision_model_name))
72
+ tools.append(LLMVisionTool(model_name=vision_model_name, on_token=console_print_token if not no_stream else None))
63
73
 
64
74
  return Agent(
65
75
  model_name=model_name,
66
76
  tools=tools,
77
+ compact_every_n_iterations=compact_every_n_iteration,
78
+ max_tokens_working_memory=max_tokens_working_memory,
67
79
  )
68
80
 
69
81
 
70
- def create_interpreter_agent(model_name: str, vision_model_name: str | None) -> Agent:
82
+ def create_interpreter_agent(
83
+ model_name: str,
84
+ vision_model_name: str | None,
85
+ no_stream: bool = False,
86
+ compact_every_n_iteration: int | None = None,
87
+ max_tokens_working_memory: int | None = None
88
+ ) -> Agent:
71
89
  """Create an interpreter agent with the specified model and tools.
72
90
 
73
91
  Args:
74
92
  model_name (str): Name of the model to use
75
93
  vision_model_name (str | None): Name of the vision model to use
94
+ no_stream (bool, optional): If True, the agent will not stream results.
95
+ compact_every_n_iteration (int | None, optional): Frequency of memory compaction.
96
+ max_tokens_working_memory (int | None, optional): Maximum tokens for working memory.
76
97
 
77
98
  Returns:
78
99
  Agent: An interpreter agent with the specified model and tools
@@ -92,18 +113,32 @@ def create_interpreter_agent(model_name: str, vision_model_name: str | None) ->
92
113
  NodeJsTool(),
93
114
  SearchDefinitionNames(),
94
115
  MarkitdownTool(),
95
- LLMTool(model_name=model_name),
116
+ LLMTool(model_name=model_name, on_token=console_print_token if not no_stream else None),
96
117
  DownloadHttpFileTool(),
97
118
  ]
98
- return Agent(model_name=model_name, tools=tools)
119
+ return Agent(
120
+ model_name=model_name,
121
+ tools=tools,
122
+ compact_every_n_iterations=compact_every_n_iteration,
123
+ max_tokens_working_memory=max_tokens_working_memory,
124
+ )
99
125
 
100
126
 
101
- def create_full_agent(model_name: str, vision_model_name: str | None) -> Agent:
127
+ def create_full_agent(
128
+ model_name: str,
129
+ vision_model_name: str | None,
130
+ no_stream: bool = False,
131
+ compact_every_n_iteration: int | None = None,
132
+ max_tokens_working_memory: int | None = None
133
+ ) -> Agent:
102
134
  """Create an agent with the specified model and many tools.
103
135
 
104
136
  Args:
105
137
  model_name (str): Name of the model to use
106
138
  vision_model_name (str | None): Name of the vision model to use
139
+ no_stream (bool, optional): If True, the agent will not stream results.
140
+ compact_every_n_iteration (int | None, optional): Frequency of memory compaction.
141
+ max_tokens_working_memory (int | None, optional): Maximum tokens for working memory.
107
142
 
108
143
  Returns:
109
144
  Agent: An agent with the specified model and tools
@@ -124,27 +159,38 @@ def create_full_agent(model_name: str, vision_model_name: str | None) -> Agent:
124
159
  NodeJsTool(),
125
160
  SearchDefinitionNames(),
126
161
  MarkitdownTool(),
127
- LLMTool(model_name=model_name),
162
+ LLMTool(model_name=model_name, on_token=console_print_token if not no_stream else None),
128
163
  DownloadHttpFileTool(),
129
164
  WikipediaSearchTool(),
130
165
  DuckDuckGoSearchTool(),
131
166
  ]
132
167
 
133
168
  if vision_model_name:
134
- tools.append(LLMVisionTool(model_name=vision_model_name))
169
+ tools.append(LLMVisionTool(model_name=vision_model_name,on_token=console_print_token if not no_stream else None))
135
170
 
136
171
  return Agent(
137
172
  model_name=model_name,
138
173
  tools=tools,
174
+ compact_every_n_iterations=compact_every_n_iteration,
175
+ max_tokens_working_memory=max_tokens_working_memory,
139
176
  )
140
177
 
141
178
 
142
- def create_orchestrator_agent(model_name: str, vision_model_name: str | None = None) -> Agent:
179
+ def create_orchestrator_agent(
180
+ model_name: str,
181
+ vision_model_name: str | None = None,
182
+ no_stream: bool = False,
183
+ compact_every_n_iteration: int | None = None,
184
+ max_tokens_working_memory: int | None = None
185
+ ) -> Agent:
143
186
  """Create an agent with the specified model and tools.
144
187
 
145
188
  Args:
146
189
  model_name (str): Name of the model to use
147
190
  vision_model_name (str | None): Name of the vision model to use
191
+ no_stream (bool, optional): If True, the agent will not stream results.
192
+ compact_every_n_iteration (int | None, optional): Frequency of memory compaction.
193
+ max_tokens_working_memory (int | None, optional): Maximum tokens for working memory.
148
194
 
149
195
  Returns:
150
196
  Agent: An agent with the specified model and tools
@@ -160,14 +206,16 @@ def create_orchestrator_agent(model_name: str, vision_model_name: str | None = N
160
206
  ReadFileBlockTool(),
161
207
  RipgrepTool(),
162
208
  SearchDefinitionNames(),
163
- LLMTool(model_name=MODEL_NAME),
209
+ LLMTool(model_name=model_name, on_token=console_print_token if not no_stream else None),
164
210
  AgentTool(agent=coding_agent_instance, agent_role="software expert", name="coder_agent_tool"),
165
211
  ]
166
212
 
167
213
  if vision_model_name:
168
- tools.append(LLMVisionTool(model_name=vision_model_name))
214
+ tools.append(LLMVisionTool(model_name=vision_model_name, on_token=console_print_token if not no_stream else None))
169
215
 
170
216
  return Agent(
171
217
  model_name=model_name,
172
218
  tools=tools,
219
+ compact_every_n_iterations=compact_every_n_iteration,
220
+ max_tokens_working_memory=max_tokens_working_memory,
173
221
  )
@@ -1,4 +1,5 @@
1
1
  from quantalogic.agent import Agent
2
+ from quantalogic.console_print_token import console_print_token
2
3
  from quantalogic.tools import (
3
4
  DuckDuckGoSearchTool,
4
5
  EditWholeContentTool,
@@ -19,13 +20,23 @@ from quantalogic.utils import get_coding_environment
19
20
  from quantalogic.utils.get_quantalogic_rules_content import get_quantalogic_rules_file_content
20
21
 
21
22
 
22
- def create_coding_agent(model_name: str, vision_model_name: str | None = None, basic: bool = False) -> Agent:
23
+ def create_coding_agent(
24
+ model_name: str,
25
+ vision_model_name: str | None = None,
26
+ basic: bool = False,
27
+ no_stream: bool = False,
28
+ compact_every_n_iteration: int | None = None,
29
+ max_tokens_working_memory: int | None = None
30
+ ) -> Agent:
23
31
  """Creates and configures a coding agent with a comprehensive set of tools.
24
32
 
25
33
  Args:
26
34
  model_name (str): Name of the language model to use for the agent's core capabilities
27
35
  vision_model_name (str | None): Name of the vision model to use for the agent's core capabilities
28
36
  basic (bool, optional): If True, the agent will be configured with a basic set of tools.
37
+ no_stream (bool, optional): If True, the agent will not stream results.
38
+ compact_every_n_iteration (int | None, optional): Frequency of memory compaction.
39
+ max_tokens_working_memory (int | None, optional): Maximum tokens for working memory.
29
40
 
30
41
  Returns:
31
42
  Agent: A fully configured coding agent instance with:
@@ -64,7 +75,7 @@ def create_coding_agent(model_name: str, vision_model_name: str | None = None, b
64
75
  ]
65
76
 
66
77
  if vision_model_name:
67
- tools.append(LLMVisionTool(model_name=vision_model_name))
78
+ tools.append(LLMVisionTool(model_name=vision_model_name, on_token=console_print_token if not no_stream else None))
68
79
 
69
80
  if not basic:
70
81
  tools.append(
@@ -72,6 +83,7 @@ def create_coding_agent(model_name: str, vision_model_name: str | None = None, b
72
83
  model_name=model_name,
73
84
  system_prompt="You are a software expert, your role is to answer coding questions.",
74
85
  name="coding_consultant", # Handles implementation-level coding questions
86
+ on_token=console_print_token if not no_stream else None,
75
87
  )
76
88
  )
77
89
  tools.append(
@@ -79,6 +91,7 @@ def create_coding_agent(model_name: str, vision_model_name: str | None = None, b
79
91
  model_name=model_name,
80
92
  system_prompt="You are a software architect, your role is to answer software architecture questions.",
81
93
  name="software_architect", # Handles system design and architecture questions
94
+ on_token=console_print_token if not no_stream else None,
82
95
  )
83
96
  )
84
97
 
@@ -87,4 +100,6 @@ def create_coding_agent(model_name: str, vision_model_name: str | None = None, b
87
100
  tools=tools,
88
101
  specific_expertise=specific_expertise,
89
102
  get_environment=get_coding_environment,
103
+ compact_every_n_iterations=compact_every_n_iteration,
104
+ max_tokens_working_memory=max_tokens_working_memory,
90
105
  )
@@ -1,5 +1,3 @@
1
- """Print events with rich formatting."""
2
-
3
1
  from typing import Any
4
2
 
5
3
  from rich import box
@@ -65,4 +63,4 @@ def console_print_events(event: str, data: dict[str, Any] | None = None):
65
63
  expand=True,
66
64
  )
67
65
 
68
- console.print(panel)
66
+ console.print(panel)
@@ -0,0 +1,16 @@
1
+ """Print events with rich formatting."""
2
+
3
+ from typing import Any
4
+
5
+ from rich.console import Console
6
+
7
+
8
+ def console_print_token(event: str, data: Any | None = None):
9
+ """Print a token with rich formatting.
10
+
11
+ Args:
12
+ event (str): The event name (e.g., 'stream_chunk')
13
+ data (Any | None): The token data to print
14
+ """
15
+ console = Console()
16
+ console.print(data, end="")
@@ -0,0 +1,50 @@
1
+ import subprocess
2
+ import os
3
+ import sys
4
+
5
+ def get_config_path():
6
+ """Get the absolute path to the mkdocs configuration file."""
7
+ return os.path.join(os.path.dirname(os.path.dirname(__file__)), 'mkdocs', 'mkdocs.yml')
8
+
9
+ def serve_docs():
10
+ """Serve MkDocs documentation locally."""
11
+ config_path = get_config_path()
12
+ try:
13
+ subprocess.run(['mkdocs', 'serve', '--config-file', config_path], check=True)
14
+ except subprocess.CalledProcessError as e:
15
+ print(f"Error serving documentation: {e}")
16
+ sys.exit(1)
17
+
18
+ def build_docs():
19
+ """Build MkDocs documentation."""
20
+ config_path = get_config_path()
21
+ try:
22
+ subprocess.run(['mkdocs', 'build', '--config-file', config_path], check=True)
23
+ print("Documentation built successfully.")
24
+ except subprocess.CalledProcessError as e:
25
+ print(f"Error building documentation: {e}")
26
+ sys.exit(1)
27
+
28
+ def deploy_docs():
29
+ """Deploy MkDocs documentation to GitHub Pages."""
30
+ config_path = get_config_path()
31
+ try:
32
+ subprocess.run(['mkdocs', 'gh-deploy', '--config-file', config_path], check=True)
33
+ print("Documentation deployed successfully.")
34
+ except subprocess.CalledProcessError as e:
35
+ print(f"Error deploying documentation: {e}")
36
+ sys.exit(1)
37
+
38
+ # Ensure the script can be run directly for testing
39
+ if __name__ == '__main__':
40
+ command = sys.argv[1] if len(sys.argv) > 1 else None
41
+
42
+ if command == 'serve':
43
+ serve_docs()
44
+ elif command == 'build':
45
+ build_docs()
46
+ elif command == 'deploy':
47
+ deploy_docs()
48
+ else:
49
+ print("Usage: python docs_cli.py [serve|build|deploy]")
50
+ sys.exit(1)