parent
b097821edd
commit
b446eca147
File diff suppressed because it is too large
Load Diff
@ -1,126 +0,0 @@
|
||||
import uuid
|
||||
import time
|
||||
from typing import List, Literal, Optional, Union
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class ModelCard(BaseModel):
|
||||
"""
|
||||
A Pydantic model representing a model card, which provides metadata about a machine learning model.
|
||||
It includes fields like model ID, owner, and creation time.
|
||||
"""
|
||||
|
||||
id: str
|
||||
object: str = "model"
|
||||
created: int = Field(default_factory=lambda: int(time.time()))
|
||||
owned_by: str = "owner"
|
||||
root: Optional[str] = None
|
||||
parent: Optional[str] = None
|
||||
permission: Optional[list] = None
|
||||
|
||||
|
||||
class ModelList(BaseModel):
|
||||
object: str = "list"
|
||||
data: List[ModelCard] = []
|
||||
|
||||
|
||||
class ImageUrl(BaseModel):
|
||||
url: str
|
||||
|
||||
|
||||
class TextContent(BaseModel):
|
||||
type: Literal["text"]
|
||||
text: str
|
||||
|
||||
|
||||
class ImageUrlContent(BaseModel):
|
||||
type: Literal["image_url"]
|
||||
image_url: ImageUrl
|
||||
|
||||
|
||||
ContentItem = Union[TextContent, ImageUrlContent]
|
||||
|
||||
|
||||
class ChatMessageInput(BaseModel):
|
||||
role: str = Field(
|
||||
...,
|
||||
description="The role of the message sender. Could be 'user', 'assistant', or 'system'.",
|
||||
)
|
||||
content: Union[str, List[ContentItem]]
|
||||
|
||||
|
||||
class ChatMessageResponse(BaseModel):
|
||||
role: str = Field(
|
||||
...,
|
||||
description="The role of the message sender. Could be 'user', 'assistant', or 'system'.",
|
||||
)
|
||||
content: str = None
|
||||
|
||||
|
||||
class DeltaMessage(BaseModel):
|
||||
role: Optional[Literal["user", "assistant", "system"]] = None
|
||||
content: Optional[str] = None
|
||||
|
||||
|
||||
class ChatCompletionRequest(BaseModel):
|
||||
model: str = "gpt-4o"
|
||||
messages: List[ChatMessageInput]
|
||||
temperature: Optional[float] = 0.8
|
||||
top_p: Optional[float] = 0.8
|
||||
max_tokens: Optional[int] = 4000
|
||||
stream: Optional[bool] = False
|
||||
repetition_penalty: Optional[float] = 1.0
|
||||
echo: Optional[bool] = False
|
||||
|
||||
|
||||
class ChatCompletionResponseChoice(BaseModel):
|
||||
index: int = Field(..., description="The index of the choice.")
|
||||
input: str = Field(..., description="The input message.")
|
||||
message: ChatMessageResponse = Field(
|
||||
..., description="The output message."
|
||||
)
|
||||
|
||||
|
||||
class ChatCompletionResponseStreamChoice(BaseModel):
|
||||
index: int
|
||||
delta: DeltaMessage
|
||||
|
||||
|
||||
class UsageInfo(BaseModel):
|
||||
prompt_tokens: int = 0
|
||||
total_tokens: int = 0
|
||||
completion_tokens: Optional[int] = 0
|
||||
|
||||
|
||||
class ChatCompletionResponse(BaseModel):
|
||||
model: str
|
||||
object: Literal["chat.completion", "chat.completion.chunk"]
|
||||
choices: List[
|
||||
Union[
|
||||
ChatCompletionResponseChoice,
|
||||
ChatCompletionResponseStreamChoice,
|
||||
]
|
||||
]
|
||||
created: Optional[int] = Field(
|
||||
default_factory=lambda: int(time.time())
|
||||
)
|
||||
|
||||
|
||||
class AgentChatCompletionResponse(BaseModel):
|
||||
id: Optional[str] = Field(
|
||||
f"agent-{uuid.uuid4().hex}",
|
||||
description="The ID of the agent that generated the completion response.",
|
||||
)
|
||||
agent_name: Optional[str] = Field(
|
||||
...,
|
||||
description="The name of the agent that generated the completion response.",
|
||||
)
|
||||
object: Optional[
|
||||
Literal["chat.completion", "chat.completion.chunk"]
|
||||
] = None
|
||||
choices: Optional[ChatCompletionResponseChoice] = None
|
||||
created: Optional[int] = Field(
|
||||
default_factory=lambda: int(time.time())
|
||||
)
|
||||
# full_usage: Optional[UsageInfo]
|
@ -1,42 +0,0 @@
|
||||
# In order to accelerate the ops of creating files, we use the async file creation method.
|
||||
import os
|
||||
import asyncio
|
||||
from aiofiles import open as aio_open
|
||||
from typing import List
|
||||
|
||||
|
||||
async def async_create_file(file_path: str, content: str) -> None:
|
||||
async with aio_open(file_path, "w") as file:
|
||||
await file.write(content)
|
||||
|
||||
|
||||
async def create_multiple_files(
|
||||
file_paths: List[str], contents: List[str]
|
||||
) -> None:
|
||||
tasks = [
|
||||
async_create_file(
|
||||
(file_path, content)
|
||||
for file_path, content in zip(file_paths, contents)
|
||||
)
|
||||
]
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
|
||||
async def create_file_with_directory(
|
||||
file_path: str, content: str
|
||||
) -> None:
|
||||
"""
|
||||
Creates a file with the specified directory path and content.
|
||||
|
||||
Args:
|
||||
file_path (str): The path of the file to be created.
|
||||
content (str): The content to be written to the file.
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
directory = os.path.dirname(file_path)
|
||||
if not os.path.exists(directory):
|
||||
os.makedirs(directory)
|
||||
|
||||
await async_create_file(file_path, content)
|
@ -1,127 +0,0 @@
|
||||
import time
|
||||
from os import cpu_count
|
||||
from typing import Any, Callable, List, Optional
|
||||
|
||||
from loguru import logger
|
||||
from pathos.multiprocessing import ProcessingPool as Pool
|
||||
|
||||
|
||||
from typing import Tuple
|
||||
|
||||
|
||||
def execute_parallel_optimized(
|
||||
callables_with_args: List[
|
||||
Tuple[Callable[..., Any], Tuple[Any, ...]]
|
||||
],
|
||||
max_workers: Optional[int] = None,
|
||||
chunk_size: Optional[int] = None,
|
||||
retries: int = 3,
|
||||
**kwargs,
|
||||
) -> List[Any]:
|
||||
"""
|
||||
Executes a list of callables in parallel, leveraging all available CPU cores.
|
||||
|
||||
This function is optimized for high performance and reliability.
|
||||
|
||||
Args:
|
||||
callables_with_args (List[Tuple[Callable[..., Any], Tuple[Any, ...]]]):
|
||||
A list of tuples, where each tuple contains a callable and a tuple of its arguments.
|
||||
max_workers (Optional[int]): The maximum number of workers to use. Defaults to the number of available cores.
|
||||
chunk_size (Optional[int]): The size of chunks to split the tasks into for balanced execution. Defaults to automatic chunking.
|
||||
retries (int): Number of retries for a failed task. Default is 3.
|
||||
|
||||
Returns:
|
||||
List[Any]: A list of results from each callable. The order corresponds to the order of the input list.
|
||||
|
||||
Raises:
|
||||
Exception: Any exception raised by the callable will be logged and re-raised after retries are exhausted.
|
||||
"""
|
||||
max_workers = cpu_count() if max_workers is None else max_workers
|
||||
results = []
|
||||
logger.info(
|
||||
f"Starting optimized parallel execution of {len(callables_with_args)} tasks."
|
||||
)
|
||||
|
||||
pool = Pool(
|
||||
nodes=max_workers, **kwargs
|
||||
) # Initialize the pool once
|
||||
|
||||
def _execute_with_retry(callable_, args, retries):
|
||||
attempt = 0
|
||||
while attempt < retries:
|
||||
try:
|
||||
result = callable_(*args)
|
||||
logger.info(
|
||||
f"Task {callable_} with args {args} completed successfully."
|
||||
)
|
||||
return result
|
||||
except Exception as e:
|
||||
attempt += 1
|
||||
logger.warning(
|
||||
f"Task {callable_} with args {args} failed on attempt {attempt}: {e}"
|
||||
)
|
||||
time.sleep(1) # Small delay before retrying
|
||||
if attempt >= retries:
|
||||
logger.error(
|
||||
f"Task {callable_} with args {args} failed after {retries} retries."
|
||||
)
|
||||
raise
|
||||
|
||||
try:
|
||||
if chunk_size is None:
|
||||
chunk_size = (
|
||||
len(callables_with_args)
|
||||
// (max_workers or pool.ncpus)
|
||||
or 1
|
||||
)
|
||||
|
||||
# Use chunking and mapping for efficient execution
|
||||
results = pool.map(
|
||||
lambda item: _execute_with_retry(
|
||||
item[0], item[1], retries
|
||||
),
|
||||
callables_with_args,
|
||||
chunksize=chunk_size,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.critical(
|
||||
f"Parallel execution failed due to an error: {e}"
|
||||
)
|
||||
raise
|
||||
|
||||
logger.info(
|
||||
f"Optimized parallel execution completed. {len(results)} tasks executed."
|
||||
)
|
||||
pool.close() # Ensure pool is properly closed
|
||||
pool.join()
|
||||
|
||||
|
||||
# return results
|
||||
|
||||
|
||||
# def add(a, b):
|
||||
# return a + b
|
||||
|
||||
|
||||
# def multiply(a, b):
|
||||
# return a * b
|
||||
|
||||
|
||||
# def power(a, b):
|
||||
# return a**b
|
||||
|
||||
|
||||
# # if __name__ == "__main__":
|
||||
# # # List of callables with their respective arguments
|
||||
# # callables_with_args = [
|
||||
# # (add, (2, 3)),
|
||||
# # (multiply, (5, 4)),
|
||||
# # (power, (2, 10)),
|
||||
# # ]
|
||||
|
||||
# # # Execute the callables in parallel
|
||||
# # results = execute_parallel_optimized(callables_with_args)
|
||||
|
||||
# # # Print the results
|
||||
# # print("Results:", results)
|
@ -1,82 +0,0 @@
|
||||
import subprocess
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from loguru import logger
|
||||
from pydantic import BaseModel
|
||||
|
||||
from swarms.structs.agent import Agent
|
||||
|
||||
|
||||
try:
|
||||
import pandas as pd
|
||||
except ImportError:
|
||||
logger.error("Failed to import pandas")
|
||||
subprocess.run(["pip", "install", "pandas"])
|
||||
import pandas as pd
|
||||
|
||||
|
||||
def display_agents_info(agents: List[Agent]) -> None:
|
||||
"""
|
||||
Displays information about all agents in a list using a DataFrame.
|
||||
|
||||
:param agents: List of Agent instances.
|
||||
"""
|
||||
# Extracting relevant information from each agent
|
||||
agent_data = []
|
||||
for agent in agents:
|
||||
try:
|
||||
agent_info = {
|
||||
"ID": agent.id,
|
||||
"Name": agent.agent_name,
|
||||
"Description": agent.description,
|
||||
"max_loops": agent.max_loops,
|
||||
# "Docs": agent.docs,
|
||||
"System Prompt": agent.system_prompt,
|
||||
"LLM Model": agent.llm.model_name, # type: ignore
|
||||
}
|
||||
agent_data.append(agent_info)
|
||||
except AttributeError as e:
|
||||
logger.error(
|
||||
f"Failed to extract information from agent {agent}: {e}"
|
||||
)
|
||||
continue
|
||||
|
||||
# Creating a DataFrame to display the data
|
||||
try:
|
||||
df = pd.DataFrame(agent_data)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to create DataFrame: {e}")
|
||||
return
|
||||
|
||||
# Displaying the DataFrame
|
||||
try:
|
||||
print(df)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to print DataFrame: {e}")
|
||||
|
||||
|
||||
def dict_to_dataframe(data: Dict[str, Any]) -> pd.DataFrame:
|
||||
"""
|
||||
Converts a dictionary into a pandas DataFrame.
|
||||
|
||||
:param data: Dictionary to convert.
|
||||
:return: A pandas DataFrame representation of the dictionary.
|
||||
"""
|
||||
# Convert dictionary to DataFrame
|
||||
df = pd.json_normalize(data)
|
||||
return df
|
||||
|
||||
|
||||
def pydantic_model_to_dataframe(model: BaseModel) -> pd.DataFrame:
|
||||
"""
|
||||
Converts a Pydantic Base Model into a pandas DataFrame.
|
||||
|
||||
:param model: Pydantic Base Model to convert.
|
||||
:return: A pandas DataFrame representation of the Pydantic model.
|
||||
"""
|
||||
# Convert Pydantic model to dictionary
|
||||
model_dict = model.dict()
|
||||
|
||||
# Convert dictionary to DataFrame
|
||||
df = dict_to_dataframe(model_dict)
|
||||
return df
|
@ -1,98 +0,0 @@
|
||||
from functools import wraps
|
||||
from loguru import logger
|
||||
import tracemalloc
|
||||
import psutil
|
||||
import time
|
||||
from typing import Callable, Any
|
||||
|
||||
|
||||
def profile_all(func: Callable) -> Callable:
|
||||
"""
|
||||
A decorator to profile memory usage, CPU usage, and I/O operations
|
||||
of a function and log the data using loguru.
|
||||
|
||||
It combines tracemalloc for memory profiling, psutil for CPU and I/O operations,
|
||||
and measures execution time.
|
||||
|
||||
Args:
|
||||
func (Callable): The function to be profiled.
|
||||
|
||||
Returns:
|
||||
Callable: The wrapped function with profiling enabled.
|
||||
"""
|
||||
|
||||
@wraps(func)
|
||||
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
||||
# Start memory tracking
|
||||
tracemalloc.start()
|
||||
|
||||
# Get initial CPU stats
|
||||
process = psutil.Process()
|
||||
initial_cpu_times = process.cpu_times()
|
||||
|
||||
# Get initial I/O stats if available
|
||||
try:
|
||||
initial_io_counters = process.io_counters()
|
||||
io_tracking_available = True
|
||||
except AttributeError:
|
||||
logger.warning(
|
||||
"I/O counters not available on this platform."
|
||||
)
|
||||
io_tracking_available = False
|
||||
|
||||
# Start timing the function execution
|
||||
start_time = time.time()
|
||||
|
||||
# Execute the function
|
||||
result = func(*args, **kwargs)
|
||||
|
||||
# Stop timing
|
||||
end_time = time.time()
|
||||
execution_time = end_time - start_time
|
||||
|
||||
# Get final CPU stats
|
||||
final_cpu_times = process.cpu_times()
|
||||
|
||||
# Get final I/O stats if available
|
||||
if io_tracking_available:
|
||||
final_io_counters = process.io_counters()
|
||||
io_read_count = (
|
||||
final_io_counters.read_count
|
||||
- initial_io_counters.read_count
|
||||
)
|
||||
io_write_count = (
|
||||
final_io_counters.write_count
|
||||
- initial_io_counters.write_count
|
||||
)
|
||||
else:
|
||||
io_read_count = io_write_count = 0
|
||||
|
||||
# Get memory usage statistics
|
||||
snapshot = tracemalloc.take_snapshot()
|
||||
top_stats = snapshot.statistics("lineno")
|
||||
|
||||
# Calculate CPU usage
|
||||
cpu_usage = (
|
||||
final_cpu_times.user
|
||||
- initial_cpu_times.user
|
||||
+ final_cpu_times.system
|
||||
- initial_cpu_times.system
|
||||
)
|
||||
|
||||
# Log the data
|
||||
logger.info(f"Execution time: {execution_time:.4f} seconds")
|
||||
logger.info(f"CPU usage: {cpu_usage:.2f} seconds")
|
||||
if io_tracking_available:
|
||||
logger.info(
|
||||
f"I/O Operations - Read: {io_read_count}, Write: {io_write_count}"
|
||||
)
|
||||
logger.info("Top memory usage:")
|
||||
for stat in top_stats[:10]:
|
||||
logger.info(stat)
|
||||
|
||||
# Stop memory tracking
|
||||
tracemalloc.stop()
|
||||
|
||||
return result
|
||||
|
||||
return wrapper
|
@ -0,0 +1,363 @@
|
||||
import os
|
||||
from typing import List
|
||||
|
||||
from dotenv import load_dotenv
|
||||
from loguru import logger
|
||||
from swarm_models import OpenAIChat
|
||||
|
||||
from swarms import Agent
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
from typing import Set
|
||||
|
||||
load_dotenv()
|
||||
|
||||
# Get the OpenAI API key from the environment variable
|
||||
api_key = os.getenv("OPENAI_API_KEY")
|
||||
|
||||
# Create an instance of the OpenAIChat class
|
||||
|
||||
SYS_PROMPT = """
|
||||
|
||||
### System Prompt for API Reference Documentation Generator
|
||||
|
||||
You are an expert documentation generator agent. Your task is to produce **high-quality Python API reference documentation** for functions and classes in Python codebases. The codebase does **not include any web APIs**, only Python functions, methods, classes, and constants. You will generate clear, concise, and professional documentation based on the structure and functionality of the given code.
|
||||
Don't use the one hashtag for the title, only use 3 hashtags for the module path
|
||||
|
||||
**Instructions:**
|
||||
1. **Documentation Style**: Follow a consistent format for documenting Python functions and classes.
|
||||
- For functions, provide:
|
||||
- **Name** of the function.
|
||||
- **Description** of what the function does.
|
||||
- **Parameters** with type annotations and a description for each parameter.
|
||||
- **Return Type** and a description of what is returned.
|
||||
- **Example Usage** in code block format.
|
||||
- For classes, provide:
|
||||
- **Name** of the class.
|
||||
- **Description** of the class and its purpose.
|
||||
- **Attributes** with a description of each attribute and its type.
|
||||
- **Methods** with the same details as functions (description, parameters, return types).
|
||||
- **Example Usage** in code block format.
|
||||
- For constants, briefly describe their purpose and value.
|
||||
|
||||
2. **Many-shot examples**:
|
||||
- Provide multiple examples of documenting both **functions** and **classes** based on the given code.
|
||||
|
||||
### Many-Shot Examples:
|
||||
|
||||
#### Example 1: Function Documentation
|
||||
|
||||
```python
|
||||
def add_numbers(a: int, b: int) -> int:
|
||||
return a + b
|
||||
```
|
||||
|
||||
**Documentation:**
|
||||
|
||||
### `add_numbers(a: int, b: int) -> int`
|
||||
**Description**:
|
||||
Adds two integers and returns their sum.
|
||||
|
||||
**Parameters**:
|
||||
- `a` (`int`): The first integer.
|
||||
- `b` (`int`): The second integer.
|
||||
|
||||
**Return**:
|
||||
- (`int`): The sum of the two input integers.
|
||||
|
||||
**Example**:
|
||||
```python
|
||||
result = add_numbers(3, 5)
|
||||
print(result) # Output: 8
|
||||
```
|
||||
|
||||
#### Example 2: Function Documentation
|
||||
|
||||
```python
|
||||
def greet_user(name: str) -> str:
|
||||
return f"Hello, {name}!"
|
||||
```
|
||||
|
||||
**Documentation:**
|
||||
|
||||
### `greet_user(name: str) -> str`
|
||||
**Description**:
|
||||
Returns a greeting message for the given user.
|
||||
|
||||
**Parameters**:
|
||||
- `name` (`str`): The name of the user to greet.
|
||||
|
||||
**Return**:
|
||||
- (`str`): A personalized greeting message.
|
||||
|
||||
**Example**:
|
||||
```python
|
||||
message = greet_user("Alice")
|
||||
print(message) # Output: "Hello, Alice!"
|
||||
```
|
||||
|
||||
#### Example 3: Class Documentation
|
||||
|
||||
```python
|
||||
class Calculator:
|
||||
def __init__(self):
|
||||
self.result = 0
|
||||
|
||||
def add(self, value: int) -> None:
|
||||
self.result += value
|
||||
|
||||
def reset(self) -> None:
|
||||
self.result = 0
|
||||
```
|
||||
|
||||
**Documentation:**
|
||||
|
||||
### `Calculator`
|
||||
**Description**:
|
||||
A simple calculator class that can add numbers and reset the result.
|
||||
|
||||
**Attributes**:
|
||||
- `result` (`int`): The current result of the calculator, initialized to 0.
|
||||
|
||||
**Methods**:
|
||||
|
||||
- `add(value: int) -> None`
|
||||
- **Description**: Adds the given value to the current result.
|
||||
- **Parameters**:
|
||||
- `value` (`int`): The value to add to the result.
|
||||
- **Return**: None.
|
||||
|
||||
- `reset() -> None`
|
||||
- **Description**: Resets the calculator result to 0.
|
||||
- **Parameters**: None.
|
||||
- **Return**: None.
|
||||
|
||||
**Example**:
|
||||
```python
|
||||
calc = Calculator()
|
||||
calc.add(5)
|
||||
print(calc.result) # Output: 5
|
||||
calc.reset()
|
||||
print(calc.result) # Output: 0
|
||||
```
|
||||
|
||||
#### Example 4: Constant Documentation
|
||||
|
||||
```python
|
||||
PI = 3.14159
|
||||
```
|
||||
|
||||
**Documentation:**
|
||||
|
||||
### `PI`
|
||||
**Description**:
|
||||
A constant representing the value of pi (π) to 5 decimal places.
|
||||
|
||||
**Value**:
|
||||
`3.14159`
|
||||
|
||||
|
||||
|
||||
"""
|
||||
|
||||
|
||||
class DocumentationAgent:
|
||||
def __init__(
|
||||
self,
|
||||
directory: str,
|
||||
output_file: str = "API_Reference.md",
|
||||
agent_name: str = "Documentation-Generator",
|
||||
):
|
||||
"""
|
||||
Initializes the DocumentationAgent.
|
||||
|
||||
:param directory: The root directory where the Python files are located.
|
||||
:param output_file: The file where all the documentation will be saved.
|
||||
:param agent_name: Name of the agent generating the documentation.
|
||||
"""
|
||||
self.directory = directory
|
||||
self.output_file = output_file
|
||||
self.agent_name = agent_name
|
||||
self.model = OpenAIChat(
|
||||
openai_api_key=api_key,
|
||||
model_name="gpt-4o-mini",
|
||||
temperature=0.1,
|
||||
max_tokens=3000,
|
||||
)
|
||||
self.agent = Agent(
|
||||
agent_name=agent_name,
|
||||
system_prompt=SYS_PROMPT,
|
||||
llm=self.model,
|
||||
max_loops=1,
|
||||
autosave=True,
|
||||
dashboard=False,
|
||||
verbose=True,
|
||||
dynamic_temperature_enabled=True,
|
||||
saved_state_path=f"{agent_name}_state.json",
|
||||
user_name="swarms_corp",
|
||||
retry_attempts=1,
|
||||
context_length=200000,
|
||||
return_step_meta=False,
|
||||
output_type=str,
|
||||
)
|
||||
self.documented_files: Set[str] = (
|
||||
set()
|
||||
) # Memory system to store documented files
|
||||
logger.info(
|
||||
f"Initialized {self.agent_name} for generating API documentation."
|
||||
)
|
||||
|
||||
# Ensure the output file is clean before starting
|
||||
with open(self.output_file, "w") as f:
|
||||
f.write("# API Reference Documentation\n\n")
|
||||
logger.info(f"Created new output file: {self.output_file}")
|
||||
|
||||
def _get_python_files(self) -> List[str]:
|
||||
"""
|
||||
Gets all Python (.py) files in the given directory, excluding 'utils', 'tools', and 'prompts' directories.
|
||||
|
||||
:return: A list of full paths to Python files.
|
||||
"""
|
||||
excluded_folders = {
|
||||
"utils",
|
||||
"tools",
|
||||
"prompts",
|
||||
"cli",
|
||||
"schemas",
|
||||
"agents",
|
||||
"artifacts",
|
||||
}
|
||||
python_files = []
|
||||
|
||||
for root, dirs, files in os.walk(self.directory):
|
||||
# Remove excluded folders from the search
|
||||
dirs[:] = [d for d in dirs if d not in excluded_folders]
|
||||
|
||||
for file in files:
|
||||
if file.endswith(".py"):
|
||||
full_path = os.path.join(root, file)
|
||||
python_files.append(full_path)
|
||||
logger.info(f"Found Python file: {full_path}")
|
||||
return python_files
|
||||
|
||||
def _get_module_path(self, file_path: str) -> str:
|
||||
"""
|
||||
Converts a file path to a Python module path.
|
||||
|
||||
:param file_path: Full path to the Python file.
|
||||
:return: The module path for the file.
|
||||
"""
|
||||
relative_path = os.path.relpath(file_path, self.directory)
|
||||
module_path = relative_path.replace(os.sep, ".").replace(
|
||||
".py", ""
|
||||
)
|
||||
logger.info(f"Formatted module path: {module_path}")
|
||||
return module_path
|
||||
|
||||
def _read_file_content(self, file_path: str) -> str:
|
||||
"""
|
||||
Reads the content of a Python file.
|
||||
|
||||
:param file_path: Full path to the Python file.
|
||||
:return: The content of the file as a string.
|
||||
"""
|
||||
try:
|
||||
with open(file_path, "r") as f:
|
||||
content = f.read()
|
||||
logger.info(f"Read content from {file_path}")
|
||||
return content
|
||||
except Exception as e:
|
||||
logger.error(f"Error reading file {file_path}: {e}")
|
||||
return ""
|
||||
|
||||
def _write_to_markdown(self, content: str) -> None:
|
||||
"""
|
||||
Appends generated content to the output markdown file.
|
||||
|
||||
:param content: Documentation content to write to the markdown file.
|
||||
"""
|
||||
try:
|
||||
with open(self.output_file, "a") as f:
|
||||
f.write(content)
|
||||
f.write(
|
||||
"\n\n"
|
||||
) # Add space between different module documentations
|
||||
logger.info(
|
||||
f"Appended documentation to {self.output_file}"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Error writing to {self.output_file}: {e}")
|
||||
|
||||
def _generate_doc_for_file(self, file_path: str) -> None:
|
||||
"""
|
||||
Generates documentation for a single Python file.
|
||||
|
||||
:param file_path: The full path to the Python file.
|
||||
"""
|
||||
if file_path in self.documented_files:
|
||||
logger.info(
|
||||
f"Skipping already documented file: {file_path}"
|
||||
)
|
||||
return
|
||||
|
||||
module_path = self._get_module_path(file_path)
|
||||
file_content = self._read_file_content(file_path)
|
||||
|
||||
if (
|
||||
file_content.strip()
|
||||
): # Ensure the file isn't empty or just whitespace
|
||||
logger.info(
|
||||
f"Generating documentation for module {module_path}..."
|
||||
)
|
||||
|
||||
# Updated task prompt to give clearer instructions
|
||||
task_prompt = f"""
|
||||
You are an expert documentation generator. Generate a comprehensive Python API reference documentation for the following module '{module_path}'.
|
||||
|
||||
The module contains the following code:
|
||||
|
||||
{file_content}
|
||||
|
||||
Provide full documentation including descriptions for all functions, classes, and methods. If there is nothing to document, simply write "No documentable code".
|
||||
|
||||
Make sure you provide full module imports in your documentation such as {self.directory}.{module_path}
|
||||
|
||||
from {self.directory}.{module_path} import *
|
||||
|
||||
### `{self.directory}.{module_path}`
|
||||
"""
|
||||
doc_output = self.agent.run(task_prompt)
|
||||
|
||||
# Add a section for the subfolder (if any)
|
||||
# markdown_content = f"# {subfolder}\n\n" if subfolder else ""
|
||||
markdown_content = f"\n\n{doc_output}\n"
|
||||
|
||||
self._write_to_markdown(markdown_content)
|
||||
self.documented_files.add(file_path)
|
||||
|
||||
def run(self) -> None:
|
||||
"""
|
||||
Generates documentation for all Python files in the directory and writes it to a markdown file using multithreading.
|
||||
"""
|
||||
python_files = self._get_python_files()
|
||||
|
||||
# with ThreadPoolExecutor() as executor:
|
||||
# futures = [executor.submit(self._generate_doc_for_file, file_path) for file_path in python_files]
|
||||
|
||||
# for future in as_completed(futures):
|
||||
# try:
|
||||
# future.result() # Raises an exception if the function failed
|
||||
# except Exception as e:
|
||||
# logger.error(f"Error processing a file: {e}")
|
||||
|
||||
for file in python_files:
|
||||
self._generate_doc_for_file(file)
|
||||
|
||||
logger.info(
|
||||
f"Documentation generation completed. All documentation written to {self.output_file}"
|
||||
)
|
||||
|
||||
|
||||
# Example usage
|
||||
if __name__ == "__main__":
|
||||
doc_agent = DocumentationAgent(directory="swarms")
|
||||
doc_agent.run()
|
Loading…
Reference in new issue