{"id":22153464,"url":"https://github.com/phil65/llmling","last_synced_at":"2025-10-12T04:34:03.310Z","repository":{"id":263522035,"uuid":"883128436","full_name":"phil65/LLMling","owner":"phil65","description":"Easy MCP (Model Context Protocol) servers and AI agents, defined as YAML.","archived":false,"fork":false,"pushed_at":"2025-10-10T00:27:05.000Z","size":1687,"stargazers_count":16,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-10T17:04:41.772Z","etag":null,"topics":["context-api","llm","mpc","resources","server","server-framework"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/phil65.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":"phil65","custom":["https://www.paypal.me/phil65"]}},"created_at":"2024-11-04T12:30:51.000Z","updated_at":"2025-10-10T00:27:08.000Z","dependencies_parsed_at":null,"dependency_job_id":"07cf00af-6d8d-421c-9a03-95029592be19","html_url":"https://github.com/phil65/LLMling","commit_stats":null,"previous_names":["phil65/llmling"],"tags_count":101,"template":false,"template_full_name":null,"purl":"pkg:github/phil65/LLMling","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phil65%2FLLMling","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phil65%2FLLMling/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phil65%2FLLMling/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phil65%2FLLMling/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/phil65","download_url":"https://codeload.github.com/phil65/LLMling/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phil65%2FLLMling/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279005265,"owners_count":26083861,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-10T02:00:06.843Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["context-api","llm","mpc","resources","server","server-framework"],"created_at":"2024-12-02T01:19:37.959Z","updated_at":"2025-10-12T04:34:03.301Z","avatar_url":"https://github.com/phil65.png","language":"Python","funding_links":["https://github.com/sponsors/phil65","https://www.paypal.me/phil65"],"categories":[],"sub_categories":[],"readme":"# LLMling\n\n[![PyPI License](https://img.shields.io/pypi/l/llmling.svg)](https://pypi.org/project/llmling/)\n[![Package status](https://img.shields.io/pypi/status/llmling.svg)](https://pypi.org/project/llmling/)\n[![Monthly downloads](https://img.shields.io/pypi/dm/llmling.svg)](https://pypi.org/project/llmling/)\n[![Distribution format](https://img.shields.io/pypi/format/llmling.svg)](https://pypi.org/project/llmling/)\n[![Wheel availability](https://img.shields.io/pypi/wheel/llmling.svg)](https://pypi.org/project/llmling/)\n[![Python version](https://img.shields.io/pypi/pyversions/llmling.svg)](https://pypi.org/project/llmling/)\n[![Implementation](https://img.shields.io/pypi/implementation/llmling.svg)](https://pypi.org/project/llmling/)\n[![Releases](https://img.shields.io/github/downloads/phil65/llmling/total.svg)](https://github.com/phil65/llmling/releases)\n[![Github Contributors](https://img.shields.io/github/contributors/phil65/llmling)](https://github.com/phil65/llmling/graphs/contributors)\n[![Github Discussions](https://img.shields.io/github/discussions/phil65/llmling)](https://github.com/phil65/llmling/discussions)\n[![Github Forks](https://img.shields.io/github/forks/phil65/llmling)](https://github.com/phil65/llmling/forks)\n[![Github Issues](https://img.shields.io/github/issues/phil65/llmling)](https://github.com/phil65/llmling/issues)\n[![Github Issues](https://img.shields.io/github/issues-pr/phil65/llmling)](https://github.com/phil65/llmling/pulls)\n[![Github Watchers](https://img.shields.io/github/watchers/phil65/llmling)](https://github.com/phil65/llmling/watchers)\n[![Github Stars](https://img.shields.io/github/stars/phil65/llmling)](https://github.com/phil65/llmling/stars)\n[![Github Repository size](https://img.shields.io/github/repo-size/phil65/llmling)](https://github.com/phil65/llmling)\n[![Github last commit](https://img.shields.io/github/last-commit/phil65/llmling)](https://github.com/phil65/llmling/commits)\n[![Github release date](https://img.shields.io/github/release-date/phil65/llmling)](https://github.com/phil65/llmling/releases)\n[![Github language count](https://img.shields.io/github/languages/count/phil65/llmling)](https://github.com/phil65/llmling)\n[![Github commits this month](https://img.shields.io/github/commit-activity/m/phil65/llmling)](https://github.com/phil65/llmling)\n[![Package status](https://codecov.io/gh/phil65/llmling/branch/main/graph/badge.svg)](https://codecov.io/gh/phil65/llmling/)\n[![PyUp](https://pyup.io/repos/github/phil65/llmling/shield.svg)](https://pyup.io/repos/github/phil65/llmling/)\n\nA framework for declarative LLM application development focused on resource management, prompt templates, and tool execution.\n\nThis package provides the backend for two consumers: [A MCP server](https://github.com/phil65/mcp-server-llmling) and [a pydantic-AI based Agent](https://github.com/phil65/llmling-agent)\n\n\n## Core Concepts\n\nLLMLing provides a YAML-based configuration system for LLM applications.\nIt allows to set up custom MPC servers serving content defined in YAML files.\n\n- **Static Declaration**: Define your LLM's environment in YAML - no code required\n- **MCP Protocol**: Built on the Machine Chat Protocol (MCP) for standardized LLM interaction\n- **Component Types**:\n  - **Resources**: Content providers (files, text, CLI output, etc.)\n  - **Prompts**: Message templates with arguments\n  - **Tools**: Python functions callable by the LLM\n\nThe YAML configuration creates a complete environment that provides the LLM with:\n- Access to content via resources\n- Structured prompts for consistent interaction\n- Tools for extending capabilities\n\n\n- Written from ground up in modern python (minimum 3.12 required)\n- 100% typed\n- pydantic(-ai) based\n\nAn overview about the whole system:\n\n```mermaid\ngraph TB\n    subgraph LLMling[LLMling Core Package]\n        RT[RuntimeConfig]\n\n        subgraph Core_Components[Core Components]\n            Resources[Resource Management\u003cbr/\u003e- Load files/URLs\u003cbr/\u003e- Process content\u003cbr/\u003e- Watch changes]\n            Tools[Tool System\u003cbr/\u003e- Execute functions\u003cbr/\u003e- Register new tools\u003cbr/\u003e- OpenAPI integration]\n            Prompts[Prompt System\u003cbr/\u003e- Static/Dynamic prompts\u003cbr/\u003e- Template rendering\u003cbr/\u003e- Completion support]\n        end\n\n        CLI[Core CLI\u003cbr/\u003e- config add/remove/list\u003cbr/\u003e- resource list/load\u003cbr/\u003e- tool list/execute\u003cbr/\u003e- prompt list/render]\n\n        Core_Components --\u003e|YAML configuration| RT\n        RT --\u003e|All components| CLI\n        CLI --\u003e|modify| Core_Components\n    end\n\n    subgraph Direct_Access[mcp-server-llmling\u003cbr/\u003eDirect Component Access]\n        MCP[HTTP/SSE Server\u003cbr/\u003e- Start/Stop server]\n        MCP_CLI[Server CLI\u003cbr/\u003e- Start/Stop server]\n        Injection[Injection Server\u003cbr/\u003e- Inject components\u003cbr/\u003eduring runtime]\n    end\n\n    subgraph Function_Access[llmling-agent\u003cbr/\u003eAccess via Function Calling]\n        LLM[LLM Integration\u003cbr/\u003e- Function calling\u003cbr/\u003e- Resource access\u003cbr/\u003e- Tool execution\u003cbr/\u003e- Structured output]\n        Agent_CLI[Agent CLI\u003cbr/\u003e- One-shot execution\u003cbr/\u003e- Batch processing]\n        Agent_Web[Agent Web UI\u003cbr/\u003e- Interactive chat]\n    end\n\n    RT --\u003e|All components| MCP\n    RT --\u003e|Resources \u0026 Tools\u003cbr/\u003evia function calling| LLM\n    MCP_CLI --\u003e CLI\n    Agent_CLI --\u003e CLI\n\n    classDef core fill:#e1f5fe,stroke:#01579b\n    classDef comp fill:#e3f2fd,stroke:#1565c0\n    classDef cli fill:#fff3e0,stroke:#e65100\n    classDef mcp fill:#f3e5f5,stroke:#4a148c\n    classDef agent fill:#e8f5e9,stroke:#1b5e20\n    classDef access fill:#e8eaf6,stroke:#666\n    classDef serverBox fill:#7986cb,stroke:#3949ab\n    classDef agentBox fill:#81c784,stroke:#2e7d32\n\n    class RT core\n    class Resources,Tools,Prompts comp\n    class CLI,MCP_CLI,Agent_CLI cli\n    class MCP,Injection mcp\n    class LLM,Agent_Web agent\n    class Direct_Access serverBox\n    class Function_Access agentBox\n```\n\n\n## Usage\n\n### 1. CLI Usage\n\nCreate a basic configuration file:\n```bash\n# Create a new config file with basic settings\nllmling config init my_config.yml\n\n# Add it to your stored configs\nllmling config add myconfig my_config.yml\nllmling config set myconfig  # Make it active\n```\n\nBasic CLI commands:\n```bash\n# List available resources\nllmling resource list\n\n# Load a resource\nllmling resource load python_files\n\n# Execute a tool\nllmling tool call open_url url=https://github.com\n\n# Show a prompt\nllmling prompt show greet\n\n# Many more commands. The CLI will get extended when installing\n# llmling-agent and mcp-server-llmling\n```\n\n### 2. Agent Usage (powered by pydantic-AI)\n\nCreate a configuration file (`config.yml`):\n```yaml\ntools:\n  open_url:\n    import_path: \"webbrowser.open\"\n\nresources:\n  bookmarks:\n    type: text\n    description: \"Common Python URLs\"\n    content: |\n      Python Website: https://python.org\n```\n\nUse the agent with this configuration:\n```python\nfrom llmling import RuntimeConfig\nfrom llmling_agent import LLMlingAgent\nfrom pydantic import BaseModel\n\nclass WebResult(BaseModel):\n    opened_url: str\n    success: bool\n\nasync with RuntimeConfig.open(\"config.yml\") as runtime:\n    agent = LLMlingAgent[WebResult](runtime)\n    result = await agent.run(\n        \"Load the bookmarks resource and open the Python website URL\"\n    )\n    print(f\"Opened: {result.data.opened_url}\")\n```\n\nThe agent will:\n1. Load the bookmarks resource\n2. Extract the Python website URL\n3. Use the `open_url` tool to open it\n4. Return the structured result\n\n### 3. Server Usage\n\n#### With Zed Editor\n\nAdd LLMLing as a context server in your `settings.json`:\n\n```json\n{\n  \"context_servers\": {\n    \"llmling\": {\n      \"command\": {\n        \"env\": {},\n        \"label\": \"llmling\",\n        \"path\": \"uvx\",\n        \"args\": [\n          \"mcp-server-llmling@latest\",\n          \"start\",\n          \"path/to/your/config.yml\",\n          \"--zed-mode\"\n        ]\n      },\n      \"settings\": {}\n    }\n  }\n}\n```\n\n#### With Claude Desktop\n\nConfigure LLMLing in your `claude_desktop_config.json`:\n\n```json\n{\n  \"mcpServers\": {\n    \"llmling\": {\n      \"command\": \"uvx\",\n      \"args\": [\n        \"mcp-server-llmling@latest\",\n        \"start\",\n        \"path/to/your/config.yml\"\n      ],\n      \"env\": {}\n    }\n  }\n}\n```\n\n#### Manual Server Start\n\nStart the server directly from command line:\n\n```bash\n# Latest version\nuvx mcp-server-llmling@latest start path/to/your/config.yml\n```\n\n\n## Resources\n\nResources are content providers that load and pre-process data from various sources.\n\n### Basic Resource Types\n\n```yaml\nglobal_config:  # declare dependencies if used for tools or function prompts\n  requirements: [\"myapp\"]\n  scripts:\n    - \"https://gist.githubusercontent.com/.../get_readme.py\"\n\n\nresources:\n  # Load and watch a file or directory\n  python_files:\n    type: path\n    path: \"./src/**/*.py\"  # Glob patterns supported\n    watch:  # Optional file watching\n      enabled: true\n      patterns:\n        - \"*.py\"\n        - \"!**/__pycache__/**\"  # Exclude patterns with !\n    processors:  # Optional processing steps\n      - name: format_python\n      - name: add_header\n        required: false  # Optional step\n\n  # Static text content\n  system_prompt:\n    type: text\n    content: |\n      You are a code reviewer specialized in Python.\n      Focus on these aspects:\n      - Code style (PEP8)\n      - Best practices\n      - Performance\n      - Security\n\n  # Execute CLI commands\n  git_changes:\n    type: cli\n    command: \"git diff HEAD~1\"  # String or list of args\n    shell: true  # Use shell for command\n    cwd: \"./src\"  # Optional working directory\n    timeout: 5.0  # Optional timeout in seconds\n\n  # Load Python source code\n  utils_module:\n    type: source\n    import_path: myapp.utils\n    recursive: true  # Include submodules\n    include_tests: false  # Exclude test files\n\n  # Execute Python callables\n  system_info:\n    type: callable\n    import_path: platform.uname\n    keyword_args:  # Optional arguments\n      aliased: true\n\n```\n\n### Resource Groups\n\nGroup related resources for easier access:\n\n```yaml\nresource_groups:\n  code_review:\n    - python_files\n    - git_changes\n    - system_prompt\n\n  documentation:\n    - architecture\n    - utils_module\n```\n\n### File Watching\n\nResources supporting file watching (`path`, `image`) can be configured to detect changes:\n\n```yaml\nresources:\n  config_files:\n    type: path\n    path: \"./config\"\n    watch:\n      enabled: true\n      patterns:  # .gitignore style patterns\n        - \"*.yml\"\n        - \"*.yaml\"\n        - \"!.private/**\"  # Exclude private files\n      ignore_file: \".gitignore\"  # Use existing ignore file\n```\n\n### Resource Processing\n\nResources can be processed through a pipeline of processors:\n\n```yaml\n# First define processors\ncontext_processors:\n  uppercase:\n    type: function\n    import_path: myapp.processors.to_upper\n    async_execution: false  # Sync function\n\n# Then use them in resources\nresources:\n  processed_file:\n    type: path\n    path: \"./input.txt\"\n    processors:\n      - name: uppercase\n```\n\n## Prompts\n\nPrompts are message templates that can be formatted with arguments. LLMLing supports both declarative YAML prompts and function-based prompts.\n\n### YAML-Based Prompts\n\n```yaml\nprompts:\n  code_review:\n    description: \"Review Python code changes\"\n    messages:\n      - role: system\n        content: |\n          You are a Python code reviewer. Focus on:\n          - Code style (PEP8)\n          - Best practices\n          - Performance\n          - Security\n\n          Always structure your review as:\n          1. Summary\n          2. Issues Found\n          3. Suggestions\n\n      - role: user\n        content: |\n          Review the following code changes:\n\n          {code}\n\n          Focus areas: {focus_areas}\n\n    arguments:\n      - name: code\n        description: \"Code to review\"\n        required: true\n      - name: focus_areas\n        description: \"Specific areas to focus on (one of: style, security, performance)\"\n        required: false\n        default: \"style\"\n```\n\n### Function-Based Prompts\n\nFunction-based prompts provide more control and enable auto-completion:\n\n```yaml\nprompts:\n  analyze_code:\n    # Import path to the prompt function\n    import_path: myapp.prompts.code_analysis\n    # Optional overrides\n    name: \"Code Analysis\"\n    description: \"Analyze Python code structure and complexity\"\n    # Optional message template override\n    template: |\n      Analyze this code: {code}\n      Focus on: {focus}\n    # Auto-completion functions for arguments\n    completions:\n      focus: myapp.prompts.get_analysis_focus_options\n```\n\n```python\n# myapp/prompts/code_analysis.py\nfrom typing import Literal\n\nFocusArea = Literal[\"complexity\", \"dependencies\", \"typing\"]\n\ndef code_analysis(\n    code: str,\n    focus: FocusArea = \"complexity\",\n    include_metrics: bool = True\n) -\u003e list[dict[str, str]]:\n    \"\"\"Analyze Python code structure and complexity.\n\n    Args:\n        code: Python source code to analyze\n        focus: Analysis focus area (one of: complexity, dependencies, typing)\n        include_metrics: Whether to include numeric metrics\n    \"\"\"\n    # Function will be converted to a prompt automatically\n    ...\n\ndef get_analysis_focus_options(current: str) -\u003e list[str]:\n    \"\"\"Provide auto-completion for focus argument.\"\"\"\n    options = [\"complexity\", \"dependencies\", \"typing\"]\n    return [opt for opt in options if opt.startswith(current)]\n```\n\n### Message Content Types\n\nPrompts support different content types:\n\n```yaml\nprompts:\n  document_review:\n    messages:\n      # Text content\n      - role: system\n        content: \"You are a document reviewer...\"\n\n      # Resource reference\n      - role: user\n        content:\n          type: resource\n          content: \"document://main.pdf\"\n          alt_text: \"Main document content\"\n\n      # Image content\n      - role: user\n        content:\n          type: image_url\n          content: \"https://example.com/diagram.png\"\n          alt_text: \"System architecture diagram\"\n```\n\n### Argument Validation\n\nPrompts validate arguments before formatting:\n\n```yaml\nprompts:\n  analyze:\n    messages:\n      - role: user\n        content: \"Analyze with level {level}\"\n\n    arguments:\n      - name: level\n        description: \"Analysis depth (one of: basic, detailed, full)\"\n        required: true\n        # Will be used for validation and auto-completion\n        type_hint: Literal[\"basic\", \"detailed\", \"full\"]\n```\n\n## Tools\n\nTools are Python functions or classes that can be called by the LLM. They provide a safe way to extend the LLM's capabilities with custom functionality.\n\n### Basic Tool Configuration\n\n```yaml\ntools:\n  # Function-based tool\n  analyze_code:\n    import_path: myapp.tools.code.analyze\n    description: \"Analyze Python code structure and metrics\"\n\n  # Class-based tool\n  browser:\n    import_path: llmling.tools.browser.BrowserTool\n    description: \"Control web browser for research\"\n\n  # Override tool name\n  code_metrics:\n    import_path: myapp.tools.analyze_complexity\n    name: \"Analyze Code Complexity\"\n    description: \"Calculate code complexity metrics\"\n\n# Include pre-built tool collections\ntoolsets:\n  - llmling.code  # Code analysis tools\n  - llmling.web   # Web/browser tools\n```\n\n## Toolsets\n\nToolsets are, like the name says, a collection of tools. Right now LLMling supports:\n\n- Extension point system\n- OpenAPI endpoints\n- class-based toolsets\n\n### Function-Based Tools\n\nTools can be created from any Python function:\n\n```python\n# myapp/tools/code.py\nfrom typing import Any\nimport ast\n\nasync def analyze(\n    code: str,\n    include_metrics: bool = True\n) -\u003e dict[str, Any]:\n    \"\"\"Analyze Python code structure and complexity.\n\n    Args:\n        code: Python source code to analyze\n        include_metrics: Whether to include numeric metrics\n\n    Returns:\n        Dictionary with analysis results\n    \"\"\"\n    tree = ast.parse(code)\n    return {\n        \"classes\": len([n for n in ast.walk(tree) if isinstance(n, ast.ClassDef)]),\n        \"functions\": len([n for n in ast.walk(tree) if isinstance(n, ast.FunctionDef)]),\n        \"complexity\": _calculate_complexity(tree) if include_metrics else None\n    }\n```\n\n### Class-Based Tools\n\nComplex tools can be implemented as classes:\n\n```python\n# myapp/tools/browser.py\nfrom typing import Literal\nfrom playwright.async_api import Page\nfrom llmling.tools.base import BaseTool\n\nclass BrowserTool(BaseTool):\n    \"\"\"Tool for web browser automation.\"\"\"\n\n    name = \"browser\"\n    description = \"Control web browser to navigate and interact with web pages\"\n\n    ...\n\n    def get_tools(self):\n        return [self.open_url, self.click_button]\n```\n\n### Tool Collections (Toolsets)\n\nGroup related tools into reusable collections:\n\n```python\n# myapp/toolsets.py\nfrom typing import Callable, Any\n\ndef get_mcp_tools() -\u003e list[Callable[..., Any]]:\n    \"\"\"Entry point exposing tools to LLMling.\"\"\"\n    from myapp.tools import (\n        analyze_code,\n        check_style,\n        count_tokens\n    )\n    return [\n        analyze_code,\n        check_style,\n        count_tokens\n    ]\n```\n\nIn pyproject.toml:\n\n```toml\n[project.entry-points.llmling]\ntools = \"llmling.testing:get_mcp_tools\"\n```\n\n### Tool Progress Reporting\n\nTools can report progress to the client:\n\n```python\nfrom llmling.tools.base import BaseTool\n\nclass AnalysisTool(BaseTool):\n    name = \"analyze\"\n    description = \"Analyze large codebase\"\n\n    async def execute(\n        self,\n        path: str,\n        _meta: dict[str, Any] | None = None,  # Progress tracking\n    ) -\u003e dict[str, Any]:\n        files = list(Path(path).glob(\"**/*.py\"))\n        results = []\n\n        for i, file in enumerate(files):\n            # Report progress if meta information provided\n            if _meta and \"progressToken\" in _meta:\n                self.notify_progress(\n                    token=_meta[\"progressToken\"],\n                    progress=i,\n                    total=len(files),\n                    description=f\"Analyzing {file.name}\"\n                )\n\n            results.append(await self._analyze_file(file))\n\n        return {\"results\": results}\n```\n\n### Complete Tool Example\n\nHere's a complete example combining multiple tool features:\n\n```yaml\n# Configuration\ntools:\n  # Basic function tool\n  analyze:\n    import_path: myapp.tools.code.analyze\n\n  # Class-based tool with lifecycle\n  browser:\n    import_path: myapp.tools.browser.BrowserTool\n\n  # Tool with progress reporting\n  batch_analysis:\n    import_path: myapp.tools.AnalysisTool\n\ntoolsets:\n  - llmling.code\n  - myapp.tools\n```\n\n```python\n# Tool implementation\nfrom typing import Any\nfrom pathlib import Path\nfrom llmling.tools.base import BaseTool\n\nclass AnalysisTool(BaseTool):\n    \"\"\"Tool for batch code analysis with progress reporting.\"\"\"\n\n    name = \"batch_analysis\"\n    description = \"Analyze multiple Python files\"\n\n    async def startup(self) -\u003e None:\n        \"\"\"Initialize analysis engine.\"\"\"\n        self.analyzer = await self._create_analyzer()\n\n    async def execute(\n        self,\n        path: str,\n        recursive: bool = True,\n        _meta: dict[str, Any] | None = None\n    ) -\u003e dict[str, Any]:\n        \"\"\"Execute batch analysis.\n\n        Args:\n            path: Directory to analyze\n            recursive: Whether to analyze subdirectories\n            _meta: Optional progress tracking metadata\n        \"\"\"\n        files = list(Path(path).glob(\"**/*.py\" if recursive else \"*.py\"))\n        results = []\n\n        for i, file in enumerate(files, 1):\n            # Report progress\n            if _meta and \"progressToken\" in _meta:\n                self.notify_progress(\n                    token=_meta[\"progressToken\"],\n                    progress=i,\n                    total=len(files),\n                    description=f\"Analyzing {file.name}\"\n                )\n\n            # Analyze file\n            try:\n                result = await self.analyzer.analyze_file(file)\n                results.append({\n                    \"file\": str(file),\n                    \"metrics\": result\n                })\n            except Exception as e:\n                results.append({\n                    \"file\": str(file),\n                    \"error\": str(e)\n                })\n\n        return {\n            \"total_files\": len(files),\n            \"successful\": len([r for r in results if \"metrics\" in r]),\n            \"failed\": len([r for r in results if \"error\" in r]),\n            \"results\": results\n        }\n\n    async def shutdown(self) -\u003e None:\n        \"\"\"Clean up analysis engine.\"\"\"\n        await self.analyzer.close()\n```\n\nMore: (ATTENTION: THESE ARE MOSTLY AI GENERATED AND OUTDATED, NO GUARANTEE FOR CORRECTNESS)\n[introduction](https://github.com/phil65/LLMling/blob/main/docs/introduction.md)\n[quick_example](https://github.com/phil65/LLMling/blob/main/docs/quick_example.md)\n[usage](https://github.com/phil65/LLMling/blob/main/docs/usage.md)\n[yaml_config](https://github.com/phil65/LLMling/blob/main/docs/yaml_config.md)\n[CLI](https://github.com/phil65/LLMling/blob/main/docs/cli.md)\n\n[resources](https://github.com/phil65/LLMling/blob/main/docs/resources.md)\n[prompts](https://github.com/phil65/LLMling/blob/main/docs/prompts.md)\n[tools](https://github.com/phil65/LLMling/blob/main/docs/tools.md)\n[server](https://github.com/phil65/LLMling/blob/main/docs/server.md)\n[extending](https://github.com/phil65/LLMling/blob/main/docs/extending.md)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fphil65%2Fllmling","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fphil65%2Fllmling","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fphil65%2Fllmling/lists"}