File size: 6,987 Bytes
d5b5f42 6f38954 d5b5f42 6f38954 44e7605 6f38954 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 |
# utils/tool_manager.py
import inspect
import re
# A dictionary to store registered tools
# Key: Internal function name (str)
# Value: Dictionary { 'name': display_name (str), 'func': tool_function (callable), 'ui_builder': ui_builder_function (callable) }
_tool_registry = {}
def tool(name: str, control_components):
"""
Decorator to register a tool function and its UI builder.
Args:
name (str): The display name of the tool in the UI dropdown.
control_components (callable): A function that builds the Gradio UI
components (input, output, button) for this tool.
This function should return a tuple of
(ui_group, input_components, output_components, button_component).
The ui_group should be a gr.Group or similar container
that holds all the tool's UI and whose visibility can be toggled.
"""
def decorator(func):
# Store the function and its metadata in the registry
if func.__name__ in _tool_registry:
print(f"Warning: Tool '{func.__name__}' is already registered. Overwriting.")
_tool_registry[func.__name__] = {
"name": name,
"func": func,
"ui_builder": control_components
}
print(f"Registered tool: {name} (Internal name: {func.__name__})")
# Return the original function so it can be called
return func
return decorator
def get_tool_registry():
"""Returns the dictionary of registered tools."""
return _tool_registry
def format_docstring_as_markdown(docstring: str | None) -> str:
"""
Converts a standard Python function docstring into a Markdown-formatted string.
Handles:
- Formatting the first sentence/paragraph as a heading (##).
- Bolding standard sections like "Args:", "Returns:", "Raises:".
- Formatting items under Args/Returns as list items (-) with inline code (` `)
for the parameter/return type part.
- Preserving blank lines for paragraph breaks.
- Converting single newlines within paragraphs/list items to Markdown line breaks
(two spaces + newline) to ensure correct rendering.
Args:
docstring (str | None): The raw docstring string from a function's __doc__.
Returns:
str: The Markdown-formatted string suitable for gr.Markdown.
Returns a default message if the input docstring is None or empty.
"""
if not docstring:
return ""
# 1. Clean common indentation using inspect.cleandoc
# Split into lines afterwards
lines = inspect.cleandoc(docstring).splitlines()
formatted_lines = []
in_params_section = False # Flag to track if we are currently inside Args, Returns, Raises sections
summary_done = False
# 2. Process lines to add structural Markdown
for i, line in enumerate(lines):
stripped_line = line.strip()
if not stripped_line:
# Preserve blank lines as is - they become paragraph breaks (\n\n after join)
formatted_lines.append("")
in_params_section = False # Blank line often signifies the end of a section
continue
if not summary_done:
# The very first non-empty, non-indented line (after cleandoc) is the summary
formatted_lines.append(f"## {stripped_line}")
summary_done = True
continue
# Check for standard section headers (Args:, Returns:, Raises:, etc.)
# Use regex to be flexible with spacing and optional colons
# Added more common section names
section_match = re.match(r"^(Args|Arguments|Params|Parameters|Returns|Return|Raises|Raise|Example|Examples|Note|Notes|Warning|Warnings|Todo|Todos):?\s*", stripped_line, re.IGNORECASE)
if section_match:
section_header = section_match.group(0).strip() # Get the matched part (e.g., "Args:")
section_name = section_match.group(1) # Get the section type (e.g., "Args")
# Format the header in bold, remove trailing colon before bolding if present
header_text_bold = f"**{section_header.rstrip(':')}**:"
formatted_lines.append(header_text_bold)
# Set section flag for parameter/return sections
if section_name.lower() in ['args', 'arguments', 'params', 'parameters', 'returns', 'return', 'raises', 'raise']:
in_params_section = True
else:
in_params_section = False # For other sections like Examples, Notes, etc.
elif in_params_section and ':' in line:
# If we are in a parameter/return section and the line contains a colon,
# it's likely a parameter/return item in "name (type): description" format
parts = line.split(':', 1)
name_type_part = parts[0].strip()
description_part = parts[1].strip() if len(parts) > 1 else ""
# Format as a list item (-) with inline code (`) for the name/type part
# and regular text for the description.
formatted_line = f"- `{name_type_part}` : {description_part}"
formatted_lines.append(formatted_line)
elif in_params_section and ':' not in line and stripped_line:
# This might be a continuation line for the description of a parameter/return.
# Just add it, it will be handled by the single newline replacement later.
# Adding some indentation can improve readability in the raw markdown string,
# but the final rendering depends on the markdown renderer. Let's just add it as is.
formatted_lines.append(line) # Use original line before strip for potential original indentation
else:
# Other lines are regular text after the summary or in unhandled sections.
# Add them as is.
formatted_lines.append(line)
# 3. Join the processed lines back into a single string
joined_docstring = "\n".join(formatted_lines)
# 4. Convert single newlines to Markdown line breaks (two spaces + newline)
# This is crucial for text within paragraphs and multi-line list items to break correctly.
# Use the regex: look for a newline (\n) that is NOT preceded by another newline (?<!\n)
# and NOT followed by another newline (?!\n). Replace it with two spaces and a newline.
processed_docstring = re.sub(r'(?<!\n)\n(?!\n)', ' \n', joined_docstring)
# Handle cases where the cleandoc() might leave leading/trailing blank lines
# after processing. Markdown handles these well, but we can strip if desired.
# Keeping them might be fine as it aligns with standard Markdown paragraph separation.
# Let's strip outer whitespace just in case.
processed_docstring = processed_docstring.strip()
return processed_docstring |