From 2df23380f34569165419bcabd31f5e6285584fdf Mon Sep 17 00:00:00 2001 From: Menendez6 Date: Tue, 17 Jun 2025 09:37:13 +0000 Subject: [PATCH 1/6] Create library --- README.md | 320 ++++++++++++++++++++++++++++++----------- pyproject.toml | 2 +- src/expert/__init__.py | 30 ++++ 3 files changed, 271 insertions(+), 81 deletions(-) diff --git a/README.md b/README.md index 10e33cd..38d3f4e 100644 --- a/README.md +++ b/README.md @@ -1,133 +1,293 @@ -# Invopop Expert ๐Ÿค– +# Invopop Expert -An AI agent that helps you with Invopop and GOBL (Go Business Language) documentation and implementation questions. It has access to invopop, gobl docs and the [gobl repo](https://github.com/invopop/gobl) +An AI-powered agent library for answering questions about Invopop and GOBL documentation using LangChain and MCP (Model Context Protocol) servers. -## ๐Ÿš€ Quick Start +## Features + +- ๐Ÿค– **Intelligent Q&A**: Answers questions about Invopop and GOBL using advanced RAG +- ๐Ÿ” **Multi-source Search**: Searches through documentation and code repositories +- ๐Ÿ’ฌ **Interactive CLI**: Command-line interface for direct interaction +- ๐Ÿ”ง **Extensible**: Easy to integrate into your own applications +- ๐Ÿ“š **Context Aware**: Maintains conversation history and context + +## Quick Start ### Prerequisites -- Node.js (for MCP servers) +- Python 3.13+ +- Node.js 20+ (for MCP servers) - OpenAI API key -- uv package manager (you can install it with pip or brew: [install uv](https://docs.astral.sh/uv/getting-started/installation/)) ### Installation -1. **Clone the repository:** +1. **Install MCP servers** (required for documentation search): + ```bash + npx mint-mcp add invopop + npx mint-mcp add gobl + ``` -```bash -git clone https://github.com/invopop/expert.git -cd expert -``` +2. **Install the package**: + ```bash + pip install git+https://github.com/invopop/expert.git + ``` -2. **Install MCP servers**: +3. **Set up environment variables**: + ```bash + export OPENAI_API_KEY=your_openai_api_key + ``` -```bash -chmod +x scripts/install_mcp_servers.sh -./scripts/install_mcp_servers.sh -``` +4. **Run the CLI**: + ```bash + expert + ``` + +## CLI Usage -3. **Install Python dependencies**: +The CLI provides an interactive chat interface: ```bash -uv sync -``` +$ expert +Welcome to Invopop Expert! Ask questions about GOBL, Invopop and the invopop/gobl library +Enter your multi-line question. Press Enter on an empty line to send. +---------------------------------------------------------------------- -4. **Configure environment**: +๐Ÿ‘ค You: How do I create an invoice with GOBL? -```bash -cp .env.example .env -# Edit .env and add your OPENAI_API_KEY +๐Ÿค– Thinking... +๐Ÿ” Searching GOBL docs: {"query": "create invoice GOBL"} + +๐Ÿค– Assistant: To create an invoice with GOBL, you need to... ``` -5. **Run the agent**: +### CLI Options + ```bash -uv run python -m src.expert.main +expert --help # Show help +expert --config config.yaml # Use custom config file +expert --verbose # Enable verbose output ``` -If you have Python > 3.13, you can also run it in cli from root like this: +## Library Usage + +You can also use Invopop Expert as a library in your own applications: + +```python +import asyncio +from expert import InvopopExpert, Config + +async def main(): + # Initialize the expert + config = Config() + expert = InvopopExpert(config) + await expert.setup() + + # Ask a question + thread_config = {"configurable": {"thread_id": "my-conversation"}} + response = await expert.get_response( + "How do I handle tax calculations in GOBL?", + thread_config + ) + print(response) + +# Run the example +asyncio.run(main()) +``` -Install the package: -```bash -uv pip install -e . +## Configuration + +The agent uses a YAML configuration file (`config.yaml`): + +```yaml +# LLM Configuration +llm: + provider: "openai" + model: "gpt-4.1-2025-04-14" + temperature: 0.1 + +# MCP Server Configuration +mcp: + servers: + invopop: + command: "node" + args: ["~/.mcp/invopop/src/index.js"] + transport: "stdio" + gobl: + command: "node" + args: ["~/.mcp/gobl/src/index.js"] + transport: "stdio" + +# Chat Interface Configuration +chat: + welcome_message: "Welcome to Invopop Expert!" + input_prompt: "Enter your question:" + max_history: 50 ``` -Run it from root: -```bash -uv run expert +### Environment Variables + +- `OPENAI_API_KEY` (required): Your OpenAI API key +- `INVOPOP_MCP_PATH` (optional): Custom path to Invopop MCP server +- `GOBL_MCP_PATH` (optional): Custom path to GOBL MCP server + +## Integration Examples + +### Building a Web API + +```python +from fastapi import FastAPI +from expert import InvopopExpert, Config + +app = FastAPI() +expert = InvopopExpert(Config()) + +@app.on_event("startup") +async def startup(): + await expert.setup() + +@app.post("/ask") +async def ask_question(question: str): + thread_config = {"configurable": {"thread_id": "api-user"}} + response = await expert.get_response(question, thread_config) + return {"answer": response} ``` -## ๐Ÿ› ๏ธ Configuration -### Environment Variables (.env) +### Building a Slack Bot -- `OPENAI_API_KEY`: Your OpenAI API key (required) -- `INVOPOP_MCP_PATH`: Custom path to Invopop MCP server (optional) -- `GOBL_MCP_PATH`: Custom path to GOBL MCP server (optional) +```python +# See our example Slack bot implementation: +# https://github.com/your-org/invopop-expert-slack +``` -### Configuration File (config.yaml) -Customize the agent behavior by editing `config.yaml`: +### Building a Discord Bot + +```python +import discord +from expert import InvopopExpert, Config + +class ExpertBot(discord.Client): + def __init__(self): + super().__init__() + self.expert = InvopopExpert(Config()) + + async def on_ready(self): + await self.expert.setup() + + async def on_message(self, message): + if message.author == self.user: + return + + thread_config = {"configurable": {"thread_id": f"discord-{message.author.id}"}} + response = await self.expert.get_response(message.content, thread_config) + await message.reply(response) +``` -- LLM model and parameters -- MCP server paths -- Chat interface settings +## Architecture -## ๐Ÿ’ฌ Usage -Start a conversation with the agent: +- **LangChain + LangGraph**: AI agent framework with memory and tools +- **MCP Protocol**: Connects to Mintlify documentation servers +- **Multi-source RAG**: Searches Invopop docs, GOBL docs, and code repositories +- **Conversation Memory**: Maintains context across interactions +- **Modular Design**: Easy to extend with new tools and integrations -```bash -uv run expert +## File Structure + +``` +src/expert/ +โ”œโ”€โ”€ agent.py # Core InvopopExpert agent implementation +โ”œโ”€โ”€ config.py # Configuration management +โ”œโ”€โ”€ main.py # CLI interface +โ”œโ”€โ”€ __init__.py # Package exports +โ””โ”€โ”€ prompts/ # System prompts and tool descriptions + โ”œโ”€โ”€ system_prompt.md + โ”œโ”€โ”€ invopop_docs_description.md + โ”œโ”€โ”€ gobl_docs_description.md + โ””โ”€โ”€ gobl_code_description.md ``` -Ask questions like: +## Development -- "How do I create a GOBL invoice?" -- "What are the tax requirements for Spain in Invopop?" -- "How do I integrate with the Invopop API?" +### Local Development Setup -Or more complex ones like: -- "Give me an example of a valid invoice in Verifactu?" -- "For my case X in greece, what would be the fields required in the invoice and where should they appear?" +1. **Clone the repository**: + ```bash + git clone https://github.com/invopop/expert.git + cd expert + ``` -In case the result of some prompt is not as expected, report it as an issue and we will look into it. +2. **Install MCP servers**: + ```bash + npx mint-mcp add invopop + npx mint-mcp add gobl + ``` -Type `exit`, `quit`, or `bye` to end the session. +3. **Install dependencies**: + ```bash + pip install uv + uv pip install -e . + ``` -Type `clear` to start a new thread. +4. **Set environment variables**: + ```bash + export OPENAI_API_KEY=your_api_key + ``` -## ๐Ÿงช Development +5. **Run the CLI**: + ```bash + python -m expert.main + ``` + +### Running Tests -### Setup Development Environment ```bash -uv sync --group dev +# Run tests (when implemented) +pytest + +# Run linting +ruff check src/ ``` -### Code Formatting +## Contributing + +1. Fork the repository +2. Create a feature branch (`git checkout -b feature/amazing-feature`) +3. Make your changes +4. Add tests for new functionality +5. Ensure all tests pass and linting is clean +6. Commit your changes (`git commit -m 'Add amazing feature'`) +7. Push to the branch (`git push origin feature/amazing-feature`) +8. Open a Pull Request + +## Troubleshooting + +### Common Issues + +**MCP servers not found:** ```bash -uv run black src/ -uv run ruff check src/ +npx mint-mcp add invopop +npx mint-mcp add gobl ``` -## ๐Ÿ“ Project Structure +**OpenAI API errors:** +- Verify your API key is correct and has sufficient credits +- Check that you're using a supported model -``` bash -invopop-expert/ -โ”œโ”€โ”€ src/invopop_expert/ # Main package -โ”œโ”€โ”€ config.yaml # Configuration -โ””โ”€โ”€ scripts/ # Installation scripts -``` +**Import errors:** +- Ensure you've installed the package: `pip install -e .` +- Check that all dependencies are installed: `uv pip install -r pyproject.toml` -## ๐Ÿค Contributing +### Getting Help -1. Fork the repository -2. Create a feature branch -3. Make your changes -4. Run tests and formatting -5. Submit a pull request +- ๐Ÿ“– [Invopop Documentation](https://docs.invopop.com) +- ๐Ÿ“š [GOBL Documentation](https://docs.gobl.org/introduction) +- ๐Ÿ› [Report Issues](https://github.com/invopop/expert/issues) + +## License -## ๐Ÿ“„ License -Apache License 2.0 - see LICENSE file for details. +See [LICENSE](LICENSE) file for details. -## ๐Ÿ†˜ Support +--- -๐Ÿ“š [Invopop Documentation](https://docs.invopop.com/home) -๐Ÿ“š [GOBL Documentation](https://docs.gobl.org/introduction) -๐Ÿ› [Report Issues or unexpected answer](https://github.com/invopop/expert/issues) \ No newline at end of file +**Want to build integrations?** Check out our [integration examples](#integration-examples) above, or see our example implementations: +- [Slack Bot Template](https://github.com/your-org/invopop-expert-slack) +- [Discord Bot Template](https://github.com/your-org/invopop-expert-discord) +- [Web API Template](https://github.com/your-org/invopop-expert-api) \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 7ff1d15..223f4ce 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "expert" version = "0.1.0" -description = "Add your description here" +description = "Invopop Expert - AI agent library for Invopop and GOBL documentation" readme = "README.md" requires-python = ">=3.13" dependencies = [ diff --git a/src/expert/__init__.py b/src/expert/__init__.py index e69de29..8ed9609 100644 --- a/src/expert/__init__.py +++ b/src/expert/__init__.py @@ -0,0 +1,30 @@ +""" +Invopop Expert - AI agent library for Invopop and GOBL documentation. + +This package provides an AI-powered agent that can answer questions about +Invopop and GOBL using advanced retrieval-augmented generation (RAG) with +MCP (Model Context Protocol) servers. + +Example usage: + import asyncio + from expert import InvopopExpert, Config + + async def main(): + config = Config() + expert = InvopopExpert(config) + await expert.setup() + + thread_config = {"configurable": {"thread_id": "my-conversation"}} + response = await expert.get_response("How do I create an invoice?", thread_config) + print(response) + + asyncio.run(main()) +""" + +from .agent import InvopopExpert +from .config import Config + +__version__ = "0.1.0" +__all__ = ["InvopopExpert", "Config"] + + From 92e86a8458a47920619fa8bc502ae562c0a31956 Mon Sep 17 00:00:00 2001 From: Menendez6 Date: Tue, 17 Jun 2025 10:38:37 +0000 Subject: [PATCH 2/6] Add prompts to package --- pyproject.toml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 223f4ce..ebdf1db 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,19 @@ dependencies = [ [project.scripts] expert = "expert.main:cli" +[build-system] +requires = ["setuptools>=61.0", "wheel"] +build-backend = "setuptools.build_meta" + +[tool.setuptools.packages.find] +where = ["src"] + +[tool.setuptools.package-dir] +"" = "src" + +[tool.setuptools.package-data] +expert = ["prompts/*.md"] + [tool.ruff] line-length = 88 target-version = "py313" From 231bcf616a5781ce3c3870e4b2b92a4f40a3c52c Mon Sep 17 00:00:00 2001 From: Menendez6 Date: Mon, 23 Jun 2025 11:49:33 +0000 Subject: [PATCH 3/6] Include ruff and black in github workflows --- .github/workflows/code-quality.yml | 43 ++++ .github/workflows/pre-commit.yml | 68 ++++++ .github/workflows/release.yml | 160 ++++++++++++++ README.md | 88 ++++---- pyproject.toml | 7 + src/expert/__init__.py | 8 +- src/expert/agent.py | 12 +- uv.lock | 342 ++++++++++++++++++++++++++++- 8 files changed, 678 insertions(+), 50 deletions(-) create mode 100644 .github/workflows/code-quality.yml create mode 100644 .github/workflows/pre-commit.yml create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml new file mode 100644 index 0000000..79f6619 --- /dev/null +++ b/.github/workflows/code-quality.yml @@ -0,0 +1,43 @@ +name: Code Quality + +on: + push: + branches: [ main, master, develop ] + pull_request: + branches: [ main, master, develop ] + +jobs: + code-quality: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.13"] + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install UV + uses: astral-sh/setup-uv@v3 + with: + enable-cache: true + + - name: Install dependencies + run: | + uv sync --extra dev + + - name: Run Black (Format Check) + run: | + uv run black --check --diff . + + - name: Run Ruff (Linting) + run: | + uv run ruff check . + + - name: Run Ruff (Format Check) + run: | + uv run ruff format --check . \ No newline at end of file diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml new file mode 100644 index 0000000..6068b27 --- /dev/null +++ b/.github/workflows/pre-commit.yml @@ -0,0 +1,68 @@ +name: Pre-commit Checks + +on: + push: + pull_request: + +jobs: + pre-commit: + runs-on: ubuntu-latest + name: Code Quality Gate + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.13" + + - name: Install UV + uses: astral-sh/setup-uv@v3 + with: + enable-cache: true + + - name: Install dependencies + run: uv sync --extra dev + + - name: Check Black formatting + id: black-check + run: | + echo "Checking code formatting with Black..." + if ! uv run black --check --quiet .; then + echo "โŒ Code formatting issues found!" + echo "Run 'uv run black .' to fix formatting issues." + exit 1 + else + echo "โœ… Code formatting is correct!" + fi + + - name: Check Ruff linting + id: ruff-lint + run: | + echo "Running Ruff linting..." + if ! uv run ruff check .; then + echo "โŒ Linting issues found!" + echo "Run 'uv run ruff check --fix .' to fix auto-fixable issues." + exit 1 + else + echo "โœ… No linting issues found!" + fi + + - name: Check Ruff formatting + id: ruff-format + run: | + echo "Checking Ruff formatting..." + if ! uv run ruff format --check .; then + echo "โŒ Format issues found!" + echo "Run 'uv run ruff format .' to fix formatting issues." + exit 1 + else + echo "โœ… Code format is correct!" + fi + + - name: Success + if: success() + run: | + echo "๐ŸŽ‰ All code quality checks passed!" + echo "Your code meets the quality standards." \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..5fc2664 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,160 @@ +name: Release Package + +on: + workflow_dispatch: + inputs: + version_type: + description: 'Version bump type' + required: true + default: 'patch' + type: choice + options: + - patch + - minor + - major + custom_version: + description: 'Custom version (optional, overrides version_type)' + required: false + type: string + push: + tags: + - 'v*' + +jobs: + release: + runs-on: ubuntu-latest + permissions: + contents: write + id-token: write + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.13" + + - name: Install UV + uses: astral-sh/setup-uv@v3 + with: + enable-cache: true + + - name: Install dependencies + run: uv sync --extra dev + + - name: Configure Git + run: | + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + + - name: Get current version + id: current_version + run: | + CURRENT_VERSION=$(grep -E '^version = ' pyproject.toml | sed 's/version = "//' | sed 's/"//') + echo "current=$CURRENT_VERSION" >> $GITHUB_OUTPUT + echo "Current version: $CURRENT_VERSION" + + - name: Calculate new version + id: new_version + run: | + CURRENT_VERSION="${{ steps.current_version.outputs.current }}" + + if [ -n "${{ github.event.inputs.custom_version }}" ]; then + NEW_VERSION="${{ github.event.inputs.custom_version }}" + echo "Using custom version: $NEW_VERSION" + elif [ "${{ github.event.inputs.version_type }}" = "major" ]; then + NEW_VERSION=$(echo $CURRENT_VERSION | awk -F. '{print $1+1".0.0"}') + elif [ "${{ github.event.inputs.version_type }}" = "minor" ]; then + NEW_VERSION=$(echo $CURRENT_VERSION | awk -F. '{print $1"."$2+1".0"}') + else + # patch (default) + NEW_VERSION=$(echo $CURRENT_VERSION | awk -F. '{print $1"."$2"."$3+1}') + fi + + echo "new=$NEW_VERSION" >> $GITHUB_OUTPUT + echo "New version: $NEW_VERSION" + + - name: Update version in pyproject.toml + if: github.event_name == 'workflow_dispatch' + run: | + NEW_VERSION="${{ steps.new_version.outputs.new }}" + sed -i "s/version = \".*\"/version = \"$NEW_VERSION\"/" pyproject.toml + echo "Updated pyproject.toml with version $NEW_VERSION" + + - name: Run pre-commit checks + run: | + echo "Running code quality checks..." + uv run black --check . + uv run ruff check . + uv run ruff format --check . + + - name: Build package + run: | + echo "Building package..." + uv build + + - name: Check package + run: | + echo "Checking built package..." + ls -la dist/ + uv run python -m twine check dist/* + + - name: Commit version bump + if: github.event_name == 'workflow_dispatch' + run: | + NEW_VERSION="${{ steps.new_version.outputs.new }}" + git add pyproject.toml uv.lock + git commit -m "Bump version to $NEW_VERSION" || echo "No changes to commit" + git tag "v$NEW_VERSION" + git push origin HEAD + git push origin "v$NEW_VERSION" + + - name: Publish to PyPI + if: startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch' + env: + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} + run: | + echo "Publishing to PyPI..." + uv run python -m twine upload dist/* + + - name: Create GitHub Release + if: startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch' + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.event_name == 'workflow_dispatch' && format('v{0}', steps.new_version.outputs.new) || github.ref_name }} + release_name: Release ${{ github.event_name == 'workflow_dispatch' && steps.new_version.outputs.new || github.ref_name }} + body: | + ## Changes in this Release + + - Package version: ${{ github.event_name == 'workflow_dispatch' && steps.new_version.outputs.new || github.ref_name }} + - Built and published to PyPI + + ## Installation + + ```bash + pip install expert==${{ github.event_name == 'workflow_dispatch' && steps.new_version.outputs.new || github.ref_name }} + ``` + + ## Artifacts + + The package has been published to PyPI and is available for installation. + draft: false + prerelease: false + + - name: Upload Release Assets + if: startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch' + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: dist/ + asset_name: expert-dist + asset_content_type: application/zip \ No newline at end of file diff --git a/README.md b/README.md index 38d3f4e..2e66d3c 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,8 @@ An AI-powered agent library for answering questions about Invopop and GOBL docum ### Installation +> **Note**: This project uses `uv` for dependency management. If you don't have `uv` installed, you can install it with `pip install uv` or use `pipx` for isolated installation. + 1. **Install MCP servers** (required for documentation search): ```bash npx mint-mcp add invopop @@ -28,6 +30,15 @@ An AI-powered agent library for answering questions about Invopop and GOBL docum 2. **Install the package**: ```bash + # Option 1: Development installation (recommended) + git clone https://github.com/invopop/expert.git + cd expert + uv pip install -e . + + # Option 2: Using pipx (installs in isolated environment) + pipx install git+https://github.com/invopop/expert.git + + # Option 3: Direct installation with pip pip install git+https://github.com/invopop/expert.git ``` @@ -38,7 +49,17 @@ An AI-powered agent library for answering questions about Invopop and GOBL docum 4. **Run the CLI**: ```bash + # If using development installation (Option 1) + uv run expert + + # If using pipx installation (Option 2) expert + + # If using pip installation (Option 3) + expert + + # Alternative: Run directly without activation (works for all options) + python -m expert.main ``` ## CLI Usage @@ -151,36 +172,6 @@ async def ask_question(question: str): return {"answer": response} ``` -### Building a Slack Bot - -```python -# See our example Slack bot implementation: -# https://github.com/your-org/invopop-expert-slack -``` - -### Building a Discord Bot - -```python -import discord -from expert import InvopopExpert, Config - -class ExpertBot(discord.Client): - def __init__(self): - super().__init__() - self.expert = InvopopExpert(Config()) - - async def on_ready(self): - await self.expert.setup() - - async def on_message(self, message): - if message.author == self.user: - return - - thread_config = {"configurable": {"thread_id": f"discord-{message.author.id}"}} - response = await self.expert.get_response(message.content, thread_config) - await message.reply(response) -``` - ## Architecture - **LangChain + LangGraph**: AI agent framework with memory and tools @@ -222,7 +213,10 @@ src/expert/ 3. **Install dependencies**: ```bash + # Install uv if not already installed pip install uv + + # Install the package in development mode uv pip install -e . ``` @@ -233,6 +227,13 @@ src/expert/ 5. **Run the CLI**: ```bash + # Activate the virtual environment created by uv + source .venv/bin/activate + + # Run the CLI + expert + + # Or run directly without activating the venv python -m expert.main ``` @@ -261,6 +262,19 @@ ruff check src/ ### Common Issues +**Command 'expert' not found:** +```bash +# If using development installation, activate the virtual environment +source .venv/bin/activate +expert + +# Or run directly as a Python module +python -m expert.main + +# If using pipx, ensure pipx bin directory is in your PATH +export PATH="$HOME/.local/bin:$PATH" +``` + **MCP servers not found:** ```bash npx mint-mcp add invopop @@ -272,8 +286,9 @@ npx mint-mcp add gobl - Check that you're using a supported model **Import errors:** -- Ensure you've installed the package: `pip install -e .` -- Check that all dependencies are installed: `uv pip install -r pyproject.toml` +- Ensure you've installed the package: `uv pip install -e .` or `pip install -e .` +- Check that all dependencies are installed +- Try reinstalling: `uv pip install -e . --force-reinstall` ### Getting Help @@ -283,11 +298,4 @@ npx mint-mcp add gobl ## License -See [LICENSE](LICENSE) file for details. - ---- - -**Want to build integrations?** Check out our [integration examples](#integration-examples) above, or see our example implementations: -- [Slack Bot Template](https://github.com/your-org/invopop-expert-slack) -- [Discord Bot Template](https://github.com/your-org/invopop-expert-discord) -- [Web API Template](https://github.com/your-org/invopop-expert-api) \ No newline at end of file +See [LICENSE](LICENSE) file for details. \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index ebdf1db..08c42e1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,6 +13,13 @@ dependencies = [ "langgraph-prebuilt>=0.2.2", ] +[project.optional-dependencies] +dev = [ + "black>=24.0.0", + "ruff>=0.6.0", + "twine>=5.0.0", +] + [project.scripts] expert = "expert.main:cli" diff --git a/src/expert/__init__.py b/src/expert/__init__.py index 8ed9609..d1f30f1 100644 --- a/src/expert/__init__.py +++ b/src/expert/__init__.py @@ -8,16 +8,16 @@ Example usage: import asyncio from expert import InvopopExpert, Config - + async def main(): config = Config() expert = InvopopExpert(config) await expert.setup() - + thread_config = {"configurable": {"thread_id": "my-conversation"}} response = await expert.get_response("How do I create an invoice?", thread_config) print(response) - + asyncio.run(main()) """ @@ -26,5 +26,3 @@ async def main(): __version__ = "0.1.0" __all__ = ["InvopopExpert", "Config"] - - diff --git a/src/expert/agent.py b/src/expert/agent.py index 2cb6375..00cc3ca 100644 --- a/src/expert/agent.py +++ b/src/expert/agent.py @@ -60,8 +60,12 @@ async def setup(self): new_name = "gobl_code_ask_question" new_description = self.gobl_code_description new_schema = tool.args_schema.copy() - new_schema['properties']['repoName']['description'] = "This value will always be 'invopop/gobl'" - new_schema['properties']['question']['description'] = "The question to ask about the invopop/gobl repo" + new_schema["properties"]["repoName"][ + "description" + ] = "This value will always be 'invopop/gobl'" + new_schema["properties"]["question"][ + "description" + ] = "The question to ask about the invopop/gobl repo" else: continue # Create a new StructuredTool with the new name @@ -106,8 +110,8 @@ async def get_response(self, user_input: str, config: Dict[str, Any]) -> str: elif func_name == "gobl_search": print( "๐Ÿ” Searching GOBL docs:", - tool_call["function"]["arguments"], - ) + tool_call["function"]["arguments"], + ) elif func_name == "gobl_code_ask_question": print( "๐Ÿ” Searching invopop/gobl repo:", diff --git a/uv.lock b/uv.lock index 8ecb379..b38baec 100644 --- a/uv.lock +++ b/uv.lock @@ -24,6 +24,26 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916 }, ] +[[package]] +name = "black" +version = "25.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "mypy-extensions" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/94/49/26a7b0f3f35da4b5a65f081943b7bcd22d7002f5f0fb8098ec1ff21cb6ef/black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666", size = 649449 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/87/0edf98916640efa5d0696e1abb0a8357b52e69e82322628f25bf14d263d1/black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f", size = 1650673 }, + { url = "https://files.pythonhosted.org/packages/52/e5/f7bf17207cf87fa6e9b676576749c6b6ed0d70f179a3d812c997870291c3/black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3", size = 1453190 }, + { url = "https://files.pythonhosted.org/packages/e3/ee/adda3d46d4a9120772fae6de454c8495603c37c4c3b9c60f25b1ab6401fe/black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171", size = 1782926 }, + { url = "https://files.pythonhosted.org/packages/cc/64/94eb5f45dcb997d2082f097a3944cfc7fe87e071907f677e80788a2d7b7a/black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18", size = 1442613 }, + { url = "https://files.pythonhosted.org/packages/09/71/54e999902aed72baf26bca0d50781b01838251a462612966e9fc4891eadd/black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717", size = 207646 }, +] + [[package]] name = "certifi" version = "2025.4.26" @@ -98,6 +118,35 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, ] +[[package]] +name = "cryptography" +version = "45.0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fe/c8/a2a376a8711c1e11708b9c9972e0c3223f5fc682552c82d8db844393d6ce/cryptography-45.0.4.tar.gz", hash = "sha256:7405ade85c83c37682c8fe65554759800a4a8c54b2d96e0f8ad114d31b808d57", size = 744890 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/14/93b69f2af9ba832ad6618a03f8a034a5851dc9a3314336a3d71c252467e1/cryptography-45.0.4-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:680806cf63baa0039b920f4976f5f31b10e772de42f16310a6839d9f21a26b0d", size = 4205335 }, + { url = "https://files.pythonhosted.org/packages/67/30/fae1000228634bf0b647fca80403db5ca9e3933b91dd060570689f0bd0f7/cryptography-45.0.4-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4ca0f52170e821bc8da6fc0cc565b7bb8ff8d90d36b5e9fdd68e8a86bdf72036", size = 4431487 }, + { url = "https://files.pythonhosted.org/packages/6d/5a/7dffcf8cdf0cb3c2430de7404b327e3db64735747d641fc492539978caeb/cryptography-45.0.4-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f3fe7a5ae34d5a414957cc7f457e2b92076e72938423ac64d215722f6cf49a9e", size = 4208922 }, + { url = "https://files.pythonhosted.org/packages/c6/f3/528729726eb6c3060fa3637253430547fbaaea95ab0535ea41baa4a6fbd8/cryptography-45.0.4-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:25eb4d4d3e54595dc8adebc6bbd5623588991d86591a78c2548ffb64797341e2", size = 3900433 }, + { url = "https://files.pythonhosted.org/packages/d9/4a/67ba2e40f619e04d83c32f7e1d484c1538c0800a17c56a22ff07d092ccc1/cryptography-45.0.4-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:ce1678a2ccbe696cf3af15a75bb72ee008d7ff183c9228592ede9db467e64f1b", size = 4464163 }, + { url = "https://files.pythonhosted.org/packages/7e/9a/b4d5aa83661483ac372464809c4b49b5022dbfe36b12fe9e323ca8512420/cryptography-45.0.4-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:49fe9155ab32721b9122975e168a6760d8ce4cffe423bcd7ca269ba41b5dfac1", size = 4208687 }, + { url = "https://files.pythonhosted.org/packages/db/b7/a84bdcd19d9c02ec5807f2ec2d1456fd8451592c5ee353816c09250e3561/cryptography-45.0.4-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:2882338b2a6e0bd337052e8b9007ced85c637da19ef9ecaf437744495c8c2999", size = 4463623 }, + { url = "https://files.pythonhosted.org/packages/d8/84/69707d502d4d905021cac3fb59a316344e9f078b1da7fb43ecde5e10840a/cryptography-45.0.4-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:23b9c3ea30c3ed4db59e7b9619272e94891f8a3a5591d0b656a7582631ccf750", size = 4332447 }, + { url = "https://files.pythonhosted.org/packages/f3/ee/d4f2ab688e057e90ded24384e34838086a9b09963389a5ba6854b5876598/cryptography-45.0.4-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b0a97c927497e3bc36b33987abb99bf17a9a175a19af38a892dc4bbb844d7ee2", size = 4572830 }, + { url = "https://files.pythonhosted.org/packages/fe/51/8c584ed426093aac257462ae62d26ad61ef1cbf5b58d8b67e6e13c39960e/cryptography-45.0.4-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6a5bf57554e80f75a7db3d4b1dacaa2764611ae166ab42ea9a72bcdb5d577637", size = 4195746 }, + { url = "https://files.pythonhosted.org/packages/5c/7d/4b0ca4d7af95a704eef2f8f80a8199ed236aaf185d55385ae1d1610c03c2/cryptography-45.0.4-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:46cf7088bf91bdc9b26f9c55636492c1cce3e7aaf8041bbf0243f5e5325cfb2d", size = 4424456 }, + { url = "https://files.pythonhosted.org/packages/1d/45/5fabacbc6e76ff056f84d9f60eeac18819badf0cefc1b6612ee03d4ab678/cryptography-45.0.4-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7bedbe4cc930fa4b100fc845ea1ea5788fcd7ae9562e669989c11618ae8d76ee", size = 4198495 }, + { url = "https://files.pythonhosted.org/packages/55/b7/ffc9945b290eb0a5d4dab9b7636706e3b5b92f14ee5d9d4449409d010d54/cryptography-45.0.4-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:eaa3e28ea2235b33220b949c5a0d6cf79baa80eab2eb5607ca8ab7525331b9ff", size = 3885540 }, + { url = "https://files.pythonhosted.org/packages/7f/e3/57b010282346980475e77d414080acdcb3dab9a0be63071efc2041a2c6bd/cryptography-45.0.4-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:7ef2dde4fa9408475038fc9aadfc1fb2676b174e68356359632e980c661ec8f6", size = 4452052 }, + { url = "https://files.pythonhosted.org/packages/37/e6/ddc4ac2558bf2ef517a358df26f45bc774a99bf4653e7ee34b5e749c03e3/cryptography-45.0.4-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:6a3511ae33f09094185d111160fd192c67aa0a2a8d19b54d36e4c78f651dc5ad", size = 4198024 }, + { url = "https://files.pythonhosted.org/packages/3a/c0/85fa358ddb063ec588aed4a6ea1df57dc3e3bc1712d87c8fa162d02a65fc/cryptography-45.0.4-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:06509dc70dd71fa56eaa138336244e2fbaf2ac164fc9b5e66828fccfd2b680d6", size = 4451442 }, + { url = "https://files.pythonhosted.org/packages/33/67/362d6ec1492596e73da24e669a7fbbaeb1c428d6bf49a29f7a12acffd5dc/cryptography-45.0.4-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:5f31e6b0a5a253f6aa49be67279be4a7e5a4ef259a9f33c69f7d1b1191939872", size = 4325038 }, + { url = "https://files.pythonhosted.org/packages/53/75/82a14bf047a96a1b13ebb47fb9811c4f73096cfa2e2b17c86879687f9027/cryptography-45.0.4-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:944e9ccf67a9594137f942d5b52c8d238b1b4e46c7a0c2891b7ae6e01e7c80a4", size = 4560964 }, +] + [[package]] name = "distro" version = "1.9.0" @@ -107,6 +156,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277 }, ] +[[package]] +name = "docutils" +version = "0.21.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/ed/aefcc8cd0ba62a0560c3c18c33925362d46c6075480bfa4df87b28e169a9/docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", size = 2204444 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2", size = 587408 }, +] + [[package]] name = "dotenv" version = "0.9.9" @@ -121,7 +179,7 @@ wheels = [ [[package]] name = "expert" version = "0.1.0" -source = { virtual = "." } +source = { editable = "." } dependencies = [ { name = "dotenv" }, { name = "langchain" }, @@ -131,15 +189,26 @@ dependencies = [ { name = "langgraph-prebuilt" }, ] +[package.optional-dependencies] +dev = [ + { name = "black" }, + { name = "ruff" }, + { name = "twine" }, +] + [package.metadata] requires-dist = [ + { name = "black", marker = "extra == 'dev'", specifier = ">=24.0.0" }, { name = "dotenv", specifier = ">=0.9.9" }, { name = "langchain", specifier = ">=0.3.25" }, { name = "langchain-mcp-adapters", specifier = ">=0.1.1" }, { name = "langchain-openai", specifier = ">=0.3.18" }, { name = "langgraph", specifier = ">=0.4.7" }, { name = "langgraph-prebuilt", specifier = ">=0.2.2" }, + { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.6.0" }, + { name = "twine", marker = "extra == 'dev'", specifier = ">=5.0.0" }, ] +provides-extras = ["dev"] [[package]] name = "greenlet" @@ -212,6 +281,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819 }, ] +[[package]] +name = "id" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/22/11/102da08f88412d875fa2f1a9a469ff7ad4c874b0ca6fed0048fe385bdb3d/id-1.5.0.tar.gz", hash = "sha256:292cb8a49eacbbdbce97244f47a97b4c62540169c976552e497fd57df0734c1d", size = 15237 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/cb/18326d2d89ad3b0dd143da971e77afd1e6ca6674f1b1c3df4b6bec6279fc/id-1.5.0-py3-none-any.whl", hash = "sha256:f1434e1cef91f2cbb8a4ec64663d5a23b9ed43ef44c4c957d02583d61714c658", size = 13611 }, +] + [[package]] name = "idna" version = "3.10" @@ -221,6 +302,48 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, ] +[[package]] +name = "jaraco-classes" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "more-itertools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/c0/ed4a27bc5571b99e3cff68f8a9fa5b56ff7df1c2251cc715a652ddd26402/jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd", size = 11780 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790", size = 6777 }, +] + +[[package]] +name = "jaraco-context" +version = "6.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/df/ad/f3777b81bf0b6e7bc7514a1656d3e637b2e8e15fab2ce3235730b3e7a4e6/jaraco_context-6.0.1.tar.gz", hash = "sha256:9bae4ea555cf0b14938dc0aee7c9f32ed303aa20a3b73e7dc80111628792d1b3", size = 13912 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/db/0c52c4cf5e4bd9f5d7135ec7669a3a767af21b3a308e1ed3674881e52b62/jaraco.context-6.0.1-py3-none-any.whl", hash = "sha256:f797fc481b490edb305122c9181830a3a5b76d84ef6d1aef2fb9b47ab956f9e4", size = 6825 }, +] + +[[package]] +name = "jaraco-functools" +version = "4.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "more-itertools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/49/1c/831faaaa0f090b711c355c6d8b2abf277c72133aab472b6932b03322294c/jaraco_functools-4.2.1.tar.gz", hash = "sha256:be634abfccabce56fa3053f8c7ebe37b682683a4ee7793670ced17bab0087353", size = 19661 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/fd/179a20f832824514df39a90bb0e5372b314fea99f217f5ab942b10a8a4e8/jaraco_functools-4.2.1-py3-none-any.whl", hash = "sha256:590486285803805f4b1f99c60ca9e94ed348d4added84b74c7a12885561e524e", size = 10349 }, +] + +[[package]] +name = "jeepney" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/6f/357efd7602486741aa73ffc0617fb310a29b588ed0fd69c2399acbb85b0c/jeepney-0.9.0.tar.gz", hash = "sha256:cf0e9e845622b81e4a28df94c40345400256ec608d0e55bb8a3feaa9163f5732", size = 106758 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b2/a3/e137168c9c44d18eff0376253da9f1e9234d0239e0ee230d2fee6cea8e55/jeepney-0.9.0-py3-none-any.whl", hash = "sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683", size = 49010 }, +] + [[package]] name = "jiter" version = "0.10.0" @@ -278,6 +401,23 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942", size = 7595 }, ] +[[package]] +name = "keyring" +version = "25.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jaraco-classes" }, + { name = "jaraco-context" }, + { name = "jaraco-functools" }, + { name = "jeepney", marker = "sys_platform == 'linux'" }, + { name = "pywin32-ctypes", marker = "sys_platform == 'win32'" }, + { name = "secretstorage", marker = "sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/70/09/d904a6e96f76ff214be59e7aa6ef7190008f52a0ab6689760a98de0bf37d/keyring-25.6.0.tar.gz", hash = "sha256:0b39998aa941431eb3d9b0d4b2460bc773b9df6fed7621c2dfb291a7e0187a66", size = 62750 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/32/da7f44bcb1105d3e88a0b74ebdca50c59121d2ddf71c9e34ba47df7f3a56/keyring-25.6.0-py3-none-any.whl", hash = "sha256:552a3f7af126ece7ed5c89753650eec89c7eaae8617d0aa4d9ad2b75111266bd", size = 39085 }, +] + [[package]] name = "langchain" version = "0.3.25" @@ -427,6 +567,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/89/8e/e8a58e0abaae3f3ac4702e9ca35d1fc6159711556b64ffd0e247771a3f12/langsmith-0.3.42-py3-none-any.whl", hash = "sha256:18114327f3364385dae4026ebfd57d1c1cb46d8f80931098f0f10abe533475ff", size = 360334 }, ] +[[package]] +name = "markdown-it-py" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 }, +] + [[package]] name = "mcp" version = "1.9.1" @@ -447,6 +599,64 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a6/c0/4ac795585a22a0a2d09cd2b1187b0252d2afcdebd01e10a68bbac4d34890/mcp-1.9.1-py3-none-any.whl", hash = "sha256:2900ded8ffafc3c8a7bfcfe8bc5204037e988e753ec398f371663e6a06ecd9a9", size = 130261 }, ] +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, +] + +[[package]] +name = "more-itertools" +version = "10.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ce/a0/834b0cebabbfc7e311f30b46c8188790a37f89fc8d756660346fe5abfd09/more_itertools-10.7.0.tar.gz", hash = "sha256:9fddd5403be01a94b204faadcff459ec3568cf110265d3c54323e1e866ad29d3", size = 127671 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2b/9f/7ba6f94fc1e9ac3d2b853fdff3035fb2fa5afbed898c4a72b8a020610594/more_itertools-10.7.0-py3-none-any.whl", hash = "sha256:d43980384673cb07d2f7d2d918c616b30c659c089ee23953f601d6609c67510e", size = 65278 }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963 }, +] + +[[package]] +name = "nh3" +version = "0.2.21" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/37/30/2f81466f250eb7f591d4d193930df661c8c23e9056bdc78e365b646054d8/nh3-0.2.21.tar.gz", hash = "sha256:4990e7ee6a55490dbf00d61a6f476c9a3258e31e711e13713b2ea7d6616f670e", size = 16581 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/81/b83775687fcf00e08ade6d4605f0be9c4584cb44c4973d9f27b7456a31c9/nh3-0.2.21-cp313-cp313t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:fcff321bd60c6c5c9cb4ddf2554e22772bb41ebd93ad88171bbbb6f271255286", size = 1297678 }, + { url = "https://files.pythonhosted.org/packages/22/ee/d0ad8fb4b5769f073b2df6807f69a5e57ca9cea504b78809921aef460d20/nh3-0.2.21-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31eedcd7d08b0eae28ba47f43fd33a653b4cdb271d64f1aeda47001618348fde", size = 733774 }, + { url = "https://files.pythonhosted.org/packages/ea/76/b450141e2d384ede43fe53953552f1c6741a499a8c20955ad049555cabc8/nh3-0.2.21-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d426d7be1a2f3d896950fe263332ed1662f6c78525b4520c8e9861f8d7f0d243", size = 760012 }, + { url = "https://files.pythonhosted.org/packages/97/90/1182275db76cd8fbb1f6bf84c770107fafee0cb7da3e66e416bcb9633da2/nh3-0.2.21-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9d67709bc0d7d1f5797b21db26e7a8b3d15d21c9c5f58ccfe48b5328483b685b", size = 923619 }, + { url = "https://files.pythonhosted.org/packages/29/c7/269a7cfbec9693fad8d767c34a755c25ccb8d048fc1dfc7a7d86bc99375c/nh3-0.2.21-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:55823c5ea1f6b267a4fad5de39bc0524d49a47783e1fe094bcf9c537a37df251", size = 1000384 }, + { url = "https://files.pythonhosted.org/packages/68/a9/48479dbf5f49ad93f0badd73fbb48b3d769189f04c6c69b0df261978b009/nh3-0.2.21-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:818f2b6df3763e058efa9e69677b5a92f9bc0acff3295af5ed013da544250d5b", size = 918908 }, + { url = "https://files.pythonhosted.org/packages/d7/da/0279c118f8be2dc306e56819880b19a1cf2379472e3b79fc8eab44e267e3/nh3-0.2.21-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:b3b5c58161e08549904ac4abd450dacd94ff648916f7c376ae4b2c0652b98ff9", size = 909180 }, + { url = "https://files.pythonhosted.org/packages/26/16/93309693f8abcb1088ae143a9c8dbcece9c8f7fb297d492d3918340c41f1/nh3-0.2.21-cp313-cp313t-win32.whl", hash = "sha256:637d4a10c834e1b7d9548592c7aad760611415fcd5bd346f77fd8a064309ae6d", size = 532747 }, + { url = "https://files.pythonhosted.org/packages/a2/3a/96eb26c56cbb733c0b4a6a907fab8408ddf3ead5d1b065830a8f6a9c3557/nh3-0.2.21-cp313-cp313t-win_amd64.whl", hash = "sha256:713d16686596e556b65e7f8c58328c2df63f1a7abe1277d87625dcbbc012ef82", size = 528908 }, + { url = "https://files.pythonhosted.org/packages/ba/1d/b1ef74121fe325a69601270f276021908392081f4953d50b03cbb38b395f/nh3-0.2.21-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:a772dec5b7b7325780922dd904709f0f5f3a79fbf756de5291c01370f6df0967", size = 1316133 }, + { url = "https://files.pythonhosted.org/packages/b8/f2/2c7f79ce6de55b41e7715f7f59b159fd59f6cdb66223c05b42adaee2b645/nh3-0.2.21-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d002b648592bf3033adfd875a48f09b8ecc000abd7f6a8769ed86b6ccc70c759", size = 758328 }, + { url = "https://files.pythonhosted.org/packages/6d/ad/07bd706fcf2b7979c51b83d8b8def28f413b090cf0cb0035ee6b425e9de5/nh3-0.2.21-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2a5174551f95f2836f2ad6a8074560f261cf9740a48437d6151fd2d4d7d617ab", size = 747020 }, + { url = "https://files.pythonhosted.org/packages/75/99/06a6ba0b8a0d79c3d35496f19accc58199a1fb2dce5e711a31be7e2c1426/nh3-0.2.21-cp38-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:b8d55ea1fc7ae3633d758a92aafa3505cd3cc5a6e40470c9164d54dff6f96d42", size = 944878 }, + { url = "https://files.pythonhosted.org/packages/79/d4/dc76f5dc50018cdaf161d436449181557373869aacf38a826885192fc587/nh3-0.2.21-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6ae319f17cd8960d0612f0f0ddff5a90700fa71926ca800e9028e7851ce44a6f", size = 903460 }, + { url = "https://files.pythonhosted.org/packages/cd/c3/d4f8037b2ab02ebf5a2e8637bd54736ed3d0e6a2869e10341f8d9085f00e/nh3-0.2.21-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:63ca02ac6f27fc80f9894409eb61de2cb20ef0a23740c7e29f9ec827139fa578", size = 839369 }, + { url = "https://files.pythonhosted.org/packages/11/a9/1cd3c6964ec51daed7b01ca4686a5c793581bf4492cbd7274b3f544c9abe/nh3-0.2.21-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5f77e62aed5c4acad635239ac1290404c7e940c81abe561fd2af011ff59f585", size = 739036 }, + { url = "https://files.pythonhosted.org/packages/fd/04/bfb3ff08d17a8a96325010ae6c53ba41de6248e63cdb1b88ef6369a6cdfc/nh3-0.2.21-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:087ffadfdcd497658c3adc797258ce0f06be8a537786a7217649fc1c0c60c293", size = 768712 }, + { url = "https://files.pythonhosted.org/packages/9e/aa/cfc0bf545d668b97d9adea4f8b4598667d2b21b725d83396c343ad12bba7/nh3-0.2.21-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ac7006c3abd097790e611fe4646ecb19a8d7f2184b882f6093293b8d9b887431", size = 930559 }, + { url = "https://files.pythonhosted.org/packages/78/9d/6f5369a801d3a1b02e6a9a097d56bcc2f6ef98cffebf03c4bb3850d8e0f0/nh3-0.2.21-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:6141caabe00bbddc869665b35fc56a478eb774a8c1dfd6fba9fe1dfdf29e6efa", size = 1008591 }, + { url = "https://files.pythonhosted.org/packages/a6/df/01b05299f68c69e480edff608248313cbb5dbd7595c5e048abe8972a57f9/nh3-0.2.21-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:20979783526641c81d2f5bfa6ca5ccca3d1e4472474b162c6256745fbfe31cd1", size = 925670 }, + { url = "https://files.pythonhosted.org/packages/3d/79/bdba276f58d15386a3387fe8d54e980fb47557c915f5448d8c6ac6f7ea9b/nh3-0.2.21-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a7ea28cd49293749d67e4fcf326c554c83ec912cd09cd94aa7ec3ab1921c8283", size = 917093 }, + { url = "https://files.pythonhosted.org/packages/e7/d8/c6f977a5cd4011c914fb58f5ae573b071d736187ccab31bfb1d539f4af9f/nh3-0.2.21-cp38-abi3-win32.whl", hash = "sha256:6c9c30b8b0d291a7c5ab0967ab200598ba33208f754f2f4920e9343bdd88f79a", size = 537623 }, + { url = "https://files.pythonhosted.org/packages/23/fc/8ce756c032c70ae3dd1d48a3552577a325475af2a2f629604b44f571165c/nh3-0.2.21-cp38-abi3-win_amd64.whl", hash = "sha256:bb0014948f04d7976aabae43fcd4cb7f551f9f8ce785a4c9ef66e6c2590f8629", size = 535283 }, +] + [[package]] name = "openai" version = "1.82.0" @@ -514,6 +724,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, ] +[[package]] +name = "pathspec" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 }, +] + +[[package]] +name = "platformdirs" +version = "4.3.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567 }, +] + [[package]] name = "pycparser" version = "2.22" @@ -580,6 +808,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b6/5f/d6d641b490fd3ec2c4c13b4244d68deea3a1b970a97be64f34fb5504ff72/pydantic_settings-2.9.1-py3-none-any.whl", hash = "sha256:59b4f431b1defb26fe620c71a7d3968a710d719f5f4cdbbdb7926edeb770f6ef", size = 44356 }, ] +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217 }, +] + [[package]] name = "python-dotenv" version = "1.1.0" @@ -598,6 +835,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546 }, ] +[[package]] +name = "pywin32-ctypes" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/85/9f/01a1a99704853cb63f253eea009390c88e7131c67e66a0a02099a8c917cb/pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755", size = 29471 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", size = 30756 }, +] + [[package]] name = "pyyaml" version = "6.0.2" @@ -615,6 +861,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, ] +[[package]] +name = "readme-renderer" +version = "44.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docutils" }, + { name = "nh3" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5a/a9/104ec9234c8448c4379768221ea6df01260cd6c2ce13182d4eac531c8342/readme_renderer-44.0.tar.gz", hash = "sha256:8712034eabbfa6805cacf1402b4eeb2a73028f72d1166d6f5cb7f9c047c5d1e1", size = 32056 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/67/921ec3024056483db83953ae8e48079ad62b92db7880013ca77632921dd0/readme_renderer-44.0-py3-none-any.whl", hash = "sha256:2fbca89b81a08526aadf1357a8c2ae889ec05fb03f5da67f9769c9a592166151", size = 13310 }, +] + [[package]] name = "regex" version = "2024.11.6" @@ -665,6 +925,66 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481 }, ] +[[package]] +name = "rfc3986" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/85/40/1520d68bfa07ab5a6f065a186815fb6610c86fe957bc065754e47f7b0840/rfc3986-2.0.0.tar.gz", hash = "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c", size = 49026 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/9a/9afaade874b2fa6c752c36f1548f718b5b83af81ed9b76628329dab81c1b/rfc3986-2.0.0-py2.py3-none-any.whl", hash = "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd", size = 31326 }, +] + +[[package]] +name = "rich" +version = "14.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/53/830aa4c3066a8ab0ae9a9955976fb770fe9c6102117c8ec4ab3ea62d89e8/rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725", size = 224078 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/9b/63f4c7ebc259242c89b3acafdb37b41d1185c07ff0011164674e9076b491/rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0", size = 243229 }, +] + +[[package]] +name = "ruff" +version = "0.11.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ed/da/9c6f995903b4d9474b39da91d2d626659af3ff1eeb43e9ae7c119349dba6/ruff-0.11.13.tar.gz", hash = "sha256:26fa247dc68d1d4e72c179e08889a25ac0c7ba4d78aecfc835d49cbfd60bf514", size = 4282054 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7d/ce/a11d381192966e0b4290842cc8d4fac7dc9214ddf627c11c1afff87da29b/ruff-0.11.13-py3-none-linux_armv6l.whl", hash = "sha256:4bdfbf1240533f40042ec00c9e09a3aade6f8c10b6414cf11b519488d2635d46", size = 10292516 }, + { url = "https://files.pythonhosted.org/packages/78/db/87c3b59b0d4e753e40b6a3b4a2642dfd1dcaefbff121ddc64d6c8b47ba00/ruff-0.11.13-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:aef9c9ed1b5ca28bb15c7eac83b8670cf3b20b478195bd49c8d756ba0a36cf48", size = 11106083 }, + { url = "https://files.pythonhosted.org/packages/77/79/d8cec175856ff810a19825d09ce700265f905c643c69f45d2b737e4a470a/ruff-0.11.13-py3-none-macosx_11_0_arm64.whl", hash = "sha256:53b15a9dfdce029c842e9a5aebc3855e9ab7771395979ff85b7c1dedb53ddc2b", size = 10436024 }, + { url = "https://files.pythonhosted.org/packages/8b/5b/f6d94f2980fa1ee854b41568368a2e1252681b9238ab2895e133d303538f/ruff-0.11.13-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab153241400789138d13f362c43f7edecc0edfffce2afa6a68434000ecd8f69a", size = 10646324 }, + { url = "https://files.pythonhosted.org/packages/6c/9c/b4c2acf24ea4426016d511dfdc787f4ce1ceb835f3c5fbdbcb32b1c63bda/ruff-0.11.13-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6c51f93029d54a910d3d24f7dd0bb909e31b6cd989a5e4ac513f4eb41629f0dc", size = 10174416 }, + { url = "https://files.pythonhosted.org/packages/f3/10/e2e62f77c65ede8cd032c2ca39c41f48feabedb6e282bfd6073d81bb671d/ruff-0.11.13-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1808b3ed53e1a777c2ef733aca9051dc9bf7c99b26ece15cb59a0320fbdbd629", size = 11724197 }, + { url = "https://files.pythonhosted.org/packages/bb/f0/466fe8469b85c561e081d798c45f8a1d21e0b4a5ef795a1d7f1a9a9ec182/ruff-0.11.13-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:d28ce58b5ecf0f43c1b71edffabe6ed7f245d5336b17805803312ec9bc665933", size = 12511615 }, + { url = "https://files.pythonhosted.org/packages/17/0e/cefe778b46dbd0cbcb03a839946c8f80a06f7968eb298aa4d1a4293f3448/ruff-0.11.13-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55e4bc3a77842da33c16d55b32c6cac1ec5fb0fbec9c8c513bdce76c4f922165", size = 12117080 }, + { url = "https://files.pythonhosted.org/packages/5d/2c/caaeda564cbe103bed145ea557cb86795b18651b0f6b3ff6a10e84e5a33f/ruff-0.11.13-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:633bf2c6f35678c56ec73189ba6fa19ff1c5e4807a78bf60ef487b9dd272cc71", size = 11326315 }, + { url = "https://files.pythonhosted.org/packages/75/f0/782e7d681d660eda8c536962920c41309e6dd4ebcea9a2714ed5127d44bd/ruff-0.11.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ffbc82d70424b275b089166310448051afdc6e914fdab90e08df66c43bb5ca9", size = 11555640 }, + { url = "https://files.pythonhosted.org/packages/5d/d4/3d580c616316c7f07fb3c99dbecfe01fbaea7b6fd9a82b801e72e5de742a/ruff-0.11.13-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4a9ddd3ec62a9a89578c85842b836e4ac832d4a2e0bfaad3b02243f930ceafcc", size = 10507364 }, + { url = "https://files.pythonhosted.org/packages/5a/dc/195e6f17d7b3ea6b12dc4f3e9de575db7983db187c378d44606e5d503319/ruff-0.11.13-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d237a496e0778d719efb05058c64d28b757c77824e04ffe8796c7436e26712b7", size = 10141462 }, + { url = "https://files.pythonhosted.org/packages/f4/8e/39a094af6967faa57ecdeacb91bedfb232474ff8c3d20f16a5514e6b3534/ruff-0.11.13-py3-none-musllinux_1_2_i686.whl", hash = "sha256:26816a218ca6ef02142343fd24c70f7cd8c5aa6c203bca284407adf675984432", size = 11121028 }, + { url = "https://files.pythonhosted.org/packages/5a/c0/b0b508193b0e8a1654ec683ebab18d309861f8bd64e3a2f9648b80d392cb/ruff-0.11.13-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:51c3f95abd9331dc5b87c47ac7f376db5616041173826dfd556cfe3d4977f492", size = 11602992 }, + { url = "https://files.pythonhosted.org/packages/7c/91/263e33ab93ab09ca06ce4f8f8547a858cc198072f873ebc9be7466790bae/ruff-0.11.13-py3-none-win32.whl", hash = "sha256:96c27935418e4e8e77a26bb05962817f28b8ef3843a6c6cc49d8783b5507f250", size = 10474944 }, + { url = "https://files.pythonhosted.org/packages/46/f4/7c27734ac2073aae8efb0119cae6931b6fb48017adf048fdf85c19337afc/ruff-0.11.13-py3-none-win_amd64.whl", hash = "sha256:29c3189895a8a6a657b7af4e97d330c8a3afd2c9c8f46c81e2fc5a31866517e3", size = 11548669 }, + { url = "https://files.pythonhosted.org/packages/ec/bf/b273dd11673fed8a6bd46032c0ea2a04b2ac9bfa9c628756a5856ba113b0/ruff-0.11.13-py3-none-win_arm64.whl", hash = "sha256:b4385285e9179d608ff1d2fb9922062663c658605819a6876d8beef0c30b7f3b", size = 10683928 }, +] + +[[package]] +name = "secretstorage" +version = "3.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "jeepney" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/53/a4/f48c9d79cb507ed1373477dbceaba7401fd8a23af63b837fa61f1dcd3691/SecretStorage-3.3.3.tar.gz", hash = "sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77", size = 19739 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/24/b4293291fa1dd830f353d2cb163295742fa87f179fcc8a20a306a81978b7/SecretStorage-3.3.3-py3-none-any.whl", hash = "sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99", size = 15221 }, +] + [[package]] name = "sniffio" version = "1.3.1" @@ -759,6 +1079,26 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540 }, ] +[[package]] +name = "twine" +version = "6.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "id" }, + { name = "keyring", marker = "platform_machine != 'ppc64le' and platform_machine != 's390x'" }, + { name = "packaging" }, + { name = "readme-renderer" }, + { name = "requests" }, + { name = "requests-toolbelt" }, + { name = "rfc3986" }, + { name = "rich" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c8/a2/6df94fc5c8e2170d21d7134a565c3a8fb84f9797c1dd65a5976aaf714418/twine-6.1.0.tar.gz", hash = "sha256:be324f6272eff91d07ee93f251edf232fc647935dd585ac003539b42404a8dbd", size = 168404 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/b6/74e927715a285743351233f33ea3c684528a0d374d2e43ff9ce9585b73fe/twine-6.1.0-py3-none-any.whl", hash = "sha256:a47f973caf122930bf0fbbf17f80b83bc1602c9ce393c7845f289a3001dc5384", size = 40791 }, +] + [[package]] name = "typing-extensions" version = "4.13.2" From e84943c6b023ebae632be7c82d7f2bf4732668b7 Mon Sep 17 00:00:00 2001 From: Menendez6 Date: Mon, 23 Jun 2025 11:54:01 +0000 Subject: [PATCH 4/6] Use ruff for format as well --- .github/workflows/code-quality.yml | 4 ---- .github/workflows/pre-commit.yml | 12 ------------ .github/workflows/release.yml | 1 - pyproject.toml | 21 +++++++++++++++++---- src/expert/agent.py | 12 ++++++------ 5 files changed, 23 insertions(+), 27 deletions(-) diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index 79f6619..e5a06cd 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -30,10 +30,6 @@ jobs: run: | uv sync --extra dev - - name: Run Black (Format Check) - run: | - uv run black --check --diff . - - name: Run Ruff (Linting) run: | uv run ruff check . diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 6068b27..50394d2 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -25,18 +25,6 @@ jobs: - name: Install dependencies run: uv sync --extra dev - - name: Check Black formatting - id: black-check - run: | - echo "Checking code formatting with Black..." - if ! uv run black --check --quiet .; then - echo "โŒ Code formatting issues found!" - echo "Run 'uv run black .' to fix formatting issues." - exit 1 - else - echo "โœ… Code formatting is correct!" - fi - - name: Check Ruff linting id: ruff-lint run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5fc2664..e753460 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -88,7 +88,6 @@ jobs: - name: Run pre-commit checks run: | echo "Running code quality checks..." - uv run black --check . uv run ruff check . uv run ruff format --check . diff --git a/pyproject.toml b/pyproject.toml index 08c42e1..8558ce1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,6 @@ dependencies = [ [project.optional-dependencies] dev = [ - "black>=24.0.0", "ruff>=0.6.0", "twine>=5.0.0", ] @@ -40,6 +39,20 @@ expert = ["prompts/*.md"] line-length = 88 target-version = "py313" -[tool.black] -line-length = 88 -target-version = ['py313'] +[tool.ruff.lint] +select = [ + "E", # pycodestyle errors + "W", # pycodestyle warnings + "F", # pyflakes + "I", # isort + "B", # flake8-bugbear + "C4", # flake8-comprehensions + "UP", # pyupgrade +] +ignore = [] + +[tool.ruff.format] +quote-style = "double" +indent-style = "space" +skip-magic-trailing-comma = false +line-ending = "auto" diff --git a/src/expert/agent.py b/src/expert/agent.py index 00cc3ca..82ed4ea 100644 --- a/src/expert/agent.py +++ b/src/expert/agent.py @@ -60,12 +60,12 @@ async def setup(self): new_name = "gobl_code_ask_question" new_description = self.gobl_code_description new_schema = tool.args_schema.copy() - new_schema["properties"]["repoName"][ - "description" - ] = "This value will always be 'invopop/gobl'" - new_schema["properties"]["question"][ - "description" - ] = "The question to ask about the invopop/gobl repo" + new_schema["properties"]["repoName"]["description"] = ( + "This value will always be 'invopop/gobl'" + ) + new_schema["properties"]["question"]["description"] = ( + "The question to ask about the invopop/gobl repo" + ) else: continue # Create a new StructuredTool with the new name From 133958c54a1212e844dbb99e0ba9953c463656e1 Mon Sep 17 00:00:00 2001 From: Menendez6 Date: Mon, 23 Jun 2025 12:01:33 +0000 Subject: [PATCH 5/6] Solve linter issues --- src/expert/__init__.py | 5 ++++- src/expert/agent.py | 20 +++++++++-------- src/expert/config.py | 23 ++++++++++---------- src/expert/main.py | 2 +- uv.lock | 49 ------------------------------------------ 5 files changed, 28 insertions(+), 71 deletions(-) diff --git a/src/expert/__init__.py b/src/expert/__init__.py index d1f30f1..184cda0 100644 --- a/src/expert/__init__.py +++ b/src/expert/__init__.py @@ -15,7 +15,10 @@ async def main(): await expert.setup() thread_config = {"configurable": {"thread_id": "my-conversation"}} - response = await expert.get_response("How do I create an invoice?", thread_config) + response = await expert.get_response( + "How do I create an invoice?", + thread_config + ) print(response) asyncio.run(main()) diff --git a/src/expert/agent.py b/src/expert/agent.py index 82ed4ea..d40e752 100644 --- a/src/expert/agent.py +++ b/src/expert/agent.py @@ -2,12 +2,12 @@ from datetime import datetime from pathlib import Path -from typing import Dict, Any +from typing import Any -from langchain_mcp_adapters.client import MultiServerMCPClient -from langgraph.prebuilt import create_react_agent from langchain_core.tools import StructuredTool +from langchain_mcp_adapters.client import MultiServerMCPClient from langgraph.checkpoint.memory import InMemorySaver +from langgraph.prebuilt import create_react_agent from .config import Config @@ -26,16 +26,16 @@ def _load_prompts(self): """Load prompt templates from files.""" prompts_dir = Path(__file__).parent / "prompts" - with open(prompts_dir / "system_prompt.md", "r") as f: + with open(prompts_dir / "system_prompt.md") as f: self.system_prompt = f.read().strip() - with open(prompts_dir / "invopop_docs_description.md", "r") as f: + with open(prompts_dir / "invopop_docs_description.md") as f: self.invopop_docs_description = f.read().strip() - with open(prompts_dir / "gobl_docs_description.md", "r") as f: + with open(prompts_dir / "gobl_docs_description.md") as f: self.gobl_docs_description = f.read().strip() - with open(prompts_dir / "gobl_code_description.md", "r") as f: + with open(prompts_dir / "gobl_code_description.md") as f: self.gobl_code_description = f.read().strip() async def setup(self): @@ -80,7 +80,9 @@ async def setup(self): # Create the agent llm_config = self.config.llm_config - model_name = f"{llm_config.get('provider', 'openai')}:{llm_config.get('model', 'gpt-4.1')}" + provider = llm_config.get("provider", "openai") + model = llm_config.get("model", "gpt-4.1") + model_name = f"{provider}:{model}" self.agent = create_react_agent( model_name, @@ -89,7 +91,7 @@ async def setup(self): prompt=self.system_prompt, ) - async def get_response(self, user_input: str, config: Dict[str, Any]) -> str: + async def get_response(self, user_input: str, config: dict[str, Any]) -> str: """Get response from the agent for a given input.""" if not self.agent: raise RuntimeError("Agent not initialized. Call setup() first.") diff --git a/src/expert/config.py b/src/expert/config.py index ed3c9a5..36ada66 100644 --- a/src/expert/config.py +++ b/src/expert/config.py @@ -1,16 +1,17 @@ """Configuration management for Invopop Expert.""" import os -import yaml from pathlib import Path -from typing import Dict, Any, Optional +from typing import Any + +import yaml from dotenv import load_dotenv class Config: """Configuration manager for Invopop Expert.""" - def __init__(self, config_path: Optional[str] = None): + def __init__(self, config_path: str | None = None): """Initialize configuration.""" # Load environment variables load_dotenv() @@ -25,15 +26,15 @@ def __init__(self, config_path: Optional[str] = None): # Validate required environment variables self._validate_env_vars() - def _load_config(self) -> Dict[str, Any]: + def _load_config(self) -> dict[str, Any]: """Load configuration from YAML file.""" try: - with open(self.config_path, "r") as f: + with open(self.config_path) as f: return yaml.safe_load(f) - except FileNotFoundError: - raise FileNotFoundError(f"Configuration file not found: {self.config_path}") + except FileNotFoundError as e: + raise FileNotFoundError(f"Configuration file not found: {self.config_path}") from e # noqa: E501 except yaml.YAMLError as e: - raise ValueError(f"Invalid YAML configuration: {e}") + raise ValueError(f"Invalid YAML configuration: {e}") from e def _validate_env_vars(self): """Validate required environment variables.""" @@ -46,12 +47,12 @@ def openai_api_key(self) -> str: return os.getenv("OPENAI_API_KEY") @property - def llm_config(self) -> Dict[str, Any]: + def llm_config(self) -> dict[str, Any]: """Get LLM configuration.""" return self.config.get("llm", {}) @property - def mcp_config(self) -> Dict[str, Any]: + def mcp_config(self) -> dict[str, Any]: """Get MCP server configuration.""" config = self.config.get("mcp", {}).get("servers", {}) @@ -74,6 +75,6 @@ def mcp_config(self) -> Dict[str, Any]: return config @property - def chat_config(self) -> Dict[str, Any]: + def chat_config(self) -> dict[str, Any]: """Get chat configuration.""" return self.config.get("chat", {}) diff --git a/src/expert/main.py b/src/expert/main.py index 42a66b7..c651124 100644 --- a/src/expert/main.py +++ b/src/expert/main.py @@ -5,8 +5,8 @@ import click -from .config import Config from .agent import InvopopExpert +from .config import Config @click.command() diff --git a/uv.lock b/uv.lock index b38baec..5975c3c 100644 --- a/uv.lock +++ b/uv.lock @@ -24,26 +24,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916 }, ] -[[package]] -name = "black" -version = "25.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "mypy-extensions" }, - { name = "packaging" }, - { name = "pathspec" }, - { name = "platformdirs" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/94/49/26a7b0f3f35da4b5a65f081943b7bcd22d7002f5f0fb8098ec1ff21cb6ef/black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666", size = 649449 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/98/87/0edf98916640efa5d0696e1abb0a8357b52e69e82322628f25bf14d263d1/black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f", size = 1650673 }, - { url = "https://files.pythonhosted.org/packages/52/e5/f7bf17207cf87fa6e9b676576749c6b6ed0d70f179a3d812c997870291c3/black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3", size = 1453190 }, - { url = "https://files.pythonhosted.org/packages/e3/ee/adda3d46d4a9120772fae6de454c8495603c37c4c3b9c60f25b1ab6401fe/black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171", size = 1782926 }, - { url = "https://files.pythonhosted.org/packages/cc/64/94eb5f45dcb997d2082f097a3944cfc7fe87e071907f677e80788a2d7b7a/black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18", size = 1442613 }, - { url = "https://files.pythonhosted.org/packages/09/71/54e999902aed72baf26bca0d50781b01838251a462612966e9fc4891eadd/black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717", size = 207646 }, -] - [[package]] name = "certifi" version = "2025.4.26" @@ -191,14 +171,12 @@ dependencies = [ [package.optional-dependencies] dev = [ - { name = "black" }, { name = "ruff" }, { name = "twine" }, ] [package.metadata] requires-dist = [ - { name = "black", marker = "extra == 'dev'", specifier = ">=24.0.0" }, { name = "dotenv", specifier = ">=0.9.9" }, { name = "langchain", specifier = ">=0.3.25" }, { name = "langchain-mcp-adapters", specifier = ">=0.1.1" }, @@ -617,15 +595,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2b/9f/7ba6f94fc1e9ac3d2b853fdff3035fb2fa5afbed898c4a72b8a020610594/more_itertools-10.7.0-py3-none-any.whl", hash = "sha256:d43980384673cb07d2f7d2d918c616b30c659c089ee23953f601d6609c67510e", size = 65278 }, ] -[[package]] -name = "mypy-extensions" -version = "1.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963 }, -] - [[package]] name = "nh3" version = "0.2.21" @@ -724,24 +693,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, ] -[[package]] -name = "pathspec" -version = "0.12.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 }, -] - -[[package]] -name = "platformdirs" -version = "4.3.8" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567 }, -] - [[package]] name = "pycparser" version = "2.22" From bdab346df71175dca54fa4e57db57cb41e137ec7 Mon Sep 17 00:00:00 2001 From: Menendez6 Date: Mon, 23 Jun 2025 12:03:09 +0000 Subject: [PATCH 6/6] ruff format solved --- src/expert/config.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/expert/config.py b/src/expert/config.py index 36ada66..d672449 100644 --- a/src/expert/config.py +++ b/src/expert/config.py @@ -32,7 +32,9 @@ def _load_config(self) -> dict[str, Any]: with open(self.config_path) as f: return yaml.safe_load(f) except FileNotFoundError as e: - raise FileNotFoundError(f"Configuration file not found: {self.config_path}") from e # noqa: E501 + raise FileNotFoundError( + f"Configuration file not found: {self.config_path}" + ) from e except yaml.YAMLError as e: raise ValueError(f"Invalid YAML configuration: {e}") from e