quantalogic 0.2.15__py3-none-any.whl → 0.2.17__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 +3 -2
- quantalogic/agent.py +58 -37
- quantalogic/agent_config.py +18 -13
- quantalogic/coding_agent.py +9 -3
- quantalogic/{print_event.py → console_print_events.py} +1 -3
- quantalogic/console_print_token.py +16 -0
- quantalogic/docs_cli.py +50 -0
- quantalogic/generative_model.py +80 -77
- quantalogic/main.py +122 -29
- quantalogic/prompts.py +1 -0
- quantalogic/search_agent.py +15 -7
- quantalogic/server/agent_server.py +2 -2
- quantalogic/tools/llm_tool.py +52 -11
- quantalogic/tools/llm_vision_tool.py +23 -7
- quantalogic/xml_parser.py +109 -49
- {quantalogic-0.2.15.dist-info → quantalogic-0.2.17.dist-info}/METADATA +21 -148
- {quantalogic-0.2.15.dist-info → quantalogic-0.2.17.dist-info}/RECORD +20 -18
- quantalogic-0.2.17.dist-info/entry_points.txt +6 -0
- quantalogic-0.2.15.dist-info/entry_points.txt +0 -3
- {quantalogic-0.2.15.dist-info → quantalogic-0.2.17.dist-info}/LICENSE +0 -0
- {quantalogic-0.2.15.dist-info → quantalogic-0.2.17.dist-info}/WHEEL +0 -0
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
|
@@ -85,6 +85,9 @@ class Agent(BaseModel):
|
|
85
85
|
"""Initialize the agent with model, memory, tools, and configurations."""
|
86
86
|
try:
|
87
87
|
logger.debug("Initializing agent...")
|
88
|
+
# Create event emitter first
|
89
|
+
event_emitter = EventEmitter()
|
90
|
+
|
88
91
|
# Add TaskCompleteTool to the tools list if not already present
|
89
92
|
if TaskCompleteTool() not in tools:
|
90
93
|
tools.append(TaskCompleteTool())
|
@@ -108,7 +111,7 @@ class Agent(BaseModel):
|
|
108
111
|
|
109
112
|
logger.debug("Base class init started ...")
|
110
113
|
super().__init__(
|
111
|
-
model=GenerativeModel(model=model_name),
|
114
|
+
model=GenerativeModel(model=model_name, event_emitter=event_emitter),
|
112
115
|
memory=memory,
|
113
116
|
variable_store=VariableMemory(),
|
114
117
|
tools=tool_manager,
|
@@ -116,19 +119,21 @@ class Agent(BaseModel):
|
|
116
119
|
ask_for_user_validation=ask_for_user_validation,
|
117
120
|
task_to_solve=task_to_solve,
|
118
121
|
specific_expertise=specific_expertise,
|
122
|
+
event_emitter=event_emitter,
|
119
123
|
)
|
120
124
|
logger.debug("Agent initialized successfully.")
|
121
125
|
except Exception as e:
|
122
126
|
logger.error(f"Failed to initialize agent: {str(e)}")
|
123
127
|
raise
|
124
128
|
|
125
|
-
def solve_task(self, task: str, max_iterations: int = 30) -> str:
|
129
|
+
def solve_task(self, task: str, max_iterations: int = 30, streaming: bool = False) -> str:
|
126
130
|
"""Solve the given task using the ReAct framework.
|
127
131
|
|
128
132
|
Args:
|
129
133
|
task (str): The task description.
|
130
134
|
max_iterations (int, optional): Maximum number of iterations to attempt solving the task.
|
131
135
|
Defaults to 30 to prevent infinite loops and ensure timely task completion.
|
136
|
+
streaming (bool, optional): Whether to use streaming mode for generating responses.
|
132
137
|
|
133
138
|
Returns:
|
134
139
|
str: The final response after task completion.
|
@@ -172,11 +177,34 @@ class Agent(BaseModel):
|
|
172
177
|
|
173
178
|
self._compact_memory_if_needed(current_prompt)
|
174
179
|
|
175
|
-
|
180
|
+
if streaming:
|
181
|
+
# For streaming, collect the response chunks
|
182
|
+
content = ""
|
183
|
+
for chunk in self.model.generate_with_history(
|
184
|
+
messages_history=self.memory.memory, prompt=current_prompt, streaming=True
|
185
|
+
):
|
186
|
+
content += chunk
|
187
|
+
|
188
|
+
# Create a response object similar to non-streaming mode
|
189
|
+
result = ResponseStats(
|
190
|
+
response=content,
|
191
|
+
usage=TokenUsage(
|
192
|
+
prompt_tokens=0, # We don't have token counts in streaming mode
|
193
|
+
completion_tokens=0,
|
194
|
+
total_tokens=0,
|
195
|
+
),
|
196
|
+
model=self.model.model,
|
197
|
+
finish_reason="stop",
|
198
|
+
)
|
199
|
+
else:
|
200
|
+
result = self.model.generate_with_history(
|
201
|
+
messages_history=self.memory.memory, prompt=current_prompt, streaming=False
|
202
|
+
)
|
176
203
|
|
177
204
|
content = result.response
|
178
|
-
|
179
|
-
|
205
|
+
if not streaming: # Only update tokens for non-streaming mode
|
206
|
+
token_usage = result.usage
|
207
|
+
self.total_tokens = token_usage.total_tokens
|
180
208
|
|
181
209
|
# Emit event: Task Think End
|
182
210
|
self._emit_event(
|
@@ -187,7 +215,7 @@ class Agent(BaseModel):
|
|
187
215
|
)
|
188
216
|
|
189
217
|
# Process the assistant's response
|
190
|
-
result = self._observe_response(
|
218
|
+
result = self._observe_response(content, iteration=self.current_iteration)
|
191
219
|
|
192
220
|
current_prompt = result.next_prompt
|
193
221
|
|
@@ -292,9 +320,10 @@ class Agent(BaseModel):
|
|
292
320
|
is_repeated_call = self._is_repeated_tool_call(tool_name, arguments_with_values)
|
293
321
|
|
294
322
|
if is_repeated_call:
|
295
|
-
|
323
|
+
executed_tool, response = self._handle_repeated_tool_call(tool_name, arguments_with_values)
|
324
|
+
else:
|
325
|
+
executed_tool, response = self._execute_tool(tool_name, tool, arguments_with_values)
|
296
326
|
|
297
|
-
executed_tool, response = self._execute_tool(tool_name, tool, arguments_with_values)
|
298
327
|
if not executed_tool:
|
299
328
|
return self._handle_tool_execution_failure(response)
|
300
329
|
|
@@ -406,7 +435,7 @@ class Agent(BaseModel):
|
|
406
435
|
|
407
436
|
formatted_response = (
|
408
437
|
"\n"
|
409
|
-
f"--- Observations for iteration {iteration} ---\n"
|
438
|
+
f"--- Observations for iteration {iteration} / max {self.max_iterations} ---\n"
|
410
439
|
"\n"
|
411
440
|
f"\n --- Tool execution result stored in variable ${variable_name}$ --- \n"
|
412
441
|
"\n"
|
@@ -427,34 +456,26 @@ class Agent(BaseModel):
|
|
427
456
|
|
428
457
|
# Format the response message
|
429
458
|
formatted_response = (
|
430
|
-
"\n"
|
431
|
-
f"---
|
432
|
-
"\n"
|
433
|
-
f"
|
434
|
-
"\n"
|
435
|
-
|
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()}"
|
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"
|
459
|
+
f"\n--- Observations for iteration {iteration} / max {self.max_iterations} ---\n"
|
460
|
+
f"\n--- Tool execution result in ${variable_name}$ ---\n"
|
461
|
+
f"<{variable_name}>\n{response_display}\n</{variable_name}>\n\n"
|
462
|
+
f"--- Tools ---\n{self._get_tools_names_prompt()}\n"
|
463
|
+
f"--- Variables ---\n{self._get_variable_prompt()}\n"
|
464
|
+
"Analyze this response to determine the next steps. If the step failed, reconsider your approach.\n"
|
465
|
+
f"--- Task to solve summary ---\n{self.task_to_solve_summary}\n"
|
452
466
|
"--- Format ---\n"
|
453
|
-
"
|
454
|
-
"
|
455
|
-
"\n"
|
456
|
-
"
|
457
|
-
"
|
467
|
+
"Respond only with two XML blocks in markdown as specified in system prompt.\n"
|
468
|
+
"No extra comments must be added.\n"
|
469
|
+
"```xml\n"
|
470
|
+
"<thinking>\n"
|
471
|
+
"...\n"
|
472
|
+
"</thinking>\n"
|
473
|
+
"```\n"
|
474
|
+
"```xml\n"
|
475
|
+
"< ...tool_name... >\n"
|
476
|
+
"...\n"
|
477
|
+
"</ ...tool_name... >\n"
|
478
|
+
"```"
|
458
479
|
)
|
459
480
|
|
460
481
|
return formatted_response
|
quantalogic/agent_config.py
CHANGED
@@ -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,20 @@ 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(model_name: str, vision_model_name: str | None, no_stream: bool = False) -> Agent:
|
35
36
|
"""Create an agent with the specified model and tools.
|
36
37
|
|
37
38
|
Args:
|
38
39
|
model_name (str): Name of the model to use
|
39
40
|
vision_model_name (str | None): Name of the vision model to use
|
41
|
+
no_stream (bool, optional): If True, the agent will not stream results.
|
40
42
|
|
41
43
|
Returns:
|
42
44
|
Agent: An agent with the specified model and tools
|
@@ -54,12 +56,12 @@ def create_agent(model_name: str, vision_model_name: str | None) -> Agent:
|
|
54
56
|
RipgrepTool(),
|
55
57
|
SearchDefinitionNames(),
|
56
58
|
MarkitdownTool(),
|
57
|
-
LLMTool(model_name=model_name),
|
59
|
+
LLMTool(model_name=model_name, on_token=console_print_token if not no_stream else None),
|
58
60
|
DownloadHttpFileTool(),
|
59
61
|
]
|
60
62
|
|
61
63
|
if vision_model_name:
|
62
|
-
tools.append(LLMVisionTool(model_name=vision_model_name))
|
64
|
+
tools.append(LLMVisionTool(model_name=vision_model_name, on_token=console_print_token if not no_stream else None))
|
63
65
|
|
64
66
|
return Agent(
|
65
67
|
model_name=model_name,
|
@@ -67,12 +69,13 @@ def create_agent(model_name: str, vision_model_name: str | None) -> Agent:
|
|
67
69
|
)
|
68
70
|
|
69
71
|
|
70
|
-
def create_interpreter_agent(model_name: str, vision_model_name: str | None) -> Agent:
|
72
|
+
def create_interpreter_agent(model_name: str, vision_model_name: str | None, no_stream: bool = False) -> Agent:
|
71
73
|
"""Create an interpreter agent with the specified model and tools.
|
72
74
|
|
73
75
|
Args:
|
74
76
|
model_name (str): Name of the model to use
|
75
77
|
vision_model_name (str | None): Name of the vision model to use
|
78
|
+
no_stream (bool, optional): If True, the agent will not stream results.
|
76
79
|
|
77
80
|
Returns:
|
78
81
|
Agent: An interpreter agent with the specified model and tools
|
@@ -92,18 +95,19 @@ def create_interpreter_agent(model_name: str, vision_model_name: str | None) ->
|
|
92
95
|
NodeJsTool(),
|
93
96
|
SearchDefinitionNames(),
|
94
97
|
MarkitdownTool(),
|
95
|
-
LLMTool(model_name=model_name),
|
98
|
+
LLMTool(model_name=model_name, on_token=console_print_token if not no_stream else None),
|
96
99
|
DownloadHttpFileTool(),
|
97
100
|
]
|
98
101
|
return Agent(model_name=model_name, tools=tools)
|
99
102
|
|
100
103
|
|
101
|
-
def create_full_agent(model_name: str, vision_model_name: str | None) -> Agent:
|
104
|
+
def create_full_agent(model_name: str, vision_model_name: str | None, no_stream: bool = False) -> Agent:
|
102
105
|
"""Create an agent with the specified model and many tools.
|
103
106
|
|
104
107
|
Args:
|
105
108
|
model_name (str): Name of the model to use
|
106
109
|
vision_model_name (str | None): Name of the vision model to use
|
110
|
+
no_stream (bool, optional): If True, the agent will not stream results.
|
107
111
|
|
108
112
|
Returns:
|
109
113
|
Agent: An agent with the specified model and tools
|
@@ -124,14 +128,14 @@ def create_full_agent(model_name: str, vision_model_name: str | None) -> Agent:
|
|
124
128
|
NodeJsTool(),
|
125
129
|
SearchDefinitionNames(),
|
126
130
|
MarkitdownTool(),
|
127
|
-
LLMTool(model_name=model_name),
|
131
|
+
LLMTool(model_name=model_name, on_token=console_print_token if not no_stream else None),
|
128
132
|
DownloadHttpFileTool(),
|
129
133
|
WikipediaSearchTool(),
|
130
134
|
DuckDuckGoSearchTool(),
|
131
135
|
]
|
132
136
|
|
133
137
|
if vision_model_name:
|
134
|
-
tools.append(LLMVisionTool(model_name=vision_model_name))
|
138
|
+
tools.append(LLMVisionTool(model_name=vision_model_name,on_token=console_print_token if not no_stream else None))
|
135
139
|
|
136
140
|
return Agent(
|
137
141
|
model_name=model_name,
|
@@ -139,12 +143,13 @@ def create_full_agent(model_name: str, vision_model_name: str | None) -> Agent:
|
|
139
143
|
)
|
140
144
|
|
141
145
|
|
142
|
-
def create_orchestrator_agent(model_name: str, vision_model_name: str | None = None) -> Agent:
|
146
|
+
def create_orchestrator_agent(model_name: str, vision_model_name: str | None = None, no_stream: bool = False) -> Agent:
|
143
147
|
"""Create an agent with the specified model and tools.
|
144
148
|
|
145
149
|
Args:
|
146
150
|
model_name (str): Name of the model to use
|
147
151
|
vision_model_name (str | None): Name of the vision model to use
|
152
|
+
no_stream (bool, optional): If True, the agent will not stream results.
|
148
153
|
|
149
154
|
Returns:
|
150
155
|
Agent: An agent with the specified model and tools
|
@@ -160,12 +165,12 @@ def create_orchestrator_agent(model_name: str, vision_model_name: str | None = N
|
|
160
165
|
ReadFileBlockTool(),
|
161
166
|
RipgrepTool(),
|
162
167
|
SearchDefinitionNames(),
|
163
|
-
LLMTool(model_name=
|
168
|
+
LLMTool(model_name=model_name, on_token=console_print_token if not no_stream else None),
|
164
169
|
AgentTool(agent=coding_agent_instance, agent_role="software expert", name="coder_agent_tool"),
|
165
170
|
]
|
166
171
|
|
167
172
|
if vision_model_name:
|
168
|
-
tools.append(LLMVisionTool(model_name=vision_model_name))
|
173
|
+
tools.append(LLMVisionTool(model_name=vision_model_name, on_token=console_print_token if not no_stream else None))
|
169
174
|
|
170
175
|
return Agent(
|
171
176
|
model_name=model_name,
|
quantalogic/coding_agent.py
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
from quantalogic.agent import Agent
|
2
|
+
from quantalogic.console_print_token import console_print_token
|
2
3
|
from quantalogic.tools import (
|
4
|
+
DuckDuckGoSearchTool,
|
3
5
|
EditWholeContentTool,
|
4
6
|
ExecuteBashCommandTool,
|
5
7
|
InputQuestionTool,
|
@@ -18,13 +20,14 @@ from quantalogic.utils import get_coding_environment
|
|
18
20
|
from quantalogic.utils.get_quantalogic_rules_content import get_quantalogic_rules_file_content
|
19
21
|
|
20
22
|
|
21
|
-
def create_coding_agent(model_name: str, vision_model_name: str | None = None, basic: bool = False) -> Agent:
|
23
|
+
def create_coding_agent(model_name: str, vision_model_name: str | None = None, basic: bool = False,no_stream: bool = False) -> Agent:
|
22
24
|
"""Creates and configures a coding agent with a comprehensive set of tools.
|
23
25
|
|
24
26
|
Args:
|
25
27
|
model_name (str): Name of the language model to use for the agent's core capabilities
|
26
28
|
vision_model_name (str | None): Name of the vision model to use for the agent's core capabilities
|
27
29
|
basic (bool, optional): If True, the agent will be configured with a basic set of tools.
|
30
|
+
no_stream (bool, optional): If True, the agent will not stream results.
|
28
31
|
|
29
32
|
Returns:
|
30
33
|
Agent: A fully configured coding agent instance with:
|
@@ -59,17 +62,19 @@ def create_coding_agent(model_name: str, vision_model_name: str | None = None, b
|
|
59
62
|
ReadFileTool(),
|
60
63
|
ExecuteBashCommandTool(),
|
61
64
|
InputQuestionTool(),
|
65
|
+
DuckDuckGoSearchTool(),
|
62
66
|
]
|
63
67
|
|
64
68
|
if vision_model_name:
|
65
|
-
tools.append(LLMVisionTool(model_name=vision_model_name))
|
69
|
+
tools.append(LLMVisionTool(model_name=vision_model_name, on_token=console_print_token if not no_stream else None))
|
66
70
|
|
67
71
|
if not basic:
|
68
72
|
tools.append(
|
69
73
|
LLMTool(
|
70
74
|
model_name=model_name,
|
71
75
|
system_prompt="You are a software expert, your role is to answer coding questions.",
|
72
|
-
name="coding_consultant", # Handles implementation-level coding questions
|
76
|
+
name="coding_consultant", # Handles implementation-level coding questions
|
77
|
+
on_token=console_print_token if not no_stream else None,
|
73
78
|
)
|
74
79
|
)
|
75
80
|
tools.append(
|
@@ -77,6 +82,7 @@ def create_coding_agent(model_name: str, vision_model_name: str | None = None, b
|
|
77
82
|
model_name=model_name,
|
78
83
|
system_prompt="You are a software architect, your role is to answer software architecture questions.",
|
79
84
|
name="software_architect", # Handles system design and architecture questions
|
85
|
+
on_token=console_print_token if not no_stream else None,
|
80
86
|
)
|
81
87
|
)
|
82
88
|
|
@@ -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="")
|
quantalogic/docs_cli.py
ADDED
@@ -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)
|