Deploy develop: FastAPI+React frontend, multi-stage Docker (ai_agent serve)
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .devcontainer/Dockerfile +16 -16
- .dockerignore +12 -6
- .env.dist +6 -0
- .gitattributes +9 -0
- .github/workflows/deploy-to-hf.yml +53 -53
- .github/workflows/deploy_docs.yml +57 -57
- .github/workflows/docker-build-pr.yml +23 -23
- .gitignore +4 -0
- AGENTS.md +92 -92
- CHANGELOG.md +21 -0
- Dockerfile +41 -33
- LICENSE +201 -201
- README.md +562 -562
- config.yaml +64 -64
- data/query.rq +8 -0
- docker-compose.yml +25 -0
- docs/architecture/agent.md +619 -619
- docs/architecture/catalog.md +455 -455
- docs/architecture/overview.md +459 -459
- docs/architecture/retrieval.md +387 -387
- docs/development/contributing.md +315 -315
- docs/development/structure.md +362 -362
- docs/development/testing.md +410 -410
- docs/getting-started/configuration.md +165 -165
- docs/getting-started/installation.md +143 -143
- docs/getting-started/quickstart.md +190 -190
- docs/guide.md +277 -277
- docs/index.md +81 -81
- docs/reference/changelog.md +79 -79
- docs/reference/cli.md +197 -197
- docs/reference/environment.md +309 -309
- docs/user-guide/advanced-features.md +387 -387
- docs/user-guide/chat-interface.md +275 -275
- docs/user-guide/file-formats.md +282 -282
- docs/user-guide/recommendations.md +329 -329
- docs/user-guide/running-demos.md +372 -372
- mkdocs.yml +108 -108
- pyproject.toml +4 -0
- src/ai_agent/agent/agent.py +650 -611
- src/ai_agent/agent/tools/__init__.py +38 -38
- src/ai_agent/agent/tools/deepwiki_tool.py +103 -103
- src/ai_agent/agent/tools/mcp/__init__.py +53 -53
- src/ai_agent/agent/tools/mcp/base.py +88 -88
- src/ai_agent/agent/tools/mcp/lungs_segmentation_tool.py +442 -442
- src/ai_agent/agent/tools/mcp/registry.py +203 -203
- src/ai_agent/agent/tools/query_utils.py +128 -128
- src/ai_agent/agent/tools/repo_info_tool.py +43 -32
- src/ai_agent/agent/tools/search_alternative_tool.py +73 -73
- src/ai_agent/agent/tools/sparql_tool.py +178 -0
- src/ai_agent/api/deps.py +103 -0
.devcontainer/Dockerfile
CHANGED
|
@@ -1,17 +1,17 @@
|
|
| 1 |
-
FROM ghcr.io/astral-sh/uv:python3.12-bookworm
|
| 2 |
-
|
| 3 |
-
# Install just and other system dependencies
|
| 4 |
-
RUN apt-get update && apt-get install -y \
|
| 5 |
-
sudo \
|
| 6 |
-
curl \
|
| 7 |
-
&& curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to /usr/local/bin \
|
| 8 |
-
&& apt-get clean \
|
| 9 |
-
&& rm -rf /var/lib/apt/lists/*
|
| 10 |
-
|
| 11 |
-
# Create non-root user with UID/GID typically used by VS Code (1000:1000)
|
| 12 |
-
RUN useradd -ms /bin/bash -u 1000 vscode \
|
| 13 |
-
&& apt-get update && apt-get install -y sudo \
|
| 14 |
-
&& echo "vscode ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
|
| 15 |
-
|
| 16 |
-
USER vscode
|
| 17 |
WORKDIR /workspaces
|
|
|
|
| 1 |
+
FROM ghcr.io/astral-sh/uv:python3.12-bookworm
|
| 2 |
+
|
| 3 |
+
# Install just and other system dependencies
|
| 4 |
+
RUN apt-get update && apt-get install -y \
|
| 5 |
+
sudo \
|
| 6 |
+
curl \
|
| 7 |
+
&& curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to /usr/local/bin \
|
| 8 |
+
&& apt-get clean \
|
| 9 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 10 |
+
|
| 11 |
+
# Create non-root user with UID/GID typically used by VS Code (1000:1000)
|
| 12 |
+
RUN useradd -ms /bin/bash -u 1000 vscode \
|
| 13 |
+
&& apt-get update && apt-get install -y sudo \
|
| 14 |
+
&& echo "vscode ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
|
| 15 |
+
|
| 16 |
+
USER vscode
|
| 17 |
WORKDIR /workspaces
|
.dockerignore
CHANGED
|
@@ -1,6 +1,12 @@
|
|
| 1 |
-
.git
|
| 2 |
-
__pycache__
|
| 3 |
-
*.pyc
|
| 4 |
-
.env
|
| 5 |
-
.env.*
|
| 6 |
-
tests
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.git
|
| 2 |
+
__pycache__
|
| 3 |
+
*.pyc
|
| 4 |
+
.env
|
| 5 |
+
.env.*
|
| 6 |
+
tests
|
| 7 |
+
|
| 8 |
+
# Frontend artifacts: copied via the node stage in the multi-stage build,
|
| 9 |
+
# so the python stage never needs the host's node_modules / dist tree.
|
| 10 |
+
src/frontend/node_modules
|
| 11 |
+
src/frontend/dist
|
| 12 |
+
src/frontend/.vite
|
.env.dist
CHANGED
|
@@ -26,6 +26,12 @@ LOG_PROMPTS=0 # write selector prompt snapshots
|
|
| 26 |
# Path to config.yaml
|
| 27 |
CONFIG_PATH=path/to/custom/config.yaml
|
| 28 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
# GraphDB
|
| 30 |
GRAPHDB_GRAPH=
|
| 31 |
GRAPHDB_URL=
|
|
|
|
| 26 |
# Path to config.yaml
|
| 27 |
CONFIG_PATH=path/to/custom/config.yaml
|
| 28 |
|
| 29 |
+
# Shared password for the new FastAPI frontend (leave empty to disable auth).
|
| 30 |
+
APP_PASSWORD=
|
| 31 |
+
|
| 32 |
+
# Session TTL in seconds for the in-process session store (defaults to 6h).
|
| 33 |
+
SESSION_TTL_SECONDS=21600
|
| 34 |
+
|
| 35 |
# GraphDB
|
| 36 |
GRAPHDB_GRAPH=
|
| 37 |
GRAPHDB_URL=
|
.gitattributes
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
*.png filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
*.tif filter=lfs diff=lfs merge=lfs -text
|
| 3 |
+
*.tiff filter=lfs diff=lfs merge=lfs -text
|
| 4 |
+
*.jpg filter=lfs diff=lfs merge=lfs -text
|
| 5 |
+
*.jpeg filter=lfs diff=lfs merge=lfs -text
|
| 6 |
+
*.gif filter=lfs diff=lfs merge=lfs -text
|
| 7 |
+
*.ico filter=lfs diff=lfs merge=lfs -text
|
| 8 |
+
*.webp filter=lfs diff=lfs merge=lfs -text
|
| 9 |
+
*.bmp filter=lfs diff=lfs merge=lfs -text
|
.github/workflows/deploy-to-hf.yml
CHANGED
|
@@ -1,54 +1,54 @@
|
|
| 1 |
-
name: Deploy to Hugging Face Space
|
| 2 |
-
|
| 3 |
-
on:
|
| 4 |
-
push:
|
| 5 |
-
branches:
|
| 6 |
-
- main
|
| 7 |
-
workflow_dispatch:
|
| 8 |
-
|
| 9 |
-
jobs:
|
| 10 |
-
deploy:
|
| 11 |
-
runs-on: ubuntu-latest
|
| 12 |
-
|
| 13 |
-
steps:
|
| 14 |
-
- name: Checkout code
|
| 15 |
-
uses: actions/checkout@v4
|
| 16 |
-
with:
|
| 17 |
-
fetch-depth: 1
|
| 18 |
-
lfs: false
|
| 19 |
-
|
| 20 |
-
- name: Prepare clean HF deploy branch without assets
|
| 21 |
-
run: |
|
| 22 |
-
git config user.email "ci@github.actions"
|
| 23 |
-
git config user.name "github-actions[bot]"
|
| 24 |
-
|
| 25 |
-
original_readme="$(cat README.md)"
|
| 26 |
-
|
| 27 |
-
cat > README.md <<EOF
|
| 28 |
-
---
|
| 29 |
-
title: AI Agent
|
| 30 |
-
emoji: 🤖
|
| 31 |
-
colorFrom: blue
|
| 32 |
-
colorTo: green
|
| 33 |
-
sdk: docker
|
| 34 |
-
app_port: 7860
|
| 35 |
-
pinned: false
|
| 36 |
-
---
|
| 37 |
-
|
| 38 |
-
$original_readme
|
| 39 |
-
EOF
|
| 40 |
-
|
| 41 |
-
rm -rf assets
|
| 42 |
-
|
| 43 |
-
git checkout --orphan hf-deploy
|
| 44 |
-
|
| 45 |
-
git add -A
|
| 46 |
-
git commit -m "Deploy to Hugging Face Space"
|
| 47 |
-
|
| 48 |
-
- name: Push to HF Space
|
| 49 |
-
env:
|
| 50 |
-
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
| 51 |
-
run: |
|
| 52 |
-
git push --force \
|
| 53 |
-
https://SDSC:${HF_TOKEN}@huggingface.co/spaces/SDSC/ai-agent \
|
| 54 |
hf-deploy:main
|
|
|
|
| 1 |
+
name: Deploy to Hugging Face Space
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
push:
|
| 5 |
+
branches:
|
| 6 |
+
- main
|
| 7 |
+
workflow_dispatch:
|
| 8 |
+
|
| 9 |
+
jobs:
|
| 10 |
+
deploy:
|
| 11 |
+
runs-on: ubuntu-latest
|
| 12 |
+
|
| 13 |
+
steps:
|
| 14 |
+
- name: Checkout code
|
| 15 |
+
uses: actions/checkout@v4
|
| 16 |
+
with:
|
| 17 |
+
fetch-depth: 1
|
| 18 |
+
lfs: false
|
| 19 |
+
|
| 20 |
+
- name: Prepare clean HF deploy branch without assets
|
| 21 |
+
run: |
|
| 22 |
+
git config user.email "ci@github.actions"
|
| 23 |
+
git config user.name "github-actions[bot]"
|
| 24 |
+
|
| 25 |
+
original_readme="$(cat README.md)"
|
| 26 |
+
|
| 27 |
+
cat > README.md <<EOF
|
| 28 |
+
---
|
| 29 |
+
title: AI Agent
|
| 30 |
+
emoji: 🤖
|
| 31 |
+
colorFrom: blue
|
| 32 |
+
colorTo: green
|
| 33 |
+
sdk: docker
|
| 34 |
+
app_port: 7860
|
| 35 |
+
pinned: false
|
| 36 |
+
---
|
| 37 |
+
|
| 38 |
+
$original_readme
|
| 39 |
+
EOF
|
| 40 |
+
|
| 41 |
+
rm -rf assets
|
| 42 |
+
|
| 43 |
+
git checkout --orphan hf-deploy
|
| 44 |
+
|
| 45 |
+
git add -A
|
| 46 |
+
git commit -m "Deploy to Hugging Face Space"
|
| 47 |
+
|
| 48 |
+
- name: Push to HF Space
|
| 49 |
+
env:
|
| 50 |
+
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
| 51 |
+
run: |
|
| 52 |
+
git push --force \
|
| 53 |
+
https://SDSC:${HF_TOKEN}@huggingface.co/spaces/SDSC/ai-agent \
|
| 54 |
hf-deploy:main
|
.github/workflows/deploy_docs.yml
CHANGED
|
@@ -1,58 +1,58 @@
|
|
| 1 |
-
name: mkdocs-ci
|
| 2 |
-
|
| 3 |
-
on:
|
| 4 |
-
push:
|
| 5 |
-
branches:
|
| 6 |
-
- main
|
| 7 |
-
|
| 8 |
-
permissions:
|
| 9 |
-
contents: read
|
| 10 |
-
pages: write
|
| 11 |
-
id-token: write
|
| 12 |
-
|
| 13 |
-
concurrency:
|
| 14 |
-
group: "pages"
|
| 15 |
-
cancel-in-progress: true
|
| 16 |
-
|
| 17 |
-
jobs:
|
| 18 |
-
build:
|
| 19 |
-
runs-on: ubuntu-latest
|
| 20 |
-
steps:
|
| 21 |
-
- name: Checkout repository
|
| 22 |
-
uses: actions/checkout@v4
|
| 23 |
-
with:
|
| 24 |
-
fetch-depth: 0
|
| 25 |
-
|
| 26 |
-
- name: Set up Pages
|
| 27 |
-
uses: actions/configure-pages@v5
|
| 28 |
-
|
| 29 |
-
- name: Set up Python
|
| 30 |
-
uses: actions/setup-python@v5
|
| 31 |
-
with:
|
| 32 |
-
python-version: '3.10'
|
| 33 |
-
cache: 'pip'
|
| 34 |
-
|
| 35 |
-
- name: Install dependencies
|
| 36 |
-
run: |
|
| 37 |
-
pip install --upgrade pip
|
| 38 |
-
pip install mkdocs-material
|
| 39 |
-
|
| 40 |
-
- name: Build with MkDocs
|
| 41 |
-
run: mkdocs build --strict
|
| 42 |
-
|
| 43 |
-
- name: Upload artifact
|
| 44 |
-
uses: actions/upload-pages-artifact@v4
|
| 45 |
-
with:
|
| 46 |
-
path: ./site
|
| 47 |
-
|
| 48 |
-
deploy:
|
| 49 |
-
if: github.ref == 'refs/heads/main'
|
| 50 |
-
environment:
|
| 51 |
-
name: github-pages
|
| 52 |
-
url: ${{ steps.deployment.outputs.page_url }}
|
| 53 |
-
runs-on: ubuntu-latest
|
| 54 |
-
needs: build
|
| 55 |
-
steps:
|
| 56 |
-
- name: Deploy to GitHub Pages
|
| 57 |
-
id: deployment
|
| 58 |
uses: actions/deploy-pages@v4
|
|
|
|
| 1 |
+
name: mkdocs-ci
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
push:
|
| 5 |
+
branches:
|
| 6 |
+
- main
|
| 7 |
+
|
| 8 |
+
permissions:
|
| 9 |
+
contents: read
|
| 10 |
+
pages: write
|
| 11 |
+
id-token: write
|
| 12 |
+
|
| 13 |
+
concurrency:
|
| 14 |
+
group: "pages"
|
| 15 |
+
cancel-in-progress: true
|
| 16 |
+
|
| 17 |
+
jobs:
|
| 18 |
+
build:
|
| 19 |
+
runs-on: ubuntu-latest
|
| 20 |
+
steps:
|
| 21 |
+
- name: Checkout repository
|
| 22 |
+
uses: actions/checkout@v4
|
| 23 |
+
with:
|
| 24 |
+
fetch-depth: 0
|
| 25 |
+
|
| 26 |
+
- name: Set up Pages
|
| 27 |
+
uses: actions/configure-pages@v5
|
| 28 |
+
|
| 29 |
+
- name: Set up Python
|
| 30 |
+
uses: actions/setup-python@v5
|
| 31 |
+
with:
|
| 32 |
+
python-version: '3.10'
|
| 33 |
+
cache: 'pip'
|
| 34 |
+
|
| 35 |
+
- name: Install dependencies
|
| 36 |
+
run: |
|
| 37 |
+
pip install --upgrade pip
|
| 38 |
+
pip install mkdocs-material
|
| 39 |
+
|
| 40 |
+
- name: Build with MkDocs
|
| 41 |
+
run: mkdocs build --strict
|
| 42 |
+
|
| 43 |
+
- name: Upload artifact
|
| 44 |
+
uses: actions/upload-pages-artifact@v4
|
| 45 |
+
with:
|
| 46 |
+
path: ./site
|
| 47 |
+
|
| 48 |
+
deploy:
|
| 49 |
+
if: github.ref == 'refs/heads/main'
|
| 50 |
+
environment:
|
| 51 |
+
name: github-pages
|
| 52 |
+
url: ${{ steps.deployment.outputs.page_url }}
|
| 53 |
+
runs-on: ubuntu-latest
|
| 54 |
+
needs: build
|
| 55 |
+
steps:
|
| 56 |
+
- name: Deploy to GitHub Pages
|
| 57 |
+
id: deployment
|
| 58 |
uses: actions/deploy-pages@v4
|
.github/workflows/docker-build-pr.yml
CHANGED
|
@@ -1,24 +1,24 @@
|
|
| 1 |
-
name: Docker build for PRs
|
| 2 |
-
|
| 3 |
-
on:
|
| 4 |
-
pull_request:
|
| 5 |
-
branches: [ main ]
|
| 6 |
-
workflow_dispatch:
|
| 7 |
-
|
| 8 |
-
jobs:
|
| 9 |
-
docker-build:
|
| 10 |
-
runs-on: ubuntu-latest
|
| 11 |
-
|
| 12 |
-
steps:
|
| 13 |
-
- name: Checkout code
|
| 14 |
-
uses: actions/checkout@v4
|
| 15 |
-
|
| 16 |
-
- name: Set up Docker Buildx
|
| 17 |
-
uses: docker/setup-buildx-action@v3
|
| 18 |
-
|
| 19 |
-
- name: Build Docker image (no push)
|
| 20 |
-
uses: docker/build-push-action@v6
|
| 21 |
-
with:
|
| 22 |
-
context: .
|
| 23 |
-
push: false
|
| 24 |
tags: ai-agent:pr-${{ github.event.number }}
|
|
|
|
| 1 |
+
name: Docker build for PRs
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
pull_request:
|
| 5 |
+
branches: [ main ]
|
| 6 |
+
workflow_dispatch:
|
| 7 |
+
|
| 8 |
+
jobs:
|
| 9 |
+
docker-build:
|
| 10 |
+
runs-on: ubuntu-latest
|
| 11 |
+
|
| 12 |
+
steps:
|
| 13 |
+
- name: Checkout code
|
| 14 |
+
uses: actions/checkout@v4
|
| 15 |
+
|
| 16 |
+
- name: Set up Docker Buildx
|
| 17 |
+
uses: docker/setup-buildx-action@v3
|
| 18 |
+
|
| 19 |
+
- name: Build Docker image (no push)
|
| 20 |
+
uses: docker/build-push-action@v6
|
| 21 |
+
with:
|
| 22 |
+
context: .
|
| 23 |
+
push: false
|
| 24 |
tags: ai-agent:pr-${{ github.event.number }}
|
.gitignore
CHANGED
|
@@ -16,6 +16,10 @@ eggs/
|
|
| 16 |
.eggs/
|
| 17 |
lib/
|
| 18 |
lib64/
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
parts/
|
| 20 |
sdist/
|
| 21 |
var/
|
|
|
|
| 16 |
.eggs/
|
| 17 |
lib/
|
| 18 |
lib64/
|
| 19 |
+
# Re-include the frontend's source lib directory which `lib/` above would
|
| 20 |
+
# otherwise swallow (the original rule was meant for Python virtualenvs).
|
| 21 |
+
!src/frontend/src/lib/
|
| 22 |
+
!src/frontend/src/lib/**
|
| 23 |
parts/
|
| 24 |
sdist/
|
| 25 |
var/
|
AGENTS.md
CHANGED
|
@@ -1,92 +1,92 @@
|
|
| 1 |
-
# AGENTS.md
|
| 2 |
-
|
| 3 |
-
This file defines repository-wide agent guidance for contributors working in this codebase.
|
| 4 |
-
|
| 5 |
-
## Scope And Priority
|
| 6 |
-
|
| 7 |
-
- Use this file for shared workflow rules and contributor expectations.
|
| 8 |
-
- Use [.github/copilot-instructions.md](.github/copilot-instructions.md) for architecture details, data-flow patterns, and domain-specific constraints.
|
| 9 |
-
- If guidance conflicts, prioritize repository reality in this order:
|
| 10 |
-
1. Executable code in [src/ai_agent/](src/ai_agent)
|
| 11 |
-
2. Runtime/config files ([pyproject.toml](pyproject.toml), [config.yaml](config.yaml), [.devcontainer/devcontainer.json](.devcontainer/devcontainer.json))
|
| 12 |
-
3. Documentation pages
|
| 13 |
-
|
| 14 |
-
## Default Execution Context
|
| 15 |
-
|
| 16 |
-
Assume work is done inside the dev container unless a task says otherwise.
|
| 17 |
-
|
| 18 |
-
- Base environment: Debian Bookworm dev container
|
| 19 |
-
- Python: 3.12 (from dev container image)
|
| 20 |
-
- Package workflow: uv-managed virtual environment in .venv
|
| 21 |
-
- Default interpreter: .venv/bin/python
|
| 22 |
-
|
| 23 |
-
Preferred setup and install commands:
|
| 24 |
-
|
| 25 |
-
```bash
|
| 26 |
-
uv venv
|
| 27 |
-
uv pip install -e .
|
| 28 |
-
uv pip install -e ".[dev]"
|
| 29 |
-
```
|
| 30 |
-
|
| 31 |
-
## Command Truth
|
| 32 |
-
|
| 33 |
-
Use these commands as the current baseline:
|
| 34 |
-
|
| 35 |
-
```bash
|
| 36 |
-
ai_agent chat
|
| 37 |
-
ai_agent sync
|
| 38 |
-
pytest tests/
|
| 39 |
-
```
|
| 40 |
-
|
| 41 |
-
Notes:
|
| 42 |
-
- The CLI modes are defined in [src/ai_agent/cli.py](src/ai_agent/cli.py).
|
| 43 |
-
- If helper scripts (for example [justfile](justfile)) disagree with CLI behavior, align docs to real CLI behavior and then fix scripts in a follow-up change.
|
| 44 |
-
|
| 45 |
-
## Contributor Workflow Expectations
|
| 46 |
-
|
| 47 |
-
1. Confirm behavior from code before updating docs.
|
| 48 |
-
2. Keep module boundaries clear:
|
| 49 |
-
- retrieval logic in `retriever/`
|
| 50 |
-
- selection schemas/prompts in `generator/`
|
| 51 |
-
- orchestration in `api/`
|
| 52 |
-
- chat/tool orchestration in `agent/`
|
| 53 |
-
- UI code in `ui/`
|
| 54 |
-
3. Prefer small, reviewable changes.
|
| 55 |
-
4. For user-facing changes, update [CHANGELOG.md](CHANGELOG.md).
|
| 56 |
-
5. For docs changes, keep [README.md](README.md), [docs/index.md](docs/index.md), and [docs/guide.md](docs/guide.md) in sync.
|
| 57 |
-
|
| 58 |
-
## Documentation Maintenance Rules
|
| 59 |
-
|
| 60 |
-
When adding or changing functionality:
|
| 61 |
-
|
| 62 |
-
1. Update architectural context if module boundaries change.
|
| 63 |
-
2. Update environment/command docs if startup, install, or test commands change.
|
| 64 |
-
3. Add or adjust examples when behavior changes.
|
| 65 |
-
4. Verify internal links in docs remain valid.
|
| 66 |
-
|
| 67 |
-
## Recommended Improvement Priorities
|
| 68 |
-
|
| 69 |
-
- Align task runners/scripts with current CLI contract (`chat`, `sync`).
|
| 70 |
-
- Expand tests around UI handlers and tool-call edge cases.
|
| 71 |
-
- Add lightweight retrieval quality regression checks.
|
| 72 |
-
- Add docs link validation in CI to prevent drift.
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
## Tests
|
| 76 |
-
|
| 77 |
-
1. Run all tests compatible with pytest in ./tests
|
| 78 |
-
|
| 79 |
-
## Developing flow
|
| 80 |
-
|
| 81 |
-
1. Check AGENTS.md
|
| 82 |
-
2. Follow the implementation
|
| 83 |
-
3. Run tests
|
| 84 |
-
4. Run linting
|
| 85 |
-
5. CHANGELOG upload following keepachangelog format.
|
| 86 |
-
|
| 87 |
-
## Related References
|
| 88 |
-
|
| 89 |
-
- [docs/guide.md](docs/guide.md)
|
| 90 |
-
- [docs/development/structure.md](docs/development/structure.md)
|
| 91 |
-
- [docs/architecture/overview.md](docs/architecture/overview.md)
|
| 92 |
-
- [.github/copilot-instructions.md](.github/copilot-instructions.md)
|
|
|
|
| 1 |
+
# AGENTS.md
|
| 2 |
+
|
| 3 |
+
This file defines repository-wide agent guidance for contributors working in this codebase.
|
| 4 |
+
|
| 5 |
+
## Scope And Priority
|
| 6 |
+
|
| 7 |
+
- Use this file for shared workflow rules and contributor expectations.
|
| 8 |
+
- Use [.github/copilot-instructions.md](.github/copilot-instructions.md) for architecture details, data-flow patterns, and domain-specific constraints.
|
| 9 |
+
- If guidance conflicts, prioritize repository reality in this order:
|
| 10 |
+
1. Executable code in [src/ai_agent/](src/ai_agent)
|
| 11 |
+
2. Runtime/config files ([pyproject.toml](pyproject.toml), [config.yaml](config.yaml), [.devcontainer/devcontainer.json](.devcontainer/devcontainer.json))
|
| 12 |
+
3. Documentation pages
|
| 13 |
+
|
| 14 |
+
## Default Execution Context
|
| 15 |
+
|
| 16 |
+
Assume work is done inside the dev container unless a task says otherwise.
|
| 17 |
+
|
| 18 |
+
- Base environment: Debian Bookworm dev container
|
| 19 |
+
- Python: 3.12 (from dev container image)
|
| 20 |
+
- Package workflow: uv-managed virtual environment in .venv
|
| 21 |
+
- Default interpreter: .venv/bin/python
|
| 22 |
+
|
| 23 |
+
Preferred setup and install commands:
|
| 24 |
+
|
| 25 |
+
```bash
|
| 26 |
+
uv venv
|
| 27 |
+
uv pip install -e .
|
| 28 |
+
uv pip install -e ".[dev]"
|
| 29 |
+
```
|
| 30 |
+
|
| 31 |
+
## Command Truth
|
| 32 |
+
|
| 33 |
+
Use these commands as the current baseline:
|
| 34 |
+
|
| 35 |
+
```bash
|
| 36 |
+
ai_agent chat
|
| 37 |
+
ai_agent sync
|
| 38 |
+
pytest tests/
|
| 39 |
+
```
|
| 40 |
+
|
| 41 |
+
Notes:
|
| 42 |
+
- The CLI modes are defined in [src/ai_agent/cli.py](src/ai_agent/cli.py).
|
| 43 |
+
- If helper scripts (for example [justfile](justfile)) disagree with CLI behavior, align docs to real CLI behavior and then fix scripts in a follow-up change.
|
| 44 |
+
|
| 45 |
+
## Contributor Workflow Expectations
|
| 46 |
+
|
| 47 |
+
1. Confirm behavior from code before updating docs.
|
| 48 |
+
2. Keep module boundaries clear:
|
| 49 |
+
- retrieval logic in `retriever/`
|
| 50 |
+
- selection schemas/prompts in `generator/`
|
| 51 |
+
- orchestration in `api/`
|
| 52 |
+
- chat/tool orchestration in `agent/`
|
| 53 |
+
- UI code in `ui/`
|
| 54 |
+
3. Prefer small, reviewable changes.
|
| 55 |
+
4. For user-facing changes, update [CHANGELOG.md](CHANGELOG.md).
|
| 56 |
+
5. For docs changes, keep [README.md](README.md), [docs/index.md](docs/index.md), and [docs/guide.md](docs/guide.md) in sync.
|
| 57 |
+
|
| 58 |
+
## Documentation Maintenance Rules
|
| 59 |
+
|
| 60 |
+
When adding or changing functionality:
|
| 61 |
+
|
| 62 |
+
1. Update architectural context if module boundaries change.
|
| 63 |
+
2. Update environment/command docs if startup, install, or test commands change.
|
| 64 |
+
3. Add or adjust examples when behavior changes.
|
| 65 |
+
4. Verify internal links in docs remain valid.
|
| 66 |
+
|
| 67 |
+
## Recommended Improvement Priorities
|
| 68 |
+
|
| 69 |
+
- Align task runners/scripts with current CLI contract (`chat`, `sync`).
|
| 70 |
+
- Expand tests around UI handlers and tool-call edge cases.
|
| 71 |
+
- Add lightweight retrieval quality regression checks.
|
| 72 |
+
- Add docs link validation in CI to prevent drift.
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
## Tests
|
| 76 |
+
|
| 77 |
+
1. Run all tests compatible with pytest in ./tests
|
| 78 |
+
|
| 79 |
+
## Developing flow
|
| 80 |
+
|
| 81 |
+
1. Check AGENTS.md
|
| 82 |
+
2. Follow the implementation
|
| 83 |
+
3. Run tests
|
| 84 |
+
4. Run linting
|
| 85 |
+
5. CHANGELOG upload following keepachangelog format.
|
| 86 |
+
|
| 87 |
+
## Related References
|
| 88 |
+
|
| 89 |
+
- [docs/guide.md](docs/guide.md)
|
| 90 |
+
- [docs/development/structure.md](docs/development/structure.md)
|
| 91 |
+
- [docs/architecture/overview.md](docs/architecture/overview.md)
|
| 92 |
+
- [.github/copilot-instructions.md](.github/copilot-instructions.md)
|
CHANGELOG.md
CHANGED
|
@@ -2,6 +2,27 @@
|
|
| 2 |
|
| 3 |
All notable changes to this project will be documented in this file.
|
| 4 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
## [1.0.0]
|
| 6 |
|
| 7 |
### 🚀 Major Features
|
|
|
|
| 2 |
|
| 3 |
All notable changes to this project will be documented in this file.
|
| 4 |
|
| 5 |
+
## [Unreleased]
|
| 6 |
+
|
| 7 |
+
### Changed
|
| 8 |
+
- Replaced all in-memory caches (image metadata, preview, repo info) with a
|
| 9 |
+
shared SQLite-backed `CacheDB` (`utils/cache_db.py`). Caches now survive
|
| 10 |
+
short process restarts and share a single on-disk file in Python's temp
|
| 11 |
+
directory (`tempfile.gettempdir()`), named `ai_agent_cache{_uid}.db`
|
| 12 |
+
(for example, `/tmp/ai_agent_cache_1000.db`), overridable via
|
| 13 |
+
`CACHE_DB_PATH`.
|
| 14 |
+
|
| 15 |
+
### Added
|
| 16 |
+
- `utils/shutdown.py`: background cleanup thread that runs immediately on
|
| 17 |
+
startup and then every `CLEANUP_INTERVAL_SECONDS` (default 7200 s):
|
| 18 |
+
- Sweeps expired rows from the cache DB.
|
| 19 |
+
- Deletes log files under `LOG_DIR` older than `LOG_RETENTION_DAYS`
|
| 20 |
+
(default 7 days); only `app_*.log*` files are touched.
|
| 21 |
+
- `atexit` hook performs a final VACUUM + connection close on process exit.
|
| 22 |
+
- New env vars: `CACHE_DB_PATH`, `CLEANUP_INTERVAL_SECONDS`, `LOG_RETENTION_DAYS`.
|
| 23 |
+
|
| 24 |
+
---
|
| 25 |
+
|
| 26 |
## [1.0.0]
|
| 27 |
|
| 28 |
### 🚀 Major Features
|
Dockerfile
CHANGED
|
@@ -1,33 +1,41 @@
|
|
| 1 |
-
#
|
| 2 |
-
|
| 3 |
-
#
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
#
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
RUN
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Multi-stage build:
|
| 2 |
+
# 1. node:20-alpine compiles the Vite/React frontend at src/frontend
|
| 3 |
+
# 2. python:3.11-slim installs the package and runs the FastAPI backend,
|
| 4 |
+
# which also serves the built bundle from /home/user/app/src/frontend/dist.
|
| 5 |
+
|
| 6 |
+
# ---- Stage 1: frontend build ----
|
| 7 |
+
FROM node:20-alpine AS frontend-build
|
| 8 |
+
WORKDIR /app
|
| 9 |
+
COPY src/frontend/package.json src/frontend/package-lock.json ./
|
| 10 |
+
RUN npm ci --no-audit --no-fund
|
| 11 |
+
COPY src/frontend ./
|
| 12 |
+
RUN npm run build
|
| 13 |
+
|
| 14 |
+
# ---- Stage 2: python runtime ----
|
| 15 |
+
FROM python:3.11-slim
|
| 16 |
+
|
| 17 |
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 18 |
+
build-essential git \
|
| 19 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 20 |
+
|
| 21 |
+
RUN useradd -m -u 1000 user
|
| 22 |
+
USER user
|
| 23 |
+
|
| 24 |
+
ENV HOME=/home/user \
|
| 25 |
+
PATH=/home/user/.local/bin:$PATH
|
| 26 |
+
WORKDIR $HOME/app
|
| 27 |
+
|
| 28 |
+
COPY --chown=user . .
|
| 29 |
+
COPY --from=frontend-build --chown=user /app/dist ./src/frontend/dist
|
| 30 |
+
|
| 31 |
+
RUN pip install --upgrade pip && \
|
| 32 |
+
pip install --no-cache-dir .
|
| 33 |
+
|
| 34 |
+
EXPOSE 7860
|
| 35 |
+
ENV PORT=7860 \
|
| 36 |
+
HOST=0.0.0.0 \
|
| 37 |
+
FRONTEND_DIST_DIR=src/frontend/dist
|
| 38 |
+
|
| 39 |
+
# Run the FastAPI backend (which also serves the SPA). To fall back to the
|
| 40 |
+
# legacy Gradio UI, override CMD: `docker run ... ai_agent chat`.
|
| 41 |
+
CMD ["ai_agent", "serve"]
|
LICENSE
CHANGED
|
@@ -1,202 +1,202 @@
|
|
| 1 |
-
|
| 2 |
-
Apache License
|
| 3 |
-
Version 2.0, January 2004
|
| 4 |
-
http://www.apache.org/licenses/
|
| 5 |
-
|
| 6 |
-
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
| 7 |
-
|
| 8 |
-
1. Definitions.
|
| 9 |
-
|
| 10 |
-
"License" shall mean the terms and conditions for use, reproduction,
|
| 11 |
-
and distribution as defined by Sections 1 through 9 of this document.
|
| 12 |
-
|
| 13 |
-
"Licensor" shall mean the copyright owner or entity authorized by
|
| 14 |
-
the copyright owner that is granting the License.
|
| 15 |
-
|
| 16 |
-
"Legal Entity" shall mean the union of the acting entity and all
|
| 17 |
-
other entities that control, are controlled by, or are under common
|
| 18 |
-
control with that entity. For the purposes of this definition,
|
| 19 |
-
"control" means (i) the power, direct or indirect, to cause the
|
| 20 |
-
direction or management of such entity, whether by contract or
|
| 21 |
-
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
| 22 |
-
outstanding shares, or (iii) beneficial ownership of such entity.
|
| 23 |
-
|
| 24 |
-
"You" (or "Your") shall mean an individual or Legal Entity
|
| 25 |
-
exercising permissions granted by this License.
|
| 26 |
-
|
| 27 |
-
"Source" form shall mean the preferred form for making modifications,
|
| 28 |
-
including but not limited to software source code, documentation
|
| 29 |
-
source, and configuration files.
|
| 30 |
-
|
| 31 |
-
"Object" form shall mean any form resulting from mechanical
|
| 32 |
-
transformation or translation of a Source form, including but
|
| 33 |
-
not limited to compiled object code, generated documentation,
|
| 34 |
-
and conversions to other media types.
|
| 35 |
-
|
| 36 |
-
"Work" shall mean the work of authorship, whether in Source or
|
| 37 |
-
Object form, made available under the License, as indicated by a
|
| 38 |
-
copyright notice that is included in or attached to the work
|
| 39 |
-
(an example is provided in the Appendix below).
|
| 40 |
-
|
| 41 |
-
"Derivative Works" shall mean any work, whether in Source or Object
|
| 42 |
-
form, that is based on (or derived from) the Work and for which the
|
| 43 |
-
editorial revisions, annotations, elaborations, or other modifications
|
| 44 |
-
represent, as a whole, an original work of authorship. For the purposes
|
| 45 |
-
of this License, Derivative Works shall not include works that remain
|
| 46 |
-
separable from, or merely link (or bind by name) to the interfaces of,
|
| 47 |
-
the Work and Derivative Works thereof.
|
| 48 |
-
|
| 49 |
-
"Contribution" shall mean any work of authorship, including
|
| 50 |
-
the original version of the Work and any modifications or additions
|
| 51 |
-
to that Work or Derivative Works thereof, that is intentionally
|
| 52 |
-
submitted to Licensor for inclusion in the Work by the copyright owner
|
| 53 |
-
or by an individual or Legal Entity authorized to submit on behalf of
|
| 54 |
-
the copyright owner. For the purposes of this definition, "submitted"
|
| 55 |
-
means any form of electronic, verbal, or written communication sent
|
| 56 |
-
to the Licensor or its representatives, including but not limited to
|
| 57 |
-
communication on electronic mailing lists, source code control systems,
|
| 58 |
-
and issue tracking systems that are managed by, or on behalf of, the
|
| 59 |
-
Licensor for the purpose of discussing and improving the Work, but
|
| 60 |
-
excluding communication that is conspicuously marked or otherwise
|
| 61 |
-
designated in writing by the copyright owner as "Not a Contribution."
|
| 62 |
-
|
| 63 |
-
"Contributor" shall mean Licensor and any individual or Legal Entity
|
| 64 |
-
on behalf of whom a Contribution has been received by Licensor and
|
| 65 |
-
subsequently incorporated within the Work.
|
| 66 |
-
|
| 67 |
-
2. Grant of Copyright License. Subject to the terms and conditions of
|
| 68 |
-
this License, each Contributor hereby grants to You a perpetual,
|
| 69 |
-
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 70 |
-
copyright license to reproduce, prepare Derivative Works of,
|
| 71 |
-
publicly display, publicly perform, sublicense, and distribute the
|
| 72 |
-
Work and such Derivative Works in Source or Object form.
|
| 73 |
-
|
| 74 |
-
3. Grant of Patent License. Subject to the terms and conditions of
|
| 75 |
-
this License, each Contributor hereby grants to You a perpetual,
|
| 76 |
-
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 77 |
-
(except as stated in this section) patent license to make, have made,
|
| 78 |
-
use, offer to sell, sell, import, and otherwise transfer the Work,
|
| 79 |
-
where such license applies only to those patent claims licensable
|
| 80 |
-
by such Contributor that are necessarily infringed by their
|
| 81 |
-
Contribution(s) alone or by combination of their Contribution(s)
|
| 82 |
-
with the Work to which such Contribution(s) was submitted. If You
|
| 83 |
-
institute patent litigation against any entity (including a
|
| 84 |
-
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
| 85 |
-
or a Contribution incorporated within the Work constitutes direct
|
| 86 |
-
or contributory patent infringement, then any patent licenses
|
| 87 |
-
granted to You under this License for that Work shall terminate
|
| 88 |
-
as of the date such litigation is filed.
|
| 89 |
-
|
| 90 |
-
4. Redistribution. You may reproduce and distribute copies of the
|
| 91 |
-
Work or Derivative Works thereof in any medium, with or without
|
| 92 |
-
modifications, and in Source or Object form, provided that You
|
| 93 |
-
meet the following conditions:
|
| 94 |
-
|
| 95 |
-
(a) You must give any other recipients of the Work or
|
| 96 |
-
Derivative Works a copy of this License; and
|
| 97 |
-
|
| 98 |
-
(b) You must cause any modified files to carry prominent notices
|
| 99 |
-
stating that You changed the files; and
|
| 100 |
-
|
| 101 |
-
(c) You must retain, in the Source form of any Derivative Works
|
| 102 |
-
that You distribute, all copyright, patent, trademark, and
|
| 103 |
-
attribution notices from the Source form of the Work,
|
| 104 |
-
excluding those notices that do not pertain to any part of
|
| 105 |
-
the Derivative Works; and
|
| 106 |
-
|
| 107 |
-
(d) If the Work includes a "NOTICE" text file as part of its
|
| 108 |
-
distribution, then any Derivative Works that You distribute must
|
| 109 |
-
include a readable copy of the attribution notices contained
|
| 110 |
-
within such NOTICE file, excluding those notices that do not
|
| 111 |
-
pertain to any part of the Derivative Works, in at least one
|
| 112 |
-
of the following places: within a NOTICE text file distributed
|
| 113 |
-
as part of the Derivative Works; within the Source form or
|
| 114 |
-
documentation, if provided along with the Derivative Works; or,
|
| 115 |
-
within a display generated by the Derivative Works, if and
|
| 116 |
-
wherever such third-party notices normally appear. The contents
|
| 117 |
-
of the NOTICE file are for informational purposes only and
|
| 118 |
-
do not modify the License. You may add Your own attribution
|
| 119 |
-
notices within Derivative Works that You distribute, alongside
|
| 120 |
-
or as an addendum to the NOTICE text from the Work, provided
|
| 121 |
-
that such additional attribution notices cannot be construed
|
| 122 |
-
as modifying the License.
|
| 123 |
-
|
| 124 |
-
You may add Your own copyright statement to Your modifications and
|
| 125 |
-
may provide additional or different license terms and conditions
|
| 126 |
-
for use, reproduction, or distribution of Your modifications, or
|
| 127 |
-
for any such Derivative Works as a whole, provided Your use,
|
| 128 |
-
reproduction, and distribution of the Work otherwise complies with
|
| 129 |
-
the conditions stated in this License.
|
| 130 |
-
|
| 131 |
-
5. Submission of Contributions. Unless You explicitly state otherwise,
|
| 132 |
-
any Contribution intentionally submitted for inclusion in the Work
|
| 133 |
-
by You to the Licensor shall be under the terms and conditions of
|
| 134 |
-
this License, without any additional terms or conditions.
|
| 135 |
-
Notwithstanding the above, nothing herein shall supersede or modify
|
| 136 |
-
the terms of any separate license agreement you may have executed
|
| 137 |
-
with Licensor regarding such Contributions.
|
| 138 |
-
|
| 139 |
-
6. Trademarks. This License does not grant permission to use the trade
|
| 140 |
-
names, trademarks, service marks, or product names of the Licensor,
|
| 141 |
-
except as required for reasonable and customary use in describing the
|
| 142 |
-
origin of the Work and reproducing the content of the NOTICE file.
|
| 143 |
-
|
| 144 |
-
7. Disclaimer of Warranty. Unless required by applicable law or
|
| 145 |
-
agreed to in writing, Licensor provides the Work (and each
|
| 146 |
-
Contributor provides its Contributions) on an "AS IS" BASIS,
|
| 147 |
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
| 148 |
-
implied, including, without limitation, any warranties or conditions
|
| 149 |
-
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
| 150 |
-
PARTICULAR PURPOSE. You are solely responsible for determining the
|
| 151 |
-
appropriateness of using or redistributing the Work and assume any
|
| 152 |
-
risks associated with Your exercise of permissions under this License.
|
| 153 |
-
|
| 154 |
-
8. Limitation of Liability. In no event and under no legal theory,
|
| 155 |
-
whether in tort (including negligence), contract, or otherwise,
|
| 156 |
-
unless required by applicable law (such as deliberate and grossly
|
| 157 |
-
negligent acts) or agreed to in writing, shall any Contributor be
|
| 158 |
-
liable to You for damages, including any direct, indirect, special,
|
| 159 |
-
incidental, or consequential damages of any character arising as a
|
| 160 |
-
result of this License or out of the use or inability to use the
|
| 161 |
-
Work (including but not limited to damages for loss of goodwill,
|
| 162 |
-
work stoppage, computer failure or malfunction, or any and all
|
| 163 |
-
other commercial damages or losses), even if such Contributor
|
| 164 |
-
has been advised of the possibility of such damages.
|
| 165 |
-
|
| 166 |
-
9. Accepting Warranty or Additional Liability. While redistributing
|
| 167 |
-
the Work or Derivative Works thereof, You may choose to offer,
|
| 168 |
-
and charge a fee for, acceptance of support, warranty, indemnity,
|
| 169 |
-
or other liability obligations and/or rights consistent with this
|
| 170 |
-
License. However, in accepting such obligations, You may act only
|
| 171 |
-
on Your own behalf and on Your sole responsibility, not on behalf
|
| 172 |
-
of any other Contributor, and only if You agree to indemnify,
|
| 173 |
-
defend, and hold each Contributor harmless for any liability
|
| 174 |
-
incurred by, or claims asserted against, such Contributor by reason
|
| 175 |
-
of your accepting any such warranty or additional liability.
|
| 176 |
-
|
| 177 |
-
END OF TERMS AND CONDITIONS
|
| 178 |
-
|
| 179 |
-
APPENDIX: How to apply the Apache License to your work.
|
| 180 |
-
|
| 181 |
-
To apply the Apache License to your work, attach the following
|
| 182 |
-
boilerplate notice, with the fields enclosed by brackets "[]"
|
| 183 |
-
replaced with your own identifying information. (Don't include
|
| 184 |
-
the brackets!) The text should be enclosed in the appropriate
|
| 185 |
-
comment syntax for the file format. We also recommend that a
|
| 186 |
-
file or class name and description of purpose be included on the
|
| 187 |
-
same "printed page" as the copyright notice for easier
|
| 188 |
-
identification within third-party archives.
|
| 189 |
-
|
| 190 |
-
Copyright 2026 Imaging Plaza
|
| 191 |
-
|
| 192 |
-
Licensed under the Apache License, Version 2.0 (the "License");
|
| 193 |
-
you may not use this file except in compliance with the License.
|
| 194 |
-
You may obtain a copy of the License at
|
| 195 |
-
|
| 196 |
-
http://www.apache.org/licenses/LICENSE-2.0
|
| 197 |
-
|
| 198 |
-
Unless required by applicable law or agreed to in writing, software
|
| 199 |
-
distributed under the License is distributed on an "AS IS" BASIS,
|
| 200 |
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 201 |
-
See the License for the specific language governing permissions and
|
| 202 |
limitations under the License.
|
|
|
|
| 1 |
+
|
| 2 |
+
Apache License
|
| 3 |
+
Version 2.0, January 2004
|
| 4 |
+
http://www.apache.org/licenses/
|
| 5 |
+
|
| 6 |
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
| 7 |
+
|
| 8 |
+
1. Definitions.
|
| 9 |
+
|
| 10 |
+
"License" shall mean the terms and conditions for use, reproduction,
|
| 11 |
+
and distribution as defined by Sections 1 through 9 of this document.
|
| 12 |
+
|
| 13 |
+
"Licensor" shall mean the copyright owner or entity authorized by
|
| 14 |
+
the copyright owner that is granting the License.
|
| 15 |
+
|
| 16 |
+
"Legal Entity" shall mean the union of the acting entity and all
|
| 17 |
+
other entities that control, are controlled by, or are under common
|
| 18 |
+
control with that entity. For the purposes of this definition,
|
| 19 |
+
"control" means (i) the power, direct or indirect, to cause the
|
| 20 |
+
direction or management of such entity, whether by contract or
|
| 21 |
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
| 22 |
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
| 23 |
+
|
| 24 |
+
"You" (or "Your") shall mean an individual or Legal Entity
|
| 25 |
+
exercising permissions granted by this License.
|
| 26 |
+
|
| 27 |
+
"Source" form shall mean the preferred form for making modifications,
|
| 28 |
+
including but not limited to software source code, documentation
|
| 29 |
+
source, and configuration files.
|
| 30 |
+
|
| 31 |
+
"Object" form shall mean any form resulting from mechanical
|
| 32 |
+
transformation or translation of a Source form, including but
|
| 33 |
+
not limited to compiled object code, generated documentation,
|
| 34 |
+
and conversions to other media types.
|
| 35 |
+
|
| 36 |
+
"Work" shall mean the work of authorship, whether in Source or
|
| 37 |
+
Object form, made available under the License, as indicated by a
|
| 38 |
+
copyright notice that is included in or attached to the work
|
| 39 |
+
(an example is provided in the Appendix below).
|
| 40 |
+
|
| 41 |
+
"Derivative Works" shall mean any work, whether in Source or Object
|
| 42 |
+
form, that is based on (or derived from) the Work and for which the
|
| 43 |
+
editorial revisions, annotations, elaborations, or other modifications
|
| 44 |
+
represent, as a whole, an original work of authorship. For the purposes
|
| 45 |
+
of this License, Derivative Works shall not include works that remain
|
| 46 |
+
separable from, or merely link (or bind by name) to the interfaces of,
|
| 47 |
+
the Work and Derivative Works thereof.
|
| 48 |
+
|
| 49 |
+
"Contribution" shall mean any work of authorship, including
|
| 50 |
+
the original version of the Work and any modifications or additions
|
| 51 |
+
to that Work or Derivative Works thereof, that is intentionally
|
| 52 |
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
| 53 |
+
or by an individual or Legal Entity authorized to submit on behalf of
|
| 54 |
+
the copyright owner. For the purposes of this definition, "submitted"
|
| 55 |
+
means any form of electronic, verbal, or written communication sent
|
| 56 |
+
to the Licensor or its representatives, including but not limited to
|
| 57 |
+
communication on electronic mailing lists, source code control systems,
|
| 58 |
+
and issue tracking systems that are managed by, or on behalf of, the
|
| 59 |
+
Licensor for the purpose of discussing and improving the Work, but
|
| 60 |
+
excluding communication that is conspicuously marked or otherwise
|
| 61 |
+
designated in writing by the copyright owner as "Not a Contribution."
|
| 62 |
+
|
| 63 |
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
| 64 |
+
on behalf of whom a Contribution has been received by Licensor and
|
| 65 |
+
subsequently incorporated within the Work.
|
| 66 |
+
|
| 67 |
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
| 68 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 69 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 70 |
+
copyright license to reproduce, prepare Derivative Works of,
|
| 71 |
+
publicly display, publicly perform, sublicense, and distribute the
|
| 72 |
+
Work and such Derivative Works in Source or Object form.
|
| 73 |
+
|
| 74 |
+
3. Grant of Patent License. Subject to the terms and conditions of
|
| 75 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 76 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 77 |
+
(except as stated in this section) patent license to make, have made,
|
| 78 |
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
| 79 |
+
where such license applies only to those patent claims licensable
|
| 80 |
+
by such Contributor that are necessarily infringed by their
|
| 81 |
+
Contribution(s) alone or by combination of their Contribution(s)
|
| 82 |
+
with the Work to which such Contribution(s) was submitted. If You
|
| 83 |
+
institute patent litigation against any entity (including a
|
| 84 |
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
| 85 |
+
or a Contribution incorporated within the Work constitutes direct
|
| 86 |
+
or contributory patent infringement, then any patent licenses
|
| 87 |
+
granted to You under this License for that Work shall terminate
|
| 88 |
+
as of the date such litigation is filed.
|
| 89 |
+
|
| 90 |
+
4. Redistribution. You may reproduce and distribute copies of the
|
| 91 |
+
Work or Derivative Works thereof in any medium, with or without
|
| 92 |
+
modifications, and in Source or Object form, provided that You
|
| 93 |
+
meet the following conditions:
|
| 94 |
+
|
| 95 |
+
(a) You must give any other recipients of the Work or
|
| 96 |
+
Derivative Works a copy of this License; and
|
| 97 |
+
|
| 98 |
+
(b) You must cause any modified files to carry prominent notices
|
| 99 |
+
stating that You changed the files; and
|
| 100 |
+
|
| 101 |
+
(c) You must retain, in the Source form of any Derivative Works
|
| 102 |
+
that You distribute, all copyright, patent, trademark, and
|
| 103 |
+
attribution notices from the Source form of the Work,
|
| 104 |
+
excluding those notices that do not pertain to any part of
|
| 105 |
+
the Derivative Works; and
|
| 106 |
+
|
| 107 |
+
(d) If the Work includes a "NOTICE" text file as part of its
|
| 108 |
+
distribution, then any Derivative Works that You distribute must
|
| 109 |
+
include a readable copy of the attribution notices contained
|
| 110 |
+
within such NOTICE file, excluding those notices that do not
|
| 111 |
+
pertain to any part of the Derivative Works, in at least one
|
| 112 |
+
of the following places: within a NOTICE text file distributed
|
| 113 |
+
as part of the Derivative Works; within the Source form or
|
| 114 |
+
documentation, if provided along with the Derivative Works; or,
|
| 115 |
+
within a display generated by the Derivative Works, if and
|
| 116 |
+
wherever such third-party notices normally appear. The contents
|
| 117 |
+
of the NOTICE file are for informational purposes only and
|
| 118 |
+
do not modify the License. You may add Your own attribution
|
| 119 |
+
notices within Derivative Works that You distribute, alongside
|
| 120 |
+
or as an addendum to the NOTICE text from the Work, provided
|
| 121 |
+
that such additional attribution notices cannot be construed
|
| 122 |
+
as modifying the License.
|
| 123 |
+
|
| 124 |
+
You may add Your own copyright statement to Your modifications and
|
| 125 |
+
may provide additional or different license terms and conditions
|
| 126 |
+
for use, reproduction, or distribution of Your modifications, or
|
| 127 |
+
for any such Derivative Works as a whole, provided Your use,
|
| 128 |
+
reproduction, and distribution of the Work otherwise complies with
|
| 129 |
+
the conditions stated in this License.
|
| 130 |
+
|
| 131 |
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
| 132 |
+
any Contribution intentionally submitted for inclusion in the Work
|
| 133 |
+
by You to the Licensor shall be under the terms and conditions of
|
| 134 |
+
this License, without any additional terms or conditions.
|
| 135 |
+
Notwithstanding the above, nothing herein shall supersede or modify
|
| 136 |
+
the terms of any separate license agreement you may have executed
|
| 137 |
+
with Licensor regarding such Contributions.
|
| 138 |
+
|
| 139 |
+
6. Trademarks. This License does not grant permission to use the trade
|
| 140 |
+
names, trademarks, service marks, or product names of the Licensor,
|
| 141 |
+
except as required for reasonable and customary use in describing the
|
| 142 |
+
origin of the Work and reproducing the content of the NOTICE file.
|
| 143 |
+
|
| 144 |
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
| 145 |
+
agreed to in writing, Licensor provides the Work (and each
|
| 146 |
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
| 147 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
| 148 |
+
implied, including, without limitation, any warranties or conditions
|
| 149 |
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
| 150 |
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
| 151 |
+
appropriateness of using or redistributing the Work and assume any
|
| 152 |
+
risks associated with Your exercise of permissions under this License.
|
| 153 |
+
|
| 154 |
+
8. Limitation of Liability. In no event and under no legal theory,
|
| 155 |
+
whether in tort (including negligence), contract, or otherwise,
|
| 156 |
+
unless required by applicable law (such as deliberate and grossly
|
| 157 |
+
negligent acts) or agreed to in writing, shall any Contributor be
|
| 158 |
+
liable to You for damages, including any direct, indirect, special,
|
| 159 |
+
incidental, or consequential damages of any character arising as a
|
| 160 |
+
result of this License or out of the use or inability to use the
|
| 161 |
+
Work (including but not limited to damages for loss of goodwill,
|
| 162 |
+
work stoppage, computer failure or malfunction, or any and all
|
| 163 |
+
other commercial damages or losses), even if such Contributor
|
| 164 |
+
has been advised of the possibility of such damages.
|
| 165 |
+
|
| 166 |
+
9. Accepting Warranty or Additional Liability. While redistributing
|
| 167 |
+
the Work or Derivative Works thereof, You may choose to offer,
|
| 168 |
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
| 169 |
+
or other liability obligations and/or rights consistent with this
|
| 170 |
+
License. However, in accepting such obligations, You may act only
|
| 171 |
+
on Your own behalf and on Your sole responsibility, not on behalf
|
| 172 |
+
of any other Contributor, and only if You agree to indemnify,
|
| 173 |
+
defend, and hold each Contributor harmless for any liability
|
| 174 |
+
incurred by, or claims asserted against, such Contributor by reason
|
| 175 |
+
of your accepting any such warranty or additional liability.
|
| 176 |
+
|
| 177 |
+
END OF TERMS AND CONDITIONS
|
| 178 |
+
|
| 179 |
+
APPENDIX: How to apply the Apache License to your work.
|
| 180 |
+
|
| 181 |
+
To apply the Apache License to your work, attach the following
|
| 182 |
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
| 183 |
+
replaced with your own identifying information. (Don't include
|
| 184 |
+
the brackets!) The text should be enclosed in the appropriate
|
| 185 |
+
comment syntax for the file format. We also recommend that a
|
| 186 |
+
file or class name and description of purpose be included on the
|
| 187 |
+
same "printed page" as the copyright notice for easier
|
| 188 |
+
identification within third-party archives.
|
| 189 |
+
|
| 190 |
+
Copyright 2026 Imaging Plaza
|
| 191 |
+
|
| 192 |
+
Licensed under the Apache License, Version 2.0 (the "License");
|
| 193 |
+
you may not use this file except in compliance with the License.
|
| 194 |
+
You may obtain a copy of the License at
|
| 195 |
+
|
| 196 |
+
http://www.apache.org/licenses/LICENSE-2.0
|
| 197 |
+
|
| 198 |
+
Unless required by applicable law or agreed to in writing, software
|
| 199 |
+
distributed under the License is distributed on an "AS IS" BASIS,
|
| 200 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 201 |
+
See the License for the specific language governing permissions and
|
| 202 |
limitations under the License.
|
README.md
CHANGED
|
@@ -1,562 +1,562 @@
|
|
| 1 |
-
---
|
| 2 |
-
title: AI Agent
|
| 3 |
-
emoji: 🤖
|
| 4 |
-
colorFrom: blue
|
| 5 |
-
colorTo: green
|
| 6 |
-
sdk: docker
|
| 7 |
-
app_port: 7860
|
| 8 |
-
pinned: false
|
| 9 |
-
---
|
| 10 |
-
|
| 11 |
-
# AI Imaging Agent (Imaging Plaza)
|
| 12 |
-
|
| 13 |
-
[](https://www.python.org/downloads/)
|
| 14 |
-
[](LICENSE)
|
| 15 |
-
|
| 16 |
-
An intelligent RAG + AI agent system that helps users discover the right imaging software for their images and tasks. Upload an image, describe what you want to do, and get ranked tool recommendations with links to runnable demos.
|
| 17 |
-
|
| 18 |
-
## ✨ Key Features
|
| 19 |
-
|
| 20 |
-
- **🤖 Conversational AI Agent**: Natural language interaction with multi-turn context
|
| 21 |
-
- **🔍 Smart Retrieval**: BGE-M3 embeddings + FAISS + CrossEncoder reranking
|
| 22 |
-
- **👁️ Vision-Aware Selection**: VLM-based tool selection considering both image content and metadata
|
| 23 |
-
- **🏥 Medical Imaging Focus**: Specialized support for CT, MRI, DICOM, NIfTI, and other medical formats
|
| 24 |
-
- **🎯 Format-Aware Matching**: IO compatibility scoring based on file formats and dimensions
|
| 25 |
-
- **🚀 Demo Integration**: Direct execution of Gradio Space demos on your images
|
| 26 |
-
- **📊 Rich UI**: Chat interface with image previews, file management, and execution traces
|
| 27 |
-
|
| 28 |
-
<p align="center">
|
| 29 |
-
<img src="https://github.com/Imaging-Plaza/ai-agent/blob/develop/assets/example.gif?raw=true" height="700">
|
| 30 |
-
</p>
|
| 31 |
-
|
| 32 |
-
---
|
| 33 |
-
|
| 34 |
-
## 🚀 Quick Start
|
| 35 |
-
|
| 36 |
-
### Prerequisites
|
| 37 |
-
|
| 38 |
-
- Python 3.10–3.12
|
| 39 |
-
- OpenAI API key (or compatible API endpoint)
|
| 40 |
-
- Internet connection for model calls
|
| 41 |
-
|
| 42 |
-
### Installation
|
| 43 |
-
|
| 44 |
-
```bash
|
| 45 |
-
# Clone the repository
|
| 46 |
-
git clone <your-repo-url>
|
| 47 |
-
cd ai-agent
|
| 48 |
-
|
| 49 |
-
# Create virtual environment
|
| 50 |
-
python -m venv .venv
|
| 51 |
-
|
| 52 |
-
# Activate virtual environment
|
| 53 |
-
# On Linux/macOS:
|
| 54 |
-
source .venv/bin/activate
|
| 55 |
-
# On Windows:
|
| 56 |
-
.venv\Scripts\activate
|
| 57 |
-
|
| 58 |
-
# Install the package
|
| 59 |
-
pip install --upgrade pip
|
| 60 |
-
pip install -e .
|
| 61 |
-
|
| 62 |
-
# For development (includes test dependencies)
|
| 63 |
-
pip install -e ".[dev]"
|
| 64 |
-
```
|
| 65 |
-
|
| 66 |
-
### Configuration
|
| 67 |
-
|
| 68 |
-
Create a `.env` file at the repository root:
|
| 69 |
-
|
| 70 |
-
```dotenv
|
| 71 |
-
# Required: OpenAI API key
|
| 72 |
-
OPENAI_API_KEY=sk-xxxx
|
| 73 |
-
|
| 74 |
-
# Optional: GitHub token for repo info tool
|
| 75 |
-
GITHUB_TOKEN=ghp_xxxx
|
| 76 |
-
|
| 77 |
-
# Optional: Alternative model providers (EPFL, etc.)
|
| 78 |
-
EPFL_API_KEY=sk-xxxx
|
| 79 |
-
EPFL_API_KEY_EMBEDDER=sk-xxxx
|
| 80 |
-
|
| 81 |
-
# Software catalog path
|
| 82 |
-
SOFTWARE_CATALOG=dataset/catalog.jsonl
|
| 83 |
-
|
| 84 |
-
# Pipeline configuration
|
| 85 |
-
TOP_K=8 # Number of candidates to retrieve
|
| 86 |
-
NUM_CHOICES=3 # Number of tools to recommend
|
| 87 |
-
AGENT_OUTPUT_RETRIES=3 # Structured output validation retries
|
| 88 |
-
EMBED_CATALOG_ON_START=1 # Pre-embed catalog if FAISS is empty
|
| 89 |
-
|
| 90 |
-
# Logging configuration
|
| 91 |
-
LOGLEVEL_CONSOLE=WARNING
|
| 92 |
-
LOGLEVEL_FILE=INFO
|
| 93 |
-
FILE_LOG=1
|
| 94 |
-
LOG_DIR=logs
|
| 95 |
-
LOG_PROMPTS=0 # Set to 1 to save prompt snapshots for debugging
|
| 96 |
-
|
| 97 |
-
# Custom config path
|
| 98 |
-
CONFIG_PATH=config.yaml
|
| 99 |
-
```
|
| 100 |
-
|
| 101 |
-
### Model Configuration
|
| 102 |
-
|
| 103 |
-
The agent model can be configured via `config.yaml`:
|
| 104 |
-
|
| 105 |
-
```yaml
|
| 106 |
-
# AI Agent Model Configuration
|
| 107 |
-
|
| 108 |
-
# Default/fallback model (used for CLI and initial startup)
|
| 109 |
-
agent_model:
|
| 110 |
-
name: "gpt-5.1"
|
| 111 |
-
base_url: null # null for default OpenAI endpoint
|
| 112 |
-
api_key_env: "OPENAI_API_KEY"
|
| 113 |
-
|
| 114 |
-
# Available models for UI dropdown
|
| 115 |
-
available_models:
|
| 116 |
-
- display_name: "gpt-4o-mini"
|
| 117 |
-
name: "gpt-4o-mini"
|
| 118 |
-
base_url: null
|
| 119 |
-
provider: "OpenAI"
|
| 120 |
-
api_key_env: "OPENAI_API_KEY"
|
| 121 |
-
|
| 122 |
-
- display_name: "gpt-4o"
|
| 123 |
-
name: "gpt-4o"
|
| 124 |
-
base_url: null
|
| 125 |
-
provider: "OpenAI"
|
| 126 |
-
api_key_env: "OPENAI_API_KEY"
|
| 127 |
-
|
| 128 |
-
- display_name: "gpt-5-mini"
|
| 129 |
-
name: "gpt-5-mini"
|
| 130 |
-
base_url: null
|
| 131 |
-
provider: "OpenAI"
|
| 132 |
-
api_key_env: "OPENAI_API_KEY"
|
| 133 |
-
|
| 134 |
-
- display_name: "gpt-5.1"
|
| 135 |
-
name: "gpt-5.1"
|
| 136 |
-
base_url: null
|
| 137 |
-
provider: "OpenAI"
|
| 138 |
-
api_key_env: "OPENAI_API_KEY"
|
| 139 |
-
|
| 140 |
-
retrieval:
|
| 141 |
-
embedder:
|
| 142 |
-
backend: "remote" # "remote" or "local"
|
| 143 |
-
model_name: "Qwen/Qwen3-Embedding-8B"
|
| 144 |
-
base_url: "https://inference-rcp.epfl.ch/v1"
|
| 145 |
-
api_key_env: "EPFL_API_KEY_EMBEDDER"
|
| 146 |
-
timeout_s: 20
|
| 147 |
-
# local example:
|
| 148 |
-
# backend: "local"
|
| 149 |
-
# model_name: "BAAI/bge-m3"
|
| 150 |
-
# device: "cpu" # optional
|
| 151 |
-
|
| 152 |
-
reranker:
|
| 153 |
-
backend: "remote" # "remote" or "local"
|
| 154 |
-
model_name: "BAAI/bge-reranker-v2-m3"
|
| 155 |
-
base_url: "https://inference-rcp.epfl.ch/v1"
|
| 156 |
-
api_key_env: "EPFL_API_KEY_EMBEDDER"
|
| 157 |
-
timeout_s: 20
|
| 158 |
-
# local example:
|
| 159 |
-
# backend: "local"
|
| 160 |
-
# model_name: "BAAI/bge-reranker-v2-m3"
|
| 161 |
-
# device: "cpu" # optional
|
| 162 |
-
```
|
| 163 |
-
|
| 164 |
-
### Running the App
|
| 165 |
-
|
| 166 |
-
```bash
|
| 167 |
-
# Start the chat interface
|
| 168 |
-
ai_agent chat
|
| 169 |
-
|
| 170 |
-
# Open your browser to:
|
| 171 |
-
# http://127.0.0.1:7860
|
| 172 |
-
```
|
| 173 |
-
|
| 174 |
-
Try uploading a cat image and asking:
|
| 175 |
-
> "I want to segment the cat from this image"
|
| 176 |
-
|
| 177 |
-
---
|
| 178 |
-
|
| 179 |
-
## 💬 Usage
|
| 180 |
-
|
| 181 |
-
### Chat Interface
|
| 182 |
-
|
| 183 |
-
The chat interface provides a natural conversation flow:
|
| 184 |
-
|
| 185 |
-
1. **Upload Files**: Drop images (PNG, JPG, TIFF, DICOM, NIfTI, etc.) or other supported files
|
| 186 |
-
2. **Describe Your Task**: Use natural language like "segment the lungs" or "register brain MRI"
|
| 187 |
-
3. **Review Recommendations**: Get ranked tool suggestions with accuracy scores and explanations
|
| 188 |
-
4. **Run Demos**: Click "Run demo" to execute tools directly on your uploaded images
|
| 189 |
-
5. **Iterate**: Ask for alternatives, refine your query, or upload different files
|
| 190 |
-
|
| 191 |
-
### Supported File Formats
|
| 192 |
-
|
| 193 |
-
**Images:**
|
| 194 |
-
- Standard: PNG, JPG, JPEG, WEBP, BMP, GIF
|
| 195 |
-
- Medical: DICOM (.dcm), NIfTI (.nii, .nii.gz), TIFF stacks
|
| 196 |
-
- Scientific: Multi-page TIFF, TIFF with metadata
|
| 197 |
-
|
| 198 |
-
**Other Files:**
|
| 199 |
-
- Data: CSV, JSON, XML
|
| 200 |
-
- Media: MP3, MP4
|
| 201 |
-
|
| 202 |
-
### Example Queries
|
| 203 |
-
|
| 204 |
-
- "Segment the lungs from this CT scan"
|
| 205 |
-
- "Register these two brain MRI images"
|
| 206 |
-
- "Extract text from this medical report image"
|
| 207 |
-
- "Classify what organ is shown in this ultrasound"
|
| 208 |
-
- "Detect tumors in this MRI scan"
|
| 209 |
-
- "I need to analyze DICOM files, what tools are available?"
|
| 210 |
-
|
| 211 |
-
### Understanding Results
|
| 212 |
-
|
| 213 |
-
Each recommendation includes:
|
| 214 |
-
- **Rank**: Priority order (1 = best match)
|
| 215 |
-
- **Accuracy Score**: Confidence level (0-100%)
|
| 216 |
-
- **Explanation**: Why this tool matches your request
|
| 217 |
-
- **Metadata**: Supported modalities, dimensions, formats, license
|
| 218 |
-
- **Demo Link**: Direct link to runnable example
|
| 219 |
-
|
| 220 |
-
---
|
| 221 |
-
|
| 222 |
-
## 🏗️ Architecture
|
| 223 |
-
|
| 224 |
-
### Pipeline Overview
|
| 225 |
-
|
| 226 |
-
The system follows a two-stage architecture:
|
| 227 |
-
|
| 228 |
-
```
|
| 229 |
-
User Input (Image + Text Query)
|
| 230 |
-
↓
|
| 231 |
-
┌───────────────────────────────┐
|
| 232 |
-
│ RETRIEVAL STAGE │
|
| 233 |
-
│ - BGE-M3 Embeddings │
|
| 234 |
-
│ - FAISS Vector Search │
|
| 235 |
-
│ - CrossEncoder Reranking │
|
| 236 |
-
│ - Format Token Matching │
|
| 237 |
-
└───────────────────────────────┘
|
| 238 |
-
↓ Top-K Candidates
|
| 239 |
-
┌───────────────────────────────┐
|
| 240 |
-
│ AGENT SELECTION │
|
| 241 |
-
│ - Pydantic AI Agent │
|
| 242 |
-
│ - OpenAI VLM │
|
| 243 |
-
│ - Image + Metadata Analysis │
|
| 244 |
-
│ - Multi-Tool Reasoning │
|
| 245 |
-
└───────────────────────────────┘
|
| 246 |
-
↓
|
| 247 |
-
Ranked Recommendations
|
| 248 |
-
```
|
| 249 |
-
|
| 250 |
-
### Retrieval Stage
|
| 251 |
-
|
| 252 |
-
**No LLM calls** - purely text-based search:
|
| 253 |
-
|
| 254 |
-
1. **Query Construction**: User task + format tokens from uploaded files
|
| 255 |
-
2. **Embedding**: BGE-M3 model generates query embedding
|
| 256 |
-
3. **Vector Search**: FAISS retrieves top candidates
|
| 257 |
-
4. **Reranking**: CrossEncoder refines results for precision
|
| 258 |
-
5. **Retry Broadening**: If too few hits, retry with a shorter/broader query
|
| 259 |
-
|
| 260 |
-
### Agent Selection Stage
|
| 261 |
-
|
| 262 |
-
**Single VLM call** - multimodal reasoning:
|
| 263 |
-
|
| 264 |
-
1. **Input Preparation**:
|
| 265 |
-
- Text: User query + candidate table + file metadata
|
| 266 |
-
- Image: PNG preview (converted from any format)
|
| 267 |
-
- Context: Original file format, dimensions, modality
|
| 268 |
-
|
| 269 |
-
2. **Agent Tools**:
|
| 270 |
-
- `search_tools`: Search catalog with query
|
| 271 |
-
- `search_alternative`: Find alternatives (iterative)
|
| 272 |
-
- `repo_info`: Fetch GitHub documentation via DeepWiki MCP
|
| 273 |
-
|
| 274 |
-
3. **Output**: Ranked tool selections with accuracy scores and explanations
|
| 275 |
-
|
| 276 |
-
### Key Components
|
| 277 |
-
|
| 278 |
-
- **`api/pipeline.py`**: RAG retrieval orchestrator
|
| 279 |
-
- **`agent/agent.py`**: Pydantic AI agent with tool definitions
|
| 280 |
-
- **`retriever/`**: Embedding, FAISS indexing, reranking
|
| 281 |
-
- **`generator/`**: Prompts and schema for tool selection
|
| 282 |
-
- **`ui/`**: Gradio chat interface components
|
| 283 |
-
- **`utils/`**: Image processing, metadata extraction, file validation
|
| 284 |
-
- **`catalog/`**: Catalog syncing from GraphDB (optional)
|
| 285 |
-
|
| 286 |
-
---
|
| 287 |
-
|
| 288 |
-
## ⚙️ Configuration
|
| 289 |
-
|
| 290 |
-
### Environment Variables
|
| 291 |
-
|
| 292 |
-
| Variable | Description | Default | Required |
|
| 293 |
-
|----------|-------------|---------|----------|
|
| 294 |
-
| `OPENAI_API_KEY` | OpenAI API key | - | ✅ |
|
| 295 |
-
| `EPFL_API_KEY_EMBEDDER` | API key for remote embedder and reranker endpoints | - | ✅ (when `retrieval.embedder.backend: remote` and/or `retrieval.reranker.backend: remote`) |
|
| 296 |
-
| `GITHUB_TOKEN` | GitHub token for repo info | - | ❌ |
|
| 297 |
-
| `SOFTWARE_CATALOG` | Path to catalog JSONL | `dataset/catalog.jsonl` | ✅ |
|
| 298 |
-
| `TOP_K` | Retrieval candidates count | `8` | ❌ |
|
| 299 |
-
| `NUM_CHOICES` | Tools to recommend | `3` | ❌ |
|
| 300 |
-
| `AGENT_OUTPUT_RETRIES` | Structured output validation retries | `3` | ❌ |
|
| 301 |
-
| `EMBED_CATALOG_ON_START` | Pre-embed catalog on startup when FAISS is empty | `1` | ❌ |
|
| 302 |
-
| `LOGLEVEL_CONSOLE` | Console log level | `WARNING` | ❌ |
|
| 303 |
-
| `LOGLEVEL_FILE` | File log level | `INFO` | ❌ |
|
| 304 |
-
| `FILE_LOG` | Enable file logging | `1` | ❌ |
|
| 305 |
-
| `LOG_DIR` | Log directory | `logs` | ❌ |
|
| 306 |
-
| `LOG_PROMPTS` | Save prompt snapshots | `0` | ❌ |
|
| 307 |
-
| `CONFIG_PATH` | Model config file | `config.yaml` | ✅ |
|
| 308 |
-
|
| 309 |
-
### GraphDB Catalog Sync (Optional)
|
| 310 |
-
|
| 311 |
-
For automatic catalog syncing from a GraphDB instance:
|
| 312 |
-
|
| 313 |
-
```dotenv
|
| 314 |
-
GRAPHDB_URL=https://your-graphdb.example.com
|
| 315 |
-
GRAPHDB_GRAPH=your-graph-name
|
| 316 |
-
GRAPHDB_USER=username
|
| 317 |
-
GRAPHDB_PASSWORD=password
|
| 318 |
-
GRAPHDB_QUERY_FILE=/path/to/query.rq
|
| 319 |
-
SYNC_EVERY_HOURS=24 # Auto-refresh interval (0 to disable)
|
| 320 |
-
OUTPUT_JSONLD=dataset/catalog.jsonld
|
| 321 |
-
OUTPUT_JSONL=dataset/catalog.jsonl
|
| 322 |
-
```
|
| 323 |
-
|
| 324 |
-
Run manual sync:
|
| 325 |
-
```bash
|
| 326 |
-
ai_agent sync
|
| 327 |
-
```
|
| 328 |
-
|
| 329 |
-
---
|
| 330 |
-
|
| 331 |
-
## 📋 Catalog Format
|
| 332 |
-
|
| 333 |
-
The catalog is a JSONL file where each line is a `SoftwareDoc` following schema.org SoftwareSourceCode structure.
|
| 334 |
-
|
| 335 |
-
### Minimal Example
|
| 336 |
-
|
| 337 |
-
```json
|
| 338 |
-
{
|
| 339 |
-
"name": "3d-lungs-segmentation",
|
| 340 |
-
"description": "3D lung segmentation from CT; returns a mask/overlay.",
|
| 341 |
-
|
| 342 |
-
"applicationCategory": ["Medical Imaging"],
|
| 343 |
-
"featureList": ["segmentation"],
|
| 344 |
-
"imagingModality": ["CT"],
|
| 345 |
-
"dims": [3],
|
| 346 |
-
"anatomy": ["lung"],
|
| 347 |
-
"keywords": ["mask", "overlay", "lung segmentation", "CT"],
|
| 348 |
-
|
| 349 |
-
"programmingLanguage": "Python",
|
| 350 |
-
"requiresGPU": false,
|
| 351 |
-
"isAccessibleForFree": true,
|
| 352 |
-
"license": "Apache-2.0",
|
| 353 |
-
|
| 354 |
-
"supportingData": [
|
| 355 |
-
{
|
| 356 |
-
"datasetFormat": "TIFF",
|
| 357 |
-
"bodySite": "lung",
|
| 358 |
-
"imagingModality": "CT",
|
| 359 |
-
"hasDimensionality": 3
|
| 360 |
-
},
|
| 361 |
-
{
|
| 362 |
-
"datasetFormat": "DICOM",
|
| 363 |
-
"bodySite": "lung",
|
| 364 |
-
"imagingModality": "CT",
|
| 365 |
-
"hasDimensionality": 3
|
| 366 |
-
}
|
| 367 |
-
],
|
| 368 |
-
|
| 369 |
-
"runnableExample": [
|
| 370 |
-
{
|
| 371 |
-
"hostType": "gradio",
|
| 372 |
-
"url": "https://huggingface.co/spaces/qchapp/3d-lungs-segmentation",
|
| 373 |
-
"name": "HF Space"
|
| 374 |
-
}
|
| 375 |
-
]
|
| 376 |
-
}
|
| 377 |
-
```
|
| 378 |
-
|
| 379 |
-
### Key Fields
|
| 380 |
-
|
| 381 |
-
- **name**: Unique identifier for the tool
|
| 382 |
-
- **description**: Clear explanation of what the tool does
|
| 383 |
-
- **featureList**: Operations (e.g., segmentation, registration, classification)
|
| 384 |
-
- **imagingModality**: Medical imaging types (CT, MRI, XR, US, PET)
|
| 385 |
-
- **dims**: Supported dimensions (2D, 3D, 4D)
|
| 386 |
-
- **anatomy**: Body parts/organs
|
| 387 |
-
- **supportingData**: Format compatibility information (critical for matching)
|
| 388 |
-
- **runnableExample**: Links to live demos (HuggingFace Spaces, notebooks, web apps)
|
| 389 |
-
|
| 390 |
-
---
|
| 391 |
-
|
| 392 |
-
## 🔧 Development
|
| 393 |
-
|
| 394 |
-
### Project Structure
|
| 395 |
-
|
| 396 |
-
```
|
| 397 |
-
ai-agent/
|
| 398 |
-
├── src/ai_agent/
|
| 399 |
-
│ ├── agent/ # Pydantic AI agent and tools
|
| 400 |
-
│ │ ├── agent.py # Agent definition
|
| 401 |
-
│ │ ├── models.py # Agent state models
|
| 402 |
-
│ │ ├── tools/ # Agent tool implementations
|
| 403 |
-
│ │ │ ├── search_tool.py
|
| 404 |
-
│ │ │ ├── search_alternative_tool.py
|
| 405 |
-
│ │ │ ├── gradio_space_tool.py
|
| 406 |
-
│ │ │ ├── repo_info_tool.py
|
| 407 |
-
│ │ │ └── deepwiki_tool.py
|
| 408 |
-
│ │ └── utils.py
|
| 409 |
-
│ ├── api/ # Pipeline orchestration
|
| 410 |
-
│ │ └── pipeline.py # RAGImagingPipeline
|
| 411 |
-
│ ├── retriever/ # Retrieval components
|
| 412 |
-
│ │ ├── text_embedder.py
|
| 413 |
-
│ │ ├── vector_index.py
|
| 414 |
-
│ │ ├── reranker.py
|
| 415 |
-
│ │ └── software_doc.py
|
| 416 |
-
│ ├── generator/ # Agent prompts and schemas
|
| 417 |
-
│ │ ├── prompts.py
|
| 418 |
-
│ │ └── schema.py
|
| 419 |
-
│ ├── ui/ # Gradio interface
|
| 420 |
-
│ │ ├── app.py
|
| 421 |
-
│ │ ├── handlers.py
|
| 422 |
-
│ │ ├── components.py
|
| 423 |
-
│ │ ├── formatters.py
|
| 424 |
-
│ │ ├── state.py
|
| 425 |
-
│ │ └── visualizations.py
|
| 426 |
-
│ ├── utils/ # Shared utilities
|
| 427 |
-
│ │ ├── config.py # Configuration management
|
| 428 |
-
│ │ ├── file_validator.py
|
| 429 |
-
│ │ ├── image_meta.py # Metadata extraction
|
| 430 |
-
│ │ ├── image_io.py
|
| 431 |
-
│ │ ├── previews.py
|
| 432 |
-
│ │ └── tags.py
|
| 433 |
-
│ ├── catalog/ # Catalog syncing
|
| 434 |
-
│ │ └── sync.py
|
| 435 |
-
│ └── cli.py # CLI entry point
|
| 436 |
-
├── tests/ # Test suite
|
| 437 |
-
│ ├── test_retrieval_pipeline.py
|
| 438 |
-
│ ├── test_repo_summary.py
|
| 439 |
-
│ └── data/
|
| 440 |
-
├── artifacts/ # Generated artifacts
|
| 441 |
-
│ └── rag_index/ # FAISS index
|
| 442 |
-
├── dataset/ # Catalog data
|
| 443 |
-
│ └── catalog.jsonl
|
| 444 |
-
├── logs/ # Application logs
|
| 445 |
-
├── config.yaml # Model configuration
|
| 446 |
-
├── pyproject.toml # Project metadata & dependencies
|
| 447 |
-
├── Dockerfile # Production Docker image
|
| 448 |
-
├── tools/image/Dockerfile # Development Docker image
|
| 449 |
-
└── justfile # Task runner commands
|
| 450 |
-
```
|
| 451 |
-
|
| 452 |
-
### Local Development
|
| 453 |
-
|
| 454 |
-
```bash
|
| 455 |
-
# Install in development mode
|
| 456 |
-
pip install -e ".[dev]"
|
| 457 |
-
|
| 458 |
-
# Run tests
|
| 459 |
-
pytest tests/
|
| 460 |
-
```
|
| 461 |
-
|
| 462 |
-
### Testing
|
| 463 |
-
|
| 464 |
-
Run the test suite:
|
| 465 |
-
|
| 466 |
-
```bash
|
| 467 |
-
# All tests
|
| 468 |
-
pytest tests/
|
| 469 |
-
|
| 470 |
-
# Specific test file
|
| 471 |
-
pytest tests/test_retrieval_pipeline.py
|
| 472 |
-
|
| 473 |
-
# With verbose output
|
| 474 |
-
pytest -v tests/
|
| 475 |
-
|
| 476 |
-
# With coverage
|
| 477 |
-
pytest --cov=ai_agent tests/
|
| 478 |
-
```
|
| 479 |
-
|
| 480 |
-
### Logging & Debugging
|
| 481 |
-
|
| 482 |
-
**Console Logs**: Set `LOGLEVEL_CONSOLE=DEBUG` for verbose output
|
| 483 |
-
|
| 484 |
-
**File Logs**: Automatically saved to `logs/app_YYYYMMDD.log` (rotates daily)
|
| 485 |
-
|
| 486 |
-
**Prompt Snapshots**: Enable `LOG_PROMPTS=1` to save:
|
| 487 |
-
- `logs/vlm_selector_YYYYMMDD_HHMMSS.txt` - System/user prompts
|
| 488 |
-
|
| 489 |
-
---
|
| 490 |
-
|
| 491 |
-
## 📚 API & CLI Reference
|
| 492 |
-
|
| 493 |
-
### CLI Commands
|
| 494 |
-
|
| 495 |
-
```bash
|
| 496 |
-
# Launch chat interface
|
| 497 |
-
ai_agent chat
|
| 498 |
-
|
| 499 |
-
# Sync catalog from GraphDB
|
| 500 |
-
ai_agent sync
|
| 501 |
-
```
|
| 502 |
-
|
| 503 |
-
## 🗺️ Maintainer Guide
|
| 504 |
-
|
| 505 |
-
For full project documentation with detailed folder responsibilities, environment defaults, and improvement guidelines, see [docs/guide.md](docs/guide.md).
|
| 506 |
-
|
| 507 |
-
---
|
| 508 |
-
|
| 509 |
-
## 📝 Changelog
|
| 510 |
-
|
| 511 |
-
See [CHANGELOG.md](CHANGELOG.md) for detailed version history.
|
| 512 |
-
|
| 513 |
-
### Recent Highlights
|
| 514 |
-
|
| 515 |
-
**[1.0.0]**
|
| 516 |
-
- ✨ New chat-based interface (`ai_agent chat`) with rich media and tool integration
|
| 517 |
-
- 🛠️ Fully agent-based architecture replacing legacy pipelines
|
| 518 |
-
- 🔍 Smarter retrieval with automatic retry
|
| 519 |
-
- 🔗 DeepWiki MCP integration for fast GitHub repository documentation access
|
| 520 |
-
- 🔧 YAML configuration (`config.yaml`) for flexible model and backend setup
|
| 521 |
-
- 🎨 Redesigned UI with Imaging Plaza branding and improved UX
|
| 522 |
-
- ⚡ Performance improvements (pre-embedding, caching, faster startup)
|
| 523 |
-
- 🧹 Major cleanup: removed deprecated code paths, legacy UI, and outdated tests
|
| 524 |
-
|
| 525 |
-
**[0.1.3] - 2025-10-22**
|
| 526 |
-
- Gradio space runner tool
|
| 527 |
-
- Repository info tool
|
| 528 |
-
- UI fixes and polish
|
| 529 |
-
|
| 530 |
-
---
|
| 531 |
-
|
| 532 |
-
## 📄 License
|
| 533 |
-
|
| 534 |
-
This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details.
|
| 535 |
-
|
| 536 |
-
---
|
| 537 |
-
|
| 538 |
-
## 🙏 Credits & Acknowledgments
|
| 539 |
-
|
| 540 |
-
**Developed by**: Imaging Plaza Team
|
| 541 |
-
|
| 542 |
-
**Technologies:**
|
| 543 |
-
- [Pydantic AI](https://github.com/pydantic/pydantic-ai) - AI agent framework
|
| 544 |
-
- [OpenAI](https://openai.com) - GPT vision model
|
| 545 |
-
- [FAISS](https://github.com/facebookresearch/faiss) - Vector search
|
| 546 |
-
- [BGE-M3](https://huggingface.co/BAAI/bge-m3) - Multilingual embeddings
|
| 547 |
-
- [Gradio](https://gradio.app) - Interactive web UI
|
| 548 |
-
- [DeepWiki](https://deepwiki.com) - GitHub repository documentation
|
| 549 |
-
|
| 550 |
-
**Medical Imaging Formats:**
|
| 551 |
-
- [pydicom](https://github.com/pydicom/pydicom) - DICOM support
|
| 552 |
-
- [nibabel](https://nipy.org/nibabel/) - NIfTI support
|
| 553 |
-
|
| 554 |
-
---
|
| 555 |
-
|
| 556 |
-
## 📮 Support
|
| 557 |
-
|
| 558 |
-
For issues, questions, or contributions, please contact the Imaging Plaza team.
|
| 559 |
-
|
| 560 |
-
---
|
| 561 |
-
|
| 562 |
-
**🏥 Medical Disclaimer**: This software is a tool recommendation system, not a diagnostic tool. Always consult qualified medical professionals for clinical decisions.
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: AI Agent
|
| 3 |
+
emoji: 🤖
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: green
|
| 6 |
+
sdk: docker
|
| 7 |
+
app_port: 7860
|
| 8 |
+
pinned: false
|
| 9 |
+
---
|
| 10 |
+
|
| 11 |
+
# AI Imaging Agent (Imaging Plaza)
|
| 12 |
+
|
| 13 |
+
[](https://www.python.org/downloads/)
|
| 14 |
+
[](LICENSE)
|
| 15 |
+
|
| 16 |
+
An intelligent RAG + AI agent system that helps users discover the right imaging software for their images and tasks. Upload an image, describe what you want to do, and get ranked tool recommendations with links to runnable demos.
|
| 17 |
+
|
| 18 |
+
## ✨ Key Features
|
| 19 |
+
|
| 20 |
+
- **🤖 Conversational AI Agent**: Natural language interaction with multi-turn context
|
| 21 |
+
- **🔍 Smart Retrieval**: BGE-M3 embeddings + FAISS + CrossEncoder reranking
|
| 22 |
+
- **👁️ Vision-Aware Selection**: VLM-based tool selection considering both image content and metadata
|
| 23 |
+
- **🏥 Medical Imaging Focus**: Specialized support for CT, MRI, DICOM, NIfTI, and other medical formats
|
| 24 |
+
- **🎯 Format-Aware Matching**: IO compatibility scoring based on file formats and dimensions
|
| 25 |
+
- **🚀 Demo Integration**: Direct execution of Gradio Space demos on your images
|
| 26 |
+
- **📊 Rich UI**: Chat interface with image previews, file management, and execution traces
|
| 27 |
+
|
| 28 |
+
<p align="center">
|
| 29 |
+
<img src="https://github.com/Imaging-Plaza/ai-agent/blob/develop/assets/example.gif?raw=true" height="700">
|
| 30 |
+
</p>
|
| 31 |
+
|
| 32 |
+
---
|
| 33 |
+
|
| 34 |
+
## 🚀 Quick Start
|
| 35 |
+
|
| 36 |
+
### Prerequisites
|
| 37 |
+
|
| 38 |
+
- Python 3.10–3.12
|
| 39 |
+
- OpenAI API key (or compatible API endpoint)
|
| 40 |
+
- Internet connection for model calls
|
| 41 |
+
|
| 42 |
+
### Installation
|
| 43 |
+
|
| 44 |
+
```bash
|
| 45 |
+
# Clone the repository
|
| 46 |
+
git clone <your-repo-url>
|
| 47 |
+
cd ai-agent
|
| 48 |
+
|
| 49 |
+
# Create virtual environment
|
| 50 |
+
python -m venv .venv
|
| 51 |
+
|
| 52 |
+
# Activate virtual environment
|
| 53 |
+
# On Linux/macOS:
|
| 54 |
+
source .venv/bin/activate
|
| 55 |
+
# On Windows:
|
| 56 |
+
.venv\Scripts\activate
|
| 57 |
+
|
| 58 |
+
# Install the package
|
| 59 |
+
pip install --upgrade pip
|
| 60 |
+
pip install -e .
|
| 61 |
+
|
| 62 |
+
# For development (includes test dependencies)
|
| 63 |
+
pip install -e ".[dev]"
|
| 64 |
+
```
|
| 65 |
+
|
| 66 |
+
### Configuration
|
| 67 |
+
|
| 68 |
+
Create a `.env` file at the repository root:
|
| 69 |
+
|
| 70 |
+
```dotenv
|
| 71 |
+
# Required: OpenAI API key
|
| 72 |
+
OPENAI_API_KEY=sk-xxxx
|
| 73 |
+
|
| 74 |
+
# Optional: GitHub token for repo info tool
|
| 75 |
+
GITHUB_TOKEN=ghp_xxxx
|
| 76 |
+
|
| 77 |
+
# Optional: Alternative model providers (EPFL, etc.)
|
| 78 |
+
EPFL_API_KEY=sk-xxxx
|
| 79 |
+
EPFL_API_KEY_EMBEDDER=sk-xxxx
|
| 80 |
+
|
| 81 |
+
# Software catalog path
|
| 82 |
+
SOFTWARE_CATALOG=dataset/catalog.jsonl
|
| 83 |
+
|
| 84 |
+
# Pipeline configuration
|
| 85 |
+
TOP_K=8 # Number of candidates to retrieve
|
| 86 |
+
NUM_CHOICES=3 # Number of tools to recommend
|
| 87 |
+
AGENT_OUTPUT_RETRIES=3 # Structured output validation retries
|
| 88 |
+
EMBED_CATALOG_ON_START=1 # Pre-embed catalog if FAISS is empty
|
| 89 |
+
|
| 90 |
+
# Logging configuration
|
| 91 |
+
LOGLEVEL_CONSOLE=WARNING
|
| 92 |
+
LOGLEVEL_FILE=INFO
|
| 93 |
+
FILE_LOG=1
|
| 94 |
+
LOG_DIR=logs
|
| 95 |
+
LOG_PROMPTS=0 # Set to 1 to save prompt snapshots for debugging
|
| 96 |
+
|
| 97 |
+
# Custom config path
|
| 98 |
+
CONFIG_PATH=config.yaml
|
| 99 |
+
```
|
| 100 |
+
|
| 101 |
+
### Model Configuration
|
| 102 |
+
|
| 103 |
+
The agent model can be configured via `config.yaml`:
|
| 104 |
+
|
| 105 |
+
```yaml
|
| 106 |
+
# AI Agent Model Configuration
|
| 107 |
+
|
| 108 |
+
# Default/fallback model (used for CLI and initial startup)
|
| 109 |
+
agent_model:
|
| 110 |
+
name: "gpt-5.1"
|
| 111 |
+
base_url: null # null for default OpenAI endpoint
|
| 112 |
+
api_key_env: "OPENAI_API_KEY"
|
| 113 |
+
|
| 114 |
+
# Available models for UI dropdown
|
| 115 |
+
available_models:
|
| 116 |
+
- display_name: "gpt-4o-mini"
|
| 117 |
+
name: "gpt-4o-mini"
|
| 118 |
+
base_url: null
|
| 119 |
+
provider: "OpenAI"
|
| 120 |
+
api_key_env: "OPENAI_API_KEY"
|
| 121 |
+
|
| 122 |
+
- display_name: "gpt-4o"
|
| 123 |
+
name: "gpt-4o"
|
| 124 |
+
base_url: null
|
| 125 |
+
provider: "OpenAI"
|
| 126 |
+
api_key_env: "OPENAI_API_KEY"
|
| 127 |
+
|
| 128 |
+
- display_name: "gpt-5-mini"
|
| 129 |
+
name: "gpt-5-mini"
|
| 130 |
+
base_url: null
|
| 131 |
+
provider: "OpenAI"
|
| 132 |
+
api_key_env: "OPENAI_API_KEY"
|
| 133 |
+
|
| 134 |
+
- display_name: "gpt-5.1"
|
| 135 |
+
name: "gpt-5.1"
|
| 136 |
+
base_url: null
|
| 137 |
+
provider: "OpenAI"
|
| 138 |
+
api_key_env: "OPENAI_API_KEY"
|
| 139 |
+
|
| 140 |
+
retrieval:
|
| 141 |
+
embedder:
|
| 142 |
+
backend: "remote" # "remote" or "local"
|
| 143 |
+
model_name: "Qwen/Qwen3-Embedding-8B"
|
| 144 |
+
base_url: "https://inference-rcp.epfl.ch/v1"
|
| 145 |
+
api_key_env: "EPFL_API_KEY_EMBEDDER"
|
| 146 |
+
timeout_s: 20
|
| 147 |
+
# local example:
|
| 148 |
+
# backend: "local"
|
| 149 |
+
# model_name: "BAAI/bge-m3"
|
| 150 |
+
# device: "cpu" # optional
|
| 151 |
+
|
| 152 |
+
reranker:
|
| 153 |
+
backend: "remote" # "remote" or "local"
|
| 154 |
+
model_name: "BAAI/bge-reranker-v2-m3"
|
| 155 |
+
base_url: "https://inference-rcp.epfl.ch/v1"
|
| 156 |
+
api_key_env: "EPFL_API_KEY_EMBEDDER"
|
| 157 |
+
timeout_s: 20
|
| 158 |
+
# local example:
|
| 159 |
+
# backend: "local"
|
| 160 |
+
# model_name: "BAAI/bge-reranker-v2-m3"
|
| 161 |
+
# device: "cpu" # optional
|
| 162 |
+
```
|
| 163 |
+
|
| 164 |
+
### Running the App
|
| 165 |
+
|
| 166 |
+
```bash
|
| 167 |
+
# Start the chat interface
|
| 168 |
+
ai_agent chat
|
| 169 |
+
|
| 170 |
+
# Open your browser to:
|
| 171 |
+
# http://127.0.0.1:7860
|
| 172 |
+
```
|
| 173 |
+
|
| 174 |
+
Try uploading a cat image and asking:
|
| 175 |
+
> "I want to segment the cat from this image"
|
| 176 |
+
|
| 177 |
+
---
|
| 178 |
+
|
| 179 |
+
## 💬 Usage
|
| 180 |
+
|
| 181 |
+
### Chat Interface
|
| 182 |
+
|
| 183 |
+
The chat interface provides a natural conversation flow:
|
| 184 |
+
|
| 185 |
+
1. **Upload Files**: Drop images (PNG, JPG, TIFF, DICOM, NIfTI, etc.) or other supported files
|
| 186 |
+
2. **Describe Your Task**: Use natural language like "segment the lungs" or "register brain MRI"
|
| 187 |
+
3. **Review Recommendations**: Get ranked tool suggestions with accuracy scores and explanations
|
| 188 |
+
4. **Run Demos**: Click "Run demo" to execute tools directly on your uploaded images
|
| 189 |
+
5. **Iterate**: Ask for alternatives, refine your query, or upload different files
|
| 190 |
+
|
| 191 |
+
### Supported File Formats
|
| 192 |
+
|
| 193 |
+
**Images:**
|
| 194 |
+
- Standard: PNG, JPG, JPEG, WEBP, BMP, GIF
|
| 195 |
+
- Medical: DICOM (.dcm), NIfTI (.nii, .nii.gz), TIFF stacks
|
| 196 |
+
- Scientific: Multi-page TIFF, TIFF with metadata
|
| 197 |
+
|
| 198 |
+
**Other Files:**
|
| 199 |
+
- Data: CSV, JSON, XML
|
| 200 |
+
- Media: MP3, MP4
|
| 201 |
+
|
| 202 |
+
### Example Queries
|
| 203 |
+
|
| 204 |
+
- "Segment the lungs from this CT scan"
|
| 205 |
+
- "Register these two brain MRI images"
|
| 206 |
+
- "Extract text from this medical report image"
|
| 207 |
+
- "Classify what organ is shown in this ultrasound"
|
| 208 |
+
- "Detect tumors in this MRI scan"
|
| 209 |
+
- "I need to analyze DICOM files, what tools are available?"
|
| 210 |
+
|
| 211 |
+
### Understanding Results
|
| 212 |
+
|
| 213 |
+
Each recommendation includes:
|
| 214 |
+
- **Rank**: Priority order (1 = best match)
|
| 215 |
+
- **Accuracy Score**: Confidence level (0-100%)
|
| 216 |
+
- **Explanation**: Why this tool matches your request
|
| 217 |
+
- **Metadata**: Supported modalities, dimensions, formats, license
|
| 218 |
+
- **Demo Link**: Direct link to runnable example
|
| 219 |
+
|
| 220 |
+
---
|
| 221 |
+
|
| 222 |
+
## 🏗️ Architecture
|
| 223 |
+
|
| 224 |
+
### Pipeline Overview
|
| 225 |
+
|
| 226 |
+
The system follows a two-stage architecture:
|
| 227 |
+
|
| 228 |
+
```
|
| 229 |
+
User Input (Image + Text Query)
|
| 230 |
+
↓
|
| 231 |
+
┌───────────────────────────────┐
|
| 232 |
+
│ RETRIEVAL STAGE │
|
| 233 |
+
│ - BGE-M3 Embeddings │
|
| 234 |
+
│ - FAISS Vector Search │
|
| 235 |
+
│ - CrossEncoder Reranking │
|
| 236 |
+
│ - Format Token Matching │
|
| 237 |
+
└───────────────────────────────┘
|
| 238 |
+
↓ Top-K Candidates
|
| 239 |
+
┌───────────────────────────────┐
|
| 240 |
+
│ AGENT SELECTION │
|
| 241 |
+
│ - Pydantic AI Agent │
|
| 242 |
+
│ - OpenAI VLM │
|
| 243 |
+
│ - Image + Metadata Analysis │
|
| 244 |
+
│ - Multi-Tool Reasoning │
|
| 245 |
+
└───────────────────────────────┘
|
| 246 |
+
↓
|
| 247 |
+
Ranked Recommendations
|
| 248 |
+
```
|
| 249 |
+
|
| 250 |
+
### Retrieval Stage
|
| 251 |
+
|
| 252 |
+
**No LLM calls** - purely text-based search:
|
| 253 |
+
|
| 254 |
+
1. **Query Construction**: User task + format tokens from uploaded files
|
| 255 |
+
2. **Embedding**: BGE-M3 model generates query embedding
|
| 256 |
+
3. **Vector Search**: FAISS retrieves top candidates
|
| 257 |
+
4. **Reranking**: CrossEncoder refines results for precision
|
| 258 |
+
5. **Retry Broadening**: If too few hits, retry with a shorter/broader query
|
| 259 |
+
|
| 260 |
+
### Agent Selection Stage
|
| 261 |
+
|
| 262 |
+
**Single VLM call** - multimodal reasoning:
|
| 263 |
+
|
| 264 |
+
1. **Input Preparation**:
|
| 265 |
+
- Text: User query + candidate table + file metadata
|
| 266 |
+
- Image: PNG preview (converted from any format)
|
| 267 |
+
- Context: Original file format, dimensions, modality
|
| 268 |
+
|
| 269 |
+
2. **Agent Tools**:
|
| 270 |
+
- `search_tools`: Search catalog with query
|
| 271 |
+
- `search_alternative`: Find alternatives (iterative)
|
| 272 |
+
- `repo_info`: Fetch GitHub documentation via DeepWiki MCP
|
| 273 |
+
|
| 274 |
+
3. **Output**: Ranked tool selections with accuracy scores and explanations
|
| 275 |
+
|
| 276 |
+
### Key Components
|
| 277 |
+
|
| 278 |
+
- **`api/pipeline.py`**: RAG retrieval orchestrator
|
| 279 |
+
- **`agent/agent.py`**: Pydantic AI agent with tool definitions
|
| 280 |
+
- **`retriever/`**: Embedding, FAISS indexing, reranking
|
| 281 |
+
- **`generator/`**: Prompts and schema for tool selection
|
| 282 |
+
- **`ui/`**: Gradio chat interface components
|
| 283 |
+
- **`utils/`**: Image processing, metadata extraction, file validation
|
| 284 |
+
- **`catalog/`**: Catalog syncing from GraphDB (optional)
|
| 285 |
+
|
| 286 |
+
---
|
| 287 |
+
|
| 288 |
+
## ⚙️ Configuration
|
| 289 |
+
|
| 290 |
+
### Environment Variables
|
| 291 |
+
|
| 292 |
+
| Variable | Description | Default | Required |
|
| 293 |
+
|----------|-------------|---------|----------|
|
| 294 |
+
| `OPENAI_API_KEY` | OpenAI API key | - | ✅ |
|
| 295 |
+
| `EPFL_API_KEY_EMBEDDER` | API key for remote embedder and reranker endpoints | - | ✅ (when `retrieval.embedder.backend: remote` and/or `retrieval.reranker.backend: remote`) |
|
| 296 |
+
| `GITHUB_TOKEN` | GitHub token for repo info | - | ❌ |
|
| 297 |
+
| `SOFTWARE_CATALOG` | Path to catalog JSONL | `dataset/catalog.jsonl` | ✅ |
|
| 298 |
+
| `TOP_K` | Retrieval candidates count | `8` | ❌ |
|
| 299 |
+
| `NUM_CHOICES` | Tools to recommend | `3` | ❌ |
|
| 300 |
+
| `AGENT_OUTPUT_RETRIES` | Structured output validation retries | `3` | ❌ |
|
| 301 |
+
| `EMBED_CATALOG_ON_START` | Pre-embed catalog on startup when FAISS is empty | `1` | ❌ |
|
| 302 |
+
| `LOGLEVEL_CONSOLE` | Console log level | `WARNING` | ❌ |
|
| 303 |
+
| `LOGLEVEL_FILE` | File log level | `INFO` | ❌ |
|
| 304 |
+
| `FILE_LOG` | Enable file logging | `1` | ❌ |
|
| 305 |
+
| `LOG_DIR` | Log directory | `logs` | ❌ |
|
| 306 |
+
| `LOG_PROMPTS` | Save prompt snapshots | `0` | ❌ |
|
| 307 |
+
| `CONFIG_PATH` | Model config file | `config.yaml` | ✅ |
|
| 308 |
+
|
| 309 |
+
### GraphDB Catalog Sync (Optional)
|
| 310 |
+
|
| 311 |
+
For automatic catalog syncing from a GraphDB instance:
|
| 312 |
+
|
| 313 |
+
```dotenv
|
| 314 |
+
GRAPHDB_URL=https://your-graphdb.example.com
|
| 315 |
+
GRAPHDB_GRAPH=your-graph-name
|
| 316 |
+
GRAPHDB_USER=username
|
| 317 |
+
GRAPHDB_PASSWORD=password
|
| 318 |
+
GRAPHDB_QUERY_FILE=/path/to/query.rq
|
| 319 |
+
SYNC_EVERY_HOURS=24 # Auto-refresh interval (0 to disable)
|
| 320 |
+
OUTPUT_JSONLD=dataset/catalog.jsonld
|
| 321 |
+
OUTPUT_JSONL=dataset/catalog.jsonl
|
| 322 |
+
```
|
| 323 |
+
|
| 324 |
+
Run manual sync:
|
| 325 |
+
```bash
|
| 326 |
+
ai_agent sync
|
| 327 |
+
```
|
| 328 |
+
|
| 329 |
+
---
|
| 330 |
+
|
| 331 |
+
## 📋 Catalog Format
|
| 332 |
+
|
| 333 |
+
The catalog is a JSONL file where each line is a `SoftwareDoc` following schema.org SoftwareSourceCode structure.
|
| 334 |
+
|
| 335 |
+
### Minimal Example
|
| 336 |
+
|
| 337 |
+
```json
|
| 338 |
+
{
|
| 339 |
+
"name": "3d-lungs-segmentation",
|
| 340 |
+
"description": "3D lung segmentation from CT; returns a mask/overlay.",
|
| 341 |
+
|
| 342 |
+
"applicationCategory": ["Medical Imaging"],
|
| 343 |
+
"featureList": ["segmentation"],
|
| 344 |
+
"imagingModality": ["CT"],
|
| 345 |
+
"dims": [3],
|
| 346 |
+
"anatomy": ["lung"],
|
| 347 |
+
"keywords": ["mask", "overlay", "lung segmentation", "CT"],
|
| 348 |
+
|
| 349 |
+
"programmingLanguage": "Python",
|
| 350 |
+
"requiresGPU": false,
|
| 351 |
+
"isAccessibleForFree": true,
|
| 352 |
+
"license": "Apache-2.0",
|
| 353 |
+
|
| 354 |
+
"supportingData": [
|
| 355 |
+
{
|
| 356 |
+
"datasetFormat": "TIFF",
|
| 357 |
+
"bodySite": "lung",
|
| 358 |
+
"imagingModality": "CT",
|
| 359 |
+
"hasDimensionality": 3
|
| 360 |
+
},
|
| 361 |
+
{
|
| 362 |
+
"datasetFormat": "DICOM",
|
| 363 |
+
"bodySite": "lung",
|
| 364 |
+
"imagingModality": "CT",
|
| 365 |
+
"hasDimensionality": 3
|
| 366 |
+
}
|
| 367 |
+
],
|
| 368 |
+
|
| 369 |
+
"runnableExample": [
|
| 370 |
+
{
|
| 371 |
+
"hostType": "gradio",
|
| 372 |
+
"url": "https://huggingface.co/spaces/qchapp/3d-lungs-segmentation",
|
| 373 |
+
"name": "HF Space"
|
| 374 |
+
}
|
| 375 |
+
]
|
| 376 |
+
}
|
| 377 |
+
```
|
| 378 |
+
|
| 379 |
+
### Key Fields
|
| 380 |
+
|
| 381 |
+
- **name**: Unique identifier for the tool
|
| 382 |
+
- **description**: Clear explanation of what the tool does
|
| 383 |
+
- **featureList**: Operations (e.g., segmentation, registration, classification)
|
| 384 |
+
- **imagingModality**: Medical imaging types (CT, MRI, XR, US, PET)
|
| 385 |
+
- **dims**: Supported dimensions (2D, 3D, 4D)
|
| 386 |
+
- **anatomy**: Body parts/organs
|
| 387 |
+
- **supportingData**: Format compatibility information (critical for matching)
|
| 388 |
+
- **runnableExample**: Links to live demos (HuggingFace Spaces, notebooks, web apps)
|
| 389 |
+
|
| 390 |
+
---
|
| 391 |
+
|
| 392 |
+
## 🔧 Development
|
| 393 |
+
|
| 394 |
+
### Project Structure
|
| 395 |
+
|
| 396 |
+
```
|
| 397 |
+
ai-agent/
|
| 398 |
+
├── src/ai_agent/
|
| 399 |
+
│ ├── agent/ # Pydantic AI agent and tools
|
| 400 |
+
│ │ ├── agent.py # Agent definition
|
| 401 |
+
│ │ ├── models.py # Agent state models
|
| 402 |
+
│ │ ├── tools/ # Agent tool implementations
|
| 403 |
+
│ │ │ ├── search_tool.py
|
| 404 |
+
│ │ │ ├── search_alternative_tool.py
|
| 405 |
+
│ │ │ ├── gradio_space_tool.py
|
| 406 |
+
│ │ │ ├── repo_info_tool.py
|
| 407 |
+
│ │ │ └── deepwiki_tool.py
|
| 408 |
+
│ │ └── utils.py
|
| 409 |
+
│ ├── api/ # Pipeline orchestration
|
| 410 |
+
│ │ └── pipeline.py # RAGImagingPipeline
|
| 411 |
+
│ ├── retriever/ # Retrieval components
|
| 412 |
+
│ │ ├── text_embedder.py
|
| 413 |
+
│ │ ├── vector_index.py
|
| 414 |
+
│ │ ├── reranker.py
|
| 415 |
+
│ │ └── software_doc.py
|
| 416 |
+
│ ├── generator/ # Agent prompts and schemas
|
| 417 |
+
│ │ ├── prompts.py
|
| 418 |
+
│ │ └── schema.py
|
| 419 |
+
│ ├── ui/ # Gradio interface
|
| 420 |
+
│ │ ├── app.py
|
| 421 |
+
│ │ ├── handlers.py
|
| 422 |
+
│ │ ├── components.py
|
| 423 |
+
│ │ ├── formatters.py
|
| 424 |
+
│ │ ├── state.py
|
| 425 |
+
│ │ └── visualizations.py
|
| 426 |
+
│ ├── utils/ # Shared utilities
|
| 427 |
+
│ │ ├── config.py # Configuration management
|
| 428 |
+
│ │ ├── file_validator.py
|
| 429 |
+
│ │ ├── image_meta.py # Metadata extraction
|
| 430 |
+
│ │ ├── image_io.py
|
| 431 |
+
│ │ ├── previews.py
|
| 432 |
+
│ │ └── tags.py
|
| 433 |
+
│ ├── catalog/ # Catalog syncing
|
| 434 |
+
│ │ └── sync.py
|
| 435 |
+
│ └── cli.py # CLI entry point
|
| 436 |
+
├── tests/ # Test suite
|
| 437 |
+
│ ├── test_retrieval_pipeline.py
|
| 438 |
+
│ ├── test_repo_summary.py
|
| 439 |
+
│ └── data/
|
| 440 |
+
├── artifacts/ # Generated artifacts
|
| 441 |
+
│ └── rag_index/ # FAISS index
|
| 442 |
+
├── dataset/ # Catalog data
|
| 443 |
+
│ └── catalog.jsonl
|
| 444 |
+
├── logs/ # Application logs
|
| 445 |
+
├── config.yaml # Model configuration
|
| 446 |
+
├── pyproject.toml # Project metadata & dependencies
|
| 447 |
+
├── Dockerfile # Production Docker image
|
| 448 |
+
├── tools/image/Dockerfile # Development Docker image
|
| 449 |
+
└── justfile # Task runner commands
|
| 450 |
+
```
|
| 451 |
+
|
| 452 |
+
### Local Development
|
| 453 |
+
|
| 454 |
+
```bash
|
| 455 |
+
# Install in development mode
|
| 456 |
+
pip install -e ".[dev]"
|
| 457 |
+
|
| 458 |
+
# Run tests
|
| 459 |
+
pytest tests/
|
| 460 |
+
```
|
| 461 |
+
|
| 462 |
+
### Testing
|
| 463 |
+
|
| 464 |
+
Run the test suite:
|
| 465 |
+
|
| 466 |
+
```bash
|
| 467 |
+
# All tests
|
| 468 |
+
pytest tests/
|
| 469 |
+
|
| 470 |
+
# Specific test file
|
| 471 |
+
pytest tests/test_retrieval_pipeline.py
|
| 472 |
+
|
| 473 |
+
# With verbose output
|
| 474 |
+
pytest -v tests/
|
| 475 |
+
|
| 476 |
+
# With coverage
|
| 477 |
+
pytest --cov=ai_agent tests/
|
| 478 |
+
```
|
| 479 |
+
|
| 480 |
+
### Logging & Debugging
|
| 481 |
+
|
| 482 |
+
**Console Logs**: Set `LOGLEVEL_CONSOLE=DEBUG` for verbose output
|
| 483 |
+
|
| 484 |
+
**File Logs**: Automatically saved to `logs/app_YYYYMMDD.log` (rotates daily)
|
| 485 |
+
|
| 486 |
+
**Prompt Snapshots**: Enable `LOG_PROMPTS=1` to save:
|
| 487 |
+
- `logs/vlm_selector_YYYYMMDD_HHMMSS.txt` - System/user prompts
|
| 488 |
+
|
| 489 |
+
---
|
| 490 |
+
|
| 491 |
+
## 📚 API & CLI Reference
|
| 492 |
+
|
| 493 |
+
### CLI Commands
|
| 494 |
+
|
| 495 |
+
```bash
|
| 496 |
+
# Launch chat interface
|
| 497 |
+
ai_agent chat
|
| 498 |
+
|
| 499 |
+
# Sync catalog from GraphDB
|
| 500 |
+
ai_agent sync
|
| 501 |
+
```
|
| 502 |
+
|
| 503 |
+
## 🗺️ Maintainer Guide
|
| 504 |
+
|
| 505 |
+
For full project documentation with detailed folder responsibilities, environment defaults, and improvement guidelines, see [docs/guide.md](docs/guide.md).
|
| 506 |
+
|
| 507 |
+
---
|
| 508 |
+
|
| 509 |
+
## 📝 Changelog
|
| 510 |
+
|
| 511 |
+
See [CHANGELOG.md](CHANGELOG.md) for detailed version history.
|
| 512 |
+
|
| 513 |
+
### Recent Highlights
|
| 514 |
+
|
| 515 |
+
**[1.0.0]**
|
| 516 |
+
- ✨ New chat-based interface (`ai_agent chat`) with rich media and tool integration
|
| 517 |
+
- 🛠️ Fully agent-based architecture replacing legacy pipelines
|
| 518 |
+
- 🔍 Smarter retrieval with automatic retry
|
| 519 |
+
- 🔗 DeepWiki MCP integration for fast GitHub repository documentation access
|
| 520 |
+
- 🔧 YAML configuration (`config.yaml`) for flexible model and backend setup
|
| 521 |
+
- 🎨 Redesigned UI with Imaging Plaza branding and improved UX
|
| 522 |
+
- ⚡ Performance improvements (pre-embedding, caching, faster startup)
|
| 523 |
+
- 🧹 Major cleanup: removed deprecated code paths, legacy UI, and outdated tests
|
| 524 |
+
|
| 525 |
+
**[0.1.3] - 2025-10-22**
|
| 526 |
+
- Gradio space runner tool
|
| 527 |
+
- Repository info tool
|
| 528 |
+
- UI fixes and polish
|
| 529 |
+
|
| 530 |
+
---
|
| 531 |
+
|
| 532 |
+
## 📄 License
|
| 533 |
+
|
| 534 |
+
This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details.
|
| 535 |
+
|
| 536 |
+
---
|
| 537 |
+
|
| 538 |
+
## 🙏 Credits & Acknowledgments
|
| 539 |
+
|
| 540 |
+
**Developed by**: Imaging Plaza Team
|
| 541 |
+
|
| 542 |
+
**Technologies:**
|
| 543 |
+
- [Pydantic AI](https://github.com/pydantic/pydantic-ai) - AI agent framework
|
| 544 |
+
- [OpenAI](https://openai.com) - GPT vision model
|
| 545 |
+
- [FAISS](https://github.com/facebookresearch/faiss) - Vector search
|
| 546 |
+
- [BGE-M3](https://huggingface.co/BAAI/bge-m3) - Multilingual embeddings
|
| 547 |
+
- [Gradio](https://gradio.app) - Interactive web UI
|
| 548 |
+
- [DeepWiki](https://deepwiki.com) - GitHub repository documentation
|
| 549 |
+
|
| 550 |
+
**Medical Imaging Formats:**
|
| 551 |
+
- [pydicom](https://github.com/pydicom/pydicom) - DICOM support
|
| 552 |
+
- [nibabel](https://nipy.org/nibabel/) - NIfTI support
|
| 553 |
+
|
| 554 |
+
---
|
| 555 |
+
|
| 556 |
+
## 📮 Support
|
| 557 |
+
|
| 558 |
+
For issues, questions, or contributions, please contact the Imaging Plaza team.
|
| 559 |
+
|
| 560 |
+
---
|
| 561 |
+
|
| 562 |
+
**🏥 Medical Disclaimer**: This software is a tool recommendation system, not a diagnostic tool. Always consult qualified medical professionals for clinical decisions.
|
config.yaml
CHANGED
|
@@ -1,64 +1,64 @@
|
|
| 1 |
-
# AI Agent Model Configuration
|
| 2 |
-
|
| 3 |
-
# Default/fallback model (used for CLI and initial startup)
|
| 4 |
-
agent_model:
|
| 5 |
-
# name: "gpt-5.1"
|
| 6 |
-
# base_url: null # null for default OpenAI endpoint
|
| 7 |
-
# api_key_env: "OPENAI_API_KEY"
|
| 8 |
-
name: "openai/gpt-oss-120b"
|
| 9 |
-
base_url: "https://inference-rcp.epfl.ch/v1"
|
| 10 |
-
api_key_env: "EPFL_API_KEY"
|
| 11 |
-
|
| 12 |
-
# Available models for UI dropdown
|
| 13 |
-
available_models:
|
| 14 |
-
- display_name: "gpt-4o-mini"
|
| 15 |
-
name: "gpt-4o-mini"
|
| 16 |
-
base_url: null
|
| 17 |
-
provider: "OpenAI"
|
| 18 |
-
api_key_env: "OPENAI_API_KEY"
|
| 19 |
-
|
| 20 |
-
- display_name: "gpt-4o"
|
| 21 |
-
name: "gpt-4o"
|
| 22 |
-
base_url: null
|
| 23 |
-
provider: "OpenAI"
|
| 24 |
-
api_key_env: "OPENAI_API_KEY"
|
| 25 |
-
|
| 26 |
-
- display_name: "gpt-5-mini"
|
| 27 |
-
name: "gpt-5-mini"
|
| 28 |
-
base_url: null
|
| 29 |
-
provider: "OpenAI"
|
| 30 |
-
api_key_env: "OPENAI_API_KEY"
|
| 31 |
-
|
| 32 |
-
- display_name: "gpt-5.1"
|
| 33 |
-
name: "gpt-5.1"
|
| 34 |
-
base_url: null
|
| 35 |
-
provider: "OpenAI"
|
| 36 |
-
api_key_env: "OPENAI_API_KEY"
|
| 37 |
-
|
| 38 |
-
- display_name: "openai/gpt-oss-120b [EPFL]"
|
| 39 |
-
name: "openai/gpt-oss-120b"
|
| 40 |
-
base_url: "https://inference-rcp.epfl.ch/v1"
|
| 41 |
-
provider: "EPFL"
|
| 42 |
-
api_key_env: "EPFL_API_KEY"
|
| 43 |
-
|
| 44 |
-
# Retrieval stack (embedder + reranker)
|
| 45 |
-
retrieval:
|
| 46 |
-
embedder:
|
| 47 |
-
backend: "remote" # "remote" or "local"
|
| 48 |
-
model_name: "Qwen/Qwen3-Embedding-8B"
|
| 49 |
-
base_url: "https://inference-rcp.epfl.ch/v1"
|
| 50 |
-
api_key_env: "EPFL_API_KEY_EMBEDDER"
|
| 51 |
-
timeout_s: 20
|
| 52 |
-
# local example:
|
| 53 |
-
# backend: "local"
|
| 54 |
-
# model_name: "BAAI/bge-m3"
|
| 55 |
-
|
| 56 |
-
reranker:
|
| 57 |
-
backend: "remote" # "remote" or "local"
|
| 58 |
-
model_name: "BAAI/bge-reranker-v2-m3"
|
| 59 |
-
base_url: "https://inference-rcp.epfl.ch/v1"
|
| 60 |
-
api_key_env: "EPFL_API_KEY_EMBEDDER"
|
| 61 |
-
timeout_s: 20
|
| 62 |
-
# local example:
|
| 63 |
-
# backend: "local"
|
| 64 |
-
# model_name: "BAAI/bge-reranker-v2-m3"
|
|
|
|
| 1 |
+
# AI Agent Model Configuration
|
| 2 |
+
|
| 3 |
+
# Default/fallback model (used for CLI and initial startup)
|
| 4 |
+
agent_model:
|
| 5 |
+
# name: "gpt-5.1"
|
| 6 |
+
# base_url: null # null for default OpenAI endpoint
|
| 7 |
+
# api_key_env: "OPENAI_API_KEY"
|
| 8 |
+
name: "openai/gpt-oss-120b"
|
| 9 |
+
base_url: "https://inference-rcp.epfl.ch/v1"
|
| 10 |
+
api_key_env: "EPFL_API_KEY"
|
| 11 |
+
|
| 12 |
+
# Available models for UI dropdown
|
| 13 |
+
available_models:
|
| 14 |
+
- display_name: "gpt-4o-mini"
|
| 15 |
+
name: "gpt-4o-mini"
|
| 16 |
+
base_url: null
|
| 17 |
+
provider: "OpenAI"
|
| 18 |
+
api_key_env: "OPENAI_API_KEY"
|
| 19 |
+
|
| 20 |
+
- display_name: "gpt-4o"
|
| 21 |
+
name: "gpt-4o"
|
| 22 |
+
base_url: null
|
| 23 |
+
provider: "OpenAI"
|
| 24 |
+
api_key_env: "OPENAI_API_KEY"
|
| 25 |
+
|
| 26 |
+
- display_name: "gpt-5-mini"
|
| 27 |
+
name: "gpt-5-mini"
|
| 28 |
+
base_url: null
|
| 29 |
+
provider: "OpenAI"
|
| 30 |
+
api_key_env: "OPENAI_API_KEY"
|
| 31 |
+
|
| 32 |
+
- display_name: "gpt-5.1"
|
| 33 |
+
name: "gpt-5.1"
|
| 34 |
+
base_url: null
|
| 35 |
+
provider: "OpenAI"
|
| 36 |
+
api_key_env: "OPENAI_API_KEY"
|
| 37 |
+
|
| 38 |
+
- display_name: "openai/gpt-oss-120b [EPFL]"
|
| 39 |
+
name: "openai/gpt-oss-120b"
|
| 40 |
+
base_url: "https://inference-rcp.epfl.ch/v1"
|
| 41 |
+
provider: "EPFL"
|
| 42 |
+
api_key_env: "EPFL_API_KEY"
|
| 43 |
+
|
| 44 |
+
# Retrieval stack (embedder + reranker)
|
| 45 |
+
retrieval:
|
| 46 |
+
embedder:
|
| 47 |
+
backend: "remote" # "remote" or "local"
|
| 48 |
+
model_name: "Qwen/Qwen3-Embedding-8B"
|
| 49 |
+
base_url: "https://inference-rcp.epfl.ch/v1"
|
| 50 |
+
api_key_env: "EPFL_API_KEY_EMBEDDER"
|
| 51 |
+
timeout_s: 20
|
| 52 |
+
# local example:
|
| 53 |
+
# backend: "local"
|
| 54 |
+
# model_name: "BAAI/bge-m3"
|
| 55 |
+
|
| 56 |
+
reranker:
|
| 57 |
+
backend: "remote" # "remote" or "local"
|
| 58 |
+
model_name: "BAAI/bge-reranker-v2-m3"
|
| 59 |
+
base_url: "https://inference-rcp.epfl.ch/v1"
|
| 60 |
+
api_key_env: "EPFL_API_KEY_EMBEDDER"
|
| 61 |
+
timeout_s: 20
|
| 62 |
+
# local example:
|
| 63 |
+
# backend: "local"
|
| 64 |
+
# model_name: "BAAI/bge-reranker-v2-m3"
|
data/query.rq
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
CONSTRUCT {{
|
| 2 |
+
?s ?p ?o
|
| 3 |
+
}}
|
| 4 |
+
WHERE {{
|
| 5 |
+
GRAPH <{graph}> {{
|
| 6 |
+
?s ?p ?o .
|
| 7 |
+
}}
|
| 8 |
+
}}
|
docker-compose.yml
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
services:
|
| 2 |
+
ai-agent:
|
| 3 |
+
build: .
|
| 4 |
+
image: ai-agent:dev
|
| 5 |
+
container_name: ai-agent
|
| 6 |
+
restart: unless-stopped
|
| 7 |
+
env_file: .env
|
| 8 |
+
environment:
|
| 9 |
+
HOST: 0.0.0.0
|
| 10 |
+
PORT: "7860"
|
| 11 |
+
expose:
|
| 12 |
+
- "7860"
|
| 13 |
+
volumes:
|
| 14 |
+
- ./data:/home/user/app/data
|
| 15 |
+
- ./dataset:/home/user/app/dataset
|
| 16 |
+
- ./artifacts:/home/user/app/artifacts
|
| 17 |
+
- ./logs:/home/user/app/logs
|
| 18 |
+
|
| 19 |
+
cloudflared:
|
| 20 |
+
image: cloudflare/cloudflared:latest
|
| 21 |
+
container_name: ai-agent-cloudflared
|
| 22 |
+
restart: unless-stopped
|
| 23 |
+
command: tunnel --no-autoupdate --url http://ai-agent:7860
|
| 24 |
+
depends_on:
|
| 25 |
+
- ai-agent
|
docs/architecture/agent.md
CHANGED
|
@@ -1,619 +1,619 @@
|
|
| 1 |
-
# Agent & VLM Selection
|
| 2 |
-
|
| 3 |
-
The second stage of the pipeline uses a vision-language model (VLM) with PydanticAI agents to select and rank the best tools from candidates.
|
| 4 |
-
|
| 5 |
-
## Overview
|
| 6 |
-
|
| 7 |
-
**Goal**: Select the most relevant tools using vision + text understanding
|
| 8 |
-
|
| 9 |
-
**Characteristics**:
|
| 10 |
-
|
| 11 |
-
- 🧠 Intelligent reasoning with explanations
|
| 12 |
-
- 👁️ Vision-aware (analyzes image content)
|
| 13 |
-
- 🎯 Comparative ranking of candidates
|
| 14 |
-
- 💬 Conversational with context
|
| 15 |
-
- 📊 Structured output (Pydantic schemas)
|
| 16 |
-
|
| 17 |
-
## Architecture
|
| 18 |
-
|
| 19 |
-
```mermaid
|
| 20 |
-
graph TB
|
| 21 |
-
A[User Message + Files] --> B[PydanticAI Agent]
|
| 22 |
-
B --> C{Agent Router}
|
| 23 |
-
C -->|Tool Call| D[Agent Tools]
|
| 24 |
-
C -->|LLM Reasoning| E[GPT-4o/4o-mini]
|
| 25 |
-
D --> B
|
| 26 |
-
E --> F[ToolSelection Schema]
|
| 27 |
-
F --> G[Structured Response]
|
| 28 |
-
G --> B
|
| 29 |
-
B --> H[Formatted Reply]
|
| 30 |
-
```
|
| 31 |
-
|
| 32 |
-
## PydanticAI Agent
|
| 33 |
-
|
| 34 |
-
### Agent Framework
|
| 35 |
-
|
| 36 |
-
**Framework**: [PydanticAI](https://ai.pydantic.dev/)
|
| 37 |
-
|
| 38 |
-
**Benefits**:
|
| 39 |
-
|
| 40 |
-
- Type-safe with Pydantic models
|
| 41 |
-
- Structured output validation
|
| 42 |
-
- Built-in tool support
|
| 43 |
-
- Async/await support
|
| 44 |
-
- Easy testing with dependency injection
|
| 45 |
-
|
| 46 |
-
### Agent Definition
|
| 47 |
-
|
| 48 |
-
```python
|
| 49 |
-
from pydantic_ai import Agent
|
| 50 |
-
from pydantic_ai.models.openai import OpenAIResponsesModel
|
| 51 |
-
from pydantic_ai.providers.openai import OpenAIProvider
|
| 52 |
-
from ai_agent.generator.prompts import get_agent_system_prompt
|
| 53 |
-
from ai_agent.generator.schema import ToolSelection
|
| 54 |
-
from ai_agent.agent.utils import AgentState
|
| 55 |
-
|
| 56 |
-
provider = OpenAIProvider(api_key=os.getenv("OPENAI_API_KEY"))
|
| 57 |
-
openai_model = OpenAIResponsesModel(model_name="gpt-4o-mini", provider=provider)
|
| 58 |
-
|
| 59 |
-
agent = Agent(
|
| 60 |
-
model=openai_model,
|
| 61 |
-
system_prompt=get_agent_system_prompt(num_choices=3),
|
| 62 |
-
deps_type=AgentState,
|
| 63 |
-
)
|
| 64 |
-
```
|
| 65 |
-
|
| 66 |
-
**Key parameters**:
|
| 67 |
-
|
| 68 |
-
- `model`: VLM model to use (configurable via `config.yaml`)
|
| 69 |
-
- `system_prompt`: Agent role, scoring rules, and output format (from `generator/prompts.py`)
|
| 70 |
-
- `deps_type`: `AgentState` — tracks tool calls, quotas, and session overrides
|
| 71 |
-
- `output_type`: `ToolSelection` — passed to `agent.run_sync()` to enforce structured JSON output (not set on the `Agent` constructor)
|
| 72 |
-
|
| 73 |
-
### Conversation State
|
| 74 |
-
|
| 75 |
-
```python
|
| 76 |
-
from pydantic import BaseModel, Field
|
| 77 |
-
from typing import List, Optional, Dict, Any, Set
|
| 78 |
-
|
| 79 |
-
class AgentState(BaseModel):
|
| 80 |
-
"""Holds incremental tool call logs and runtime overrides."""
|
| 81 |
-
|
| 82 |
-
tool_calls: List[Dict[str, Any]] = Field(default_factory=list)
|
| 83 |
-
tool_counts: Dict[str, int] = Field(default_factory=dict)
|
| 84 |
-
disabled_tools: Set[str] = Field(default_factory=set)
|
| 85 |
-
excluded_tools: List[str] = Field(default_factory=list)
|
| 86 |
-
|
| 87 |
-
# Runtime overrides (session-only)
|
| 88 |
-
override_model: Optional[str] = None
|
| 89 |
-
override_base_url: Optional[str] = None
|
| 90 |
-
override_top_k: Optional[int] = None
|
| 91 |
-
override_num_choices: Optional[int] = None
|
| 92 |
-
|
| 93 |
-
image_paths: List[str] = Field(default_factory=list)
|
| 94 |
-
original_formats: List[str] = Field(default_factory=list)
|
| 95 |
-
```
|
| 96 |
-
|
| 97 |
-
**Passed to every tool call** via dependency injection. Also carries per-tool call counts for quota enforcement.
|
| 98 |
-
|
| 99 |
-
## Agent Tools
|
| 100 |
-
|
| 101 |
-
Tools extend agent capabilities beyond chat:
|
| 102 |
-
|
| 103 |
-
### search_alternative
|
| 104 |
-
|
| 105 |
-
Request alternative search with different query formulation:
|
| 106 |
-
|
| 107 |
-
```python
|
| 108 |
-
@agent.tool(retries=2, prepare=cap_prepare)
|
| 109 |
-
@limit_tool_calls("search_alternative", cap=3)
|
| 110 |
-
async def search_alternative(
|
| 111 |
-
ctx: RunContext[AgentState],
|
| 112 |
-
alternative_query: str,
|
| 113 |
-
excluded: List[str] | None = None,
|
| 114 |
-
top_k: int = 12,
|
| 115 |
-
) -> List[dict]:
|
| 116 |
-
"""Search for tools using an alternative query formulation."""
|
| 117 |
-
|
| 118 |
-
inp = SearchAlternativeInput(
|
| 119 |
-
alternative_query=alternative_query,
|
| 120 |
-
excluded=excluded or [],
|
| 121 |
-
top_k=top_k,
|
| 122 |
-
original_formats=ctx.deps.original_formats,
|
| 123 |
-
image_paths=ctx.deps.image_paths,
|
| 124 |
-
)
|
| 125 |
-
out = tool_search_alternative(inp)
|
| 126 |
-
return [c.model_dump(mode="python") for c in out.candidates]
|
| 127 |
-
```
|
| 128 |
-
|
| 129 |
-
**Usage**:
|
| 130 |
-
|
| 131 |
-
- Agent invokes when user asks for alternatives
|
| 132 |
-
- Up to 3 calls per conversation
|
| 133 |
-
- Formulates semantically different queries
|
| 134 |
-
|
| 135 |
-
**Example**:
|
| 136 |
-
```
|
| 137 |
-
User: Show me alternatives
|
| 138 |
-
Agent: [Calls search_alternative with "pulmonary segmentation CT"]
|
| 139 |
-
```
|
| 140 |
-
|
| 141 |
-
### repo_info
|
| 142 |
-
|
| 143 |
-
Fetch GitHub repository details:
|
| 144 |
-
|
| 145 |
-
```python
|
| 146 |
-
@agent.tool(retries=2, prepare=cap_prepare)
|
| 147 |
-
@limit_tool_calls("repo_info", cap=12)
|
| 148 |
-
async def repo_info(ctx: RunContext[AgentState], url: str, tool_name: str | None = None) -> dict:
|
| 149 |
-
"""Fetch a short summary of a GitHub repository."""
|
| 150 |
-
|
| 151 |
-
# Normalize to canonical GitHub URL
|
| 152 |
-
norm_url = coerce_github_url_or_none(url)
|
| 153 |
-
|
| 154 |
-
# Call tool_repo_summary (tries DeepWiki MCP first, falls back to repocards)
|
| 155 |
-
out = await tool_repo_summary(RepoSummaryInput(url=norm_url, tool_name=tool_name))
|
| 156 |
-
return out.model_dump(mode="python")
|
| 157 |
-
```
|
| 158 |
-
|
| 159 |
-
**Data sources**:
|
| 160 |
-
|
| 161 |
-
1. **DeepWiki MCP**: Pre-indexed, fast, no rate limits
|
| 162 |
-
2. **Repocards**: Direct fetch, fallback for new repos
|
| 163 |
-
|
| 164 |
-
**Returns**:
|
| 165 |
-
|
| 166 |
-
- Repository description
|
| 167 |
-
- Stars, language, topics
|
| 168 |
-
- Last update date
|
| 169 |
-
- License information
|
| 170 |
-
|
| 171 |
-
**Example**:
|
| 172 |
-
```
|
| 173 |
-
User: Tell me about TotalSegmentator
|
| 174 |
-
Agent: [Calls repo_info("https://github.com/wasserth/TotalSegmentator")]
|
| 175 |
-
|
| 176 |
-
TotalSegmentator is an automated multi-organ segmentation tool...
|
| 177 |
-
⭐ 1.2k stars | Python | Apache-2.0 license
|
| 178 |
-
Topics: segmentation, medical-imaging, deep-learning
|
| 179 |
-
```
|
| 180 |
-
|
| 181 |
-
### run_example
|
| 182 |
-
|
| 183 |
-
Execute Gradio Space demos (optional, experimental):
|
| 184 |
-
|
| 185 |
-
```python
|
| 186 |
-
@agent.tool(retries=0, prepare=cap_prepare)
|
| 187 |
-
@limit_tool_calls("run_example", cap=1)
|
| 188 |
-
async def run_example(
|
| 189 |
-
ctx: RunContext[AgentState],
|
| 190 |
-
tool_name: str,
|
| 191 |
-
endpoint_url: str | None = None,
|
| 192 |
-
extra_text: str | None = None,
|
| 193 |
-
) -> dict:
|
| 194 |
-
"""Run an example / demo for a given tool via its Gradio space."""
|
| 195 |
-
|
| 196 |
-
out = tool_run_example(RunExampleInput(
|
| 197 |
-
tool_name=tool_name,
|
| 198 |
-
endpoint_url=endpoint_url,
|
| 199 |
-
extra_text=extra_text,
|
| 200 |
-
))
|
| 201 |
-
return out.model_dump(mode="python")
|
| 202 |
-
```
|
| 203 |
-
|
| 204 |
-
**Status**: Partially implemented, limited to specific demo formats.
|
| 205 |
-
|
| 206 |
-
## Selection and Ranking
|
| 207 |
-
|
| 208 |
-
The PydanticAI agent performs tool selection and ranking directly as part of its LLM reasoning step. There is no separate `VLMToolSelector` class — the agent's system prompt (defined in `generator/prompts.py`) encodes the scoring rules, and the `ToolSelection` Pydantic schema (defined in `generator/schema.py`) enforces structured output.
|
| 209 |
-
|
| 210 |
-
### System Prompt
|
| 211 |
-
|
| 212 |
-
The agent system prompt is assembled by `get_agent_system_prompt()` in `generator/prompts.py` and covers:
|
| 213 |
-
|
| 214 |
-
- **Image analysis**: Instructions to analyze the attached preview image and reference visual observations in explanations
|
| 215 |
-
- **Tool call sequence**: When to call `search_tools`, `search_alternative`, `repo_info`, and `run_example`
|
| 216 |
-
- **Scoring rules**: Accuracy (0–100) = Task match (40) + Format compatibility (30) + Features (30)
|
| 217 |
-
- **Output format**: Single JSON object matching the `ToolSelection` schema
|
| 218 |
-
|
| 219 |
-
```python
|
| 220 |
-
from ai_agent.generator.prompts import get_agent_system_prompt
|
| 221 |
-
|
| 222 |
-
# Generates a prompt that instructs the agent to return up to N ranked choices
|
| 223 |
-
system_prompt = get_agent_system_prompt(num_choices=3)
|
| 224 |
-
```
|
| 225 |
-
|
| 226 |
-
### Selection Process
|
| 227 |
-
|
| 228 |
-
#### Step 1: Tool Calls (Retrieval)
|
| 229 |
-
|
| 230 |
-
The agent calls `search_tools` once (and optionally `search_alternative` up to 3 times) to retrieve candidate tools from the vector index:
|
| 231 |
-
|
| 232 |
-
```
|
| 233 |
-
Agent → search_tools(query="segment lungs", top_k=12)
|
| 234 |
-
← [TotalSegmentator, MedSAM, nnU-Net, ...]
|
| 235 |
-
```
|
| 236 |
-
|
| 237 |
-
#### Step 2: Verification
|
| 238 |
-
|
| 239 |
-
For each finalist the agent plans to recommend, it calls `repo_info` to fetch up-to-date GitHub metadata:
|
| 240 |
-
|
| 241 |
-
```
|
| 242 |
-
Agent → repo_info(url="https://github.com/wasserth/TotalSegmentator")
|
| 243 |
-
← {stars: 1200, language: "Python", topics: [...], description: "..."}
|
| 244 |
-
```
|
| 245 |
-
|
| 246 |
-
#### Step 3: Structured Output
|
| 247 |
-
|
| 248 |
-
The agent returns one JSON object (no prose) that is validated against the `ToolSelection` schema:
|
| 249 |
-
|
| 250 |
-
```python
|
| 251 |
-
run_result = agent_instance.run_sync(
|
| 252 |
-
user_prompt, # text + optional BinaryContent image
|
| 253 |
-
deps=deps, # AgentState with image_paths, excluded_tools, etc.
|
| 254 |
-
output_type=ToolSelection,
|
| 255 |
-
usage_limits=UsageLimits(tool_calls_limit=20),
|
| 256 |
-
)
|
| 257 |
-
result = run_result.output # ToolSelection instance
|
| 258 |
-
```
|
| 259 |
-
|
| 260 |
-
**Multimodal input**:
|
| 261 |
-
|
| 262 |
-
- Text: User task + hidden metadata (format hints, image dimensions)
|
| 263 |
-
- Image: PNG preview bytes passed as `BinaryContent(data=image_bytes, media_type="image/png")`
|
| 264 |
-
- Context: Conversation history prepended to the prompt
|
| 265 |
-
|
| 266 |
-
### Structured Response Schema
|
| 267 |
-
|
| 268 |
-
The `ToolSelection` Pydantic model (in `generator/schema.py`) validates the agent output:
|
| 269 |
-
|
| 270 |
-
```python
|
| 271 |
-
from ai_agent.generator.schema import (
|
| 272 |
-
ToolSelection, ToolChoice, Conversation,
|
| 273 |
-
ConversationStatus, NoToolReason
|
| 274 |
-
)
|
| 275 |
-
|
| 276 |
-
class ToolChoice(BaseModel):
|
| 277 |
-
name: str
|
| 278 |
-
rank: int
|
| 279 |
-
accuracy: float # 0-100
|
| 280 |
-
why: str
|
| 281 |
-
demo_link: Optional[str] = None
|
| 282 |
-
|
| 283 |
-
class Conversation(BaseModel):
|
| 284 |
-
status: ConversationStatus
|
| 285 |
-
question: Optional[str] = None # required if status=needs_clarification
|
| 286 |
-
context: Optional[str] = None # required if status=needs_clarification
|
| 287 |
-
options: Optional[List[str]] = None
|
| 288 |
-
|
| 289 |
-
class ToolSelection(BaseModel):
|
| 290 |
-
conversation: Conversation
|
| 291 |
-
choices: List[ToolChoice] = []
|
| 292 |
-
explanation: Optional[str] = None
|
| 293 |
-
reason: Optional[NoToolReason] = None
|
| 294 |
-
```
|
| 295 |
-
|
| 296 |
-
**Example response** (`ToolSelection`):
|
| 297 |
-
```json
|
| 298 |
-
{
|
| 299 |
-
"conversation": {"status": "complete", "question": null, "context": null},
|
| 300 |
-
"choices": [
|
| 301 |
-
{
|
| 302 |
-
"rank": 1,
|
| 303 |
-
"name": "TotalSegmentator",
|
| 304 |
-
"accuracy": 95.0,
|
| 305 |
-
"why": "Specifically designed for automated multi-organ CT segmentation...",
|
| 306 |
-
"demo_link": "https://huggingface.co/spaces/..."
|
| 307 |
-
},
|
| 308 |
-
{
|
| 309 |
-
"rank": 2,
|
| 310 |
-
"name": "MedSAM",
|
| 311 |
-
"accuracy": 85.0,
|
| 312 |
-
"why": "Flexible SAM-based segmentation supporting DICOM input...",
|
| 313 |
-
"demo_link": "https://huggingface.co/spaces/..."
|
| 314 |
-
}
|
| 315 |
-
],
|
| 316 |
-
"explanation": null,
|
| 317 |
-
"reason": null
|
| 318 |
-
}
|
| 319 |
-
```
|
| 320 |
-
|
| 321 |
-
### Validation
|
| 322 |
-
|
| 323 |
-
Pydantic validates:
|
| 324 |
-
|
| 325 |
-
- All required fields present
|
| 326 |
-
- Types correct (`int`, `float`, `str`, enum)
|
| 327 |
-
- `accuracy` within 0–100 range
|
| 328 |
-
- `ConversationStatus` is one of the allowed enum values
|
| 329 |
-
- `NoToolReason` is a valid enum value when `choices` is empty
|
| 330 |
-
|
| 331 |
-
`ToolSelection.normalize()` also enforces consistency rules automatically (e.g. setting `status=complete` when choices are returned, `status=needs_clarification` when a question is present).
|
| 332 |
-
|
| 333 |
-
## Conversation States
|
| 334 |
-
|
| 335 |
-
State machine for conversation flow:
|
| 336 |
-
|
| 337 |
-
```python
|
| 338 |
-
from ai_agent.generator.schema import ConversationStatus
|
| 339 |
-
|
| 340 |
-
class ConversationStatus(str, Enum):
|
| 341 |
-
COMPLETE = "complete" # Recommendations provided (or no tool found)
|
| 342 |
-
NEEDS_CLARIFICATION = "needs_clarification" # Agent needs more info
|
| 343 |
-
```
|
| 344 |
-
|
| 345 |
-
### Complete
|
| 346 |
-
|
| 347 |
-
Normal successful response:
|
| 348 |
-
|
| 349 |
-
```python
|
| 350 |
-
{
|
| 351 |
-
"conversation": {"status": "complete", "question": null, "context": null},
|
| 352 |
-
"choices": [...],
|
| 353 |
-
"explanation": null,
|
| 354 |
-
"reason": null
|
| 355 |
-
}
|
| 356 |
-
```
|
| 357 |
-
|
| 358 |
-
**Triggers**:
|
| 359 |
-
|
| 360 |
-
- Query is clear
|
| 361 |
-
- Candidates found
|
| 362 |
-
- Image/metadata sufficient
|
| 363 |
-
|
| 364 |
-
### Needs Clarification
|
| 365 |
-
|
| 366 |
-
Agent requests more information:
|
| 367 |
-
|
| 368 |
-
```python
|
| 369 |
-
{
|
| 370 |
-
"conversation": {
|
| 371 |
-
"status": "needs_clarification",
|
| 372 |
-
"question": "Which specific organ would you like to segment?",
|
| 373 |
-
"context": "Several segmentation tools available; target organ narrows choices.",
|
| 374 |
-
"options": ["Lungs", "Brain", "Liver", "Other (briefly specify)"]
|
| 375 |
-
},
|
| 376 |
-
"choices": [],
|
| 377 |
-
"explanation": null,
|
| 378 |
-
"reason": null
|
| 379 |
-
}
|
| 380 |
-
```
|
| 381 |
-
|
| 382 |
-
**Triggers**:
|
| 383 |
-
- Ambiguous query
|
| 384 |
-
- Multiple valid interpretations
|
| 385 |
-
- Missing critical information
|
| 386 |
-
|
| 387 |
-
**Example flow**:
|
| 388 |
-
```
|
| 389 |
-
User: Segment this MRI
|
| 390 |
-
Agent: [STATUS: needs_clarification] Which organ would you like to segment?
|
| 391 |
-
User: The brain
|
| 392 |
-
Agent: [STATUS: complete] Here are brain segmentation tools...
|
| 393 |
-
```
|
| 394 |
-
|
| 395 |
-
### No Tool Terminal
|
| 396 |
-
|
| 397 |
-
No suitable tools in catalog — `status` is still `complete`, but `choices` is empty and a `reason` + `explanation` are provided:
|
| 398 |
-
|
| 399 |
-
```python
|
| 400 |
-
{
|
| 401 |
-
"conversation": {"status": "complete", "question": null, "context": null},
|
| 402 |
-
"choices": [],
|
| 403 |
-
"reason": "no_task_match",
|
| 404 |
-
"explanation": "No tools in the catalog handle audio processing. This catalog covers imaging analysis software."
|
| 405 |
-
}
|
| 406 |
-
```
|
| 407 |
-
|
| 408 |
-
Available `NoToolReason` values: `no_suitable_tool`, `no_modality_match`, `no_task_match`, `no_dimension_match`, `invalid_files`.
|
| 409 |
-
|
| 410 |
-
## Ranking Logic
|
| 411 |
-
|
| 412 |
-
### Scoring Factors
|
| 413 |
-
|
| 414 |
-
The agent considers:
|
| 415 |
-
|
| 416 |
-
#### High Priority
|
| 417 |
-
1. **Task Match**: Tool designed for this specific task
|
| 418 |
-
2. **Format Compatibility**: Supports user's file format
|
| 419 |
-
3. **Visual Analysis**: Image content matches tool's domain
|
| 420 |
-
|
| 421 |
-
#### Medium Priority
|
| 422 |
-
4. **Modality Alignment**: CT tool for CT image, MRI for MRI
|
| 423 |
-
5. **Dimension Match**: 3D tool for 3D volume
|
| 424 |
-
6. **Feature Coverage**: Specific capabilities mentioned
|
| 425 |
-
|
| 426 |
-
#### Low Priority
|
| 427 |
-
7. **License**: Open-source preference (if no preference stated)
|
| 428 |
-
8. **Demo Availability**: Has runnable demo
|
| 429 |
-
9. **Popularity**: Community adoption
|
| 430 |
-
|
| 431 |
-
### Explanation Generation
|
| 432 |
-
|
| 433 |
-
Each recommendation includes explanation:
|
| 434 |
-
|
| 435 |
-
**Good explanation template**:
|
| 436 |
-
```
|
| 437 |
-
{Tool} is {specifically designed / well-suited} for {task}
|
| 438 |
-
on {modality} images. It supports {format} input {with/without}
|
| 439 |
-
preprocessing and provides {key features}. {Caveats if any}.
|
| 440 |
-
```
|
| 441 |
-
|
| 442 |
-
**Example**:
|
| 443 |
-
```
|
| 444 |
-
TotalSegmentator is specifically designed for automated multi-organ
|
| 445 |
-
segmentation on CT scans. It supports DICOM input without preprocessing
|
| 446 |
-
and can segment 104 anatomical structures including lungs, air airways,
|
| 447 |
-
and vessels. It works best on whole-body CT but also performs well on
|
| 448 |
-
thoracic scans.
|
| 449 |
-
```
|
| 450 |
-
|
| 451 |
-
### Rank Assignment
|
| 452 |
-
|
| 453 |
-
- **Rank 1**: Best overall match (highest accuracy score)
|
| 454 |
-
- **Rank 2**: Strong alternative or different approach
|
| 455 |
-
- **Rank 3**: Fallback option or specialized capability
|
| 456 |
-
|
| 457 |
-
**Important**: Ranks are relative to **this specific query**, not absolute tool quality.
|
| 458 |
-
|
| 459 |
-
## Model Configuration
|
| 460 |
-
|
| 461 |
-
### Model Selection
|
| 462 |
-
|
| 463 |
-
Available via `config.yaml`:
|
| 464 |
-
|
| 465 |
-
```yaml
|
| 466 |
-
agent_model:
|
| 467 |
-
name: "gpt-4o-mini"
|
| 468 |
-
base_url: null
|
| 469 |
-
api_key_env: "OPENAI_API_KEY"
|
| 470 |
-
```
|
| 471 |
-
|
| 472 |
-
### Model Comparison
|
| 473 |
-
|
| 474 |
-
| Model | Vision | Speed | Cost | Best For |
|
| 475 |
-
|-------|--------|-------|------|----------|
|
| 476 |
-
| gpt-4o-mini | ✅ | ⚡⚡⚡ | $ | Most queries, fast iteration |
|
| 477 |
-
| gpt-4o | ✅✅ | ⚡⚡ | $$ | Complex visual analysis |
|
| 478 |
-
| gpt-5.1 | ✅✅✅ | ⚡ | $$$ | Maximum accuracy needed |
|
| 479 |
-
|
| 480 |
-
### Custom Endpoints
|
| 481 |
-
|
| 482 |
-
Support for OpenAI-compatible APIs:
|
| 483 |
-
|
| 484 |
-
```yaml
|
| 485 |
-
agent_model:
|
| 486 |
-
name: "llama-3.2-vision"
|
| 487 |
-
base_url: "https://inference.epfl.ch/v1"
|
| 488 |
-
api_key_env: "EPFL_API_KEY"
|
| 489 |
-
```
|
| 490 |
-
|
| 491 |
-
## Error Handling
|
| 492 |
-
|
| 493 |
-
### Agent Errors
|
| 494 |
-
|
| 495 |
-
**Tool quota exceeded** (handled gracefully in `run_agent`):
|
| 496 |
-
```python
|
| 497 |
-
except UsageLimitExceeded:
|
| 498 |
-
# Returns a ToolSelection with empty choices and an explanation
|
| 499 |
-
result = ToolSelection(
|
| 500 |
-
conversation=Conversation(status=ConversationStatus.COMPLETE, ...),
|
| 501 |
-
choices=[],
|
| 502 |
-
explanation="Tool call limit reached. Try a more specific query.",
|
| 503 |
-
)
|
| 504 |
-
```
|
| 505 |
-
|
| 506 |
-
**Invalid structured output**:
|
| 507 |
-
|
| 508 |
-
PydanticAI automatically retries the LLM call (up to `retries=2` per tool) if the model returns output that fails `ToolSelection` validation. The `ToolSelection.normalize()` model validator also auto-corrects minor inconsistencies.
|
| 509 |
-
|
| 510 |
-
**API Errors**:
|
| 511 |
-
```python
|
| 512 |
-
except Exception as e:
|
| 513 |
-
log.warning(f"Agent execution encountered an error: {e}")
|
| 514 |
-
raise # propagated to the UI layer
|
| 515 |
-
```
|
| 516 |
-
|
| 517 |
-
### Graceful Degradation
|
| 518 |
-
|
| 519 |
-
If the agent fails after all retries:
|
| 520 |
-
|
| 521 |
-
1. Return empty `choices` with an `explanation` describing what was searched
|
| 522 |
-
2. UI surfaces the explanation so users can refine their query
|
| 523 |
-
3. Suggest manual exploration of the catalog
|
| 524 |
-
|
| 525 |
-
<!-- ## Performance
|
| 526 |
-
|
| 527 |
-
### Latency
|
| 528 |
-
|
| 529 |
-
Typical VLM call: **2-5 seconds**
|
| 530 |
-
|
| 531 |
-
Breakdown:
|
| 532 |
-
|
| 533 |
-
- Prompt construction: <100ms
|
| 534 |
-
- API call: 2-4s (network + inference)
|
| 535 |
-
- Response parsing: <100ms
|
| 536 |
-
- Validation: <50ms
|
| 537 |
-
|
| 538 |
-
### Optimization
|
| 539 |
-
|
| 540 |
-
**Prompt optimization**:
|
| 541 |
-
|
| 542 |
-
- Concise candidate descriptions
|
| 543 |
-
- Limit to top-8 candidates
|
| 544 |
-
- Structured format for parsing
|
| 545 |
-
|
| 546 |
-
**Caching**:
|
| 547 |
-
|
| 548 |
-
- Model endpoint reused
|
| 549 |
-
- Agent instance persists across requests
|
| 550 |
-
|
| 551 |
-
**Batch processing** (for testing):
|
| 552 |
-
```python
|
| 553 |
-
# Process multiple queries
|
| 554 |
-
responses = await asyncio.gather(*[
|
| 555 |
-
agent.run(query1),
|
| 556 |
-
agent.run(query2),
|
| 557 |
-
agent.run(query3)
|
| 558 |
-
])
|
| 559 |
-
``` -->
|
| 560 |
-
|
| 561 |
-
## Testing
|
| 562 |
-
|
| 563 |
-
### Unit Tests
|
| 564 |
-
|
| 565 |
-
Test agent selection with PydanticAI's built-in test model (your catalog should contain the choice provided below, i.e. the `TotalSegmentator` tool):
|
| 566 |
-
|
| 567 |
-
```python
|
| 568 |
-
from pydantic_ai import Agent
|
| 569 |
-
from pydantic_ai.models.test import TestModel
|
| 570 |
-
from ai_agent.generator.schema import ToolSelection, Conversation, ConversationStatus, ToolChoice
|
| 571 |
-
from ai_agent.agent.utils import AgentState
|
| 572 |
-
|
| 573 |
-
def test_agent_selection():
|
| 574 |
-
test_model = TestModel()
|
| 575 |
-
test_agent = Agent(model=test_model, deps_type=AgentState)
|
| 576 |
-
|
| 577 |
-
mock_output = ToolSelection(
|
| 578 |
-
conversation=Conversation(status=ConversationStatus.COMPLETE),
|
| 579 |
-
choices=[
|
| 580 |
-
ToolChoice(name="TotalSegmentator", rank=1, accuracy=95.0, why="Best CT segmenter")
|
| 581 |
-
]
|
| 582 |
-
)
|
| 583 |
-
|
| 584 |
-
with test_agent.override(model=test_model):
|
| 585 |
-
result = test_agent.run_sync("segment lungs", deps=AgentState(), output_type=ToolSelection)
|
| 586 |
-
|
| 587 |
-
assert result.output.conversation.status == ConversationStatus.COMPLETE
|
| 588 |
-
assert len(result.output.choices) == 1
|
| 589 |
-
assert result.output.choices[0].rank == 1
|
| 590 |
-
```
|
| 591 |
-
|
| 592 |
-
### Integration Tests
|
| 593 |
-
|
| 594 |
-
Test with real VLM (expensive, slow):
|
| 595 |
-
|
| 596 |
-
```python
|
| 597 |
-
@pytest.mark.integration
|
| 598 |
-
def test_real_agent():
|
| 599 |
-
from ai_agent.agent.agent import run_agent
|
| 600 |
-
|
| 601 |
-
with open("tests/data/sample.tif", "rb") as f:
|
| 602 |
-
image_bytes = f.read()
|
| 603 |
-
|
| 604 |
-
result = run_agent(
|
| 605 |
-
task="I want to segment the lungs of this CT scan",
|
| 606 |
-
image_paths=["tests/data/sample.tif"],
|
| 607 |
-
image_bytes=image_bytes,
|
| 608 |
-
)
|
| 609 |
-
|
| 610 |
-
assert result.conversation.status == ConversationStatus.COMPLETE
|
| 611 |
-
assert len(result.choices) > 0
|
| 612 |
-
assert all(0 <= c.accuracy <= 100 for c in result.choices)
|
| 613 |
-
```
|
| 614 |
-
|
| 615 |
-
## Next Steps
|
| 616 |
-
|
| 617 |
-
- Learn about [Software Catalog](catalog.md)
|
| 618 |
-
- Return to [Architecture Overview](overview.md)
|
| 619 |
-
- Explore [Retrieval Pipeline](retrieval.md)
|
|
|
|
| 1 |
+
# Agent & VLM Selection
|
| 2 |
+
|
| 3 |
+
The second stage of the pipeline uses a vision-language model (VLM) with PydanticAI agents to select and rank the best tools from candidates.
|
| 4 |
+
|
| 5 |
+
## Overview
|
| 6 |
+
|
| 7 |
+
**Goal**: Select the most relevant tools using vision + text understanding
|
| 8 |
+
|
| 9 |
+
**Characteristics**:
|
| 10 |
+
|
| 11 |
+
- 🧠 Intelligent reasoning with explanations
|
| 12 |
+
- 👁️ Vision-aware (analyzes image content)
|
| 13 |
+
- 🎯 Comparative ranking of candidates
|
| 14 |
+
- 💬 Conversational with context
|
| 15 |
+
- 📊 Structured output (Pydantic schemas)
|
| 16 |
+
|
| 17 |
+
## Architecture
|
| 18 |
+
|
| 19 |
+
```mermaid
|
| 20 |
+
graph TB
|
| 21 |
+
A[User Message + Files] --> B[PydanticAI Agent]
|
| 22 |
+
B --> C{Agent Router}
|
| 23 |
+
C -->|Tool Call| D[Agent Tools]
|
| 24 |
+
C -->|LLM Reasoning| E[GPT-4o/4o-mini]
|
| 25 |
+
D --> B
|
| 26 |
+
E --> F[ToolSelection Schema]
|
| 27 |
+
F --> G[Structured Response]
|
| 28 |
+
G --> B
|
| 29 |
+
B --> H[Formatted Reply]
|
| 30 |
+
```
|
| 31 |
+
|
| 32 |
+
## PydanticAI Agent
|
| 33 |
+
|
| 34 |
+
### Agent Framework
|
| 35 |
+
|
| 36 |
+
**Framework**: [PydanticAI](https://ai.pydantic.dev/)
|
| 37 |
+
|
| 38 |
+
**Benefits**:
|
| 39 |
+
|
| 40 |
+
- Type-safe with Pydantic models
|
| 41 |
+
- Structured output validation
|
| 42 |
+
- Built-in tool support
|
| 43 |
+
- Async/await support
|
| 44 |
+
- Easy testing with dependency injection
|
| 45 |
+
|
| 46 |
+
### Agent Definition
|
| 47 |
+
|
| 48 |
+
```python
|
| 49 |
+
from pydantic_ai import Agent
|
| 50 |
+
from pydantic_ai.models.openai import OpenAIResponsesModel
|
| 51 |
+
from pydantic_ai.providers.openai import OpenAIProvider
|
| 52 |
+
from ai_agent.generator.prompts import get_agent_system_prompt
|
| 53 |
+
from ai_agent.generator.schema import ToolSelection
|
| 54 |
+
from ai_agent.agent.utils import AgentState
|
| 55 |
+
|
| 56 |
+
provider = OpenAIProvider(api_key=os.getenv("OPENAI_API_KEY"))
|
| 57 |
+
openai_model = OpenAIResponsesModel(model_name="gpt-4o-mini", provider=provider)
|
| 58 |
+
|
| 59 |
+
agent = Agent(
|
| 60 |
+
model=openai_model,
|
| 61 |
+
system_prompt=get_agent_system_prompt(num_choices=3),
|
| 62 |
+
deps_type=AgentState,
|
| 63 |
+
)
|
| 64 |
+
```
|
| 65 |
+
|
| 66 |
+
**Key parameters**:
|
| 67 |
+
|
| 68 |
+
- `model`: VLM model to use (configurable via `config.yaml`)
|
| 69 |
+
- `system_prompt`: Agent role, scoring rules, and output format (from `generator/prompts.py`)
|
| 70 |
+
- `deps_type`: `AgentState` — tracks tool calls, quotas, and session overrides
|
| 71 |
+
- `output_type`: `ToolSelection` — passed to `agent.run_sync()` to enforce structured JSON output (not set on the `Agent` constructor)
|
| 72 |
+
|
| 73 |
+
### Conversation State
|
| 74 |
+
|
| 75 |
+
```python
|
| 76 |
+
from pydantic import BaseModel, Field
|
| 77 |
+
from typing import List, Optional, Dict, Any, Set
|
| 78 |
+
|
| 79 |
+
class AgentState(BaseModel):
|
| 80 |
+
"""Holds incremental tool call logs and runtime overrides."""
|
| 81 |
+
|
| 82 |
+
tool_calls: List[Dict[str, Any]] = Field(default_factory=list)
|
| 83 |
+
tool_counts: Dict[str, int] = Field(default_factory=dict)
|
| 84 |
+
disabled_tools: Set[str] = Field(default_factory=set)
|
| 85 |
+
excluded_tools: List[str] = Field(default_factory=list)
|
| 86 |
+
|
| 87 |
+
# Runtime overrides (session-only)
|
| 88 |
+
override_model: Optional[str] = None
|
| 89 |
+
override_base_url: Optional[str] = None
|
| 90 |
+
override_top_k: Optional[int] = None
|
| 91 |
+
override_num_choices: Optional[int] = None
|
| 92 |
+
|
| 93 |
+
image_paths: List[str] = Field(default_factory=list)
|
| 94 |
+
original_formats: List[str] = Field(default_factory=list)
|
| 95 |
+
```
|
| 96 |
+
|
| 97 |
+
**Passed to every tool call** via dependency injection. Also carries per-tool call counts for quota enforcement.
|
| 98 |
+
|
| 99 |
+
## Agent Tools
|
| 100 |
+
|
| 101 |
+
Tools extend agent capabilities beyond chat:
|
| 102 |
+
|
| 103 |
+
### search_alternative
|
| 104 |
+
|
| 105 |
+
Request alternative search with different query formulation:
|
| 106 |
+
|
| 107 |
+
```python
|
| 108 |
+
@agent.tool(retries=2, prepare=cap_prepare)
|
| 109 |
+
@limit_tool_calls("search_alternative", cap=3)
|
| 110 |
+
async def search_alternative(
|
| 111 |
+
ctx: RunContext[AgentState],
|
| 112 |
+
alternative_query: str,
|
| 113 |
+
excluded: List[str] | None = None,
|
| 114 |
+
top_k: int = 12,
|
| 115 |
+
) -> List[dict]:
|
| 116 |
+
"""Search for tools using an alternative query formulation."""
|
| 117 |
+
|
| 118 |
+
inp = SearchAlternativeInput(
|
| 119 |
+
alternative_query=alternative_query,
|
| 120 |
+
excluded=excluded or [],
|
| 121 |
+
top_k=top_k,
|
| 122 |
+
original_formats=ctx.deps.original_formats,
|
| 123 |
+
image_paths=ctx.deps.image_paths,
|
| 124 |
+
)
|
| 125 |
+
out = tool_search_alternative(inp)
|
| 126 |
+
return [c.model_dump(mode="python") for c in out.candidates]
|
| 127 |
+
```
|
| 128 |
+
|
| 129 |
+
**Usage**:
|
| 130 |
+
|
| 131 |
+
- Agent invokes when user asks for alternatives
|
| 132 |
+
- Up to 3 calls per conversation
|
| 133 |
+
- Formulates semantically different queries
|
| 134 |
+
|
| 135 |
+
**Example**:
|
| 136 |
+
```
|
| 137 |
+
User: Show me alternatives
|
| 138 |
+
Agent: [Calls search_alternative with "pulmonary segmentation CT"]
|
| 139 |
+
```
|
| 140 |
+
|
| 141 |
+
### repo_info
|
| 142 |
+
|
| 143 |
+
Fetch GitHub repository details:
|
| 144 |
+
|
| 145 |
+
```python
|
| 146 |
+
@agent.tool(retries=2, prepare=cap_prepare)
|
| 147 |
+
@limit_tool_calls("repo_info", cap=12)
|
| 148 |
+
async def repo_info(ctx: RunContext[AgentState], url: str, tool_name: str | None = None) -> dict:
|
| 149 |
+
"""Fetch a short summary of a GitHub repository."""
|
| 150 |
+
|
| 151 |
+
# Normalize to canonical GitHub URL
|
| 152 |
+
norm_url = coerce_github_url_or_none(url)
|
| 153 |
+
|
| 154 |
+
# Call tool_repo_summary (tries DeepWiki MCP first, falls back to repocards)
|
| 155 |
+
out = await tool_repo_summary(RepoSummaryInput(url=norm_url, tool_name=tool_name))
|
| 156 |
+
return out.model_dump(mode="python")
|
| 157 |
+
```
|
| 158 |
+
|
| 159 |
+
**Data sources**:
|
| 160 |
+
|
| 161 |
+
1. **DeepWiki MCP**: Pre-indexed, fast, no rate limits
|
| 162 |
+
2. **Repocards**: Direct fetch, fallback for new repos
|
| 163 |
+
|
| 164 |
+
**Returns**:
|
| 165 |
+
|
| 166 |
+
- Repository description
|
| 167 |
+
- Stars, language, topics
|
| 168 |
+
- Last update date
|
| 169 |
+
- License information
|
| 170 |
+
|
| 171 |
+
**Example**:
|
| 172 |
+
```
|
| 173 |
+
User: Tell me about TotalSegmentator
|
| 174 |
+
Agent: [Calls repo_info("https://github.com/wasserth/TotalSegmentator")]
|
| 175 |
+
|
| 176 |
+
TotalSegmentator is an automated multi-organ segmentation tool...
|
| 177 |
+
⭐ 1.2k stars | Python | Apache-2.0 license
|
| 178 |
+
Topics: segmentation, medical-imaging, deep-learning
|
| 179 |
+
```
|
| 180 |
+
|
| 181 |
+
### run_example
|
| 182 |
+
|
| 183 |
+
Execute Gradio Space demos (optional, experimental):
|
| 184 |
+
|
| 185 |
+
```python
|
| 186 |
+
@agent.tool(retries=0, prepare=cap_prepare)
|
| 187 |
+
@limit_tool_calls("run_example", cap=1)
|
| 188 |
+
async def run_example(
|
| 189 |
+
ctx: RunContext[AgentState],
|
| 190 |
+
tool_name: str,
|
| 191 |
+
endpoint_url: str | None = None,
|
| 192 |
+
extra_text: str | None = None,
|
| 193 |
+
) -> dict:
|
| 194 |
+
"""Run an example / demo for a given tool via its Gradio space."""
|
| 195 |
+
|
| 196 |
+
out = tool_run_example(RunExampleInput(
|
| 197 |
+
tool_name=tool_name,
|
| 198 |
+
endpoint_url=endpoint_url,
|
| 199 |
+
extra_text=extra_text,
|
| 200 |
+
))
|
| 201 |
+
return out.model_dump(mode="python")
|
| 202 |
+
```
|
| 203 |
+
|
| 204 |
+
**Status**: Partially implemented, limited to specific demo formats.
|
| 205 |
+
|
| 206 |
+
## Selection and Ranking
|
| 207 |
+
|
| 208 |
+
The PydanticAI agent performs tool selection and ranking directly as part of its LLM reasoning step. There is no separate `VLMToolSelector` class — the agent's system prompt (defined in `generator/prompts.py`) encodes the scoring rules, and the `ToolSelection` Pydantic schema (defined in `generator/schema.py`) enforces structured output.
|
| 209 |
+
|
| 210 |
+
### System Prompt
|
| 211 |
+
|
| 212 |
+
The agent system prompt is assembled by `get_agent_system_prompt()` in `generator/prompts.py` and covers:
|
| 213 |
+
|
| 214 |
+
- **Image analysis**: Instructions to analyze the attached preview image and reference visual observations in explanations
|
| 215 |
+
- **Tool call sequence**: When to call `search_tools`, `search_alternative`, `repo_info`, and `run_example`
|
| 216 |
+
- **Scoring rules**: Accuracy (0–100) = Task match (40) + Format compatibility (30) + Features (30)
|
| 217 |
+
- **Output format**: Single JSON object matching the `ToolSelection` schema
|
| 218 |
+
|
| 219 |
+
```python
|
| 220 |
+
from ai_agent.generator.prompts import get_agent_system_prompt
|
| 221 |
+
|
| 222 |
+
# Generates a prompt that instructs the agent to return up to N ranked choices
|
| 223 |
+
system_prompt = get_agent_system_prompt(num_choices=3)
|
| 224 |
+
```
|
| 225 |
+
|
| 226 |
+
### Selection Process
|
| 227 |
+
|
| 228 |
+
#### Step 1: Tool Calls (Retrieval)
|
| 229 |
+
|
| 230 |
+
The agent calls `search_tools` once (and optionally `search_alternative` up to 3 times) to retrieve candidate tools from the vector index:
|
| 231 |
+
|
| 232 |
+
```
|
| 233 |
+
Agent → search_tools(query="segment lungs", top_k=12)
|
| 234 |
+
← [TotalSegmentator, MedSAM, nnU-Net, ...]
|
| 235 |
+
```
|
| 236 |
+
|
| 237 |
+
#### Step 2: Verification
|
| 238 |
+
|
| 239 |
+
For each finalist the agent plans to recommend, it calls `repo_info` to fetch up-to-date GitHub metadata:
|
| 240 |
+
|
| 241 |
+
```
|
| 242 |
+
Agent → repo_info(url="https://github.com/wasserth/TotalSegmentator")
|
| 243 |
+
← {stars: 1200, language: "Python", topics: [...], description: "..."}
|
| 244 |
+
```
|
| 245 |
+
|
| 246 |
+
#### Step 3: Structured Output
|
| 247 |
+
|
| 248 |
+
The agent returns one JSON object (no prose) that is validated against the `ToolSelection` schema:
|
| 249 |
+
|
| 250 |
+
```python
|
| 251 |
+
run_result = agent_instance.run_sync(
|
| 252 |
+
user_prompt, # text + optional BinaryContent image
|
| 253 |
+
deps=deps, # AgentState with image_paths, excluded_tools, etc.
|
| 254 |
+
output_type=ToolSelection,
|
| 255 |
+
usage_limits=UsageLimits(tool_calls_limit=20),
|
| 256 |
+
)
|
| 257 |
+
result = run_result.output # ToolSelection instance
|
| 258 |
+
```
|
| 259 |
+
|
| 260 |
+
**Multimodal input**:
|
| 261 |
+
|
| 262 |
+
- Text: User task + hidden metadata (format hints, image dimensions)
|
| 263 |
+
- Image: PNG preview bytes passed as `BinaryContent(data=image_bytes, media_type="image/png")`
|
| 264 |
+
- Context: Conversation history prepended to the prompt
|
| 265 |
+
|
| 266 |
+
### Structured Response Schema
|
| 267 |
+
|
| 268 |
+
The `ToolSelection` Pydantic model (in `generator/schema.py`) validates the agent output:
|
| 269 |
+
|
| 270 |
+
```python
|
| 271 |
+
from ai_agent.generator.schema import (
|
| 272 |
+
ToolSelection, ToolChoice, Conversation,
|
| 273 |
+
ConversationStatus, NoToolReason
|
| 274 |
+
)
|
| 275 |
+
|
| 276 |
+
class ToolChoice(BaseModel):
|
| 277 |
+
name: str
|
| 278 |
+
rank: int
|
| 279 |
+
accuracy: float # 0-100
|
| 280 |
+
why: str
|
| 281 |
+
demo_link: Optional[str] = None
|
| 282 |
+
|
| 283 |
+
class Conversation(BaseModel):
|
| 284 |
+
status: ConversationStatus
|
| 285 |
+
question: Optional[str] = None # required if status=needs_clarification
|
| 286 |
+
context: Optional[str] = None # required if status=needs_clarification
|
| 287 |
+
options: Optional[List[str]] = None
|
| 288 |
+
|
| 289 |
+
class ToolSelection(BaseModel):
|
| 290 |
+
conversation: Conversation
|
| 291 |
+
choices: List[ToolChoice] = []
|
| 292 |
+
explanation: Optional[str] = None
|
| 293 |
+
reason: Optional[NoToolReason] = None
|
| 294 |
+
```
|
| 295 |
+
|
| 296 |
+
**Example response** (`ToolSelection`):
|
| 297 |
+
```json
|
| 298 |
+
{
|
| 299 |
+
"conversation": {"status": "complete", "question": null, "context": null},
|
| 300 |
+
"choices": [
|
| 301 |
+
{
|
| 302 |
+
"rank": 1,
|
| 303 |
+
"name": "TotalSegmentator",
|
| 304 |
+
"accuracy": 95.0,
|
| 305 |
+
"why": "Specifically designed for automated multi-organ CT segmentation...",
|
| 306 |
+
"demo_link": "https://huggingface.co/spaces/..."
|
| 307 |
+
},
|
| 308 |
+
{
|
| 309 |
+
"rank": 2,
|
| 310 |
+
"name": "MedSAM",
|
| 311 |
+
"accuracy": 85.0,
|
| 312 |
+
"why": "Flexible SAM-based segmentation supporting DICOM input...",
|
| 313 |
+
"demo_link": "https://huggingface.co/spaces/..."
|
| 314 |
+
}
|
| 315 |
+
],
|
| 316 |
+
"explanation": null,
|
| 317 |
+
"reason": null
|
| 318 |
+
}
|
| 319 |
+
```
|
| 320 |
+
|
| 321 |
+
### Validation
|
| 322 |
+
|
| 323 |
+
Pydantic validates:
|
| 324 |
+
|
| 325 |
+
- All required fields present
|
| 326 |
+
- Types correct (`int`, `float`, `str`, enum)
|
| 327 |
+
- `accuracy` within 0–100 range
|
| 328 |
+
- `ConversationStatus` is one of the allowed enum values
|
| 329 |
+
- `NoToolReason` is a valid enum value when `choices` is empty
|
| 330 |
+
|
| 331 |
+
`ToolSelection.normalize()` also enforces consistency rules automatically (e.g. setting `status=complete` when choices are returned, `status=needs_clarification` when a question is present).
|
| 332 |
+
|
| 333 |
+
## Conversation States
|
| 334 |
+
|
| 335 |
+
State machine for conversation flow:
|
| 336 |
+
|
| 337 |
+
```python
|
| 338 |
+
from ai_agent.generator.schema import ConversationStatus
|
| 339 |
+
|
| 340 |
+
class ConversationStatus(str, Enum):
|
| 341 |
+
COMPLETE = "complete" # Recommendations provided (or no tool found)
|
| 342 |
+
NEEDS_CLARIFICATION = "needs_clarification" # Agent needs more info
|
| 343 |
+
```
|
| 344 |
+
|
| 345 |
+
### Complete
|
| 346 |
+
|
| 347 |
+
Normal successful response:
|
| 348 |
+
|
| 349 |
+
```python
|
| 350 |
+
{
|
| 351 |
+
"conversation": {"status": "complete", "question": null, "context": null},
|
| 352 |
+
"choices": [...],
|
| 353 |
+
"explanation": null,
|
| 354 |
+
"reason": null
|
| 355 |
+
}
|
| 356 |
+
```
|
| 357 |
+
|
| 358 |
+
**Triggers**:
|
| 359 |
+
|
| 360 |
+
- Query is clear
|
| 361 |
+
- Candidates found
|
| 362 |
+
- Image/metadata sufficient
|
| 363 |
+
|
| 364 |
+
### Needs Clarification
|
| 365 |
+
|
| 366 |
+
Agent requests more information:
|
| 367 |
+
|
| 368 |
+
```python
|
| 369 |
+
{
|
| 370 |
+
"conversation": {
|
| 371 |
+
"status": "needs_clarification",
|
| 372 |
+
"question": "Which specific organ would you like to segment?",
|
| 373 |
+
"context": "Several segmentation tools available; target organ narrows choices.",
|
| 374 |
+
"options": ["Lungs", "Brain", "Liver", "Other (briefly specify)"]
|
| 375 |
+
},
|
| 376 |
+
"choices": [],
|
| 377 |
+
"explanation": null,
|
| 378 |
+
"reason": null
|
| 379 |
+
}
|
| 380 |
+
```
|
| 381 |
+
|
| 382 |
+
**Triggers**:
|
| 383 |
+
- Ambiguous query
|
| 384 |
+
- Multiple valid interpretations
|
| 385 |
+
- Missing critical information
|
| 386 |
+
|
| 387 |
+
**Example flow**:
|
| 388 |
+
```
|
| 389 |
+
User: Segment this MRI
|
| 390 |
+
Agent: [STATUS: needs_clarification] Which organ would you like to segment?
|
| 391 |
+
User: The brain
|
| 392 |
+
Agent: [STATUS: complete] Here are brain segmentation tools...
|
| 393 |
+
```
|
| 394 |
+
|
| 395 |
+
### No Tool Terminal
|
| 396 |
+
|
| 397 |
+
No suitable tools in catalog — `status` is still `complete`, but `choices` is empty and a `reason` + `explanation` are provided:
|
| 398 |
+
|
| 399 |
+
```python
|
| 400 |
+
{
|
| 401 |
+
"conversation": {"status": "complete", "question": null, "context": null},
|
| 402 |
+
"choices": [],
|
| 403 |
+
"reason": "no_task_match",
|
| 404 |
+
"explanation": "No tools in the catalog handle audio processing. This catalog covers imaging analysis software."
|
| 405 |
+
}
|
| 406 |
+
```
|
| 407 |
+
|
| 408 |
+
Available `NoToolReason` values: `no_suitable_tool`, `no_modality_match`, `no_task_match`, `no_dimension_match`, `invalid_files`.
|
| 409 |
+
|
| 410 |
+
## Ranking Logic
|
| 411 |
+
|
| 412 |
+
### Scoring Factors
|
| 413 |
+
|
| 414 |
+
The agent considers:
|
| 415 |
+
|
| 416 |
+
#### High Priority
|
| 417 |
+
1. **Task Match**: Tool designed for this specific task
|
| 418 |
+
2. **Format Compatibility**: Supports user's file format
|
| 419 |
+
3. **Visual Analysis**: Image content matches tool's domain
|
| 420 |
+
|
| 421 |
+
#### Medium Priority
|
| 422 |
+
4. **Modality Alignment**: CT tool for CT image, MRI for MRI
|
| 423 |
+
5. **Dimension Match**: 3D tool for 3D volume
|
| 424 |
+
6. **Feature Coverage**: Specific capabilities mentioned
|
| 425 |
+
|
| 426 |
+
#### Low Priority
|
| 427 |
+
7. **License**: Open-source preference (if no preference stated)
|
| 428 |
+
8. **Demo Availability**: Has runnable demo
|
| 429 |
+
9. **Popularity**: Community adoption
|
| 430 |
+
|
| 431 |
+
### Explanation Generation
|
| 432 |
+
|
| 433 |
+
Each recommendation includes explanation:
|
| 434 |
+
|
| 435 |
+
**Good explanation template**:
|
| 436 |
+
```
|
| 437 |
+
{Tool} is {specifically designed / well-suited} for {task}
|
| 438 |
+
on {modality} images. It supports {format} input {with/without}
|
| 439 |
+
preprocessing and provides {key features}. {Caveats if any}.
|
| 440 |
+
```
|
| 441 |
+
|
| 442 |
+
**Example**:
|
| 443 |
+
```
|
| 444 |
+
TotalSegmentator is specifically designed for automated multi-organ
|
| 445 |
+
segmentation on CT scans. It supports DICOM input without preprocessing
|
| 446 |
+
and can segment 104 anatomical structures including lungs, air airways,
|
| 447 |
+
and vessels. It works best on whole-body CT but also performs well on
|
| 448 |
+
thoracic scans.
|
| 449 |
+
```
|
| 450 |
+
|
| 451 |
+
### Rank Assignment
|
| 452 |
+
|
| 453 |
+
- **Rank 1**: Best overall match (highest accuracy score)
|
| 454 |
+
- **Rank 2**: Strong alternative or different approach
|
| 455 |
+
- **Rank 3**: Fallback option or specialized capability
|
| 456 |
+
|
| 457 |
+
**Important**: Ranks are relative to **this specific query**, not absolute tool quality.
|
| 458 |
+
|
| 459 |
+
## Model Configuration
|
| 460 |
+
|
| 461 |
+
### Model Selection
|
| 462 |
+
|
| 463 |
+
Available via `config.yaml`:
|
| 464 |
+
|
| 465 |
+
```yaml
|
| 466 |
+
agent_model:
|
| 467 |
+
name: "gpt-4o-mini"
|
| 468 |
+
base_url: null
|
| 469 |
+
api_key_env: "OPENAI_API_KEY"
|
| 470 |
+
```
|
| 471 |
+
|
| 472 |
+
### Model Comparison
|
| 473 |
+
|
| 474 |
+
| Model | Vision | Speed | Cost | Best For |
|
| 475 |
+
|-------|--------|-------|------|----------|
|
| 476 |
+
| gpt-4o-mini | ✅ | ⚡⚡⚡ | $ | Most queries, fast iteration |
|
| 477 |
+
| gpt-4o | ✅✅ | ⚡⚡ | $$ | Complex visual analysis |
|
| 478 |
+
| gpt-5.1 | ✅✅✅ | ⚡ | $$$ | Maximum accuracy needed |
|
| 479 |
+
|
| 480 |
+
### Custom Endpoints
|
| 481 |
+
|
| 482 |
+
Support for OpenAI-compatible APIs:
|
| 483 |
+
|
| 484 |
+
```yaml
|
| 485 |
+
agent_model:
|
| 486 |
+
name: "llama-3.2-vision"
|
| 487 |
+
base_url: "https://inference.epfl.ch/v1"
|
| 488 |
+
api_key_env: "EPFL_API_KEY"
|
| 489 |
+
```
|
| 490 |
+
|
| 491 |
+
## Error Handling
|
| 492 |
+
|
| 493 |
+
### Agent Errors
|
| 494 |
+
|
| 495 |
+
**Tool quota exceeded** (handled gracefully in `run_agent`):
|
| 496 |
+
```python
|
| 497 |
+
except UsageLimitExceeded:
|
| 498 |
+
# Returns a ToolSelection with empty choices and an explanation
|
| 499 |
+
result = ToolSelection(
|
| 500 |
+
conversation=Conversation(status=ConversationStatus.COMPLETE, ...),
|
| 501 |
+
choices=[],
|
| 502 |
+
explanation="Tool call limit reached. Try a more specific query.",
|
| 503 |
+
)
|
| 504 |
+
```
|
| 505 |
+
|
| 506 |
+
**Invalid structured output**:
|
| 507 |
+
|
| 508 |
+
PydanticAI automatically retries the LLM call (up to `retries=2` per tool) if the model returns output that fails `ToolSelection` validation. The `ToolSelection.normalize()` model validator also auto-corrects minor inconsistencies.
|
| 509 |
+
|
| 510 |
+
**API Errors**:
|
| 511 |
+
```python
|
| 512 |
+
except Exception as e:
|
| 513 |
+
log.warning(f"Agent execution encountered an error: {e}")
|
| 514 |
+
raise # propagated to the UI layer
|
| 515 |
+
```
|
| 516 |
+
|
| 517 |
+
### Graceful Degradation
|
| 518 |
+
|
| 519 |
+
If the agent fails after all retries:
|
| 520 |
+
|
| 521 |
+
1. Return empty `choices` with an `explanation` describing what was searched
|
| 522 |
+
2. UI surfaces the explanation so users can refine their query
|
| 523 |
+
3. Suggest manual exploration of the catalog
|
| 524 |
+
|
| 525 |
+
<!-- ## Performance
|
| 526 |
+
|
| 527 |
+
### Latency
|
| 528 |
+
|
| 529 |
+
Typical VLM call: **2-5 seconds**
|
| 530 |
+
|
| 531 |
+
Breakdown:
|
| 532 |
+
|
| 533 |
+
- Prompt construction: <100ms
|
| 534 |
+
- API call: 2-4s (network + inference)
|
| 535 |
+
- Response parsing: <100ms
|
| 536 |
+
- Validation: <50ms
|
| 537 |
+
|
| 538 |
+
### Optimization
|
| 539 |
+
|
| 540 |
+
**Prompt optimization**:
|
| 541 |
+
|
| 542 |
+
- Concise candidate descriptions
|
| 543 |
+
- Limit to top-8 candidates
|
| 544 |
+
- Structured format for parsing
|
| 545 |
+
|
| 546 |
+
**Caching**:
|
| 547 |
+
|
| 548 |
+
- Model endpoint reused
|
| 549 |
+
- Agent instance persists across requests
|
| 550 |
+
|
| 551 |
+
**Batch processing** (for testing):
|
| 552 |
+
```python
|
| 553 |
+
# Process multiple queries
|
| 554 |
+
responses = await asyncio.gather(*[
|
| 555 |
+
agent.run(query1),
|
| 556 |
+
agent.run(query2),
|
| 557 |
+
agent.run(query3)
|
| 558 |
+
])
|
| 559 |
+
``` -->
|
| 560 |
+
|
| 561 |
+
## Testing
|
| 562 |
+
|
| 563 |
+
### Unit Tests
|
| 564 |
+
|
| 565 |
+
Test agent selection with PydanticAI's built-in test model (your catalog should contain the choice provided below, i.e. the `TotalSegmentator` tool):
|
| 566 |
+
|
| 567 |
+
```python
|
| 568 |
+
from pydantic_ai import Agent
|
| 569 |
+
from pydantic_ai.models.test import TestModel
|
| 570 |
+
from ai_agent.generator.schema import ToolSelection, Conversation, ConversationStatus, ToolChoice
|
| 571 |
+
from ai_agent.agent.utils import AgentState
|
| 572 |
+
|
| 573 |
+
def test_agent_selection():
|
| 574 |
+
test_model = TestModel()
|
| 575 |
+
test_agent = Agent(model=test_model, deps_type=AgentState)
|
| 576 |
+
|
| 577 |
+
mock_output = ToolSelection(
|
| 578 |
+
conversation=Conversation(status=ConversationStatus.COMPLETE),
|
| 579 |
+
choices=[
|
| 580 |
+
ToolChoice(name="TotalSegmentator", rank=1, accuracy=95.0, why="Best CT segmenter")
|
| 581 |
+
]
|
| 582 |
+
)
|
| 583 |
+
|
| 584 |
+
with test_agent.override(model=test_model):
|
| 585 |
+
result = test_agent.run_sync("segment lungs", deps=AgentState(), output_type=ToolSelection)
|
| 586 |
+
|
| 587 |
+
assert result.output.conversation.status == ConversationStatus.COMPLETE
|
| 588 |
+
assert len(result.output.choices) == 1
|
| 589 |
+
assert result.output.choices[0].rank == 1
|
| 590 |
+
```
|
| 591 |
+
|
| 592 |
+
### Integration Tests
|
| 593 |
+
|
| 594 |
+
Test with real VLM (expensive, slow):
|
| 595 |
+
|
| 596 |
+
```python
|
| 597 |
+
@pytest.mark.integration
|
| 598 |
+
def test_real_agent():
|
| 599 |
+
from ai_agent.agent.agent import run_agent
|
| 600 |
+
|
| 601 |
+
with open("tests/data/sample.tif", "rb") as f:
|
| 602 |
+
image_bytes = f.read()
|
| 603 |
+
|
| 604 |
+
result = run_agent(
|
| 605 |
+
task="I want to segment the lungs of this CT scan",
|
| 606 |
+
image_paths=["tests/data/sample.tif"],
|
| 607 |
+
image_bytes=image_bytes,
|
| 608 |
+
)
|
| 609 |
+
|
| 610 |
+
assert result.conversation.status == ConversationStatus.COMPLETE
|
| 611 |
+
assert len(result.choices) > 0
|
| 612 |
+
assert all(0 <= c.accuracy <= 100 for c in result.choices)
|
| 613 |
+
```
|
| 614 |
+
|
| 615 |
+
## Next Steps
|
| 616 |
+
|
| 617 |
+
- Learn about [Software Catalog](catalog.md)
|
| 618 |
+
- Return to [Architecture Overview](overview.md)
|
| 619 |
+
- Explore [Retrieval Pipeline](retrieval.md)
|
docs/architecture/catalog.md
CHANGED
|
@@ -1,455 +1,455 @@
|
|
| 1 |
-
# Software Catalog
|
| 2 |
-
|
| 3 |
-
The software catalog is the foundation of the AI Imaging Agent, containing curated information about imaging analysis tools.
|
| 4 |
-
|
| 5 |
-
## Overview
|
| 6 |
-
|
| 7 |
-
**Format**: JSON Lines (JSONL)
|
| 8 |
-
**Location**: `dataset/catalog.jsonl`
|
| 9 |
-
**Schema**: Based on schema.org SoftwareSourceCode
|
| 10 |
-
**Size**: ~150 tools currently
|
| 11 |
-
|
| 12 |
-
## Catalog Schema
|
| 13 |
-
|
| 14 |
-
### Core Fields
|
| 15 |
-
|
| 16 |
-
Based on [schema.org/SoftwareSourceCode](https://schema.org/SoftwareSourceCode):
|
| 17 |
-
|
| 18 |
-
```json
|
| 19 |
-
{
|
| 20 |
-
"@type": "SoftwareSourceCode",
|
| 21 |
-
"name": "TotalSegmentator",
|
| 22 |
-
"description": "Tool for automated segmentation of 104 anatomical structures",
|
| 23 |
-
"url": "https://github.com/wasserth/TotalSegmentator",
|
| 24 |
-
"codeRepository": "https://github.com/wasserth/TotalSegmentator",
|
| 25 |
-
"programmingLanguage": "Python",
|
| 26 |
-
"runtimePlatform": "PyTorch",
|
| 27 |
-
"license": "Apache-2.0",
|
| 28 |
-
"keywords": ["segmentation", "CT", "MRI", "medical-imaging"],
|
| 29 |
-
"applicationCategory": "Medical Imaging",
|
| 30 |
-
"operatingSystem": ["Linux", "Windows", "macOS"],
|
| 31 |
-
"softwareVersion": "2.0.0",
|
| 32 |
-
"datePublished": "2022-09-01",
|
| 33 |
-
"dateModified": "2024-01-15",
|
| 34 |
-
"author": {
|
| 35 |
-
"@type": "Person",
|
| 36 |
-
"name": "Jakob Wasserthal"
|
| 37 |
-
}
|
| 38 |
-
}
|
| 39 |
-
```
|
| 40 |
-
|
| 41 |
-
### Extended Fields
|
| 42 |
-
|
| 43 |
-
Custom fields in `supportingData`:
|
| 44 |
-
|
| 45 |
-
```json
|
| 46 |
-
{
|
| 47 |
-
"supportingData": {
|
| 48 |
-
"modalities": ["CT", "MRI"],
|
| 49 |
-
"dimensions": ["3D"],
|
| 50 |
-
"formats": ["DICOM", "NIfTI", "PNG"],
|
| 51 |
-
"tasks": ["segmentation", "organ-segmentation"],
|
| 52 |
-
"demo_url": "https://huggingface.co/spaces/username/totalsegmentator",
|
| 53 |
-
"paper_url": "https://doi.org/10.1000/example",
|
| 54 |
-
"citations": 150,
|
| 55 |
-
"github_stars": 1200
|
| 56 |
-
}
|
| 57 |
-
}
|
| 58 |
-
```
|
| 59 |
-
|
| 60 |
-
### Field Descriptions
|
| 61 |
-
|
| 62 |
-
#### name
|
| 63 |
-
Canonical tool name (matches repository or published name)
|
| 64 |
-
|
| 65 |
-
**Example**: `"TotalSegmentator"`, `"nnU-Net"`, `"MedSAM"`
|
| 66 |
-
|
| 67 |
-
#### description
|
| 68 |
-
Brief description of tool's purpose and capabilities
|
| 69 |
-
|
| 70 |
-
**Guidelines**:
|
| 71 |
-
|
| 72 |
-
- 1-2 sentences
|
| 73 |
-
- Mention key features
|
| 74 |
-
- Include domain/modality if specific
|
| 75 |
-
|
| 76 |
-
#### url
|
| 77 |
-
Primary landing page (usually GitHub repo)
|
| 78 |
-
|
| 79 |
-
#### codeRepository
|
| 80 |
-
Source code repository URL (GitHub, GitLab, etc.)
|
| 81 |
-
|
| 82 |
-
#### programmingLanguage
|
| 83 |
-
Primary language(s)
|
| 84 |
-
|
| 85 |
-
**Common values**: `"Python"`, `"C++"`, `"JavaScript"`, `"Jupyter Notebook"`
|
| 86 |
-
|
| 87 |
-
#### license
|
| 88 |
-
Software license identifier (SPDX format)
|
| 89 |
-
|
| 90 |
-
**Common values**:
|
| 91 |
-
|
| 92 |
-
- `"Apache-2.0"`: Permissive, commercial OK
|
| 93 |
-
- `"MIT"`: Very permissive
|
| 94 |
-
- `"GPL-3.0"`: Copyleft
|
| 95 |
-
- `"BSD-3-Clause"`: Permissive
|
| 96 |
-
- `"Proprietary"`: Restricted
|
| 97 |
-
|
| 98 |
-
#### keywords
|
| 99 |
-
Array of relevant tags/keywords
|
| 100 |
-
|
| 101 |
-
**Categories**:
|
| 102 |
-
|
| 103 |
-
- **Tasks**: segmentation, classification, registration, detection
|
| 104 |
-
- **Modalities**: CT, MRI, X-ray, ultrasound, microscopy
|
| 105 |
-
- **Techniques**: deep-learning, traditional-cv, machine-learning
|
| 106 |
-
- **Domains**: medical-imaging, scientific-imaging, neuroscience
|
| 107 |
-
|
| 108 |
-
#### supportingData.modalities
|
| 109 |
-
Medical imaging modalities supported
|
| 110 |
-
|
| 111 |
-
**Standard values**:
|
| 112 |
-
|
| 113 |
-
- `"CT"`: Computed Tomography
|
| 114 |
-
- `"MRI"`: Magnetic Resonance Imaging
|
| 115 |
-
- `"XR"`: X-ray radiography
|
| 116 |
-
- `"US"`: Ultrasound
|
| 117 |
-
- `"PET"`: Positron Emission Tomography
|
| 118 |
-
- `"SPECT"`: Single-Photon Emission CT
|
| 119 |
-
- `"OCT"`: Optical Coherence Tomography
|
| 120 |
-
- `"Microscopy"`: Various microscopy types
|
| 121 |
-
|
| 122 |
-
#### supportingData.dimensions
|
| 123 |
-
Spatial dimensions supported
|
| 124 |
-
|
| 125 |
-
**Values**: `["2D"]`, `["3D"]`, `["2D", "3D"]`, `["4D"]`
|
| 126 |
-
|
| 127 |
-
- **2D**: Single slice images
|
| 128 |
-
- **3D**: Volumetric data
|
| 129 |
-
- **4D**: Time-series volumes (3D + time)
|
| 130 |
-
|
| 131 |
-
#### supportingData.formats
|
| 132 |
-
File formats supported for input/output
|
| 133 |
-
|
| 134 |
-
**Common values**:
|
| 135 |
-
|
| 136 |
-
- Medical: `"DICOM"`, `"NIfTI"`, `"NRRD"`, `"Analyze"`
|
| 137 |
-
- Standard: `"PNG"`, `"JPEG"`, `"TIFF"`, `"BMP"`
|
| 138 |
-
- Scientific: `"HDF5"`, `"Zarr"`, `"OME-TIFF"`
|
| 139 |
-
- Other: `"NumPy"`, `"MAT"`
|
| 140 |
-
|
| 141 |
-
#### supportingData.tasks
|
| 142 |
-
Analysis tasks the tool performs
|
| 143 |
-
|
| 144 |
-
**Common values**:
|
| 145 |
-
|
| 146 |
-
- `"segmentation"`: Image segmentation
|
| 147 |
-
- `"classification"`: Image classification
|
| 148 |
-
- `"detection"`: Object detection
|
| 149 |
-
- `"registration"`: Image registration/alignment
|
| 150 |
-
- `"reconstruction"`: 3D reconstruction
|
| 151 |
-
- `"enhancement"`: Image enhancement
|
| 152 |
-
- `"analysis"`: General analysis
|
| 153 |
-
|
| 154 |
-
#### supportingData.demo_url
|
| 155 |
-
Link to runnable demo (HuggingFace Space, Colab, web app)
|
| 156 |
-
|
| 157 |
-
**Preferred**: HuggingFace Gradio Spaces (best integration)
|
| 158 |
-
|
| 159 |
-
**Example**: `"https://huggingface.co/spaces/username/toolname"`
|
| 160 |
-
|
| 161 |
-
## Catalog Structure
|
| 162 |
-
|
| 163 |
-
### File Format
|
| 164 |
-
|
| 165 |
-
JSON Lines (JSONL): Each line is a complete JSON object
|
| 166 |
-
|
| 167 |
-
```jsonl
|
| 168 |
-
{"@type": "SoftwareSourceCode", "name": "Tool1", ...}
|
| 169 |
-
{"@type": "SoftwareSourceCode", "name": "Tool2", ...}
|
| 170 |
-
{"@type": "SoftwareSourceCode", "name": "Tool3", ...}
|
| 171 |
-
```
|
| 172 |
-
|
| 173 |
-
**Benefits**:
|
| 174 |
-
|
| 175 |
-
- Easy to append new tools
|
| 176 |
-
- Stream processing for large catalogs
|
| 177 |
-
- Each line independently parseable
|
| 178 |
-
- Git-friendly (line-based diffs)
|
| 179 |
-
|
| 180 |
-
### Catalog Loading
|
| 181 |
-
|
| 182 |
-
```python
|
| 183 |
-
import json
|
| 184 |
-
|
| 185 |
-
def load_catalog(path: str) -> list[dict]:
|
| 186 |
-
tools = []
|
| 187 |
-
with open(path) as f:
|
| 188 |
-
for line in f:
|
| 189 |
-
if line.strip():
|
| 190 |
-
tools.append(json.loads(line))
|
| 191 |
-
return tools
|
| 192 |
-
```
|
| 193 |
-
|
| 194 |
-
### Validation
|
| 195 |
-
|
| 196 |
-
Tools are validated on load:
|
| 197 |
-
|
| 198 |
-
```python
|
| 199 |
-
from pydantic import BaseModel, HttpUrl
|
| 200 |
-
|
| 201 |
-
class SoftwareSourceCode(BaseModel):
|
| 202 |
-
name: str
|
| 203 |
-
description: str
|
| 204 |
-
url: HttpUrl
|
| 205 |
-
license: str
|
| 206 |
-
keywords: list[str]
|
| 207 |
-
supportingData: dict
|
| 208 |
-
|
| 209 |
-
class Config:
|
| 210 |
-
extra = "allow" # Allow additional schema.org fields
|
| 211 |
-
```
|
| 212 |
-
|
| 213 |
-
## Catalog Management
|
| 214 |
-
|
| 215 |
-
### Adding New Tools
|
| 216 |
-
|
| 217 |
-
1. **Create entry** following schema:
|
| 218 |
-
|
| 219 |
-
```json
|
| 220 |
-
{
|
| 221 |
-
"@type": "SoftwareSourceCode",
|
| 222 |
-
"name": "NewTool",
|
| 223 |
-
"description": "Brief description of the tool",
|
| 224 |
-
"url": "https://github.com/user/newtool",
|
| 225 |
-
"codeRepository": "https://github.com/user/newtool",
|
| 226 |
-
"programmingLanguage": "Python",
|
| 227 |
-
"license": "MIT",
|
| 228 |
-
"keywords": ["segmentation", "CT"],
|
| 229 |
-
"supportingData": {
|
| 230 |
-
"modalities": ["CT"],
|
| 231 |
-
"dimensions": ["3D"],
|
| 232 |
-
"formats": ["DICOM", "NIfTI"],
|
| 233 |
-
"tasks": ["segmentation"],
|
| 234 |
-
"demo_url": "https://huggingface.co/spaces/user/newtool"
|
| 235 |
-
}
|
| 236 |
-
}
|
| 237 |
-
```
|
| 238 |
-
|
| 239 |
-
2. **Append to catalog.jsonl** (as single line, no pretty printing)
|
| 240 |
-
|
| 241 |
-
3. **Update checksum**:
|
| 242 |
-
|
| 243 |
-
```bash
|
| 244 |
-
shasum dataset/catalog.jsonl > dataset/catalog.jsonl.sha1
|
| 245 |
-
```
|
| 246 |
-
|
| 247 |
-
4. **Sync catalog**:
|
| 248 |
-
|
| 249 |
-
```bash
|
| 250 |
-
ai_agent sync
|
| 251 |
-
```
|
| 252 |
-
|
| 253 |
-
This rebuilds the embeddings and FAISS index.
|
| 254 |
-
|
| 255 |
-
### Updating Existing Tools
|
| 256 |
-
|
| 257 |
-
1. **Find tool** in `catalog.jsonl`
|
| 258 |
-
2. **Edit JSON** (update fields)
|
| 259 |
-
3. **Validate JSON** syntax
|
| 260 |
-
4. **Update checksum** and **sync**
|
| 261 |
-
|
| 262 |
-
### Removing Tools
|
| 263 |
-
|
| 264 |
-
1. **Delete line** from `catalog.jsonl`
|
| 265 |
-
2. **Update checksum** and **sync**
|
| 266 |
-
|
| 267 |
-
<!-- ## Catalog Sources
|
| 268 |
-
|
| 269 |
-
### Current Catalog
|
| 270 |
-
|
| 271 |
-
Built from:
|
| 272 |
-
- **Medical Imaging Tools**: TotalSegmentator, nnU-Net, MedSAM, etc.
|
| 273 |
-
- **Computer Vision Libraries**: OpenCV, scikit-image
|
| 274 |
-
- **Deep Learning Frameworks**: PyTorch, TensorFlow tools
|
| 275 |
-
- **Specialized Tools**: ITK, SimpleITK, 3D Slicer modules
|
| 276 |
-
- **HuggingFace Spaces**: Gradio apps for imaging
|
| 277 |
-
|
| 278 |
-
### Curation Process
|
| 279 |
-
|
| 280 |
-
Tools are included based on:
|
| 281 |
-
1. **Relevance**: Imaging analysis tasks
|
| 282 |
-
2. **Quality**: Actively maintained, documented
|
| 283 |
-
3. **Accessibility**: Open-source or free demos
|
| 284 |
-
4. **Runnable**: Has demo or clear usage examples
|
| 285 |
-
|
| 286 |
-
### Catalog Growth
|
| 287 |
-
|
| 288 |
-
**Current**: ~150 tools
|
| 289 |
-
**Target**: 500+ tools covering:
|
| 290 |
-
- Medical imaging (CT, MRI, X-ray, ultrasound, pathology)
|
| 291 |
-
- Scientific imaging (microscopy, astronomy, remote sensing)
|
| 292 |
-
- Computer vision (general object detection, segmentation, etc.) -->
|
| 293 |
-
|
| 294 |
-
## Synchronization
|
| 295 |
-
|
| 296 |
-
### Auto-Sync
|
| 297 |
-
|
| 298 |
-
Configured via `.env`:
|
| 299 |
-
|
| 300 |
-
```dotenv
|
| 301 |
-
SYNC_EVERY_HOURS=24
|
| 302 |
-
```
|
| 303 |
-
|
| 304 |
-
**Process**:
|
| 305 |
-
1. Background thread checks catalog every 24h
|
| 306 |
-
2. Compares SHA1 checksum
|
| 307 |
-
3. If changed:
|
| 308 |
-
- Reload catalog
|
| 309 |
-
- Re-embed all tools
|
| 310 |
-
- Rebuild FAISS index
|
| 311 |
-
|
| 312 |
-
### Manual Sync
|
| 313 |
-
|
| 314 |
-
```bash
|
| 315 |
-
ai_agent sync
|
| 316 |
-
```
|
| 317 |
-
|
| 318 |
-
**Output**:
|
| 319 |
-
```
|
| 320 |
-
[sync] 150 → dataset/catalog.jsonl
|
| 321 |
-
[sync] Rebuilding embeddings...
|
| 322 |
-
[sync] Embedding 150 tools... (5.2s)
|
| 323 |
-
[sync] Building FAISS index...
|
| 324 |
-
[sync] Saved to artifacts/rag_index/
|
| 325 |
-
[sync] Sync complete.
|
| 326 |
-
```
|
| 327 |
-
|
| 328 |
-
## Embeddings and Index
|
| 329 |
-
|
| 330 |
-
### Embedding Process
|
| 331 |
-
|
| 332 |
-
For each tool, create text representation:
|
| 333 |
-
|
| 334 |
-
```python
|
| 335 |
-
tool_text = f"{tool['name']} {tool['description']} {' '.join(tool['keywords'])}"
|
| 336 |
-
|
| 337 |
-
# Optional: Include supportingData
|
| 338 |
-
if 'supportingData' in tool:
|
| 339 |
-
sd = tool['supportingData']
|
| 340 |
-
tool_text += f" {' '.join(sd.get('modalities', []))}"
|
| 341 |
-
tool_text += f" {' '.join(sd.get('tasks', []))}"
|
| 342 |
-
|
| 343 |
-
# Embed
|
| 344 |
-
embedding = embedder.encode(tool_text, normalize_embeddings=True)
|
| 345 |
-
```
|
| 346 |
-
|
| 347 |
-
### Index Storage
|
| 348 |
-
|
| 349 |
-
```
|
| 350 |
-
artifacts/rag_index/
|
| 351 |
-
├── index.faiss # FAISS IndexFlatIP
|
| 352 |
-
└── meta.json # Tool IDs, config, timestamps
|
| 353 |
-
```
|
| 354 |
-
|
| 355 |
-
**meta.json** structure:
|
| 356 |
-
|
| 357 |
-
```json
|
| 358 |
-
{
|
| 359 |
-
"tool_ids": ["tool1", "tool2", ...],
|
| 360 |
-
"version": "1.0",
|
| 361 |
-
"embedding_model": "BAAI/bge-m3",
|
| 362 |
-
"embedding_dim": 1024,
|
| 363 |
-
"num_tools": 150,
|
| 364 |
-
"created_at": "2024-03-01T12:00:00Z",
|
| 365 |
-
"catalog_sha1": "abc123..."
|
| 366 |
-
}
|
| 367 |
-
```
|
| 368 |
-
|
| 369 |
-
## Quality Assurance
|
| 370 |
-
|
| 371 |
-
### Validation Rules
|
| 372 |
-
|
| 373 |
-
1. **Required fields**: name, description, url, license
|
| 374 |
-
2. **Valid URLs**: Well-formed HTTP/HTTPS URLs
|
| 375 |
-
3. **Standard licenses**: SPDX identifiers preferred
|
| 376 |
-
4. **Consistent keywords**: Use standard terminology
|
| 377 |
-
5. **Demo URLs**: Verify demos are live and accessible
|
| 378 |
-
|
| 379 |
-
### Automated Checks
|
| 380 |
-
|
| 381 |
-
```python
|
| 382 |
-
def validate_catalog(catalog_path):
|
| 383 |
-
errors = []
|
| 384 |
-
|
| 385 |
-
with open(catalog_path) as f:
|
| 386 |
-
for i, line in enumerate(f, 1):
|
| 387 |
-
try:
|
| 388 |
-
tool = json.loads(line)
|
| 389 |
-
|
| 390 |
-
# Required fields
|
| 391 |
-
for field in ['name', 'description', 'url']:
|
| 392 |
-
if field not in tool:
|
| 393 |
-
errors.append(f"Line {i}: Missing {field}")
|
| 394 |
-
|
| 395 |
-
# URL validation
|
| 396 |
-
if not tool['url'].startswith('http'):
|
| 397 |
-
errors.append(f"Line {i}: Invalid URL")
|
| 398 |
-
|
| 399 |
-
# supportingData structure
|
| 400 |
-
if 'supportingData' in tool:
|
| 401 |
-
sd = tool['supportingData']
|
| 402 |
-
if 'demo_url' in sd and sd['demo_url']:
|
| 403 |
-
if not sd['demo_url'].startswith('http'):
|
| 404 |
-
errors.append(f"Line {i}: Invalid demo_url")
|
| 405 |
-
|
| 406 |
-
except json.JSONDecodeError as e:
|
| 407 |
-
errors.append(f"Line {i}: JSON syntax error - {e}")
|
| 408 |
-
|
| 409 |
-
return errors
|
| 410 |
-
```
|
| 411 |
-
|
| 412 |
-
## Best Practices
|
| 413 |
-
|
| 414 |
-
### Tool Descriptions
|
| 415 |
-
|
| 416 |
-
✅ **Good**:
|
| 417 |
-
```
|
| 418 |
-
"Automated multi-organ segmentation for CT and MRI supporting 104 anatomical structures"
|
| 419 |
-
```
|
| 420 |
-
|
| 421 |
-
❌ **Bad**:
|
| 422 |
-
```
|
| 423 |
-
"A tool" # Too vague
|
| 424 |
-
"The best segmentation tool ever created with amazing accuracy..." # Too marketing-y
|
| 425 |
-
```
|
| 426 |
-
|
| 427 |
-
### Keywords
|
| 428 |
-
|
| 429 |
-
✅ **Good**:
|
| 430 |
-
```
|
| 431 |
-
["segmentation", "CT", "MRI", "medical-imaging", "deep-learning", "organ-segmentation"]
|
| 432 |
-
```
|
| 433 |
-
|
| 434 |
-
❌ **Bad**:
|
| 435 |
-
```
|
| 436 |
-
["cool", "awesome", "the best"] # Not searchable terms
|
| 437 |
-
```
|
| 438 |
-
|
| 439 |
-
### Demo URLs
|
| 440 |
-
|
| 441 |
-
✅ **Preferred**:
|
| 442 |
-
- HuggingFace Gradio Spaces
|
| 443 |
-
- Google Colab notebooks
|
| 444 |
-
- Live web demos
|
| 445 |
-
|
| 446 |
-
❌ **Avoid**:
|
| 447 |
-
- Dead links
|
| 448 |
-
- Paywalled demos
|
| 449 |
-
- Demos requiring registration
|
| 450 |
-
|
| 451 |
-
## Next Steps
|
| 452 |
-
|
| 453 |
-
- Return to [Architecture Overview](overview.md)
|
| 454 |
-
- Learn about [Retrieval Pipeline](retrieval.md)
|
| 455 |
-
- Explore [Agent & VLM Selection](agent.md)
|
|
|
|
| 1 |
+
# Software Catalog
|
| 2 |
+
|
| 3 |
+
The software catalog is the foundation of the AI Imaging Agent, containing curated information about imaging analysis tools.
|
| 4 |
+
|
| 5 |
+
## Overview
|
| 6 |
+
|
| 7 |
+
**Format**: JSON Lines (JSONL)
|
| 8 |
+
**Location**: `dataset/catalog.jsonl`
|
| 9 |
+
**Schema**: Based on schema.org SoftwareSourceCode
|
| 10 |
+
**Size**: ~150 tools currently
|
| 11 |
+
|
| 12 |
+
## Catalog Schema
|
| 13 |
+
|
| 14 |
+
### Core Fields
|
| 15 |
+
|
| 16 |
+
Based on [schema.org/SoftwareSourceCode](https://schema.org/SoftwareSourceCode):
|
| 17 |
+
|
| 18 |
+
```json
|
| 19 |
+
{
|
| 20 |
+
"@type": "SoftwareSourceCode",
|
| 21 |
+
"name": "TotalSegmentator",
|
| 22 |
+
"description": "Tool for automated segmentation of 104 anatomical structures",
|
| 23 |
+
"url": "https://github.com/wasserth/TotalSegmentator",
|
| 24 |
+
"codeRepository": "https://github.com/wasserth/TotalSegmentator",
|
| 25 |
+
"programmingLanguage": "Python",
|
| 26 |
+
"runtimePlatform": "PyTorch",
|
| 27 |
+
"license": "Apache-2.0",
|
| 28 |
+
"keywords": ["segmentation", "CT", "MRI", "medical-imaging"],
|
| 29 |
+
"applicationCategory": "Medical Imaging",
|
| 30 |
+
"operatingSystem": ["Linux", "Windows", "macOS"],
|
| 31 |
+
"softwareVersion": "2.0.0",
|
| 32 |
+
"datePublished": "2022-09-01",
|
| 33 |
+
"dateModified": "2024-01-15",
|
| 34 |
+
"author": {
|
| 35 |
+
"@type": "Person",
|
| 36 |
+
"name": "Jakob Wasserthal"
|
| 37 |
+
}
|
| 38 |
+
}
|
| 39 |
+
```
|
| 40 |
+
|
| 41 |
+
### Extended Fields
|
| 42 |
+
|
| 43 |
+
Custom fields in `supportingData`:
|
| 44 |
+
|
| 45 |
+
```json
|
| 46 |
+
{
|
| 47 |
+
"supportingData": {
|
| 48 |
+
"modalities": ["CT", "MRI"],
|
| 49 |
+
"dimensions": ["3D"],
|
| 50 |
+
"formats": ["DICOM", "NIfTI", "PNG"],
|
| 51 |
+
"tasks": ["segmentation", "organ-segmentation"],
|
| 52 |
+
"demo_url": "https://huggingface.co/spaces/username/totalsegmentator",
|
| 53 |
+
"paper_url": "https://doi.org/10.1000/example",
|
| 54 |
+
"citations": 150,
|
| 55 |
+
"github_stars": 1200
|
| 56 |
+
}
|
| 57 |
+
}
|
| 58 |
+
```
|
| 59 |
+
|
| 60 |
+
### Field Descriptions
|
| 61 |
+
|
| 62 |
+
#### name
|
| 63 |
+
Canonical tool name (matches repository or published name)
|
| 64 |
+
|
| 65 |
+
**Example**: `"TotalSegmentator"`, `"nnU-Net"`, `"MedSAM"`
|
| 66 |
+
|
| 67 |
+
#### description
|
| 68 |
+
Brief description of tool's purpose and capabilities
|
| 69 |
+
|
| 70 |
+
**Guidelines**:
|
| 71 |
+
|
| 72 |
+
- 1-2 sentences
|
| 73 |
+
- Mention key features
|
| 74 |
+
- Include domain/modality if specific
|
| 75 |
+
|
| 76 |
+
#### url
|
| 77 |
+
Primary landing page (usually GitHub repo)
|
| 78 |
+
|
| 79 |
+
#### codeRepository
|
| 80 |
+
Source code repository URL (GitHub, GitLab, etc.)
|
| 81 |
+
|
| 82 |
+
#### programmingLanguage
|
| 83 |
+
Primary language(s)
|
| 84 |
+
|
| 85 |
+
**Common values**: `"Python"`, `"C++"`, `"JavaScript"`, `"Jupyter Notebook"`
|
| 86 |
+
|
| 87 |
+
#### license
|
| 88 |
+
Software license identifier (SPDX format)
|
| 89 |
+
|
| 90 |
+
**Common values**:
|
| 91 |
+
|
| 92 |
+
- `"Apache-2.0"`: Permissive, commercial OK
|
| 93 |
+
- `"MIT"`: Very permissive
|
| 94 |
+
- `"GPL-3.0"`: Copyleft
|
| 95 |
+
- `"BSD-3-Clause"`: Permissive
|
| 96 |
+
- `"Proprietary"`: Restricted
|
| 97 |
+
|
| 98 |
+
#### keywords
|
| 99 |
+
Array of relevant tags/keywords
|
| 100 |
+
|
| 101 |
+
**Categories**:
|
| 102 |
+
|
| 103 |
+
- **Tasks**: segmentation, classification, registration, detection
|
| 104 |
+
- **Modalities**: CT, MRI, X-ray, ultrasound, microscopy
|
| 105 |
+
- **Techniques**: deep-learning, traditional-cv, machine-learning
|
| 106 |
+
- **Domains**: medical-imaging, scientific-imaging, neuroscience
|
| 107 |
+
|
| 108 |
+
#### supportingData.modalities
|
| 109 |
+
Medical imaging modalities supported
|
| 110 |
+
|
| 111 |
+
**Standard values**:
|
| 112 |
+
|
| 113 |
+
- `"CT"`: Computed Tomography
|
| 114 |
+
- `"MRI"`: Magnetic Resonance Imaging
|
| 115 |
+
- `"XR"`: X-ray radiography
|
| 116 |
+
- `"US"`: Ultrasound
|
| 117 |
+
- `"PET"`: Positron Emission Tomography
|
| 118 |
+
- `"SPECT"`: Single-Photon Emission CT
|
| 119 |
+
- `"OCT"`: Optical Coherence Tomography
|
| 120 |
+
- `"Microscopy"`: Various microscopy types
|
| 121 |
+
|
| 122 |
+
#### supportingData.dimensions
|
| 123 |
+
Spatial dimensions supported
|
| 124 |
+
|
| 125 |
+
**Values**: `["2D"]`, `["3D"]`, `["2D", "3D"]`, `["4D"]`
|
| 126 |
+
|
| 127 |
+
- **2D**: Single slice images
|
| 128 |
+
- **3D**: Volumetric data
|
| 129 |
+
- **4D**: Time-series volumes (3D + time)
|
| 130 |
+
|
| 131 |
+
#### supportingData.formats
|
| 132 |
+
File formats supported for input/output
|
| 133 |
+
|
| 134 |
+
**Common values**:
|
| 135 |
+
|
| 136 |
+
- Medical: `"DICOM"`, `"NIfTI"`, `"NRRD"`, `"Analyze"`
|
| 137 |
+
- Standard: `"PNG"`, `"JPEG"`, `"TIFF"`, `"BMP"`
|
| 138 |
+
- Scientific: `"HDF5"`, `"Zarr"`, `"OME-TIFF"`
|
| 139 |
+
- Other: `"NumPy"`, `"MAT"`
|
| 140 |
+
|
| 141 |
+
#### supportingData.tasks
|
| 142 |
+
Analysis tasks the tool performs
|
| 143 |
+
|
| 144 |
+
**Common values**:
|
| 145 |
+
|
| 146 |
+
- `"segmentation"`: Image segmentation
|
| 147 |
+
- `"classification"`: Image classification
|
| 148 |
+
- `"detection"`: Object detection
|
| 149 |
+
- `"registration"`: Image registration/alignment
|
| 150 |
+
- `"reconstruction"`: 3D reconstruction
|
| 151 |
+
- `"enhancement"`: Image enhancement
|
| 152 |
+
- `"analysis"`: General analysis
|
| 153 |
+
|
| 154 |
+
#### supportingData.demo_url
|
| 155 |
+
Link to runnable demo (HuggingFace Space, Colab, web app)
|
| 156 |
+
|
| 157 |
+
**Preferred**: HuggingFace Gradio Spaces (best integration)
|
| 158 |
+
|
| 159 |
+
**Example**: `"https://huggingface.co/spaces/username/toolname"`
|
| 160 |
+
|
| 161 |
+
## Catalog Structure
|
| 162 |
+
|
| 163 |
+
### File Format
|
| 164 |
+
|
| 165 |
+
JSON Lines (JSONL): Each line is a complete JSON object
|
| 166 |
+
|
| 167 |
+
```jsonl
|
| 168 |
+
{"@type": "SoftwareSourceCode", "name": "Tool1", ...}
|
| 169 |
+
{"@type": "SoftwareSourceCode", "name": "Tool2", ...}
|
| 170 |
+
{"@type": "SoftwareSourceCode", "name": "Tool3", ...}
|
| 171 |
+
```
|
| 172 |
+
|
| 173 |
+
**Benefits**:
|
| 174 |
+
|
| 175 |
+
- Easy to append new tools
|
| 176 |
+
- Stream processing for large catalogs
|
| 177 |
+
- Each line independently parseable
|
| 178 |
+
- Git-friendly (line-based diffs)
|
| 179 |
+
|
| 180 |
+
### Catalog Loading
|
| 181 |
+
|
| 182 |
+
```python
|
| 183 |
+
import json
|
| 184 |
+
|
| 185 |
+
def load_catalog(path: str) -> list[dict]:
|
| 186 |
+
tools = []
|
| 187 |
+
with open(path) as f:
|
| 188 |
+
for line in f:
|
| 189 |
+
if line.strip():
|
| 190 |
+
tools.append(json.loads(line))
|
| 191 |
+
return tools
|
| 192 |
+
```
|
| 193 |
+
|
| 194 |
+
### Validation
|
| 195 |
+
|
| 196 |
+
Tools are validated on load:
|
| 197 |
+
|
| 198 |
+
```python
|
| 199 |
+
from pydantic import BaseModel, HttpUrl
|
| 200 |
+
|
| 201 |
+
class SoftwareSourceCode(BaseModel):
|
| 202 |
+
name: str
|
| 203 |
+
description: str
|
| 204 |
+
url: HttpUrl
|
| 205 |
+
license: str
|
| 206 |
+
keywords: list[str]
|
| 207 |
+
supportingData: dict
|
| 208 |
+
|
| 209 |
+
class Config:
|
| 210 |
+
extra = "allow" # Allow additional schema.org fields
|
| 211 |
+
```
|
| 212 |
+
|
| 213 |
+
## Catalog Management
|
| 214 |
+
|
| 215 |
+
### Adding New Tools
|
| 216 |
+
|
| 217 |
+
1. **Create entry** following schema:
|
| 218 |
+
|
| 219 |
+
```json
|
| 220 |
+
{
|
| 221 |
+
"@type": "SoftwareSourceCode",
|
| 222 |
+
"name": "NewTool",
|
| 223 |
+
"description": "Brief description of the tool",
|
| 224 |
+
"url": "https://github.com/user/newtool",
|
| 225 |
+
"codeRepository": "https://github.com/user/newtool",
|
| 226 |
+
"programmingLanguage": "Python",
|
| 227 |
+
"license": "MIT",
|
| 228 |
+
"keywords": ["segmentation", "CT"],
|
| 229 |
+
"supportingData": {
|
| 230 |
+
"modalities": ["CT"],
|
| 231 |
+
"dimensions": ["3D"],
|
| 232 |
+
"formats": ["DICOM", "NIfTI"],
|
| 233 |
+
"tasks": ["segmentation"],
|
| 234 |
+
"demo_url": "https://huggingface.co/spaces/user/newtool"
|
| 235 |
+
}
|
| 236 |
+
}
|
| 237 |
+
```
|
| 238 |
+
|
| 239 |
+
2. **Append to catalog.jsonl** (as single line, no pretty printing)
|
| 240 |
+
|
| 241 |
+
3. **Update checksum**:
|
| 242 |
+
|
| 243 |
+
```bash
|
| 244 |
+
shasum dataset/catalog.jsonl > dataset/catalog.jsonl.sha1
|
| 245 |
+
```
|
| 246 |
+
|
| 247 |
+
4. **Sync catalog**:
|
| 248 |
+
|
| 249 |
+
```bash
|
| 250 |
+
ai_agent sync
|
| 251 |
+
```
|
| 252 |
+
|
| 253 |
+
This rebuilds the embeddings and FAISS index.
|
| 254 |
+
|
| 255 |
+
### Updating Existing Tools
|
| 256 |
+
|
| 257 |
+
1. **Find tool** in `catalog.jsonl`
|
| 258 |
+
2. **Edit JSON** (update fields)
|
| 259 |
+
3. **Validate JSON** syntax
|
| 260 |
+
4. **Update checksum** and **sync**
|
| 261 |
+
|
| 262 |
+
### Removing Tools
|
| 263 |
+
|
| 264 |
+
1. **Delete line** from `catalog.jsonl`
|
| 265 |
+
2. **Update checksum** and **sync**
|
| 266 |
+
|
| 267 |
+
<!-- ## Catalog Sources
|
| 268 |
+
|
| 269 |
+
### Current Catalog
|
| 270 |
+
|
| 271 |
+
Built from:
|
| 272 |
+
- **Medical Imaging Tools**: TotalSegmentator, nnU-Net, MedSAM, etc.
|
| 273 |
+
- **Computer Vision Libraries**: OpenCV, scikit-image
|
| 274 |
+
- **Deep Learning Frameworks**: PyTorch, TensorFlow tools
|
| 275 |
+
- **Specialized Tools**: ITK, SimpleITK, 3D Slicer modules
|
| 276 |
+
- **HuggingFace Spaces**: Gradio apps for imaging
|
| 277 |
+
|
| 278 |
+
### Curation Process
|
| 279 |
+
|
| 280 |
+
Tools are included based on:
|
| 281 |
+
1. **Relevance**: Imaging analysis tasks
|
| 282 |
+
2. **Quality**: Actively maintained, documented
|
| 283 |
+
3. **Accessibility**: Open-source or free demos
|
| 284 |
+
4. **Runnable**: Has demo or clear usage examples
|
| 285 |
+
|
| 286 |
+
### Catalog Growth
|
| 287 |
+
|
| 288 |
+
**Current**: ~150 tools
|
| 289 |
+
**Target**: 500+ tools covering:
|
| 290 |
+
- Medical imaging (CT, MRI, X-ray, ultrasound, pathology)
|
| 291 |
+
- Scientific imaging (microscopy, astronomy, remote sensing)
|
| 292 |
+
- Computer vision (general object detection, segmentation, etc.) -->
|
| 293 |
+
|
| 294 |
+
## Synchronization
|
| 295 |
+
|
| 296 |
+
### Auto-Sync
|
| 297 |
+
|
| 298 |
+
Configured via `.env`:
|
| 299 |
+
|
| 300 |
+
```dotenv
|
| 301 |
+
SYNC_EVERY_HOURS=24
|
| 302 |
+
```
|
| 303 |
+
|
| 304 |
+
**Process**:
|
| 305 |
+
1. Background thread checks catalog every 24h
|
| 306 |
+
2. Compares SHA1 checksum
|
| 307 |
+
3. If changed:
|
| 308 |
+
- Reload catalog
|
| 309 |
+
- Re-embed all tools
|
| 310 |
+
- Rebuild FAISS index
|
| 311 |
+
|
| 312 |
+
### Manual Sync
|
| 313 |
+
|
| 314 |
+
```bash
|
| 315 |
+
ai_agent sync
|
| 316 |
+
```
|
| 317 |
+
|
| 318 |
+
**Output**:
|
| 319 |
+
```
|
| 320 |
+
[sync] 150 → dataset/catalog.jsonl
|
| 321 |
+
[sync] Rebuilding embeddings...
|
| 322 |
+
[sync] Embedding 150 tools... (5.2s)
|
| 323 |
+
[sync] Building FAISS index...
|
| 324 |
+
[sync] Saved to artifacts/rag_index/
|
| 325 |
+
[sync] Sync complete.
|
| 326 |
+
```
|
| 327 |
+
|
| 328 |
+
## Embeddings and Index
|
| 329 |
+
|
| 330 |
+
### Embedding Process
|
| 331 |
+
|
| 332 |
+
For each tool, create text representation:
|
| 333 |
+
|
| 334 |
+
```python
|
| 335 |
+
tool_text = f"{tool['name']} {tool['description']} {' '.join(tool['keywords'])}"
|
| 336 |
+
|
| 337 |
+
# Optional: Include supportingData
|
| 338 |
+
if 'supportingData' in tool:
|
| 339 |
+
sd = tool['supportingData']
|
| 340 |
+
tool_text += f" {' '.join(sd.get('modalities', []))}"
|
| 341 |
+
tool_text += f" {' '.join(sd.get('tasks', []))}"
|
| 342 |
+
|
| 343 |
+
# Embed
|
| 344 |
+
embedding = embedder.encode(tool_text, normalize_embeddings=True)
|
| 345 |
+
```
|
| 346 |
+
|
| 347 |
+
### Index Storage
|
| 348 |
+
|
| 349 |
+
```
|
| 350 |
+
artifacts/rag_index/
|
| 351 |
+
├── index.faiss # FAISS IndexFlatIP
|
| 352 |
+
└── meta.json # Tool IDs, config, timestamps
|
| 353 |
+
```
|
| 354 |
+
|
| 355 |
+
**meta.json** structure:
|
| 356 |
+
|
| 357 |
+
```json
|
| 358 |
+
{
|
| 359 |
+
"tool_ids": ["tool1", "tool2", ...],
|
| 360 |
+
"version": "1.0",
|
| 361 |
+
"embedding_model": "BAAI/bge-m3",
|
| 362 |
+
"embedding_dim": 1024,
|
| 363 |
+
"num_tools": 150,
|
| 364 |
+
"created_at": "2024-03-01T12:00:00Z",
|
| 365 |
+
"catalog_sha1": "abc123..."
|
| 366 |
+
}
|
| 367 |
+
```
|
| 368 |
+
|
| 369 |
+
## Quality Assurance
|
| 370 |
+
|
| 371 |
+
### Validation Rules
|
| 372 |
+
|
| 373 |
+
1. **Required fields**: name, description, url, license
|
| 374 |
+
2. **Valid URLs**: Well-formed HTTP/HTTPS URLs
|
| 375 |
+
3. **Standard licenses**: SPDX identifiers preferred
|
| 376 |
+
4. **Consistent keywords**: Use standard terminology
|
| 377 |
+
5. **Demo URLs**: Verify demos are live and accessible
|
| 378 |
+
|
| 379 |
+
### Automated Checks
|
| 380 |
+
|
| 381 |
+
```python
|
| 382 |
+
def validate_catalog(catalog_path):
|
| 383 |
+
errors = []
|
| 384 |
+
|
| 385 |
+
with open(catalog_path) as f:
|
| 386 |
+
for i, line in enumerate(f, 1):
|
| 387 |
+
try:
|
| 388 |
+
tool = json.loads(line)
|
| 389 |
+
|
| 390 |
+
# Required fields
|
| 391 |
+
for field in ['name', 'description', 'url']:
|
| 392 |
+
if field not in tool:
|
| 393 |
+
errors.append(f"Line {i}: Missing {field}")
|
| 394 |
+
|
| 395 |
+
# URL validation
|
| 396 |
+
if not tool['url'].startswith('http'):
|
| 397 |
+
errors.append(f"Line {i}: Invalid URL")
|
| 398 |
+
|
| 399 |
+
# supportingData structure
|
| 400 |
+
if 'supportingData' in tool:
|
| 401 |
+
sd = tool['supportingData']
|
| 402 |
+
if 'demo_url' in sd and sd['demo_url']:
|
| 403 |
+
if not sd['demo_url'].startswith('http'):
|
| 404 |
+
errors.append(f"Line {i}: Invalid demo_url")
|
| 405 |
+
|
| 406 |
+
except json.JSONDecodeError as e:
|
| 407 |
+
errors.append(f"Line {i}: JSON syntax error - {e}")
|
| 408 |
+
|
| 409 |
+
return errors
|
| 410 |
+
```
|
| 411 |
+
|
| 412 |
+
## Best Practices
|
| 413 |
+
|
| 414 |
+
### Tool Descriptions
|
| 415 |
+
|
| 416 |
+
✅ **Good**:
|
| 417 |
+
```
|
| 418 |
+
"Automated multi-organ segmentation for CT and MRI supporting 104 anatomical structures"
|
| 419 |
+
```
|
| 420 |
+
|
| 421 |
+
❌ **Bad**:
|
| 422 |
+
```
|
| 423 |
+
"A tool" # Too vague
|
| 424 |
+
"The best segmentation tool ever created with amazing accuracy..." # Too marketing-y
|
| 425 |
+
```
|
| 426 |
+
|
| 427 |
+
### Keywords
|
| 428 |
+
|
| 429 |
+
✅ **Good**:
|
| 430 |
+
```
|
| 431 |
+
["segmentation", "CT", "MRI", "medical-imaging", "deep-learning", "organ-segmentation"]
|
| 432 |
+
```
|
| 433 |
+
|
| 434 |
+
❌ **Bad**:
|
| 435 |
+
```
|
| 436 |
+
["cool", "awesome", "the best"] # Not searchable terms
|
| 437 |
+
```
|
| 438 |
+
|
| 439 |
+
### Demo URLs
|
| 440 |
+
|
| 441 |
+
✅ **Preferred**:
|
| 442 |
+
- HuggingFace Gradio Spaces
|
| 443 |
+
- Google Colab notebooks
|
| 444 |
+
- Live web demos
|
| 445 |
+
|
| 446 |
+
❌ **Avoid**:
|
| 447 |
+
- Dead links
|
| 448 |
+
- Paywalled demos
|
| 449 |
+
- Demos requiring registration
|
| 450 |
+
|
| 451 |
+
## Next Steps
|
| 452 |
+
|
| 453 |
+
- Return to [Architecture Overview](overview.md)
|
| 454 |
+
- Learn about [Retrieval Pipeline](retrieval.md)
|
| 455 |
+
- Explore [Agent & VLM Selection](agent.md)
|
docs/architecture/overview.md
CHANGED
|
@@ -1,459 +1,459 @@
|
|
| 1 |
-
# Architecture Overview
|
| 2 |
-
|
| 3 |
-
The AI Imaging Agent uses a **two-stage pipeline** that combines fast text retrieval with vision-language model selection to recommend imaging tools.
|
| 4 |
-
|
| 5 |
-
## System Architecture
|
| 6 |
-
|
| 7 |
-
```mermaid
|
| 8 |
-
graph TB
|
| 9 |
-
subgraph "User Interface"
|
| 10 |
-
UI[Gradio Chat Interface]
|
| 11 |
-
end
|
| 12 |
-
|
| 13 |
-
subgraph "API Layer"
|
| 14 |
-
Pipeline[RAGImagingPipeline]
|
| 15 |
-
Validator[File Validator]
|
| 16 |
-
MetaExtractor[Metadata Extractor]
|
| 17 |
-
end
|
| 18 |
-
|
| 19 |
-
subgraph "Stage 1: Retrieval"
|
| 20 |
-
Embedder[BGE-M3 Text Embedder]
|
| 21 |
-
FAISS[FAISS Vector Index]
|
| 22 |
-
Reranker[CrossEncoder Reranker]
|
| 23 |
-
Catalog[Software Catalog JSONL]
|
| 24 |
-
end
|
| 25 |
-
|
| 26 |
-
subgraph "Stage 2: Agent Selection"
|
| 27 |
-
Agent[PydanticAI Agent]
|
| 28 |
-
VLM[GPT-4o/4o-mini VLM]
|
| 29 |
-
Tools[Agent Tools]
|
| 30 |
-
end
|
| 31 |
-
|
| 32 |
-
UI --> Pipeline
|
| 33 |
-
Pipeline --> Validator
|
| 34 |
-
Pipeline --> MetaExtractor
|
| 35 |
-
Pipeline --> Embedder
|
| 36 |
-
Embedder --> FAISS
|
| 37 |
-
FAISS --> Reranker
|
| 38 |
-
Catalog -.-> FAISS
|
| 39 |
-
Reranker --> Agent
|
| 40 |
-
Agent --> VLM
|
| 41 |
-
Agent --> Tools
|
| 42 |
-
Agent --> UI
|
| 43 |
-
```
|
| 44 |
-
|
| 45 |
-
## Design Principles
|
| 46 |
-
|
| 47 |
-
### 1. Two-Stage Pipeline
|
| 48 |
-
|
| 49 |
-
**Why two stages?**
|
| 50 |
-
|
| 51 |
-
- **Speed**: Text retrieval is fast (~100ms), VLM calls are slower (~2-5s)
|
| 52 |
-
- **Cost**: Only run expensive VLM on top candidates
|
| 53 |
-
- **Quality**: Combine semantic search (Stage 1) with reasoning (Stage 2)
|
| 54 |
-
|
| 55 |
-
### 2. No Generation in Retrieval
|
| 56 |
-
|
| 57 |
-
Stage 1 uses **no LLMs**:
|
| 58 |
-
|
| 59 |
-
- Deterministic text search
|
| 60 |
-
- Reproducible results
|
| 61 |
-
- Fast iteration
|
| 62 |
-
- Lower cost
|
| 63 |
-
|
| 64 |
-
### 3. Single VLM Call in Selection
|
| 65 |
-
|
| 66 |
-
Stage 2 makes **exactly one VLM call**:
|
| 67 |
-
|
| 68 |
-
- Sees all candidates at once
|
| 69 |
-
- Performs comparative reasoning
|
| 70 |
-
- Returns complete rankings
|
| 71 |
-
- Efficient use of context window
|
| 72 |
-
|
| 73 |
-
### 4. Vision + Text Integration
|
| 74 |
-
|
| 75 |
-
VLM receives:
|
| 76 |
-
|
| 77 |
-
- **Visual**: PNG preview of image
|
| 78 |
-
- **Textual**: Query, metadata, candidate descriptions
|
| 79 |
-
- **Structured**: Candidate metadata table
|
| 80 |
-
|
| 81 |
-
Enables image-aware tool selection.
|
| 82 |
-
|
| 83 |
-
## Data Flow
|
| 84 |
-
|
| 85 |
-
### Input Processing
|
| 86 |
-
|
| 87 |
-
```
|
| 88 |
-
User uploads: scan.dcm
|
| 89 |
-
"Segment the lungs"
|
| 90 |
-
|
| 91 |
-
↓ File Validation
|
| 92 |
-
- Size check (< 200MB for DICOM)
|
| 93 |
-
- Format validation
|
| 94 |
-
- Security checks
|
| 95 |
-
|
| 96 |
-
↓ Metadata Extraction
|
| 97 |
-
- Format: DICOM
|
| 98 |
-
- Modality: CT
|
| 99 |
-
- Dimensions: 512×512×300 (3D)
|
| 100 |
-
- Spacing: 0.7×0.7×1.5mm
|
| 101 |
-
|
| 102 |
-
↓ Preview Generation
|
| 103 |
-
- Extract middle slice: scan_preview.png
|
| 104 |
-
- Format: PNG, RGB
|
| 105 |
-
- Preserve metadata separately
|
| 106 |
-
```
|
| 107 |
-
|
| 108 |
-
### Stage 1: Retrieval
|
| 109 |
-
|
| 110 |
-
```
|
| 111 |
-
Query: "Segment the lungs"
|
| 112 |
-
Uploaded: scan.dcm (DICOM, CT, 3D)
|
| 113 |
-
|
| 114 |
-
↓ Query Enhancement
|
| 115 |
-
Enhanced: "Segment the lungs format:DICOM format:CT format:3D"
|
| 116 |
-
|
| 117 |
-
↓ Metadata-Aware Hinting
|
| 118 |
-
+ image metadata summary (modality/anatomy/dims)
|
| 119 |
-
|
| 120 |
-
↓ Embedding (BGE-M3)
|
| 121 |
-
Vector: [0.23, -0.15, 0.87, ..., 0.34] # 1024 dims
|
| 122 |
-
|
| 123 |
-
↓ FAISS Search
|
| 124 |
-
Top 20 candidates by cosine similarity
|
| 125 |
-
|
| 126 |
-
↓ Retry Broadening (if low results)
|
| 127 |
-
retry with a shorter query formulation
|
| 128 |
-
|
| 129 |
-
↓ CrossEncoder Reranking
|
| 130 |
-
Re-score with cross-attention
|
| 131 |
-
Top 8 candidates
|
| 132 |
-
|
| 133 |
-
→ Candidates passed to Stage 2
|
| 134 |
-
```
|
| 135 |
-
|
| 136 |
-
### Stage 2: Agent Selection
|
| 137 |
-
|
| 138 |
-
```
|
| 139 |
-
Inputs:
|
| 140 |
-
- User query: "Segment the lungs"
|
| 141 |
-
- Image preview: scan_preview.png
|
| 142 |
-
- Candidates: [tool1, tool2, ..., tool8]
|
| 143 |
-
- Metadata: DICOM, CT, 3D, 512×512×300
|
| 144 |
-
|
| 145 |
-
↓ VLM Prompt Construction
|
| 146 |
-
System: "You are an imaging tool expert..."
|
| 147 |
-
User text: Query + metadata + candidate table
|
| 148 |
-
User image: PNG preview
|
| 149 |
-
|
| 150 |
-
↓ VLM Call (GPT-4o)
|
| 151 |
-
- Analyzes image content (CT thorax)
|
| 152 |
-
- Reads candidate descriptions
|
| 153 |
-
- Considers format compatibility
|
| 154 |
-
- Reasons about task alignment
|
| 155 |
-
|
| 156 |
-
↓ Response (Structured)
|
| 157 |
-
{
|
| 158 |
-
"status": "complete",
|
| 159 |
-
"recommendations": [
|
| 160 |
-
{
|
| 161 |
-
"rank": 1,
|
| 162 |
-
"name": "TotalSegmentator",
|
| 163 |
-
"accuracy": 95,
|
| 164 |
-
"explanation": "...",
|
| 165 |
-
"reason": "task_match"
|
| 166 |
-
},
|
| 167 |
-
...
|
| 168 |
-
]
|
| 169 |
-
}
|
| 170 |
-
|
| 171 |
-
→ Formatted recommendations to user
|
| 172 |
-
```
|
| 173 |
-
|
| 174 |
-
## Key Components
|
| 175 |
-
|
| 176 |
-
### api/pipeline.py
|
| 177 |
-
|
| 178 |
-
**RAGImagingPipeline**: Main orchestrator
|
| 179 |
-
|
| 180 |
-
```python
|
| 181 |
-
class RAGImagingPipeline:
|
| 182 |
-
def __init__(self, catalog_path, index_dir):
|
| 183 |
-
self.retriever = TextRetriever(...)
|
| 184 |
-
# Stage 2 (selection/ranking) is handled by the PydanticAI agent
|
| 185 |
-
# configured in generator/prompts.py using models from generator/schema.py
|
| 186 |
-
|
| 187 |
-
def recommend(self, query, files):
|
| 188 |
-
# Stage 1: Retrieval
|
| 189 |
-
candidates = self.retriever.retrieve(query)
|
| 190 |
-
|
| 191 |
-
# Stage 2: Selection via PydanticAI agent
|
| 192 |
-
recommendations = run_selection_agent(
|
| 193 |
-
query=query,
|
| 194 |
-
candidates=candidates,
|
| 195 |
-
files=files,
|
| 196 |
-
)
|
| 197 |
-
|
| 198 |
-
return recommendations
|
| 199 |
-
```
|
| 200 |
-
|
| 201 |
-
**Responsibilities**:
|
| 202 |
-
|
| 203 |
-
- File validation
|
| 204 |
-
- Metadata extraction
|
| 205 |
-
- Pipeline orchestration
|
| 206 |
-
- Error handling
|
| 207 |
-
|
| 208 |
-
### retriever/
|
| 209 |
-
|
| 210 |
-
**Text-based retrieval, no LLMs**
|
| 211 |
-
|
| 212 |
-
Components:
|
| 213 |
-
|
| 214 |
-
- `text_embedder.py`: BGE-M3 embedding model
|
| 215 |
-
- `vector_index.py`: FAISS index management
|
| 216 |
-
- `reranker.py`: CrossEncoder reranking
|
| 217 |
-
- `software_doc.py`: Catalog schema and loading
|
| 218 |
-
|
| 219 |
-
**Retrieval flow**:
|
| 220 |
-
|
| 221 |
-
1. Embed query → vector
|
| 222 |
-
2. FAISS search → top-N by similarity
|
| 223 |
-
3. CrossEncoder → rerank with cross-attention
|
| 224 |
-
4. Return top-K candidates
|
| 225 |
-
|
| 226 |
-
### generator/
|
| 227 |
-
|
| 228 |
-
**VLM-based tool selection building blocks**
|
| 229 |
-
|
| 230 |
-
Components:
|
| 231 |
-
|
| 232 |
-
- `schema.py`: Pydantic models for agent responses and tool recommendations
|
| 233 |
-
- `prompts.py`: System and tool-selection prompts used by the PydanticAI agent
|
| 234 |
-
|
| 235 |
-
**Selection logic**:
|
| 236 |
-
|
| 237 |
-
- Implemented in the PydanticAI agent (`agent/agent.py`) using these schemas and prompts
|
| 238 |
-
- Single VLM call with all candidates
|
| 239 |
-
- Structured output (Pydantic schemas) with ranked recommendations
|
| 240 |
-
- Vision + text multimodal input
|
| 241 |
-
|
| 242 |
-
### agent/
|
| 243 |
-
|
| 244 |
-
**PydanticAI conversational agent**
|
| 245 |
-
|
| 246 |
-
Components:
|
| 247 |
-
|
| 248 |
-
- `agent.py`: Agent definition and tools
|
| 249 |
-
- `state.py`: ChatState dataclass
|
| 250 |
-
- `tools.py`: Agent tools (search, repo_info, demo_exec)
|
| 251 |
-
|
| 252 |
-
**Tools**:
|
| 253 |
-
|
| 254 |
-
- `search_alternative`: Request alternative search
|
| 255 |
-
- `repo_info`: Fetch GitHub repository details
|
| 256 |
-
- `run_gradio_demo`: Execute Gradio Space demos
|
| 257 |
-
|
| 258 |
-
### utils/
|
| 259 |
-
|
| 260 |
-
**Shared utilities**
|
| 261 |
-
|
| 262 |
-
- `image_meta.py`: DICOM/NIfTI/TIFF metadata extraction
|
| 263 |
-
- `file_validator.py`: Size and format validation
|
| 264 |
-
- `previews.py`: Image conversion to PNG
|
| 265 |
-
- `tags.py`: Control tag parsing (`[EXCLUDE:...]`, etc.)
|
| 266 |
-
- `config.py`: Configuration management
|
| 267 |
-
|
| 268 |
-
### ui/
|
| 269 |
-
|
| 270 |
-
**Gradio interface**
|
| 271 |
-
|
| 272 |
-
Components:
|
| 273 |
-
|
| 274 |
-
- `app.py`: Gradio application
|
| 275 |
-
- `components.py`: Reusable UI components
|
| 276 |
-
- `handlers.py`: Message handlers
|
| 277 |
-
- `formatters.py`: Response formatting
|
| 278 |
-
- `visualizations.py`: Previews and traces
|
| 279 |
-
|
| 280 |
-
**Key function**:
|
| 281 |
-
```python
|
| 282 |
-
def respond(message: str, files: list, state: dict) -> tuple:
|
| 283 |
-
"""
|
| 284 |
-
Main interaction function.
|
| 285 |
-
|
| 286 |
-
Returns: (reply, media, updated_state)
|
| 287 |
-
"""
|
| 288 |
-
```
|
| 289 |
-
|
| 290 |
-
## Module Boundaries
|
| 291 |
-
|
| 292 |
-
Clear separation of concerns:
|
| 293 |
-
|
| 294 |
-
| Module | Purpose | Dependencies |
|
| 295 |
-
|--------|---------|--------------|
|
| 296 |
-
| `api/` | Pipeline orchestration | `retriever/`, `generator/`, `utils/` |
|
| 297 |
-
| `retriever/` | Text search only | None (pure retrieval) |
|
| 298 |
-
| `generator/` | VLM selection only | None (pure generation) |
|
| 299 |
-
| `agent/` | Conversational logic | `api/`, `utils/` |
|
| 300 |
-
| `ui/` | Interface only | `agent/`, `api/` |
|
| 301 |
-
| `utils/` | Shared functionality | None (pure utilities) |
|
| 302 |
-
|
| 303 |
-
**Benefits**:
|
| 304 |
-
|
| 305 |
-
- Independent testing
|
| 306 |
-
- Clear interfaces
|
| 307 |
-
- Modular replacement
|
| 308 |
-
- No circular dependencies
|
| 309 |
-
|
| 310 |
-
## Data Schemas
|
| 311 |
-
|
| 312 |
-
### Software Catalog
|
| 313 |
-
|
| 314 |
-
JSONL format, based on schema.org SoftwareSourceCode:
|
| 315 |
-
|
| 316 |
-
```json
|
| 317 |
-
{
|
| 318 |
-
"name": "TotalSegmentator",
|
| 319 |
-
"description": "Automated multi-organ segmentation...",
|
| 320 |
-
"url": "https://github.com/wasserth/TotalSegmentator",
|
| 321 |
-
"codeRepository": "https://github.com/wasserth/TotalSegmentator",
|
| 322 |
-
"programmingLanguage": "Python",
|
| 323 |
-
"license": "Apache-2.0",
|
| 324 |
-
"keywords": ["segmentation", "medical-imaging", "CT"],
|
| 325 |
-
"applicationCategory": "Medical Imaging",
|
| 326 |
-
"operatingSystem": ["Linux", "Windows", "macOS"],
|
| 327 |
-
"softwareRequirements": ["Python 3.9+", "PyTorch"],
|
| 328 |
-
"supportingData": {
|
| 329 |
-
"modalities": ["CT", "MRI"],
|
| 330 |
-
"dimensions": ["3D"],
|
| 331 |
-
"formats": ["DICOM", "NIfTI"],
|
| 332 |
-
"tasks": ["segmentation"],
|
| 333 |
-
"demo_url": "https://huggingface.co/spaces/..."
|
| 334 |
-
}
|
| 335 |
-
}
|
| 336 |
-
```
|
| 337 |
-
|
| 338 |
-
### Agent Response
|
| 339 |
-
|
| 340 |
-
Pydantic models in `generator/schema.py`:
|
| 341 |
-
|
| 342 |
-
```python
|
| 343 |
-
class ToolRecommendation(BaseModel):
|
| 344 |
-
rank: int
|
| 345 |
-
name: str
|
| 346 |
-
accuracy_score: int # 0-100
|
| 347 |
-
explanation: str
|
| 348 |
-
reason: ToolReason # Enum
|
| 349 |
-
supporting_data: dict
|
| 350 |
-
|
| 351 |
-
class AgentResponse(BaseModel):
|
| 352 |
-
status: ConversationStatus # Enum
|
| 353 |
-
recommendations: list[ToolRecommendation]
|
| 354 |
-
message: str | None
|
| 355 |
-
```
|
| 356 |
-
|
| 357 |
-
**Validation**:
|
| 358 |
-
|
| 359 |
-
- Type checking via Pydantic
|
| 360 |
-
- Enum constraints
|
| 361 |
-
- Field aliases for LLM compatibility
|
| 362 |
-
|
| 363 |
-
## Extension Points
|
| 364 |
-
|
| 365 |
-
### Adding New Models
|
| 366 |
-
|
| 367 |
-
In `config.yaml`:
|
| 368 |
-
|
| 369 |
-
```yaml
|
| 370 |
-
available_models:
|
| 371 |
-
- display_name: "Custom Model"
|
| 372 |
-
name: "model-name"
|
| 373 |
-
base_url: "https://api.example.com/v1"
|
| 374 |
-
api_key_env: "CUSTOM_API_KEY"
|
| 375 |
-
```
|
| 376 |
-
|
| 377 |
-
### Adding New Tools
|
| 378 |
-
|
| 379 |
-
Add a tool in `agent/agent.py` and route implementation to `agent/tools/` modules:
|
| 380 |
-
|
| 381 |
-
```python
|
| 382 |
-
@agent.tool
|
| 383 |
-
async def new_tool(ctx: RunContext[AgentState], param: str) -> str:
|
| 384 |
-
"""Tool description for the agent."""
|
| 385 |
-
# Delegate to ai_agent.agent.tools.* implementation
|
| 386 |
-
return result
|
| 387 |
-
```
|
| 388 |
-
|
| 389 |
-
### Custom Metadata Extractors
|
| 390 |
-
|
| 391 |
-
In `utils/image_meta.py`:
|
| 392 |
-
|
| 393 |
-
```python
|
| 394 |
-
def extract_custom_format(file_path: str) -> dict:
|
| 395 |
-
"""Extract metadata from custom format."""
|
| 396 |
-
# Implementation
|
| 397 |
-
return metadata
|
| 398 |
-
```
|
| 399 |
-
|
| 400 |
-
<!-- ## Performance Characteristics
|
| 401 |
-
|
| 402 |
-
### Latency Breakdown
|
| 403 |
-
|
| 404 |
-
Typical request (~3-5 seconds total):
|
| 405 |
-
|
| 406 |
-
| Stage | Time | Notes |
|
| 407 |
-
|-------|------|-------|
|
| 408 |
-
| File upload | 100-500ms | Network + validation |
|
| 409 |
-
| Metadata extraction | 50-200ms | Format-dependent |
|
| 410 |
-
| Preview generation | 100-500ms | Image conversion |
|
| 411 |
-
| Retrieval (Stage 1) | 100-200ms | Embedding + FAISS |
|
| 412 |
-
| Reranking | 200-500ms | CrossEncoder |
|
| 413 |
-
| VLM call (Stage 2) | 2-4s | OpenAI API |
|
| 414 |
-
| Response formatting | 50ms | JSON → UI |
|
| 415 |
-
|
| 416 |
-
**Bottleneck**: VLM API call (Stage 2)
|
| 417 |
-
|
| 418 |
-
### Scalability
|
| 419 |
-
|
| 420 |
-
**Current**:
|
| 421 |
-
|
| 422 |
-
- Single-user Gradio app
|
| 423 |
-
- In-memory FAISS index
|
| 424 |
-
- Synchronous processing
|
| 425 |
-
|
| 426 |
-
**Production considerations**:
|
| 427 |
-
|
| 428 |
-
- FastAPI backend for multi-user
|
| 429 |
-
- Async VLM calls
|
| 430 |
-
- Redis for session state
|
| 431 |
-
- CDN for catalog + index -->
|
| 432 |
-
|
| 433 |
-
## Security Considerations
|
| 434 |
-
|
| 435 |
-
### User Data
|
| 436 |
-
|
| 437 |
-
- **Images**: Sent to OpenAI API (preview PNG) if gpt is selected
|
| 438 |
-
- **Metadata**: Processed locally, sent to VLM as text
|
| 439 |
-
- **Queries**: Sent to OpenAI API
|
| 440 |
-
|
| 441 |
-
**Privacy**: User data sees OpenAI's VLM API only.
|
| 442 |
-
|
| 443 |
-
### Catalog Integrity
|
| 444 |
-
|
| 445 |
-
- Software catalog is curated
|
| 446 |
-
- SHA1 checksums verify integrity
|
| 447 |
-
- No user-generated catalog entries
|
| 448 |
-
|
| 449 |
-
### Demo Execution
|
| 450 |
-
|
| 451 |
-
- Calls external Gradio Spaces (user choice)
|
| 452 |
-
- No credentials shared with demos
|
| 453 |
-
- User's image uploaded to public spaces (warn users)
|
| 454 |
-
|
| 455 |
-
## Next Steps
|
| 456 |
-
|
| 457 |
-
- Deep dive into [Retrieval Pipeline](retrieval.md)
|
| 458 |
-
- Learn about [Agent & VLM Selection](agent.md)
|
| 459 |
-
- Explore [Software Catalog](catalog.md)
|
|
|
|
| 1 |
+
# Architecture Overview
|
| 2 |
+
|
| 3 |
+
The AI Imaging Agent uses a **two-stage pipeline** that combines fast text retrieval with vision-language model selection to recommend imaging tools.
|
| 4 |
+
|
| 5 |
+
## System Architecture
|
| 6 |
+
|
| 7 |
+
```mermaid
|
| 8 |
+
graph TB
|
| 9 |
+
subgraph "User Interface"
|
| 10 |
+
UI[Gradio Chat Interface]
|
| 11 |
+
end
|
| 12 |
+
|
| 13 |
+
subgraph "API Layer"
|
| 14 |
+
Pipeline[RAGImagingPipeline]
|
| 15 |
+
Validator[File Validator]
|
| 16 |
+
MetaExtractor[Metadata Extractor]
|
| 17 |
+
end
|
| 18 |
+
|
| 19 |
+
subgraph "Stage 1: Retrieval"
|
| 20 |
+
Embedder[BGE-M3 Text Embedder]
|
| 21 |
+
FAISS[FAISS Vector Index]
|
| 22 |
+
Reranker[CrossEncoder Reranker]
|
| 23 |
+
Catalog[Software Catalog JSONL]
|
| 24 |
+
end
|
| 25 |
+
|
| 26 |
+
subgraph "Stage 2: Agent Selection"
|
| 27 |
+
Agent[PydanticAI Agent]
|
| 28 |
+
VLM[GPT-4o/4o-mini VLM]
|
| 29 |
+
Tools[Agent Tools]
|
| 30 |
+
end
|
| 31 |
+
|
| 32 |
+
UI --> Pipeline
|
| 33 |
+
Pipeline --> Validator
|
| 34 |
+
Pipeline --> MetaExtractor
|
| 35 |
+
Pipeline --> Embedder
|
| 36 |
+
Embedder --> FAISS
|
| 37 |
+
FAISS --> Reranker
|
| 38 |
+
Catalog -.-> FAISS
|
| 39 |
+
Reranker --> Agent
|
| 40 |
+
Agent --> VLM
|
| 41 |
+
Agent --> Tools
|
| 42 |
+
Agent --> UI
|
| 43 |
+
```
|
| 44 |
+
|
| 45 |
+
## Design Principles
|
| 46 |
+
|
| 47 |
+
### 1. Two-Stage Pipeline
|
| 48 |
+
|
| 49 |
+
**Why two stages?**
|
| 50 |
+
|
| 51 |
+
- **Speed**: Text retrieval is fast (~100ms), VLM calls are slower (~2-5s)
|
| 52 |
+
- **Cost**: Only run expensive VLM on top candidates
|
| 53 |
+
- **Quality**: Combine semantic search (Stage 1) with reasoning (Stage 2)
|
| 54 |
+
|
| 55 |
+
### 2. No Generation in Retrieval
|
| 56 |
+
|
| 57 |
+
Stage 1 uses **no LLMs**:
|
| 58 |
+
|
| 59 |
+
- Deterministic text search
|
| 60 |
+
- Reproducible results
|
| 61 |
+
- Fast iteration
|
| 62 |
+
- Lower cost
|
| 63 |
+
|
| 64 |
+
### 3. Single VLM Call in Selection
|
| 65 |
+
|
| 66 |
+
Stage 2 makes **exactly one VLM call**:
|
| 67 |
+
|
| 68 |
+
- Sees all candidates at once
|
| 69 |
+
- Performs comparative reasoning
|
| 70 |
+
- Returns complete rankings
|
| 71 |
+
- Efficient use of context window
|
| 72 |
+
|
| 73 |
+
### 4. Vision + Text Integration
|
| 74 |
+
|
| 75 |
+
VLM receives:
|
| 76 |
+
|
| 77 |
+
- **Visual**: PNG preview of image
|
| 78 |
+
- **Textual**: Query, metadata, candidate descriptions
|
| 79 |
+
- **Structured**: Candidate metadata table
|
| 80 |
+
|
| 81 |
+
Enables image-aware tool selection.
|
| 82 |
+
|
| 83 |
+
## Data Flow
|
| 84 |
+
|
| 85 |
+
### Input Processing
|
| 86 |
+
|
| 87 |
+
```
|
| 88 |
+
User uploads: scan.dcm
|
| 89 |
+
"Segment the lungs"
|
| 90 |
+
|
| 91 |
+
↓ File Validation
|
| 92 |
+
- Size check (< 200MB for DICOM)
|
| 93 |
+
- Format validation
|
| 94 |
+
- Security checks
|
| 95 |
+
|
| 96 |
+
↓ Metadata Extraction
|
| 97 |
+
- Format: DICOM
|
| 98 |
+
- Modality: CT
|
| 99 |
+
- Dimensions: 512×512×300 (3D)
|
| 100 |
+
- Spacing: 0.7×0.7×1.5mm
|
| 101 |
+
|
| 102 |
+
↓ Preview Generation
|
| 103 |
+
- Extract middle slice: scan_preview.png
|
| 104 |
+
- Format: PNG, RGB
|
| 105 |
+
- Preserve metadata separately
|
| 106 |
+
```
|
| 107 |
+
|
| 108 |
+
### Stage 1: Retrieval
|
| 109 |
+
|
| 110 |
+
```
|
| 111 |
+
Query: "Segment the lungs"
|
| 112 |
+
Uploaded: scan.dcm (DICOM, CT, 3D)
|
| 113 |
+
|
| 114 |
+
↓ Query Enhancement
|
| 115 |
+
Enhanced: "Segment the lungs format:DICOM format:CT format:3D"
|
| 116 |
+
|
| 117 |
+
↓ Metadata-Aware Hinting
|
| 118 |
+
+ image metadata summary (modality/anatomy/dims)
|
| 119 |
+
|
| 120 |
+
↓ Embedding (BGE-M3)
|
| 121 |
+
Vector: [0.23, -0.15, 0.87, ..., 0.34] # 1024 dims
|
| 122 |
+
|
| 123 |
+
↓ FAISS Search
|
| 124 |
+
Top 20 candidates by cosine similarity
|
| 125 |
+
|
| 126 |
+
↓ Retry Broadening (if low results)
|
| 127 |
+
retry with a shorter query formulation
|
| 128 |
+
|
| 129 |
+
↓ CrossEncoder Reranking
|
| 130 |
+
Re-score with cross-attention
|
| 131 |
+
Top 8 candidates
|
| 132 |
+
|
| 133 |
+
→ Candidates passed to Stage 2
|
| 134 |
+
```
|
| 135 |
+
|
| 136 |
+
### Stage 2: Agent Selection
|
| 137 |
+
|
| 138 |
+
```
|
| 139 |
+
Inputs:
|
| 140 |
+
- User query: "Segment the lungs"
|
| 141 |
+
- Image preview: scan_preview.png
|
| 142 |
+
- Candidates: [tool1, tool2, ..., tool8]
|
| 143 |
+
- Metadata: DICOM, CT, 3D, 512×512×300
|
| 144 |
+
|
| 145 |
+
↓ VLM Prompt Construction
|
| 146 |
+
System: "You are an imaging tool expert..."
|
| 147 |
+
User text: Query + metadata + candidate table
|
| 148 |
+
User image: PNG preview
|
| 149 |
+
|
| 150 |
+
↓ VLM Call (GPT-4o)
|
| 151 |
+
- Analyzes image content (CT thorax)
|
| 152 |
+
- Reads candidate descriptions
|
| 153 |
+
- Considers format compatibility
|
| 154 |
+
- Reasons about task alignment
|
| 155 |
+
|
| 156 |
+
↓ Response (Structured)
|
| 157 |
+
{
|
| 158 |
+
"status": "complete",
|
| 159 |
+
"recommendations": [
|
| 160 |
+
{
|
| 161 |
+
"rank": 1,
|
| 162 |
+
"name": "TotalSegmentator",
|
| 163 |
+
"accuracy": 95,
|
| 164 |
+
"explanation": "...",
|
| 165 |
+
"reason": "task_match"
|
| 166 |
+
},
|
| 167 |
+
...
|
| 168 |
+
]
|
| 169 |
+
}
|
| 170 |
+
|
| 171 |
+
→ Formatted recommendations to user
|
| 172 |
+
```
|
| 173 |
+
|
| 174 |
+
## Key Components
|
| 175 |
+
|
| 176 |
+
### api/pipeline.py
|
| 177 |
+
|
| 178 |
+
**RAGImagingPipeline**: Main orchestrator
|
| 179 |
+
|
| 180 |
+
```python
|
| 181 |
+
class RAGImagingPipeline:
|
| 182 |
+
def __init__(self, catalog_path, index_dir):
|
| 183 |
+
self.retriever = TextRetriever(...)
|
| 184 |
+
# Stage 2 (selection/ranking) is handled by the PydanticAI agent
|
| 185 |
+
# configured in generator/prompts.py using models from generator/schema.py
|
| 186 |
+
|
| 187 |
+
def recommend(self, query, files):
|
| 188 |
+
# Stage 1: Retrieval
|
| 189 |
+
candidates = self.retriever.retrieve(query)
|
| 190 |
+
|
| 191 |
+
# Stage 2: Selection via PydanticAI agent
|
| 192 |
+
recommendations = run_selection_agent(
|
| 193 |
+
query=query,
|
| 194 |
+
candidates=candidates,
|
| 195 |
+
files=files,
|
| 196 |
+
)
|
| 197 |
+
|
| 198 |
+
return recommendations
|
| 199 |
+
```
|
| 200 |
+
|
| 201 |
+
**Responsibilities**:
|
| 202 |
+
|
| 203 |
+
- File validation
|
| 204 |
+
- Metadata extraction
|
| 205 |
+
- Pipeline orchestration
|
| 206 |
+
- Error handling
|
| 207 |
+
|
| 208 |
+
### retriever/
|
| 209 |
+
|
| 210 |
+
**Text-based retrieval, no LLMs**
|
| 211 |
+
|
| 212 |
+
Components:
|
| 213 |
+
|
| 214 |
+
- `text_embedder.py`: BGE-M3 embedding model
|
| 215 |
+
- `vector_index.py`: FAISS index management
|
| 216 |
+
- `reranker.py`: CrossEncoder reranking
|
| 217 |
+
- `software_doc.py`: Catalog schema and loading
|
| 218 |
+
|
| 219 |
+
**Retrieval flow**:
|
| 220 |
+
|
| 221 |
+
1. Embed query → vector
|
| 222 |
+
2. FAISS search → top-N by similarity
|
| 223 |
+
3. CrossEncoder → rerank with cross-attention
|
| 224 |
+
4. Return top-K candidates
|
| 225 |
+
|
| 226 |
+
### generator/
|
| 227 |
+
|
| 228 |
+
**VLM-based tool selection building blocks**
|
| 229 |
+
|
| 230 |
+
Components:
|
| 231 |
+
|
| 232 |
+
- `schema.py`: Pydantic models for agent responses and tool recommendations
|
| 233 |
+
- `prompts.py`: System and tool-selection prompts used by the PydanticAI agent
|
| 234 |
+
|
| 235 |
+
**Selection logic**:
|
| 236 |
+
|
| 237 |
+
- Implemented in the PydanticAI agent (`agent/agent.py`) using these schemas and prompts
|
| 238 |
+
- Single VLM call with all candidates
|
| 239 |
+
- Structured output (Pydantic schemas) with ranked recommendations
|
| 240 |
+
- Vision + text multimodal input
|
| 241 |
+
|
| 242 |
+
### agent/
|
| 243 |
+
|
| 244 |
+
**PydanticAI conversational agent**
|
| 245 |
+
|
| 246 |
+
Components:
|
| 247 |
+
|
| 248 |
+
- `agent.py`: Agent definition and tools
|
| 249 |
+
- `state.py`: ChatState dataclass
|
| 250 |
+
- `tools.py`: Agent tools (search, repo_info, demo_exec)
|
| 251 |
+
|
| 252 |
+
**Tools**:
|
| 253 |
+
|
| 254 |
+
- `search_alternative`: Request alternative search
|
| 255 |
+
- `repo_info`: Fetch GitHub repository details
|
| 256 |
+
- `run_gradio_demo`: Execute Gradio Space demos
|
| 257 |
+
|
| 258 |
+
### utils/
|
| 259 |
+
|
| 260 |
+
**Shared utilities**
|
| 261 |
+
|
| 262 |
+
- `image_meta.py`: DICOM/NIfTI/TIFF metadata extraction
|
| 263 |
+
- `file_validator.py`: Size and format validation
|
| 264 |
+
- `previews.py`: Image conversion to PNG
|
| 265 |
+
- `tags.py`: Control tag parsing (`[EXCLUDE:...]`, etc.)
|
| 266 |
+
- `config.py`: Configuration management
|
| 267 |
+
|
| 268 |
+
### ui/
|
| 269 |
+
|
| 270 |
+
**Gradio interface**
|
| 271 |
+
|
| 272 |
+
Components:
|
| 273 |
+
|
| 274 |
+
- `app.py`: Gradio application
|
| 275 |
+
- `components.py`: Reusable UI components
|
| 276 |
+
- `handlers.py`: Message handlers
|
| 277 |
+
- `formatters.py`: Response formatting
|
| 278 |
+
- `visualizations.py`: Previews and traces
|
| 279 |
+
|
| 280 |
+
**Key function**:
|
| 281 |
+
```python
|
| 282 |
+
def respond(message: str, files: list, state: dict) -> tuple:
|
| 283 |
+
"""
|
| 284 |
+
Main interaction function.
|
| 285 |
+
|
| 286 |
+
Returns: (reply, media, updated_state)
|
| 287 |
+
"""
|
| 288 |
+
```
|
| 289 |
+
|
| 290 |
+
## Module Boundaries
|
| 291 |
+
|
| 292 |
+
Clear separation of concerns:
|
| 293 |
+
|
| 294 |
+
| Module | Purpose | Dependencies |
|
| 295 |
+
|--------|---------|--------------|
|
| 296 |
+
| `api/` | Pipeline orchestration | `retriever/`, `generator/`, `utils/` |
|
| 297 |
+
| `retriever/` | Text search only | None (pure retrieval) |
|
| 298 |
+
| `generator/` | VLM selection only | None (pure generation) |
|
| 299 |
+
| `agent/` | Conversational logic | `api/`, `utils/` |
|
| 300 |
+
| `ui/` | Interface only | `agent/`, `api/` |
|
| 301 |
+
| `utils/` | Shared functionality | None (pure utilities) |
|
| 302 |
+
|
| 303 |
+
**Benefits**:
|
| 304 |
+
|
| 305 |
+
- Independent testing
|
| 306 |
+
- Clear interfaces
|
| 307 |
+
- Modular replacement
|
| 308 |
+
- No circular dependencies
|
| 309 |
+
|
| 310 |
+
## Data Schemas
|
| 311 |
+
|
| 312 |
+
### Software Catalog
|
| 313 |
+
|
| 314 |
+
JSONL format, based on schema.org SoftwareSourceCode:
|
| 315 |
+
|
| 316 |
+
```json
|
| 317 |
+
{
|
| 318 |
+
"name": "TotalSegmentator",
|
| 319 |
+
"description": "Automated multi-organ segmentation...",
|
| 320 |
+
"url": "https://github.com/wasserth/TotalSegmentator",
|
| 321 |
+
"codeRepository": "https://github.com/wasserth/TotalSegmentator",
|
| 322 |
+
"programmingLanguage": "Python",
|
| 323 |
+
"license": "Apache-2.0",
|
| 324 |
+
"keywords": ["segmentation", "medical-imaging", "CT"],
|
| 325 |
+
"applicationCategory": "Medical Imaging",
|
| 326 |
+
"operatingSystem": ["Linux", "Windows", "macOS"],
|
| 327 |
+
"softwareRequirements": ["Python 3.9+", "PyTorch"],
|
| 328 |
+
"supportingData": {
|
| 329 |
+
"modalities": ["CT", "MRI"],
|
| 330 |
+
"dimensions": ["3D"],
|
| 331 |
+
"formats": ["DICOM", "NIfTI"],
|
| 332 |
+
"tasks": ["segmentation"],
|
| 333 |
+
"demo_url": "https://huggingface.co/spaces/..."
|
| 334 |
+
}
|
| 335 |
+
}
|
| 336 |
+
```
|
| 337 |
+
|
| 338 |
+
### Agent Response
|
| 339 |
+
|
| 340 |
+
Pydantic models in `generator/schema.py`:
|
| 341 |
+
|
| 342 |
+
```python
|
| 343 |
+
class ToolRecommendation(BaseModel):
|
| 344 |
+
rank: int
|
| 345 |
+
name: str
|
| 346 |
+
accuracy_score: int # 0-100
|
| 347 |
+
explanation: str
|
| 348 |
+
reason: ToolReason # Enum
|
| 349 |
+
supporting_data: dict
|
| 350 |
+
|
| 351 |
+
class AgentResponse(BaseModel):
|
| 352 |
+
status: ConversationStatus # Enum
|
| 353 |
+
recommendations: list[ToolRecommendation]
|
| 354 |
+
message: str | None
|
| 355 |
+
```
|
| 356 |
+
|
| 357 |
+
**Validation**:
|
| 358 |
+
|
| 359 |
+
- Type checking via Pydantic
|
| 360 |
+
- Enum constraints
|
| 361 |
+
- Field aliases for LLM compatibility
|
| 362 |
+
|
| 363 |
+
## Extension Points
|
| 364 |
+
|
| 365 |
+
### Adding New Models
|
| 366 |
+
|
| 367 |
+
In `config.yaml`:
|
| 368 |
+
|
| 369 |
+
```yaml
|
| 370 |
+
available_models:
|
| 371 |
+
- display_name: "Custom Model"
|
| 372 |
+
name: "model-name"
|
| 373 |
+
base_url: "https://api.example.com/v1"
|
| 374 |
+
api_key_env: "CUSTOM_API_KEY"
|
| 375 |
+
```
|
| 376 |
+
|
| 377 |
+
### Adding New Tools
|
| 378 |
+
|
| 379 |
+
Add a tool in `agent/agent.py` and route implementation to `agent/tools/` modules:
|
| 380 |
+
|
| 381 |
+
```python
|
| 382 |
+
@agent.tool
|
| 383 |
+
async def new_tool(ctx: RunContext[AgentState], param: str) -> str:
|
| 384 |
+
"""Tool description for the agent."""
|
| 385 |
+
# Delegate to ai_agent.agent.tools.* implementation
|
| 386 |
+
return result
|
| 387 |
+
```
|
| 388 |
+
|
| 389 |
+
### Custom Metadata Extractors
|
| 390 |
+
|
| 391 |
+
In `utils/image_meta.py`:
|
| 392 |
+
|
| 393 |
+
```python
|
| 394 |
+
def extract_custom_format(file_path: str) -> dict:
|
| 395 |
+
"""Extract metadata from custom format."""
|
| 396 |
+
# Implementation
|
| 397 |
+
return metadata
|
| 398 |
+
```
|
| 399 |
+
|
| 400 |
+
<!-- ## Performance Characteristics
|
| 401 |
+
|
| 402 |
+
### Latency Breakdown
|
| 403 |
+
|
| 404 |
+
Typical request (~3-5 seconds total):
|
| 405 |
+
|
| 406 |
+
| Stage | Time | Notes |
|
| 407 |
+
|-------|------|-------|
|
| 408 |
+
| File upload | 100-500ms | Network + validation |
|
| 409 |
+
| Metadata extraction | 50-200ms | Format-dependent |
|
| 410 |
+
| Preview generation | 100-500ms | Image conversion |
|
| 411 |
+
| Retrieval (Stage 1) | 100-200ms | Embedding + FAISS |
|
| 412 |
+
| Reranking | 200-500ms | CrossEncoder |
|
| 413 |
+
| VLM call (Stage 2) | 2-4s | OpenAI API |
|
| 414 |
+
| Response formatting | 50ms | JSON → UI |
|
| 415 |
+
|
| 416 |
+
**Bottleneck**: VLM API call (Stage 2)
|
| 417 |
+
|
| 418 |
+
### Scalability
|
| 419 |
+
|
| 420 |
+
**Current**:
|
| 421 |
+
|
| 422 |
+
- Single-user Gradio app
|
| 423 |
+
- In-memory FAISS index
|
| 424 |
+
- Synchronous processing
|
| 425 |
+
|
| 426 |
+
**Production considerations**:
|
| 427 |
+
|
| 428 |
+
- FastAPI backend for multi-user
|
| 429 |
+
- Async VLM calls
|
| 430 |
+
- Redis for session state
|
| 431 |
+
- CDN for catalog + index -->
|
| 432 |
+
|
| 433 |
+
## Security Considerations
|
| 434 |
+
|
| 435 |
+
### User Data
|
| 436 |
+
|
| 437 |
+
- **Images**: Sent to OpenAI API (preview PNG) if gpt is selected
|
| 438 |
+
- **Metadata**: Processed locally, sent to VLM as text
|
| 439 |
+
- **Queries**: Sent to OpenAI API
|
| 440 |
+
|
| 441 |
+
**Privacy**: User data sees OpenAI's VLM API only.
|
| 442 |
+
|
| 443 |
+
### Catalog Integrity
|
| 444 |
+
|
| 445 |
+
- Software catalog is curated
|
| 446 |
+
- SHA1 checksums verify integrity
|
| 447 |
+
- No user-generated catalog entries
|
| 448 |
+
|
| 449 |
+
### Demo Execution
|
| 450 |
+
|
| 451 |
+
- Calls external Gradio Spaces (user choice)
|
| 452 |
+
- No credentials shared with demos
|
| 453 |
+
- User's image uploaded to public spaces (warn users)
|
| 454 |
+
|
| 455 |
+
## Next Steps
|
| 456 |
+
|
| 457 |
+
- Deep dive into [Retrieval Pipeline](retrieval.md)
|
| 458 |
+
- Learn about [Agent & VLM Selection](agent.md)
|
| 459 |
+
- Explore [Software Catalog](catalog.md)
|
docs/architecture/retrieval.md
CHANGED
|
@@ -1,387 +1,387 @@
|
|
| 1 |
-
# Retrieval Pipeline
|
| 2 |
-
|
| 3 |
-
The retrieval stage is the first phase of the AI Imaging Agent's two-stage pipeline. It performs fast text-based search to find candidate tools from the software catalog.
|
| 4 |
-
|
| 5 |
-
## Overview
|
| 6 |
-
|
| 7 |
-
**Goal**: Quickly narrow down the software catalog to most relevant candidates
|
| 8 |
-
|
| 9 |
-
**Characteristics**:
|
| 10 |
-
|
| 11 |
-
- ⚡ Fast (~100-300ms)
|
| 12 |
-
- 🔢 Deterministic and reproducible
|
| 13 |
-
- 🚫 No LLM calls
|
| 14 |
-
- 💰 Low cost (no API fees)
|
| 15 |
-
|
| 16 |
-
## Pipeline Stages
|
| 17 |
-
|
| 18 |
-
```mermaid
|
| 19 |
-
graph LR
|
| 20 |
-
A[User Query] --> B[Query Enhancement]
|
| 21 |
-
B --> C[Embedding]
|
| 22 |
-
C --> D[FAISS Search]
|
| 23 |
-
D --> E[CrossEncoder Rerank]
|
| 24 |
-
E --> F[Top-K Candidates]
|
| 25 |
-
```
|
| 26 |
-
|
| 27 |
-
## Step 1: Query Enhancement
|
| 28 |
-
|
| 29 |
-
### Format Token Injection
|
| 30 |
-
|
| 31 |
-
When users upload files, format tokens are added to the query:
|
| 32 |
-
|
| 33 |
-
```python
|
| 34 |
-
# User uploads: scan.dcm
|
| 35 |
-
# User query: "segment lungs"
|
| 36 |
-
|
| 37 |
-
# Enhanced query:
|
| 38 |
-
"segment lungs format:DICOM format:CT format:3D"
|
| 39 |
-
```
|
| 40 |
-
|
| 41 |
-
**Format tokens added**:
|
| 42 |
-
|
| 43 |
-
- File extension (`format:DICOM`, `format:NIfTI`)
|
| 44 |
-
- Image modality from metadata (`format:CT`, `format:MRI`)
|
| 45 |
-
- Dimensionality (`format:2D`, `format:3D`, `format:4D`)
|
| 46 |
-
|
| 47 |
-
**Why this helps**:
|
| 48 |
-
|
| 49 |
-
- Matches tools that support specific formats
|
| 50 |
-
- Boosts DICOM-compatible tools for DICOM input
|
| 51 |
-
- Ensures dimension compatibility (3D tools for volumes)
|
| 52 |
-
|
| 53 |
-
### Control Tag Processing
|
| 54 |
-
|
| 55 |
-
Special tags are extracted and processed:
|
| 56 |
-
|
| 57 |
-
```python
|
| 58 |
-
query = "segment lungs [EXCLUDE:tool1|tool2]"
|
| 59 |
-
|
| 60 |
-
# Extracted:
|
| 61 |
-
clean_query = "segment lungs"
|
| 62 |
-
excluded_tools = ["tool1", "tool2"]
|
| 63 |
-
```
|
| 64 |
-
|
| 65 |
-
**Supported tags**:
|
| 66 |
-
- `[EXCLUDE:tool1|tool2]`: Filter tools from results
|
| 67 |
-
|
| 68 |
-
## Step 2: Metadata-Aware Querying
|
| 69 |
-
|
| 70 |
-
The pipeline does not perform semantic vocabulary expansion. Instead, retrieval combines:
|
| 71 |
-
|
| 72 |
-
- cleaned task text
|
| 73 |
-
- format tokens inferred from uploaded files (for example `format:DICOM`)
|
| 74 |
-
- compact image metadata hints (modality/anatomy/dimensionality when available)
|
| 75 |
-
|
| 76 |
-
This keeps retrieval deterministic and closely tied to the user's data.
|
| 77 |
-
|
| 78 |
-
### Alternative Query Generation
|
| 79 |
-
|
| 80 |
-
On retry (when initial results < 5 tools):
|
| 81 |
-
|
| 82 |
-
```python
|
| 83 |
-
# Initial query
|
| 84 |
-
query1 = "segment rare pulmonary structure"
|
| 85 |
-
results = 2 tools # Too few!
|
| 86 |
-
|
| 87 |
-
# Retry 1: Broader formulation (keep first 2-3 words)
|
| 88 |
-
query2 = "segment rare pulmonary"
|
| 89 |
-
results = 7 tools # Better!
|
| 90 |
-
|
| 91 |
-
# Retry 2: If still insufficient, repeat with same broadening strategy
|
| 92 |
-
```
|
| 93 |
-
|
| 94 |
-
**Max retries**: 2
|
| 95 |
-
|
| 96 |
-
## Step 3: Embedding
|
| 97 |
-
|
| 98 |
-
### BGE-M3 Model
|
| 99 |
-
|
| 100 |
-
**Model**: `BAAI/bge-m3`
|
| 101 |
-
|
| 102 |
-
**Characteristics**:
|
| 103 |
-
|
| 104 |
-
- Multilingual (but used for English)
|
| 105 |
-
- 1024-dimensional embeddings
|
| 106 |
-
- Trained for retrieval tasks
|
| 107 |
-
- Fast inference (~10ms per query)
|
| 108 |
-
|
| 109 |
-
**Embedding process**:
|
| 110 |
-
|
| 111 |
-
```python
|
| 112 |
-
from sentence_transformers import SentenceTransformer
|
| 113 |
-
|
| 114 |
-
model = SentenceTransformer("BAAI/bge-m3")
|
| 115 |
-
query_vector = model.encode(
|
| 116 |
-
query,
|
| 117 |
-
normalize_embeddings=True # L2 normalization for cosine similarity
|
| 118 |
-
)
|
| 119 |
-
# Returns: np.array of shape (1024,)
|
| 120 |
-
```
|
| 121 |
-
|
| 122 |
-
### Catalog Embedding
|
| 123 |
-
|
| 124 |
-
Software tools are pre-embedded during indexing:
|
| 125 |
-
|
| 126 |
-
```python
|
| 127 |
-
# For each tool in catalog:
|
| 128 |
-
tool_text = f"{tool.name} {tool.description} {' '.join(tool.keywords)}"
|
| 129 |
-
tool_vector = model.encode(tool_text, normalize_embeddings=True)
|
| 130 |
-
|
| 131 |
-
# Store in FAISS index
|
| 132 |
-
faiss_index.add(tool_vector)
|
| 133 |
-
```
|
| 134 |
-
|
| 135 |
-
**Index structure**:
|
| 136 |
-
|
| 137 |
-
- FAISS IndexFlatIP (inner product = cosine similarity for normalized vectors)
|
| 138 |
-
- ~150 tools in current catalog
|
| 139 |
-
- Index size: ~600KB
|
| 140 |
-
|
| 141 |
-
## Step 4: FAISS Search
|
| 142 |
-
|
| 143 |
-
### Vector Search
|
| 144 |
-
|
| 145 |
-
FAISS performs fast similarity search:
|
| 146 |
-
|
| 147 |
-
```python
|
| 148 |
-
import faiss
|
| 149 |
-
|
| 150 |
-
# Search for top 20 most similar tools
|
| 151 |
-
scores, indices = faiss_index.search(
|
| 152 |
-
query_vector.reshape(1, -1),
|
| 153 |
-
k=20
|
| 154 |
-
)
|
| 155 |
-
|
| 156 |
-
# Returns:
|
| 157 |
-
# scores: [0.85, 0.82, 0.79, ...] # Cosine similarities
|
| 158 |
-
# indices: [42, 17, 89, ...] # Tool IDs in catalog
|
| 159 |
-
```
|
| 160 |
-
|
| 161 |
-
**Search algorithm**:
|
| 162 |
-
|
| 163 |
-
- IndexFlatIP: Exact search (brute force)
|
| 164 |
-
- Fast for catalog size (~150 tools)
|
| 165 |
-
- Could use IVF for larger catalogs (>10k tools)
|
| 166 |
-
|
| 167 |
-
**Why top-20**:
|
| 168 |
-
|
| 169 |
-
- More candidates than needed (default final: 8)
|
| 170 |
-
- Provides options for reranking
|
| 171 |
-
- Balances recall vs. later stage cost
|
| 172 |
-
|
| 173 |
-
### Candidate Retrieval
|
| 174 |
-
|
| 175 |
-
```python
|
| 176 |
-
candidates = [catalog[idx] for idx in indices[:20]]
|
| 177 |
-
candidate_scores = scores[:20].tolist()
|
| 178 |
-
|
| 179 |
-
# Example candidates:
|
| 180 |
-
[
|
| 181 |
-
{
|
| 182 |
-
"name": "TotalSegmentator",
|
| 183 |
-
"score": 0.85,
|
| 184 |
-
"description": "Automated multi-organ segmentation...",
|
| 185 |
-
...
|
| 186 |
-
},
|
| 187 |
-
...
|
| 188 |
-
]
|
| 189 |
-
```
|
| 190 |
-
|
| 191 |
-
## Step 5: CrossEncoder Reranking
|
| 192 |
-
|
| 193 |
-
### Why Rerank?
|
| 194 |
-
|
| 195 |
-
**BiEncoder (BGE-M3)** limitations:
|
| 196 |
-
|
| 197 |
-
- Encodes query and documents independently
|
| 198 |
-
- No query-document interaction
|
| 199 |
-
- Misses subtle relevance signals
|
| 200 |
-
|
| 201 |
-
**CrossEncoder** benefits:
|
| 202 |
-
|
| 203 |
-
- Jointly encodes query + document
|
| 204 |
-
- Cross-attention between query and doc
|
| 205 |
-
- More accurate relevance scoring
|
| 206 |
-
- Slower (not suitable for entire catalog)
|
| 207 |
-
|
| 208 |
-
### Reranking Model
|
| 209 |
-
|
| 210 |
-
**Model**: `BAAI/bge-reranker-v2-m3`
|
| 211 |
-
|
| 212 |
-
**Characteristics**:
|
| 213 |
-
|
| 214 |
-
- Trained on MS-MARCO passage ranking
|
| 215 |
-
- 6 layers, fast inference (~50ms per pair)
|
| 216 |
-
- Direct relevance score (no embedding)
|
| 217 |
-
|
| 218 |
-
**Reranking process**:
|
| 219 |
-
|
| 220 |
-
```python
|
| 221 |
-
from sentence_transformers import CrossEncoder
|
| 222 |
-
|
| 223 |
-
reranker = CrossEncoder("BAAI/bge-reranker-v2-m3")
|
| 224 |
-
|
| 225 |
-
# Score each (query, candidate) pair
|
| 226 |
-
pairs = [(query, candidate.description) for candidate in candidates]
|
| 227 |
-
rerank_scores = reranker.predict(pairs)
|
| 228 |
-
|
| 229 |
-
# Re-sort by rerank scores
|
| 230 |
-
sorted_indices = np.argsort(rerank_scores)[::-1]
|
| 231 |
-
reranked_candidates = [candidates[i] for i in sorted_indices][:8]
|
| 232 |
-
```
|
| 233 |
-
|
| 234 |
-
**Output**: Top-8 candidates with refined ranking
|
| 235 |
-
|
| 236 |
-
## Output Format
|
| 237 |
-
|
| 238 |
-
### Candidate Schema
|
| 239 |
-
|
| 240 |
-
Each candidate passed to Stage 2:
|
| 241 |
-
|
| 242 |
-
```python
|
| 243 |
-
{
|
| 244 |
-
"name": "TotalSegmentator",
|
| 245 |
-
"description": "Automated multi-organ segmentation for CT and MRI",
|
| 246 |
-
"url": "https://github.com/wasserth/TotalSegmentator",
|
| 247 |
-
"keywords": ["segmentation", "medical-imaging", "CT", "MRI"],
|
| 248 |
-
"license": "Apache-2.0",
|
| 249 |
-
"supporting_data": {
|
| 250 |
-
"modalities": ["CT", "MRI"],
|
| 251 |
-
"dimensions": ["3D"],
|
| 252 |
-
"formats": ["DICOM", "NIfTI"],
|
| 253 |
-
"demo_url": "https://huggingface.co/spaces/..."
|
| 254 |
-
},
|
| 255 |
-
"retrieval_score": 0.85, # FAISS or rerank score
|
| 256 |
-
}
|
| 257 |
-
```
|
| 258 |
-
|
| 259 |
-
**Fields used by VLM**:
|
| 260 |
-
|
| 261 |
-
- Essential for understanding tool capability
|
| 262 |
-
- Formatted as table in VLM prompt
|
| 263 |
-
- Enables comparative reasoning
|
| 264 |
-
|
| 265 |
-
## Index Management
|
| 266 |
-
|
| 267 |
-
### Building the Index
|
| 268 |
-
|
| 269 |
-
Done during catalog sync:
|
| 270 |
-
|
| 271 |
-
```bash
|
| 272 |
-
ai_agent sync
|
| 273 |
-
```
|
| 274 |
-
|
| 275 |
-
**Process**:
|
| 276 |
-
|
| 277 |
-
1. Load catalog JSONL
|
| 278 |
-
2. Embed each tool description
|
| 279 |
-
3. Build FAISS index
|
| 280 |
-
4. Save to disk: `artifacts/rag_index/`
|
| 281 |
-
|
| 282 |
-
**Files**:
|
| 283 |
-
```
|
| 284 |
-
artifacts/rag_index/
|
| 285 |
-
├── index.faiss # FAISS binary index
|
| 286 |
-
└── meta.json # Metadata (tool IDs, config)
|
| 287 |
-
```
|
| 288 |
-
|
| 289 |
-
### Loading the Index
|
| 290 |
-
|
| 291 |
-
At startup:
|
| 292 |
-
|
| 293 |
-
```python
|
| 294 |
-
from retriever.vector_index import VectorIndex
|
| 295 |
-
|
| 296 |
-
index = VectorIndex()
|
| 297 |
-
index.load("artifacts/rag_index")
|
| 298 |
-
|
| 299 |
-
# Ready for queries
|
| 300 |
-
results = index.search(query, k=20)
|
| 301 |
-
```
|
| 302 |
-
|
| 303 |
-
### Updating the Index
|
| 304 |
-
|
| 305 |
-
When catalog changes:
|
| 306 |
-
1. Sync detects new/modified tools
|
| 307 |
-
2. Re-embed entire catalog (fast, ~2 seconds)
|
| 308 |
-
3. Rebuild FAISS index
|
| 309 |
-
4. Reload in pipeline (no restart needed)
|
| 310 |
-
|
| 311 |
-
## Performance Optimization
|
| 312 |
-
|
| 313 |
-
### Caching
|
| 314 |
-
|
| 315 |
-
**Model loading**:
|
| 316 |
-
|
| 317 |
-
- BGE-M3 and CrossEncoder loaded once at startup
|
| 318 |
-
- Kept in memory for entire session
|
| 319 |
-
|
| 320 |
-
**Index loading**:
|
| 321 |
-
|
| 322 |
-
- FAISS index loaded once
|
| 323 |
-
- Small enough to fit in memory (~MB)
|
| 324 |
-
|
| 325 |
-
### Batch Processing
|
| 326 |
-
|
| 327 |
-
For multiple queries (testing, batch mode):
|
| 328 |
-
|
| 329 |
-
```python
|
| 330 |
-
# Batch embed multiple queries
|
| 331 |
-
query_vectors = model.encode(queries, batch_size=32)
|
| 332 |
-
|
| 333 |
-
# Batch FAISS search
|
| 334 |
-
scores, indices = index.search(query_vectors, k=20)
|
| 335 |
-
```
|
| 336 |
-
|
| 337 |
-
### GPU Acceleration
|
| 338 |
-
|
| 339 |
-
Models can use GPU if available:
|
| 340 |
-
|
| 341 |
-
```python
|
| 342 |
-
model = SentenceTransformer("BAAI/bge-m3", device="cuda")
|
| 343 |
-
reranker = CrossEncoder("...", device="cuda")
|
| 344 |
-
```
|
| 345 |
-
|
| 346 |
-
## Retrieval Metrics
|
| 347 |
-
|
| 348 |
-
### Monitored Metrics
|
| 349 |
-
|
| 350 |
-
During retrieval:
|
| 351 |
-
|
| 352 |
-
- **Number of candidates found**: Should be ≥8 for good coverage
|
| 353 |
-
- **Average similarity score**: Higher = better match
|
| 354 |
-
- **Reranking impact**: Score change after reranking
|
| 355 |
-
- **Retry usage**: Whether broadening retry was triggered
|
| 356 |
-
|
| 357 |
-
### Logging
|
| 358 |
-
|
| 359 |
-
Retrieval events logged:
|
| 360 |
-
|
| 361 |
-
```
|
| 362 |
-
INFO retriever.vector_index: FAISS search: query="segment lungs", results=20, top_score=0.85
|
| 363 |
-
INFO retriever.reranker: Reranking 20 candidates, top_score_change=+0.12
|
| 364 |
-
INFO retriever.pipeline: Final candidates: 8, avg_score=0.78
|
| 365 |
-
```
|
| 366 |
-
|
| 367 |
-
## Limitations
|
| 368 |
-
|
| 369 |
-
### Current Limitations
|
| 370 |
-
|
| 371 |
-
1. **English only**: No multilingual support (though model is capable)
|
| 372 |
-
2. **Small catalog**: ~150 tools (FAISS overkill, but scales)
|
| 373 |
-
3. **No filtering**: Can't filter by license, modality in retrieval (done in Stage 2)
|
| 374 |
-
4. **Heuristic retries**: Broadening strategy is simple prefix-based shortening
|
| 375 |
-
|
| 376 |
-
### Future Enhancements
|
| 377 |
-
|
| 378 |
-
- **Hybrid search**: Combine semantic + keyword (BM25)
|
| 379 |
-
- **Metadata filters**: Pre-filter by modality, license, format
|
| 380 |
-
- **Personalization**: User history, preferences
|
| 381 |
-
- **Adaptive retries**: Learn better broadening formulations from query logs
|
| 382 |
-
|
| 383 |
-
## Next Steps
|
| 384 |
-
|
| 385 |
-
- Learn about [Agent & VLM Selection](agent.md)
|
| 386 |
-
- Explore [Software Catalog](catalog.md)
|
| 387 |
-
- Return to [Architecture Overview](overview.md)
|
|
|
|
| 1 |
+
# Retrieval Pipeline
|
| 2 |
+
|
| 3 |
+
The retrieval stage is the first phase of the AI Imaging Agent's two-stage pipeline. It performs fast text-based search to find candidate tools from the software catalog.
|
| 4 |
+
|
| 5 |
+
## Overview
|
| 6 |
+
|
| 7 |
+
**Goal**: Quickly narrow down the software catalog to most relevant candidates
|
| 8 |
+
|
| 9 |
+
**Characteristics**:
|
| 10 |
+
|
| 11 |
+
- ⚡ Fast (~100-300ms)
|
| 12 |
+
- 🔢 Deterministic and reproducible
|
| 13 |
+
- 🚫 No LLM calls
|
| 14 |
+
- 💰 Low cost (no API fees)
|
| 15 |
+
|
| 16 |
+
## Pipeline Stages
|
| 17 |
+
|
| 18 |
+
```mermaid
|
| 19 |
+
graph LR
|
| 20 |
+
A[User Query] --> B[Query Enhancement]
|
| 21 |
+
B --> C[Embedding]
|
| 22 |
+
C --> D[FAISS Search]
|
| 23 |
+
D --> E[CrossEncoder Rerank]
|
| 24 |
+
E --> F[Top-K Candidates]
|
| 25 |
+
```
|
| 26 |
+
|
| 27 |
+
## Step 1: Query Enhancement
|
| 28 |
+
|
| 29 |
+
### Format Token Injection
|
| 30 |
+
|
| 31 |
+
When users upload files, format tokens are added to the query:
|
| 32 |
+
|
| 33 |
+
```python
|
| 34 |
+
# User uploads: scan.dcm
|
| 35 |
+
# User query: "segment lungs"
|
| 36 |
+
|
| 37 |
+
# Enhanced query:
|
| 38 |
+
"segment lungs format:DICOM format:CT format:3D"
|
| 39 |
+
```
|
| 40 |
+
|
| 41 |
+
**Format tokens added**:
|
| 42 |
+
|
| 43 |
+
- File extension (`format:DICOM`, `format:NIfTI`)
|
| 44 |
+
- Image modality from metadata (`format:CT`, `format:MRI`)
|
| 45 |
+
- Dimensionality (`format:2D`, `format:3D`, `format:4D`)
|
| 46 |
+
|
| 47 |
+
**Why this helps**:
|
| 48 |
+
|
| 49 |
+
- Matches tools that support specific formats
|
| 50 |
+
- Boosts DICOM-compatible tools for DICOM input
|
| 51 |
+
- Ensures dimension compatibility (3D tools for volumes)
|
| 52 |
+
|
| 53 |
+
### Control Tag Processing
|
| 54 |
+
|
| 55 |
+
Special tags are extracted and processed:
|
| 56 |
+
|
| 57 |
+
```python
|
| 58 |
+
query = "segment lungs [EXCLUDE:tool1|tool2]"
|
| 59 |
+
|
| 60 |
+
# Extracted:
|
| 61 |
+
clean_query = "segment lungs"
|
| 62 |
+
excluded_tools = ["tool1", "tool2"]
|
| 63 |
+
```
|
| 64 |
+
|
| 65 |
+
**Supported tags**:
|
| 66 |
+
- `[EXCLUDE:tool1|tool2]`: Filter tools from results
|
| 67 |
+
|
| 68 |
+
## Step 2: Metadata-Aware Querying
|
| 69 |
+
|
| 70 |
+
The pipeline does not perform semantic vocabulary expansion. Instead, retrieval combines:
|
| 71 |
+
|
| 72 |
+
- cleaned task text
|
| 73 |
+
- format tokens inferred from uploaded files (for example `format:DICOM`)
|
| 74 |
+
- compact image metadata hints (modality/anatomy/dimensionality when available)
|
| 75 |
+
|
| 76 |
+
This keeps retrieval deterministic and closely tied to the user's data.
|
| 77 |
+
|
| 78 |
+
### Alternative Query Generation
|
| 79 |
+
|
| 80 |
+
On retry (when initial results < 5 tools):
|
| 81 |
+
|
| 82 |
+
```python
|
| 83 |
+
# Initial query
|
| 84 |
+
query1 = "segment rare pulmonary structure"
|
| 85 |
+
results = 2 tools # Too few!
|
| 86 |
+
|
| 87 |
+
# Retry 1: Broader formulation (keep first 2-3 words)
|
| 88 |
+
query2 = "segment rare pulmonary"
|
| 89 |
+
results = 7 tools # Better!
|
| 90 |
+
|
| 91 |
+
# Retry 2: If still insufficient, repeat with same broadening strategy
|
| 92 |
+
```
|
| 93 |
+
|
| 94 |
+
**Max retries**: 2
|
| 95 |
+
|
| 96 |
+
## Step 3: Embedding
|
| 97 |
+
|
| 98 |
+
### BGE-M3 Model
|
| 99 |
+
|
| 100 |
+
**Model**: `BAAI/bge-m3`
|
| 101 |
+
|
| 102 |
+
**Characteristics**:
|
| 103 |
+
|
| 104 |
+
- Multilingual (but used for English)
|
| 105 |
+
- 1024-dimensional embeddings
|
| 106 |
+
- Trained for retrieval tasks
|
| 107 |
+
- Fast inference (~10ms per query)
|
| 108 |
+
|
| 109 |
+
**Embedding process**:
|
| 110 |
+
|
| 111 |
+
```python
|
| 112 |
+
from sentence_transformers import SentenceTransformer
|
| 113 |
+
|
| 114 |
+
model = SentenceTransformer("BAAI/bge-m3")
|
| 115 |
+
query_vector = model.encode(
|
| 116 |
+
query,
|
| 117 |
+
normalize_embeddings=True # L2 normalization for cosine similarity
|
| 118 |
+
)
|
| 119 |
+
# Returns: np.array of shape (1024,)
|
| 120 |
+
```
|
| 121 |
+
|
| 122 |
+
### Catalog Embedding
|
| 123 |
+
|
| 124 |
+
Software tools are pre-embedded during indexing:
|
| 125 |
+
|
| 126 |
+
```python
|
| 127 |
+
# For each tool in catalog:
|
| 128 |
+
tool_text = f"{tool.name} {tool.description} {' '.join(tool.keywords)}"
|
| 129 |
+
tool_vector = model.encode(tool_text, normalize_embeddings=True)
|
| 130 |
+
|
| 131 |
+
# Store in FAISS index
|
| 132 |
+
faiss_index.add(tool_vector)
|
| 133 |
+
```
|
| 134 |
+
|
| 135 |
+
**Index structure**:
|
| 136 |
+
|
| 137 |
+
- FAISS IndexFlatIP (inner product = cosine similarity for normalized vectors)
|
| 138 |
+
- ~150 tools in current catalog
|
| 139 |
+
- Index size: ~600KB
|
| 140 |
+
|
| 141 |
+
## Step 4: FAISS Search
|
| 142 |
+
|
| 143 |
+
### Vector Search
|
| 144 |
+
|
| 145 |
+
FAISS performs fast similarity search:
|
| 146 |
+
|
| 147 |
+
```python
|
| 148 |
+
import faiss
|
| 149 |
+
|
| 150 |
+
# Search for top 20 most similar tools
|
| 151 |
+
scores, indices = faiss_index.search(
|
| 152 |
+
query_vector.reshape(1, -1),
|
| 153 |
+
k=20
|
| 154 |
+
)
|
| 155 |
+
|
| 156 |
+
# Returns:
|
| 157 |
+
# scores: [0.85, 0.82, 0.79, ...] # Cosine similarities
|
| 158 |
+
# indices: [42, 17, 89, ...] # Tool IDs in catalog
|
| 159 |
+
```
|
| 160 |
+
|
| 161 |
+
**Search algorithm**:
|
| 162 |
+
|
| 163 |
+
- IndexFlatIP: Exact search (brute force)
|
| 164 |
+
- Fast for catalog size (~150 tools)
|
| 165 |
+
- Could use IVF for larger catalogs (>10k tools)
|
| 166 |
+
|
| 167 |
+
**Why top-20**:
|
| 168 |
+
|
| 169 |
+
- More candidates than needed (default final: 8)
|
| 170 |
+
- Provides options for reranking
|
| 171 |
+
- Balances recall vs. later stage cost
|
| 172 |
+
|
| 173 |
+
### Candidate Retrieval
|
| 174 |
+
|
| 175 |
+
```python
|
| 176 |
+
candidates = [catalog[idx] for idx in indices[:20]]
|
| 177 |
+
candidate_scores = scores[:20].tolist()
|
| 178 |
+
|
| 179 |
+
# Example candidates:
|
| 180 |
+
[
|
| 181 |
+
{
|
| 182 |
+
"name": "TotalSegmentator",
|
| 183 |
+
"score": 0.85,
|
| 184 |
+
"description": "Automated multi-organ segmentation...",
|
| 185 |
+
...
|
| 186 |
+
},
|
| 187 |
+
...
|
| 188 |
+
]
|
| 189 |
+
```
|
| 190 |
+
|
| 191 |
+
## Step 5: CrossEncoder Reranking
|
| 192 |
+
|
| 193 |
+
### Why Rerank?
|
| 194 |
+
|
| 195 |
+
**BiEncoder (BGE-M3)** limitations:
|
| 196 |
+
|
| 197 |
+
- Encodes query and documents independently
|
| 198 |
+
- No query-document interaction
|
| 199 |
+
- Misses subtle relevance signals
|
| 200 |
+
|
| 201 |
+
**CrossEncoder** benefits:
|
| 202 |
+
|
| 203 |
+
- Jointly encodes query + document
|
| 204 |
+
- Cross-attention between query and doc
|
| 205 |
+
- More accurate relevance scoring
|
| 206 |
+
- Slower (not suitable for entire catalog)
|
| 207 |
+
|
| 208 |
+
### Reranking Model
|
| 209 |
+
|
| 210 |
+
**Model**: `BAAI/bge-reranker-v2-m3`
|
| 211 |
+
|
| 212 |
+
**Characteristics**:
|
| 213 |
+
|
| 214 |
+
- Trained on MS-MARCO passage ranking
|
| 215 |
+
- 6 layers, fast inference (~50ms per pair)
|
| 216 |
+
- Direct relevance score (no embedding)
|
| 217 |
+
|
| 218 |
+
**Reranking process**:
|
| 219 |
+
|
| 220 |
+
```python
|
| 221 |
+
from sentence_transformers import CrossEncoder
|
| 222 |
+
|
| 223 |
+
reranker = CrossEncoder("BAAI/bge-reranker-v2-m3")
|
| 224 |
+
|
| 225 |
+
# Score each (query, candidate) pair
|
| 226 |
+
pairs = [(query, candidate.description) for candidate in candidates]
|
| 227 |
+
rerank_scores = reranker.predict(pairs)
|
| 228 |
+
|
| 229 |
+
# Re-sort by rerank scores
|
| 230 |
+
sorted_indices = np.argsort(rerank_scores)[::-1]
|
| 231 |
+
reranked_candidates = [candidates[i] for i in sorted_indices][:8]
|
| 232 |
+
```
|
| 233 |
+
|
| 234 |
+
**Output**: Top-8 candidates with refined ranking
|
| 235 |
+
|
| 236 |
+
## Output Format
|
| 237 |
+
|
| 238 |
+
### Candidate Schema
|
| 239 |
+
|
| 240 |
+
Each candidate passed to Stage 2:
|
| 241 |
+
|
| 242 |
+
```python
|
| 243 |
+
{
|
| 244 |
+
"name": "TotalSegmentator",
|
| 245 |
+
"description": "Automated multi-organ segmentation for CT and MRI",
|
| 246 |
+
"url": "https://github.com/wasserth/TotalSegmentator",
|
| 247 |
+
"keywords": ["segmentation", "medical-imaging", "CT", "MRI"],
|
| 248 |
+
"license": "Apache-2.0",
|
| 249 |
+
"supporting_data": {
|
| 250 |
+
"modalities": ["CT", "MRI"],
|
| 251 |
+
"dimensions": ["3D"],
|
| 252 |
+
"formats": ["DICOM", "NIfTI"],
|
| 253 |
+
"demo_url": "https://huggingface.co/spaces/..."
|
| 254 |
+
},
|
| 255 |
+
"retrieval_score": 0.85, # FAISS or rerank score
|
| 256 |
+
}
|
| 257 |
+
```
|
| 258 |
+
|
| 259 |
+
**Fields used by VLM**:
|
| 260 |
+
|
| 261 |
+
- Essential for understanding tool capability
|
| 262 |
+
- Formatted as table in VLM prompt
|
| 263 |
+
- Enables comparative reasoning
|
| 264 |
+
|
| 265 |
+
## Index Management
|
| 266 |
+
|
| 267 |
+
### Building the Index
|
| 268 |
+
|
| 269 |
+
Done during catalog sync:
|
| 270 |
+
|
| 271 |
+
```bash
|
| 272 |
+
ai_agent sync
|
| 273 |
+
```
|
| 274 |
+
|
| 275 |
+
**Process**:
|
| 276 |
+
|
| 277 |
+
1. Load catalog JSONL
|
| 278 |
+
2. Embed each tool description
|
| 279 |
+
3. Build FAISS index
|
| 280 |
+
4. Save to disk: `artifacts/rag_index/`
|
| 281 |
+
|
| 282 |
+
**Files**:
|
| 283 |
+
```
|
| 284 |
+
artifacts/rag_index/
|
| 285 |
+
├── index.faiss # FAISS binary index
|
| 286 |
+
└── meta.json # Metadata (tool IDs, config)
|
| 287 |
+
```
|
| 288 |
+
|
| 289 |
+
### Loading the Index
|
| 290 |
+
|
| 291 |
+
At startup:
|
| 292 |
+
|
| 293 |
+
```python
|
| 294 |
+
from retriever.vector_index import VectorIndex
|
| 295 |
+
|
| 296 |
+
index = VectorIndex()
|
| 297 |
+
index.load("artifacts/rag_index")
|
| 298 |
+
|
| 299 |
+
# Ready for queries
|
| 300 |
+
results = index.search(query, k=20)
|
| 301 |
+
```
|
| 302 |
+
|
| 303 |
+
### Updating the Index
|
| 304 |
+
|
| 305 |
+
When catalog changes:
|
| 306 |
+
1. Sync detects new/modified tools
|
| 307 |
+
2. Re-embed entire catalog (fast, ~2 seconds)
|
| 308 |
+
3. Rebuild FAISS index
|
| 309 |
+
4. Reload in pipeline (no restart needed)
|
| 310 |
+
|
| 311 |
+
## Performance Optimization
|
| 312 |
+
|
| 313 |
+
### Caching
|
| 314 |
+
|
| 315 |
+
**Model loading**:
|
| 316 |
+
|
| 317 |
+
- BGE-M3 and CrossEncoder loaded once at startup
|
| 318 |
+
- Kept in memory for entire session
|
| 319 |
+
|
| 320 |
+
**Index loading**:
|
| 321 |
+
|
| 322 |
+
- FAISS index loaded once
|
| 323 |
+
- Small enough to fit in memory (~MB)
|
| 324 |
+
|
| 325 |
+
### Batch Processing
|
| 326 |
+
|
| 327 |
+
For multiple queries (testing, batch mode):
|
| 328 |
+
|
| 329 |
+
```python
|
| 330 |
+
# Batch embed multiple queries
|
| 331 |
+
query_vectors = model.encode(queries, batch_size=32)
|
| 332 |
+
|
| 333 |
+
# Batch FAISS search
|
| 334 |
+
scores, indices = index.search(query_vectors, k=20)
|
| 335 |
+
```
|
| 336 |
+
|
| 337 |
+
### GPU Acceleration
|
| 338 |
+
|
| 339 |
+
Models can use GPU if available:
|
| 340 |
+
|
| 341 |
+
```python
|
| 342 |
+
model = SentenceTransformer("BAAI/bge-m3", device="cuda")
|
| 343 |
+
reranker = CrossEncoder("...", device="cuda")
|
| 344 |
+
```
|
| 345 |
+
|
| 346 |
+
## Retrieval Metrics
|
| 347 |
+
|
| 348 |
+
### Monitored Metrics
|
| 349 |
+
|
| 350 |
+
During retrieval:
|
| 351 |
+
|
| 352 |
+
- **Number of candidates found**: Should be ≥8 for good coverage
|
| 353 |
+
- **Average similarity score**: Higher = better match
|
| 354 |
+
- **Reranking impact**: Score change after reranking
|
| 355 |
+
- **Retry usage**: Whether broadening retry was triggered
|
| 356 |
+
|
| 357 |
+
### Logging
|
| 358 |
+
|
| 359 |
+
Retrieval events logged:
|
| 360 |
+
|
| 361 |
+
```
|
| 362 |
+
INFO retriever.vector_index: FAISS search: query="segment lungs", results=20, top_score=0.85
|
| 363 |
+
INFO retriever.reranker: Reranking 20 candidates, top_score_change=+0.12
|
| 364 |
+
INFO retriever.pipeline: Final candidates: 8, avg_score=0.78
|
| 365 |
+
```
|
| 366 |
+
|
| 367 |
+
## Limitations
|
| 368 |
+
|
| 369 |
+
### Current Limitations
|
| 370 |
+
|
| 371 |
+
1. **English only**: No multilingual support (though model is capable)
|
| 372 |
+
2. **Small catalog**: ~150 tools (FAISS overkill, but scales)
|
| 373 |
+
3. **No filtering**: Can't filter by license, modality in retrieval (done in Stage 2)
|
| 374 |
+
4. **Heuristic retries**: Broadening strategy is simple prefix-based shortening
|
| 375 |
+
|
| 376 |
+
### Future Enhancements
|
| 377 |
+
|
| 378 |
+
- **Hybrid search**: Combine semantic + keyword (BM25)
|
| 379 |
+
- **Metadata filters**: Pre-filter by modality, license, format
|
| 380 |
+
- **Personalization**: User history, preferences
|
| 381 |
+
- **Adaptive retries**: Learn better broadening formulations from query logs
|
| 382 |
+
|
| 383 |
+
## Next Steps
|
| 384 |
+
|
| 385 |
+
- Learn about [Agent & VLM Selection](agent.md)
|
| 386 |
+
- Explore [Software Catalog](catalog.md)
|
| 387 |
+
- Return to [Architecture Overview](overview.md)
|
docs/development/contributing.md
CHANGED
|
@@ -1,315 +1,315 @@
|
|
| 1 |
-
# Contributing
|
| 2 |
-
|
| 3 |
-
Thank you for your interest in contributing to the AI Imaging Agent! This guide will help you get started.
|
| 4 |
-
|
| 5 |
-
## Getting Started
|
| 6 |
-
|
| 7 |
-
### 1. Fork and Clone
|
| 8 |
-
|
| 9 |
-
```bash
|
| 10 |
-
# Fork the repository on GitHub
|
| 11 |
-
# Then clone your fork
|
| 12 |
-
git clone https://github.com/YOUR_USERNAME/ai-agent.git
|
| 13 |
-
cd ai-agent
|
| 14 |
-
```
|
| 15 |
-
|
| 16 |
-
### 2. Set Up Development Environment
|
| 17 |
-
|
| 18 |
-
```bash
|
| 19 |
-
# Create virtual environment
|
| 20 |
-
python -m venv .venv
|
| 21 |
-
source .venv/bin/activate # On Windows: .venv\Scripts\activate
|
| 22 |
-
|
| 23 |
-
# Install in development mode
|
| 24 |
-
pip install -e ".[dev]"
|
| 25 |
-
```
|
| 26 |
-
|
| 27 |
-
### 3. Create a Branch
|
| 28 |
-
|
| 29 |
-
```bash
|
| 30 |
-
git checkout -b feature/your-feature-name
|
| 31 |
-
# or
|
| 32 |
-
git checkout -b fix/issue-description
|
| 33 |
-
```
|
| 34 |
-
|
| 35 |
-
## Development Workflow
|
| 36 |
-
|
| 37 |
-
### Making Changes
|
| 38 |
-
|
| 39 |
-
1. **Make your changes** in the appropriate module
|
| 40 |
-
2. **Test your changes** (see [Testing](testing.md))
|
| 41 |
-
3. **Update documentation** if needed
|
| 42 |
-
4. **Update CHANGELOG.md** following [Keep a Changelog](https://keepachangelog.com/) format
|
| 43 |
-
|
| 44 |
-
<!-- ### Code Style
|
| 45 |
-
|
| 46 |
-
We use standard Python tools for code quality:
|
| 47 |
-
|
| 48 |
-
#### Black
|
| 49 |
-
|
| 50 |
-
Code formatting:
|
| 51 |
-
|
| 52 |
-
```bash
|
| 53 |
-
black src/ tests/
|
| 54 |
-
```
|
| 55 |
-
|
| 56 |
-
#### Ruff
|
| 57 |
-
|
| 58 |
-
Linting:
|
| 59 |
-
|
| 60 |
-
```bash
|
| 61 |
-
ruff check src/ tests/
|
| 62 |
-
``` -->
|
| 63 |
-
|
| 64 |
-
#### Type Checking
|
| 65 |
-
|
| 66 |
-
MyPy for type checking:
|
| 67 |
-
|
| 68 |
-
```bash
|
| 69 |
-
mypy src/
|
| 70 |
-
```
|
| 71 |
-
|
| 72 |
-
### Running Tests
|
| 73 |
-
|
| 74 |
-
```bash
|
| 75 |
-
# Run all tests
|
| 76 |
-
pytest tests/
|
| 77 |
-
|
| 78 |
-
# Run specific test file
|
| 79 |
-
pytest tests/test_retrieval_pipeline.py
|
| 80 |
-
|
| 81 |
-
# Run with coverage
|
| 82 |
-
pytest --cov=ai_agent tests/
|
| 83 |
-
```
|
| 84 |
-
|
| 85 |
-
## Contribution Guidelines
|
| 86 |
-
|
| 87 |
-
### Code Quality
|
| 88 |
-
|
| 89 |
-
- **Follow PEP 8**: Use Black for formatting
|
| 90 |
-
- **Type hints**: Add type annotations to new functions
|
| 91 |
-
- **Docstrings**: Document all public functions and classes
|
| 92 |
-
- **Tests**: Add tests for new functionality
|
| 93 |
-
- **No warnings**: Fix any linter warnings before submitting
|
| 94 |
-
|
| 95 |
-
### Commit Messages
|
| 96 |
-
|
| 97 |
-
Use clear, descriptive commit messages:
|
| 98 |
-
|
| 99 |
-
```
|
| 100 |
-
feat: Add alternative search tool for agent
|
| 101 |
-
|
| 102 |
-
- Implement search_alternative tool
|
| 103 |
-
- Add retry logic for insufficient results
|
| 104 |
-
- Update agent prompt with tool description
|
| 105 |
-
```
|
| 106 |
-
|
| 107 |
-
**Format**:
|
| 108 |
-
|
| 109 |
-
- `feat:` New features
|
| 110 |
-
- `fix:` Bug fixes
|
| 111 |
-
- `docs:` Documentation changes
|
| 112 |
-
- `test:` Test additions/changes
|
| 113 |
-
- `refactor:` Code refactoring
|
| 114 |
-
- `chore:` Maintenance tasks
|
| 115 |
-
|
| 116 |
-
### Pull Requests
|
| 117 |
-
|
| 118 |
-
1. **Update CHANGELOG.md** under `[Unreleased]` section
|
| 119 |
-
2. **Write clear PR description** explaining changes
|
| 120 |
-
3. **Link related issues** if applicable
|
| 121 |
-
4. **Request review** from maintainers
|
| 122 |
-
|
| 123 |
-
**PR description template**:
|
| 124 |
-
|
| 125 |
-
```markdown
|
| 126 |
-
## Description
|
| 127 |
-
Brief description of changes
|
| 128 |
-
|
| 129 |
-
## Type of Change
|
| 130 |
-
- [ ] Bug fix
|
| 131 |
-
- [ ] New feature
|
| 132 |
-
- [ ] Documentation update
|
| 133 |
-
- [ ] Refactoring
|
| 134 |
-
|
| 135 |
-
## Testing
|
| 136 |
-
How to test these changes
|
| 137 |
-
|
| 138 |
-
## Checklist
|
| 139 |
-
- [ ] Code follows project style
|
| 140 |
-
- [ ] Tests added/updated
|
| 141 |
-
- [ ] Documentation updated
|
| 142 |
-
- [ ] CHANGELOG.md updated
|
| 143 |
-
```
|
| 144 |
-
|
| 145 |
-
## Areas to Contribute
|
| 146 |
-
|
| 147 |
-
### High Priority
|
| 148 |
-
|
| 149 |
-
- **Catalog expansion**: Add more imaging tools
|
| 150 |
-
- **Demo integration**: Improve Gradio Space execution and add new spaces for tools
|
| 151 |
-
- **Format support**: Add new image format handlers
|
| 152 |
-
|
| 153 |
-
<!-- ### Good First Issues
|
| 154 |
-
|
| 155 |
-
Look for issues tagged `good-first-issue` on GitHub:
|
| 156 |
-
|
| 157 |
-
- Bug fixes
|
| 158 |
-
- Documentation improvements
|
| 159 |
-
- Test coverage expansion
|
| 160 |
-
- Example scripts -->
|
| 161 |
-
|
| 162 |
-
### Feature Requests
|
| 163 |
-
|
| 164 |
-
Before implementing new features:
|
| 165 |
-
|
| 166 |
-
1. **Check existing issues** for similar requests
|
| 167 |
-
2. **Open an issue** to discuss the feature
|
| 168 |
-
3. **Get feedback** from maintainers
|
| 169 |
-
4. **Implement** after discussion
|
| 170 |
-
|
| 171 |
-
## Documentation
|
| 172 |
-
|
| 173 |
-
### Writing Documentation
|
| 174 |
-
|
| 175 |
-
Documentation lives in `docs/` and uses MkDocs Material.
|
| 176 |
-
|
| 177 |
-
```bash
|
| 178 |
-
# Install MkDocs
|
| 179 |
-
pip install mkdocs-material
|
| 180 |
-
|
| 181 |
-
# Serve locally
|
| 182 |
-
mkdocs serve
|
| 183 |
-
|
| 184 |
-
# Open http://127.0.0.1:8000
|
| 185 |
-
```
|
| 186 |
-
|
| 187 |
-
### Documentation Style
|
| 188 |
-
|
| 189 |
-
- Use **clear headings** and structure
|
| 190 |
-
- Include **code examples** where relevant
|
| 191 |
-
- Add **warnings** and **tips** for important information
|
| 192 |
-
- Keep **language simple** and accessible
|
| 193 |
-
|
| 194 |
-
## Adding Tools to Catalog
|
| 195 |
-
|
| 196 |
-
### Process
|
| 197 |
-
|
| 198 |
-
1. **Create tool entry** in `dataset/catalog.jsonl`:
|
| 199 |
-
|
| 200 |
-
```json
|
| 201 |
-
{
|
| 202 |
-
"@type": "SoftwareSourceCode",
|
| 203 |
-
"name": "ToolName",
|
| 204 |
-
"description": "Tool description",
|
| 205 |
-
"url": "https://github.com/user/tool",
|
| 206 |
-
"license": "Apache-2.0",
|
| 207 |
-
"keywords": ["segmentation", "CT"],
|
| 208 |
-
"supportingData": {
|
| 209 |
-
"modalities": ["CT"],
|
| 210 |
-
"dimensions": ["3D"],
|
| 211 |
-
"formats": ["DICOM"],
|
| 212 |
-
"demo_url": "https://huggingface.co/spaces/user/tool"
|
| 213 |
-
}
|
| 214 |
-
}
|
| 215 |
-
```
|
| 216 |
-
|
| 217 |
-
2. **Validate entry**:
|
| 218 |
-
|
| 219 |
-
```bash
|
| 220 |
-
# Check JSON syntax
|
| 221 |
-
python -c "import json; print(json.loads('YOUR_JSON_HERE'))"
|
| 222 |
-
```
|
| 223 |
-
|
| 224 |
-
3. **Update checksum**:
|
| 225 |
-
|
| 226 |
-
```bash
|
| 227 |
-
shasum dataset/catalog.jsonl > dataset/catalog.jsonl.sha1
|
| 228 |
-
```
|
| 229 |
-
|
| 230 |
-
4. **Sync catalog**:
|
| 231 |
-
|
| 232 |
-
```bash
|
| 233 |
-
ai_agent sync
|
| 234 |
-
```
|
| 235 |
-
|
| 236 |
-
5. **Test retrieval**:
|
| 237 |
-
|
| 238 |
-
```bash
|
| 239 |
-
ai_agent chat
|
| 240 |
-
# Try queries that should return your new tool
|
| 241 |
-
```
|
| 242 |
-
|
| 243 |
-
### Tool Criteria
|
| 244 |
-
|
| 245 |
-
Tools should:
|
| 246 |
-
|
| 247 |
-
- ✅ Be relevant to imaging analysis
|
| 248 |
-
- ✅ Be actively maintained
|
| 249 |
-
- ✅ Have clear documentation
|
| 250 |
-
- ✅ Preferably have a runnable demo
|
| 251 |
-
- ✅ Be open-source or have free tier
|
| 252 |
-
|
| 253 |
-
## Reporting Issues
|
| 254 |
-
|
| 255 |
-
### Bug Reports
|
| 256 |
-
|
| 257 |
-
Include:
|
| 258 |
-
|
| 259 |
-
- **Description** of the bug
|
| 260 |
-
- **Steps to reproduce**
|
| 261 |
-
- **Expected behavior**
|
| 262 |
-
- **Actual behavior**
|
| 263 |
-
- **Environment** (OS, Python version, etc.)
|
| 264 |
-
- **Logs** if available
|
| 265 |
-
|
| 266 |
-
### Feature Requests
|
| 267 |
-
|
| 268 |
-
Include:
|
| 269 |
-
|
| 270 |
-
- **Use case** for the feature
|
| 271 |
-
- **Proposed solution** (if you have one)
|
| 272 |
-
- **Alternatives considered**
|
| 273 |
-
- **Examples** of similar features
|
| 274 |
-
|
| 275 |
-
## Code Review Process
|
| 276 |
-
|
| 277 |
-
1. **Automated checks** run on PR (tests, linting)
|
| 278 |
-
2. **Maintainer review** provides feedback
|
| 279 |
-
3. **Address feedback** and update PR
|
| 280 |
-
4. **Approval** from maintainer(s)
|
| 281 |
-
5. **Merge** into main branch
|
| 282 |
-
|
| 283 |
-
## Release Process
|
| 284 |
-
|
| 285 |
-
Releases follow semantic versioning:
|
| 286 |
-
|
| 287 |
-
1. Update version in `pyproject.toml`
|
| 288 |
-
2. Update `CHANGELOG.md` with release date
|
| 289 |
-
3. Create git tag: `git tag v0.2.0`
|
| 290 |
-
4. Push tag: `git push origin v0.2.0`
|
| 291 |
-
5. GitHub Actions deploys documentation
|
| 292 |
-
|
| 293 |
-
## Getting Help
|
| 294 |
-
|
| 295 |
-
- **GitHub Discussions**: Ask questions
|
| 296 |
-
- **GitHub Issues**: Report bugs
|
| 297 |
-
|
| 298 |
-
## Code of Conduct
|
| 299 |
-
|
| 300 |
-
Be respectful and professional:
|
| 301 |
-
|
| 302 |
-
- Use welcoming and inclusive language
|
| 303 |
-
- Respect differing viewpoints
|
| 304 |
-
- Accept constructive criticism
|
| 305 |
-
- Focus on what's best for the community
|
| 306 |
-
|
| 307 |
-
## License
|
| 308 |
-
|
| 309 |
-
By contributing, you agree that your contributions will be licensed under the Apache 2.0 License.
|
| 310 |
-
|
| 311 |
-
## Next Steps
|
| 312 |
-
|
| 313 |
-
- Review [Project Structure](structure.md)
|
| 314 |
-
- Learn about [Testing](testing.md)
|
| 315 |
-
- Read [Architecture Overview](../architecture/overview.md)
|
|
|
|
| 1 |
+
# Contributing
|
| 2 |
+
|
| 3 |
+
Thank you for your interest in contributing to the AI Imaging Agent! This guide will help you get started.
|
| 4 |
+
|
| 5 |
+
## Getting Started
|
| 6 |
+
|
| 7 |
+
### 1. Fork and Clone
|
| 8 |
+
|
| 9 |
+
```bash
|
| 10 |
+
# Fork the repository on GitHub
|
| 11 |
+
# Then clone your fork
|
| 12 |
+
git clone https://github.com/YOUR_USERNAME/ai-agent.git
|
| 13 |
+
cd ai-agent
|
| 14 |
+
```
|
| 15 |
+
|
| 16 |
+
### 2. Set Up Development Environment
|
| 17 |
+
|
| 18 |
+
```bash
|
| 19 |
+
# Create virtual environment
|
| 20 |
+
python -m venv .venv
|
| 21 |
+
source .venv/bin/activate # On Windows: .venv\Scripts\activate
|
| 22 |
+
|
| 23 |
+
# Install in development mode
|
| 24 |
+
pip install -e ".[dev]"
|
| 25 |
+
```
|
| 26 |
+
|
| 27 |
+
### 3. Create a Branch
|
| 28 |
+
|
| 29 |
+
```bash
|
| 30 |
+
git checkout -b feature/your-feature-name
|
| 31 |
+
# or
|
| 32 |
+
git checkout -b fix/issue-description
|
| 33 |
+
```
|
| 34 |
+
|
| 35 |
+
## Development Workflow
|
| 36 |
+
|
| 37 |
+
### Making Changes
|
| 38 |
+
|
| 39 |
+
1. **Make your changes** in the appropriate module
|
| 40 |
+
2. **Test your changes** (see [Testing](testing.md))
|
| 41 |
+
3. **Update documentation** if needed
|
| 42 |
+
4. **Update CHANGELOG.md** following [Keep a Changelog](https://keepachangelog.com/) format
|
| 43 |
+
|
| 44 |
+
<!-- ### Code Style
|
| 45 |
+
|
| 46 |
+
We use standard Python tools for code quality:
|
| 47 |
+
|
| 48 |
+
#### Black
|
| 49 |
+
|
| 50 |
+
Code formatting:
|
| 51 |
+
|
| 52 |
+
```bash
|
| 53 |
+
black src/ tests/
|
| 54 |
+
```
|
| 55 |
+
|
| 56 |
+
#### Ruff
|
| 57 |
+
|
| 58 |
+
Linting:
|
| 59 |
+
|
| 60 |
+
```bash
|
| 61 |
+
ruff check src/ tests/
|
| 62 |
+
``` -->
|
| 63 |
+
|
| 64 |
+
#### Type Checking
|
| 65 |
+
|
| 66 |
+
MyPy for type checking:
|
| 67 |
+
|
| 68 |
+
```bash
|
| 69 |
+
mypy src/
|
| 70 |
+
```
|
| 71 |
+
|
| 72 |
+
### Running Tests
|
| 73 |
+
|
| 74 |
+
```bash
|
| 75 |
+
# Run all tests
|
| 76 |
+
pytest tests/
|
| 77 |
+
|
| 78 |
+
# Run specific test file
|
| 79 |
+
pytest tests/test_retrieval_pipeline.py
|
| 80 |
+
|
| 81 |
+
# Run with coverage
|
| 82 |
+
pytest --cov=ai_agent tests/
|
| 83 |
+
```
|
| 84 |
+
|
| 85 |
+
## Contribution Guidelines
|
| 86 |
+
|
| 87 |
+
### Code Quality
|
| 88 |
+
|
| 89 |
+
- **Follow PEP 8**: Use Black for formatting
|
| 90 |
+
- **Type hints**: Add type annotations to new functions
|
| 91 |
+
- **Docstrings**: Document all public functions and classes
|
| 92 |
+
- **Tests**: Add tests for new functionality
|
| 93 |
+
- **No warnings**: Fix any linter warnings before submitting
|
| 94 |
+
|
| 95 |
+
### Commit Messages
|
| 96 |
+
|
| 97 |
+
Use clear, descriptive commit messages:
|
| 98 |
+
|
| 99 |
+
```
|
| 100 |
+
feat: Add alternative search tool for agent
|
| 101 |
+
|
| 102 |
+
- Implement search_alternative tool
|
| 103 |
+
- Add retry logic for insufficient results
|
| 104 |
+
- Update agent prompt with tool description
|
| 105 |
+
```
|
| 106 |
+
|
| 107 |
+
**Format**:
|
| 108 |
+
|
| 109 |
+
- `feat:` New features
|
| 110 |
+
- `fix:` Bug fixes
|
| 111 |
+
- `docs:` Documentation changes
|
| 112 |
+
- `test:` Test additions/changes
|
| 113 |
+
- `refactor:` Code refactoring
|
| 114 |
+
- `chore:` Maintenance tasks
|
| 115 |
+
|
| 116 |
+
### Pull Requests
|
| 117 |
+
|
| 118 |
+
1. **Update CHANGELOG.md** under `[Unreleased]` section
|
| 119 |
+
2. **Write clear PR description** explaining changes
|
| 120 |
+
3. **Link related issues** if applicable
|
| 121 |
+
4. **Request review** from maintainers
|
| 122 |
+
|
| 123 |
+
**PR description template**:
|
| 124 |
+
|
| 125 |
+
```markdown
|
| 126 |
+
## Description
|
| 127 |
+
Brief description of changes
|
| 128 |
+
|
| 129 |
+
## Type of Change
|
| 130 |
+
- [ ] Bug fix
|
| 131 |
+
- [ ] New feature
|
| 132 |
+
- [ ] Documentation update
|
| 133 |
+
- [ ] Refactoring
|
| 134 |
+
|
| 135 |
+
## Testing
|
| 136 |
+
How to test these changes
|
| 137 |
+
|
| 138 |
+
## Checklist
|
| 139 |
+
- [ ] Code follows project style
|
| 140 |
+
- [ ] Tests added/updated
|
| 141 |
+
- [ ] Documentation updated
|
| 142 |
+
- [ ] CHANGELOG.md updated
|
| 143 |
+
```
|
| 144 |
+
|
| 145 |
+
## Areas to Contribute
|
| 146 |
+
|
| 147 |
+
### High Priority
|
| 148 |
+
|
| 149 |
+
- **Catalog expansion**: Add more imaging tools
|
| 150 |
+
- **Demo integration**: Improve Gradio Space execution and add new spaces for tools
|
| 151 |
+
- **Format support**: Add new image format handlers
|
| 152 |
+
|
| 153 |
+
<!-- ### Good First Issues
|
| 154 |
+
|
| 155 |
+
Look for issues tagged `good-first-issue` on GitHub:
|
| 156 |
+
|
| 157 |
+
- Bug fixes
|
| 158 |
+
- Documentation improvements
|
| 159 |
+
- Test coverage expansion
|
| 160 |
+
- Example scripts -->
|
| 161 |
+
|
| 162 |
+
### Feature Requests
|
| 163 |
+
|
| 164 |
+
Before implementing new features:
|
| 165 |
+
|
| 166 |
+
1. **Check existing issues** for similar requests
|
| 167 |
+
2. **Open an issue** to discuss the feature
|
| 168 |
+
3. **Get feedback** from maintainers
|
| 169 |
+
4. **Implement** after discussion
|
| 170 |
+
|
| 171 |
+
## Documentation
|
| 172 |
+
|
| 173 |
+
### Writing Documentation
|
| 174 |
+
|
| 175 |
+
Documentation lives in `docs/` and uses MkDocs Material.
|
| 176 |
+
|
| 177 |
+
```bash
|
| 178 |
+
# Install MkDocs
|
| 179 |
+
pip install mkdocs-material
|
| 180 |
+
|
| 181 |
+
# Serve locally
|
| 182 |
+
mkdocs serve
|
| 183 |
+
|
| 184 |
+
# Open http://127.0.0.1:8000
|
| 185 |
+
```
|
| 186 |
+
|
| 187 |
+
### Documentation Style
|
| 188 |
+
|
| 189 |
+
- Use **clear headings** and structure
|
| 190 |
+
- Include **code examples** where relevant
|
| 191 |
+
- Add **warnings** and **tips** for important information
|
| 192 |
+
- Keep **language simple** and accessible
|
| 193 |
+
|
| 194 |
+
## Adding Tools to Catalog
|
| 195 |
+
|
| 196 |
+
### Process
|
| 197 |
+
|
| 198 |
+
1. **Create tool entry** in `dataset/catalog.jsonl`:
|
| 199 |
+
|
| 200 |
+
```json
|
| 201 |
+
{
|
| 202 |
+
"@type": "SoftwareSourceCode",
|
| 203 |
+
"name": "ToolName",
|
| 204 |
+
"description": "Tool description",
|
| 205 |
+
"url": "https://github.com/user/tool",
|
| 206 |
+
"license": "Apache-2.0",
|
| 207 |
+
"keywords": ["segmentation", "CT"],
|
| 208 |
+
"supportingData": {
|
| 209 |
+
"modalities": ["CT"],
|
| 210 |
+
"dimensions": ["3D"],
|
| 211 |
+
"formats": ["DICOM"],
|
| 212 |
+
"demo_url": "https://huggingface.co/spaces/user/tool"
|
| 213 |
+
}
|
| 214 |
+
}
|
| 215 |
+
```
|
| 216 |
+
|
| 217 |
+
2. **Validate entry**:
|
| 218 |
+
|
| 219 |
+
```bash
|
| 220 |
+
# Check JSON syntax
|
| 221 |
+
python -c "import json; print(json.loads('YOUR_JSON_HERE'))"
|
| 222 |
+
```
|
| 223 |
+
|
| 224 |
+
3. **Update checksum**:
|
| 225 |
+
|
| 226 |
+
```bash
|
| 227 |
+
shasum dataset/catalog.jsonl > dataset/catalog.jsonl.sha1
|
| 228 |
+
```
|
| 229 |
+
|
| 230 |
+
4. **Sync catalog**:
|
| 231 |
+
|
| 232 |
+
```bash
|
| 233 |
+
ai_agent sync
|
| 234 |
+
```
|
| 235 |
+
|
| 236 |
+
5. **Test retrieval**:
|
| 237 |
+
|
| 238 |
+
```bash
|
| 239 |
+
ai_agent chat
|
| 240 |
+
# Try queries that should return your new tool
|
| 241 |
+
```
|
| 242 |
+
|
| 243 |
+
### Tool Criteria
|
| 244 |
+
|
| 245 |
+
Tools should:
|
| 246 |
+
|
| 247 |
+
- ✅ Be relevant to imaging analysis
|
| 248 |
+
- ✅ Be actively maintained
|
| 249 |
+
- ✅ Have clear documentation
|
| 250 |
+
- ✅ Preferably have a runnable demo
|
| 251 |
+
- ✅ Be open-source or have free tier
|
| 252 |
+
|
| 253 |
+
## Reporting Issues
|
| 254 |
+
|
| 255 |
+
### Bug Reports
|
| 256 |
+
|
| 257 |
+
Include:
|
| 258 |
+
|
| 259 |
+
- **Description** of the bug
|
| 260 |
+
- **Steps to reproduce**
|
| 261 |
+
- **Expected behavior**
|
| 262 |
+
- **Actual behavior**
|
| 263 |
+
- **Environment** (OS, Python version, etc.)
|
| 264 |
+
- **Logs** if available
|
| 265 |
+
|
| 266 |
+
### Feature Requests
|
| 267 |
+
|
| 268 |
+
Include:
|
| 269 |
+
|
| 270 |
+
- **Use case** for the feature
|
| 271 |
+
- **Proposed solution** (if you have one)
|
| 272 |
+
- **Alternatives considered**
|
| 273 |
+
- **Examples** of similar features
|
| 274 |
+
|
| 275 |
+
## Code Review Process
|
| 276 |
+
|
| 277 |
+
1. **Automated checks** run on PR (tests, linting)
|
| 278 |
+
2. **Maintainer review** provides feedback
|
| 279 |
+
3. **Address feedback** and update PR
|
| 280 |
+
4. **Approval** from maintainer(s)
|
| 281 |
+
5. **Merge** into main branch
|
| 282 |
+
|
| 283 |
+
## Release Process
|
| 284 |
+
|
| 285 |
+
Releases follow semantic versioning:
|
| 286 |
+
|
| 287 |
+
1. Update version in `pyproject.toml`
|
| 288 |
+
2. Update `CHANGELOG.md` with release date
|
| 289 |
+
3. Create git tag: `git tag v0.2.0`
|
| 290 |
+
4. Push tag: `git push origin v0.2.0`
|
| 291 |
+
5. GitHub Actions deploys documentation
|
| 292 |
+
|
| 293 |
+
## Getting Help
|
| 294 |
+
|
| 295 |
+
- **GitHub Discussions**: Ask questions
|
| 296 |
+
- **GitHub Issues**: Report bugs
|
| 297 |
+
|
| 298 |
+
## Code of Conduct
|
| 299 |
+
|
| 300 |
+
Be respectful and professional:
|
| 301 |
+
|
| 302 |
+
- Use welcoming and inclusive language
|
| 303 |
+
- Respect differing viewpoints
|
| 304 |
+
- Accept constructive criticism
|
| 305 |
+
- Focus on what's best for the community
|
| 306 |
+
|
| 307 |
+
## License
|
| 308 |
+
|
| 309 |
+
By contributing, you agree that your contributions will be licensed under the Apache 2.0 License.
|
| 310 |
+
|
| 311 |
+
## Next Steps
|
| 312 |
+
|
| 313 |
+
- Review [Project Structure](structure.md)
|
| 314 |
+
- Learn about [Testing](testing.md)
|
| 315 |
+
- Read [Architecture Overview](../architecture/overview.md)
|
docs/development/structure.md
CHANGED
|
@@ -1,362 +1,362 @@
|
|
| 1 |
-
# Project Structure
|
| 2 |
-
|
| 3 |
-
The AI Imaging Agent is organized into modular components with clear separation of concerns.
|
| 4 |
-
|
| 5 |
-
## Directory Layout
|
| 6 |
-
|
| 7 |
-
```
|
| 8 |
-
ai-agent/
|
| 9 |
-
├── .github/
|
| 10 |
-
│ └── workflows/ # CI/CD workflows
|
| 11 |
-
│ └── deploy_docs.yml # Documentation deployment
|
| 12 |
-
├── artifacts/
|
| 13 |
-
│ └── rag_index/ # FAISS index and embeddings
|
| 14 |
-
├── dataset/
|
| 15 |
-
│ └── catalog.jsonl # Software catalog
|
| 16 |
-
├── docs/ # MkDocs documentation
|
| 17 |
-
├── logs/ # Application logs
|
| 18 |
-
├── src/
|
| 19 |
-
│ └── ai_agent/ # Main package
|
| 20 |
-
│ ├── agent/ # PydanticAI agent
|
| 21 |
-
│ ├── api/ # Pipeline orchestration
|
| 22 |
-
│ ├── catalog/ # Catalog management
|
| 23 |
-
│ ├── generator/ # VLM selection (schemas)
|
| 24 |
-
│ ├── retriever/ # Text retrieval
|
| 25 |
-
│ ├── ui/ # Gradio interface
|
| 26 |
-
│ └── utils/ # Shared utilities
|
| 27 |
-
├── tests/ # Test suite
|
| 28 |
-
├── config.yaml # Model configuration
|
| 29 |
-
├── mkdocs.yml # Documentation config
|
| 30 |
-
├── pyproject.toml # Package metadata
|
| 31 |
-
└── README.md # Project readme
|
| 32 |
-
```
|
| 33 |
-
|
| 34 |
-
## Core Modules
|
| 35 |
-
|
| 36 |
-
### src/ai_agent/
|
| 37 |
-
|
| 38 |
-
Main package containing all application code.
|
| 39 |
-
|
| 40 |
-
#### agent/
|
| 41 |
-
|
| 42 |
-
PydanticAI conversational agent implementation.
|
| 43 |
-
|
| 44 |
-
```
|
| 45 |
-
agent/
|
| 46 |
-
├── __init__.py
|
| 47 |
-
├── agent.py # Agent definition, tool adapters
|
| 48 |
-
├── models.py # Agent output/log models
|
| 49 |
-
├── utils.py # Agent state and tool quota helpers
|
| 50 |
-
└── tools/ # Tool implementations (search, repo_info, mcp)
|
| 51 |
-
```
|
| 52 |
-
|
| 53 |
-
**Key components**:
|
| 54 |
-
|
| 55 |
-
- `agent.py`: Agent instance, system prompt, tool definitions
|
| 56 |
-
- `models.py`: Agent output and tool usage schemas
|
| 57 |
-
- `utils.py`: `AgentState` plus call caps/prepare hooks
|
| 58 |
-
- `tools/`: Tool implementations (search, alternatives, repo info, mcp tools)
|
| 59 |
-
|
| 60 |
-
**Dependencies**: `api/`, `utils/`
|
| 61 |
-
|
| 62 |
-
#### api/
|
| 63 |
-
|
| 64 |
-
Pipeline orchestration and core logic.
|
| 65 |
-
|
| 66 |
-
```
|
| 67 |
-
api/
|
| 68 |
-
├── __init__.py
|
| 69 |
-
└── pipeline.py # RAGImagingPipeline main class
|
| 70 |
-
```
|
| 71 |
-
|
| 72 |
-
**Responsibilities**:
|
| 73 |
-
|
| 74 |
-
- File validation and metadata extraction
|
| 75 |
-
- Retrieval + VLM selection orchestration
|
| 76 |
-
- Error handling and logging
|
| 77 |
-
- Index management
|
| 78 |
-
|
| 79 |
-
**Dependencies**: `retriever/`, `generator/`, `utils/`
|
| 80 |
-
|
| 81 |
-
#### catalog/
|
| 82 |
-
|
| 83 |
-
Software catalog synchronization.
|
| 84 |
-
|
| 85 |
-
```
|
| 86 |
-
catalog/
|
| 87 |
-
├── __init__.py
|
| 88 |
-
└── sync.py # Catalog sync logic
|
| 89 |
-
```
|
| 90 |
-
|
| 91 |
-
**Functions**:
|
| 92 |
-
|
| 93 |
-
- Load catalog from JSONL
|
| 94 |
-
- Check for changes (SHA1)
|
| 95 |
-
- Trigger index rebuild
|
| 96 |
-
|
| 97 |
-
**Dependencies**: `retriever/`
|
| 98 |
-
|
| 99 |
-
#### generator/
|
| 100 |
-
|
| 101 |
-
VLM selection schemas and types.
|
| 102 |
-
|
| 103 |
-
```
|
| 104 |
-
generator/
|
| 105 |
-
├── __init__.py
|
| 106 |
-
└── schema.py # Pydantic models for responses
|
| 107 |
-
```
|
| 108 |
-
|
| 109 |
-
**Models**:
|
| 110 |
-
|
| 111 |
-
- `ToolRecommendation`: Individual tool recommendation
|
| 112 |
-
- `AgentResponse`: Complete response with status
|
| 113 |
-
- `ConversationStatus`: Enum for conversation states
|
| 114 |
-
- `ToolReason`: Enum for recommendation reasons
|
| 115 |
-
|
| 116 |
-
**Dependencies**: None (pure schemas)
|
| 117 |
-
|
| 118 |
-
#### retriever/
|
| 119 |
-
|
| 120 |
-
Text-based retrieval pipeline.
|
| 121 |
-
|
| 122 |
-
```
|
| 123 |
-
retriever/
|
| 124 |
-
├── __init__.py
|
| 125 |
-
├── text_embedder.py # BGE-M3 embedding model
|
| 126 |
-
├── vector_index.py # FAISS index management
|
| 127 |
-
├── reranker.py # CrossEncoder reranking
|
| 128 |
-
└── software_doc.py # Catalog schema and loading
|
| 129 |
-
```
|
| 130 |
-
|
| 131 |
-
**Pipeline flow**:
|
| 132 |
-
|
| 133 |
-
1. `text_embedder.py`: Embed query
|
| 134 |
-
2. `vector_index.py`: FAISS search
|
| 135 |
-
3. `reranker.py`: CrossEncoder reranking
|
| 136 |
-
4. Output: Top-K candidates
|
| 137 |
-
|
| 138 |
-
**Dependencies**: None (pure retrieval)
|
| 139 |
-
|
| 140 |
-
#### ui/
|
| 141 |
-
|
| 142 |
-
Gradio web interface.
|
| 143 |
-
|
| 144 |
-
```
|
| 145 |
-
ui/
|
| 146 |
-
├── __init__.py
|
| 147 |
-
├── app.py # Gradio app definition
|
| 148 |
-
├── components.py # Reusable UI components
|
| 149 |
-
├── formatters.py # Response formatting
|
| 150 |
-
├── handlers.py # Message handlers
|
| 151 |
-
├── state.py # UI state management
|
| 152 |
-
└── visualizations.py # Preview and trace rendering
|
| 153 |
-
```
|
| 154 |
-
|
| 155 |
-
**Key files**:
|
| 156 |
-
|
| 157 |
-
- `app.py`: Main Gradio interface
|
| 158 |
-
- `handlers.py`: `respond()` function - core interaction logic
|
| 159 |
-
- `formatters.py`: Format recommendations as markdown/cards
|
| 160 |
-
- `components.py`: Reusable Gradio components
|
| 161 |
-
|
| 162 |
-
**Dependencies**: `agent/`, `api/`
|
| 163 |
-
|
| 164 |
-
#### utils/
|
| 165 |
-
|
| 166 |
-
Shared utilities.
|
| 167 |
-
|
| 168 |
-
```
|
| 169 |
-
utils/
|
| 170 |
-
├── __init__.py
|
| 171 |
-
├── config.py # Configuration loading
|
| 172 |
-
├── file_validator.py # File validation
|
| 173 |
-
├── image_meta.py # Metadata extraction (DICOM, NIfTI, TIFF)
|
| 174 |
-
├── previews.py # Image preview generation
|
| 175 |
-
└── tags.py # Control tag parsing
|
| 176 |
-
```
|
| 177 |
-
|
| 178 |
-
**Common utilities**:
|
| 179 |
-
|
| 180 |
-
- `config.py`: Load `config.yaml` with Pydantic validation
|
| 181 |
-
- `file_validator.py`: Size limits, format checks
|
| 182 |
-
- `image_meta.py`: Extract DICOM/NIfTI/TIFF metadata
|
| 183 |
-
- `previews.py`: Convert medical images to PNG
|
| 184 |
-
- `tags.py`: Parse exclusion tags and strip control tags from queries
|
| 185 |
-
|
| 186 |
-
**Dependencies**: None (pure utilities)
|
| 187 |
-
|
| 188 |
-
#### cli.py
|
| 189 |
-
|
| 190 |
-
Command-line interface entry point.
|
| 191 |
-
|
| 192 |
-
```python
|
| 193 |
-
def main():
|
| 194 |
-
# Parse arguments
|
| 195 |
-
# Route to chat or sync
|
| 196 |
-
```
|
| 197 |
-
|
| 198 |
-
**Commands**:
|
| 199 |
-
|
| 200 |
-
- `ai_agent chat`: Launch UI
|
| 201 |
-
- `ai_agent sync`: Sync catalog
|
| 202 |
-
|
| 203 |
-
### tests/
|
| 204 |
-
|
| 205 |
-
Test suite.
|
| 206 |
-
|
| 207 |
-
```
|
| 208 |
-
tests/
|
| 209 |
-
├── data/
|
| 210 |
-
│ └── test_data.json # Test cases
|
| 211 |
-
├── test_retrieval_pipeline.py
|
| 212 |
-
├── test_deepwiki_repo_info.py
|
| 213 |
-
└── ...
|
| 214 |
-
```
|
| 215 |
-
|
| 216 |
-
**Test categories**:
|
| 217 |
-
|
| 218 |
-
- Unit tests: Individual components
|
| 219 |
-
- Integration tests: Full pipeline
|
| 220 |
-
- End-to-end tests: Real API calls (optional)
|
| 221 |
-
|
| 222 |
-
## Configuration Files
|
| 223 |
-
|
| 224 |
-
### pyproject.toml
|
| 225 |
-
|
| 226 |
-
Python package metadata and dependencies.
|
| 227 |
-
|
| 228 |
-
```toml
|
| 229 |
-
[project]
|
| 230 |
-
name = "ai_agent"
|
| 231 |
-
version = "1.0.0"
|
| 232 |
-
dependencies = [...]
|
| 233 |
-
|
| 234 |
-
[project.scripts]
|
| 235 |
-
ai_agent = "ai_agent.cli:main"
|
| 236 |
-
```
|
| 237 |
-
|
| 238 |
-
### config.yaml
|
| 239 |
-
|
| 240 |
-
Model configuration.
|
| 241 |
-
|
| 242 |
-
```yaml
|
| 243 |
-
agent_model:
|
| 244 |
-
name: "gpt-4o-mini"
|
| 245 |
-
base_url: null
|
| 246 |
-
api_key_env: "OPENAI_API_KEY"
|
| 247 |
-
|
| 248 |
-
available_models:
|
| 249 |
-
- display_name: "gpt-4o-mini"
|
| 250 |
-
name: "gpt-4o-mini"
|
| 251 |
-
...
|
| 252 |
-
```
|
| 253 |
-
|
| 254 |
-
### mkdocs.yml
|
| 255 |
-
|
| 256 |
-
Documentation configuration.
|
| 257 |
-
|
| 258 |
-
```yaml
|
| 259 |
-
site_name: AI Imaging Agent
|
| 260 |
-
theme:
|
| 261 |
-
name: material
|
| 262 |
-
nav: [...]
|
| 263 |
-
```
|
| 264 |
-
|
| 265 |
-
### .env
|
| 266 |
-
|
| 267 |
-
Environment variables (not committed).
|
| 268 |
-
|
| 269 |
-
```dotenv
|
| 270 |
-
OPENAI_API_KEY=sk-xxxx
|
| 271 |
-
SOFTWARE_CATALOG=dataset/catalog.jsonl
|
| 272 |
-
```
|
| 273 |
-
|
| 274 |
-
## Data Files
|
| 275 |
-
|
| 276 |
-
### dataset/catalog.jsonl
|
| 277 |
-
|
| 278 |
-
Software catalog in JSON Lines format.
|
| 279 |
-
|
| 280 |
-
Each line is a complete JSON object following schema.org SoftwareSourceCode.
|
| 281 |
-
|
| 282 |
-
### artifacts/rag_index/
|
| 283 |
-
|
| 284 |
-
Pre-built FAISS index and metadata.
|
| 285 |
-
|
| 286 |
-
```
|
| 287 |
-
artifacts/rag_index/
|
| 288 |
-
├── index.faiss # FAISS binary index
|
| 289 |
-
└── meta.json # Tool IDs, config, timestamps
|
| 290 |
-
```
|
| 291 |
-
|
| 292 |
-
## Module Boundaries
|
| 293 |
-
|
| 294 |
-
Clear separation prevents circular dependencies:
|
| 295 |
-
|
| 296 |
-
```
|
| 297 |
-
ui/ → agent/ → api/ → retriever/
|
| 298 |
-
→ generator/
|
| 299 |
-
→ utils/
|
| 300 |
-
```
|
| 301 |
-
|
| 302 |
-
**Rules**:
|
| 303 |
-
|
| 304 |
-
- `utils/`: No dependencies on other modules
|
| 305 |
-
- `retriever/`: Pure retrieval, no generation
|
| 306 |
-
- `generator/`: Pure schemas, no retrieval
|
| 307 |
-
- `api/`: Orchestrates retriever + generator
|
| 308 |
-
- `agent/`: Uses api for tool calls
|
| 309 |
-
- `ui/`: Top-level, depends on agent + api
|
| 310 |
-
|
| 311 |
-
## Import Patterns
|
| 312 |
-
|
| 313 |
-
All imports use absolute paths from `ai_agent`:
|
| 314 |
-
|
| 315 |
-
```python
|
| 316 |
-
from ai_agent.retriever.vector_index import VectorIndex
|
| 317 |
-
from ai_agent.utils.config import load_config
|
| 318 |
-
from ai_agent.agent.utils import AgentState
|
| 319 |
-
```
|
| 320 |
-
|
| 321 |
-
**Never use** relative imports like `from ..utils import ...`
|
| 322 |
-
|
| 323 |
-
## Extension Points
|
| 324 |
-
|
| 325 |
-
### Adding New Tools
|
| 326 |
-
|
| 327 |
-
Add tool adapters to `agent/agent.py` and implement logic in `agent/tools/`:
|
| 328 |
-
|
| 329 |
-
```python
|
| 330 |
-
@agent.tool
|
| 331 |
-
async def new_tool(ctx: RunContext[AgentState], param: str) -> str:
|
| 332 |
-
"""Tool description."""
|
| 333 |
-
# Implementation
|
| 334 |
-
return result
|
| 335 |
-
```
|
| 336 |
-
|
| 337 |
-
### Adding New Metadata Extractors
|
| 338 |
-
|
| 339 |
-
Add to `utils/image_meta.py`:
|
| 340 |
-
|
| 341 |
-
```python
|
| 342 |
-
def extract_custom_format(file_path: str) -> dict:
|
| 343 |
-
"""Extract metadata from custom format."""
|
| 344 |
-
# Implementation
|
| 345 |
-
return metadata
|
| 346 |
-
```
|
| 347 |
-
|
| 348 |
-
### Adding New Retrieval Models
|
| 349 |
-
|
| 350 |
-
Replace in `retriever/text_embedder.py`:
|
| 351 |
-
|
| 352 |
-
```python
|
| 353 |
-
class TextEmbedder:
|
| 354 |
-
def __init__(self, model_name="new-embedding-model"):
|
| 355 |
-
self.model = SentenceTransformer(model_name)
|
| 356 |
-
```
|
| 357 |
-
|
| 358 |
-
## Next Steps
|
| 359 |
-
|
| 360 |
-
- Learn about [Contributing](contributing.md)
|
| 361 |
-
- Explore [Testing](testing.md)
|
| 362 |
-
- Return to [Architecture Overview](../architecture/overview.md)
|
|
|
|
| 1 |
+
# Project Structure
|
| 2 |
+
|
| 3 |
+
The AI Imaging Agent is organized into modular components with clear separation of concerns.
|
| 4 |
+
|
| 5 |
+
## Directory Layout
|
| 6 |
+
|
| 7 |
+
```
|
| 8 |
+
ai-agent/
|
| 9 |
+
├── .github/
|
| 10 |
+
│ └── workflows/ # CI/CD workflows
|
| 11 |
+
│ └── deploy_docs.yml # Documentation deployment
|
| 12 |
+
├── artifacts/
|
| 13 |
+
│ └── rag_index/ # FAISS index and embeddings
|
| 14 |
+
├── dataset/
|
| 15 |
+
│ └── catalog.jsonl # Software catalog
|
| 16 |
+
├── docs/ # MkDocs documentation
|
| 17 |
+
├── logs/ # Application logs
|
| 18 |
+
├── src/
|
| 19 |
+
│ └── ai_agent/ # Main package
|
| 20 |
+
│ ├── agent/ # PydanticAI agent
|
| 21 |
+
│ ├── api/ # Pipeline orchestration
|
| 22 |
+
│ ├── catalog/ # Catalog management
|
| 23 |
+
│ ├── generator/ # VLM selection (schemas)
|
| 24 |
+
│ ├── retriever/ # Text retrieval
|
| 25 |
+
│ ├── ui/ # Gradio interface
|
| 26 |
+
│ └── utils/ # Shared utilities
|
| 27 |
+
├── tests/ # Test suite
|
| 28 |
+
├── config.yaml # Model configuration
|
| 29 |
+
├── mkdocs.yml # Documentation config
|
| 30 |
+
├── pyproject.toml # Package metadata
|
| 31 |
+
└── README.md # Project readme
|
| 32 |
+
```
|
| 33 |
+
|
| 34 |
+
## Core Modules
|
| 35 |
+
|
| 36 |
+
### src/ai_agent/
|
| 37 |
+
|
| 38 |
+
Main package containing all application code.
|
| 39 |
+
|
| 40 |
+
#### agent/
|
| 41 |
+
|
| 42 |
+
PydanticAI conversational agent implementation.
|
| 43 |
+
|
| 44 |
+
```
|
| 45 |
+
agent/
|
| 46 |
+
├── __init__.py
|
| 47 |
+
├── agent.py # Agent definition, tool adapters
|
| 48 |
+
├── models.py # Agent output/log models
|
| 49 |
+
├── utils.py # Agent state and tool quota helpers
|
| 50 |
+
└── tools/ # Tool implementations (search, repo_info, mcp)
|
| 51 |
+
```
|
| 52 |
+
|
| 53 |
+
**Key components**:
|
| 54 |
+
|
| 55 |
+
- `agent.py`: Agent instance, system prompt, tool definitions
|
| 56 |
+
- `models.py`: Agent output and tool usage schemas
|
| 57 |
+
- `utils.py`: `AgentState` plus call caps/prepare hooks
|
| 58 |
+
- `tools/`: Tool implementations (search, alternatives, repo info, mcp tools)
|
| 59 |
+
|
| 60 |
+
**Dependencies**: `api/`, `utils/`
|
| 61 |
+
|
| 62 |
+
#### api/
|
| 63 |
+
|
| 64 |
+
Pipeline orchestration and core logic.
|
| 65 |
+
|
| 66 |
+
```
|
| 67 |
+
api/
|
| 68 |
+
├── __init__.py
|
| 69 |
+
└── pipeline.py # RAGImagingPipeline main class
|
| 70 |
+
```
|
| 71 |
+
|
| 72 |
+
**Responsibilities**:
|
| 73 |
+
|
| 74 |
+
- File validation and metadata extraction
|
| 75 |
+
- Retrieval + VLM selection orchestration
|
| 76 |
+
- Error handling and logging
|
| 77 |
+
- Index management
|
| 78 |
+
|
| 79 |
+
**Dependencies**: `retriever/`, `generator/`, `utils/`
|
| 80 |
+
|
| 81 |
+
#### catalog/
|
| 82 |
+
|
| 83 |
+
Software catalog synchronization.
|
| 84 |
+
|
| 85 |
+
```
|
| 86 |
+
catalog/
|
| 87 |
+
├── __init__.py
|
| 88 |
+
└── sync.py # Catalog sync logic
|
| 89 |
+
```
|
| 90 |
+
|
| 91 |
+
**Functions**:
|
| 92 |
+
|
| 93 |
+
- Load catalog from JSONL
|
| 94 |
+
- Check for changes (SHA1)
|
| 95 |
+
- Trigger index rebuild
|
| 96 |
+
|
| 97 |
+
**Dependencies**: `retriever/`
|
| 98 |
+
|
| 99 |
+
#### generator/
|
| 100 |
+
|
| 101 |
+
VLM selection schemas and types.
|
| 102 |
+
|
| 103 |
+
```
|
| 104 |
+
generator/
|
| 105 |
+
├── __init__.py
|
| 106 |
+
└── schema.py # Pydantic models for responses
|
| 107 |
+
```
|
| 108 |
+
|
| 109 |
+
**Models**:
|
| 110 |
+
|
| 111 |
+
- `ToolRecommendation`: Individual tool recommendation
|
| 112 |
+
- `AgentResponse`: Complete response with status
|
| 113 |
+
- `ConversationStatus`: Enum for conversation states
|
| 114 |
+
- `ToolReason`: Enum for recommendation reasons
|
| 115 |
+
|
| 116 |
+
**Dependencies**: None (pure schemas)
|
| 117 |
+
|
| 118 |
+
#### retriever/
|
| 119 |
+
|
| 120 |
+
Text-based retrieval pipeline.
|
| 121 |
+
|
| 122 |
+
```
|
| 123 |
+
retriever/
|
| 124 |
+
├── __init__.py
|
| 125 |
+
├── text_embedder.py # BGE-M3 embedding model
|
| 126 |
+
├── vector_index.py # FAISS index management
|
| 127 |
+
├── reranker.py # CrossEncoder reranking
|
| 128 |
+
└── software_doc.py # Catalog schema and loading
|
| 129 |
+
```
|
| 130 |
+
|
| 131 |
+
**Pipeline flow**:
|
| 132 |
+
|
| 133 |
+
1. `text_embedder.py`: Embed query
|
| 134 |
+
2. `vector_index.py`: FAISS search
|
| 135 |
+
3. `reranker.py`: CrossEncoder reranking
|
| 136 |
+
4. Output: Top-K candidates
|
| 137 |
+
|
| 138 |
+
**Dependencies**: None (pure retrieval)
|
| 139 |
+
|
| 140 |
+
#### ui/
|
| 141 |
+
|
| 142 |
+
Gradio web interface.
|
| 143 |
+
|
| 144 |
+
```
|
| 145 |
+
ui/
|
| 146 |
+
├── __init__.py
|
| 147 |
+
├── app.py # Gradio app definition
|
| 148 |
+
├── components.py # Reusable UI components
|
| 149 |
+
├── formatters.py # Response formatting
|
| 150 |
+
├── handlers.py # Message handlers
|
| 151 |
+
├── state.py # UI state management
|
| 152 |
+
└── visualizations.py # Preview and trace rendering
|
| 153 |
+
```
|
| 154 |
+
|
| 155 |
+
**Key files**:
|
| 156 |
+
|
| 157 |
+
- `app.py`: Main Gradio interface
|
| 158 |
+
- `handlers.py`: `respond()` function - core interaction logic
|
| 159 |
+
- `formatters.py`: Format recommendations as markdown/cards
|
| 160 |
+
- `components.py`: Reusable Gradio components
|
| 161 |
+
|
| 162 |
+
**Dependencies**: `agent/`, `api/`
|
| 163 |
+
|
| 164 |
+
#### utils/
|
| 165 |
+
|
| 166 |
+
Shared utilities.
|
| 167 |
+
|
| 168 |
+
```
|
| 169 |
+
utils/
|
| 170 |
+
├── __init__.py
|
| 171 |
+
├── config.py # Configuration loading
|
| 172 |
+
├── file_validator.py # File validation
|
| 173 |
+
├── image_meta.py # Metadata extraction (DICOM, NIfTI, TIFF)
|
| 174 |
+
├── previews.py # Image preview generation
|
| 175 |
+
└── tags.py # Control tag parsing
|
| 176 |
+
```
|
| 177 |
+
|
| 178 |
+
**Common utilities**:
|
| 179 |
+
|
| 180 |
+
- `config.py`: Load `config.yaml` with Pydantic validation
|
| 181 |
+
- `file_validator.py`: Size limits, format checks
|
| 182 |
+
- `image_meta.py`: Extract DICOM/NIfTI/TIFF metadata
|
| 183 |
+
- `previews.py`: Convert medical images to PNG
|
| 184 |
+
- `tags.py`: Parse exclusion tags and strip control tags from queries
|
| 185 |
+
|
| 186 |
+
**Dependencies**: None (pure utilities)
|
| 187 |
+
|
| 188 |
+
#### cli.py
|
| 189 |
+
|
| 190 |
+
Command-line interface entry point.
|
| 191 |
+
|
| 192 |
+
```python
|
| 193 |
+
def main():
|
| 194 |
+
# Parse arguments
|
| 195 |
+
# Route to chat or sync
|
| 196 |
+
```
|
| 197 |
+
|
| 198 |
+
**Commands**:
|
| 199 |
+
|
| 200 |
+
- `ai_agent chat`: Launch UI
|
| 201 |
+
- `ai_agent sync`: Sync catalog
|
| 202 |
+
|
| 203 |
+
### tests/
|
| 204 |
+
|
| 205 |
+
Test suite.
|
| 206 |
+
|
| 207 |
+
```
|
| 208 |
+
tests/
|
| 209 |
+
├── data/
|
| 210 |
+
│ └── test_data.json # Test cases
|
| 211 |
+
├── test_retrieval_pipeline.py
|
| 212 |
+
├── test_deepwiki_repo_info.py
|
| 213 |
+
└── ...
|
| 214 |
+
```
|
| 215 |
+
|
| 216 |
+
**Test categories**:
|
| 217 |
+
|
| 218 |
+
- Unit tests: Individual components
|
| 219 |
+
- Integration tests: Full pipeline
|
| 220 |
+
- End-to-end tests: Real API calls (optional)
|
| 221 |
+
|
| 222 |
+
## Configuration Files
|
| 223 |
+
|
| 224 |
+
### pyproject.toml
|
| 225 |
+
|
| 226 |
+
Python package metadata and dependencies.
|
| 227 |
+
|
| 228 |
+
```toml
|
| 229 |
+
[project]
|
| 230 |
+
name = "ai_agent"
|
| 231 |
+
version = "1.0.0"
|
| 232 |
+
dependencies = [...]
|
| 233 |
+
|
| 234 |
+
[project.scripts]
|
| 235 |
+
ai_agent = "ai_agent.cli:main"
|
| 236 |
+
```
|
| 237 |
+
|
| 238 |
+
### config.yaml
|
| 239 |
+
|
| 240 |
+
Model configuration.
|
| 241 |
+
|
| 242 |
+
```yaml
|
| 243 |
+
agent_model:
|
| 244 |
+
name: "gpt-4o-mini"
|
| 245 |
+
base_url: null
|
| 246 |
+
api_key_env: "OPENAI_API_KEY"
|
| 247 |
+
|
| 248 |
+
available_models:
|
| 249 |
+
- display_name: "gpt-4o-mini"
|
| 250 |
+
name: "gpt-4o-mini"
|
| 251 |
+
...
|
| 252 |
+
```
|
| 253 |
+
|
| 254 |
+
### mkdocs.yml
|
| 255 |
+
|
| 256 |
+
Documentation configuration.
|
| 257 |
+
|
| 258 |
+
```yaml
|
| 259 |
+
site_name: AI Imaging Agent
|
| 260 |
+
theme:
|
| 261 |
+
name: material
|
| 262 |
+
nav: [...]
|
| 263 |
+
```
|
| 264 |
+
|
| 265 |
+
### .env
|
| 266 |
+
|
| 267 |
+
Environment variables (not committed).
|
| 268 |
+
|
| 269 |
+
```dotenv
|
| 270 |
+
OPENAI_API_KEY=sk-xxxx
|
| 271 |
+
SOFTWARE_CATALOG=dataset/catalog.jsonl
|
| 272 |
+
```
|
| 273 |
+
|
| 274 |
+
## Data Files
|
| 275 |
+
|
| 276 |
+
### dataset/catalog.jsonl
|
| 277 |
+
|
| 278 |
+
Software catalog in JSON Lines format.
|
| 279 |
+
|
| 280 |
+
Each line is a complete JSON object following schema.org SoftwareSourceCode.
|
| 281 |
+
|
| 282 |
+
### artifacts/rag_index/
|
| 283 |
+
|
| 284 |
+
Pre-built FAISS index and metadata.
|
| 285 |
+
|
| 286 |
+
```
|
| 287 |
+
artifacts/rag_index/
|
| 288 |
+
├── index.faiss # FAISS binary index
|
| 289 |
+
└── meta.json # Tool IDs, config, timestamps
|
| 290 |
+
```
|
| 291 |
+
|
| 292 |
+
## Module Boundaries
|
| 293 |
+
|
| 294 |
+
Clear separation prevents circular dependencies:
|
| 295 |
+
|
| 296 |
+
```
|
| 297 |
+
ui/ → agent/ → api/ → retriever/
|
| 298 |
+
→ generator/
|
| 299 |
+
→ utils/
|
| 300 |
+
```
|
| 301 |
+
|
| 302 |
+
**Rules**:
|
| 303 |
+
|
| 304 |
+
- `utils/`: No dependencies on other modules
|
| 305 |
+
- `retriever/`: Pure retrieval, no generation
|
| 306 |
+
- `generator/`: Pure schemas, no retrieval
|
| 307 |
+
- `api/`: Orchestrates retriever + generator
|
| 308 |
+
- `agent/`: Uses api for tool calls
|
| 309 |
+
- `ui/`: Top-level, depends on agent + api
|
| 310 |
+
|
| 311 |
+
## Import Patterns
|
| 312 |
+
|
| 313 |
+
All imports use absolute paths from `ai_agent`:
|
| 314 |
+
|
| 315 |
+
```python
|
| 316 |
+
from ai_agent.retriever.vector_index import VectorIndex
|
| 317 |
+
from ai_agent.utils.config import load_config
|
| 318 |
+
from ai_agent.agent.utils import AgentState
|
| 319 |
+
```
|
| 320 |
+
|
| 321 |
+
**Never use** relative imports like `from ..utils import ...`
|
| 322 |
+
|
| 323 |
+
## Extension Points
|
| 324 |
+
|
| 325 |
+
### Adding New Tools
|
| 326 |
+
|
| 327 |
+
Add tool adapters to `agent/agent.py` and implement logic in `agent/tools/`:
|
| 328 |
+
|
| 329 |
+
```python
|
| 330 |
+
@agent.tool
|
| 331 |
+
async def new_tool(ctx: RunContext[AgentState], param: str) -> str:
|
| 332 |
+
"""Tool description."""
|
| 333 |
+
# Implementation
|
| 334 |
+
return result
|
| 335 |
+
```
|
| 336 |
+
|
| 337 |
+
### Adding New Metadata Extractors
|
| 338 |
+
|
| 339 |
+
Add to `utils/image_meta.py`:
|
| 340 |
+
|
| 341 |
+
```python
|
| 342 |
+
def extract_custom_format(file_path: str) -> dict:
|
| 343 |
+
"""Extract metadata from custom format."""
|
| 344 |
+
# Implementation
|
| 345 |
+
return metadata
|
| 346 |
+
```
|
| 347 |
+
|
| 348 |
+
### Adding New Retrieval Models
|
| 349 |
+
|
| 350 |
+
Replace in `retriever/text_embedder.py`:
|
| 351 |
+
|
| 352 |
+
```python
|
| 353 |
+
class TextEmbedder:
|
| 354 |
+
def __init__(self, model_name="new-embedding-model"):
|
| 355 |
+
self.model = SentenceTransformer(model_name)
|
| 356 |
+
```
|
| 357 |
+
|
| 358 |
+
## Next Steps
|
| 359 |
+
|
| 360 |
+
- Learn about [Contributing](contributing.md)
|
| 361 |
+
- Explore [Testing](testing.md)
|
| 362 |
+
- Return to [Architecture Overview](../architecture/overview.md)
|
docs/development/testing.md
CHANGED
|
@@ -1,410 +1,410 @@
|
|
| 1 |
-
# Testing (still under development)
|
| 2 |
-
|
| 3 |
-
The AI Imaging Agent uses pytest for testing. This guide covers running tests and writing new ones.
|
| 4 |
-
|
| 5 |
-
**Note:** We are still developing some tests for the agent, hence this part is not relevant for now.
|
| 6 |
-
|
| 7 |
-
## Running Tests
|
| 8 |
-
|
| 9 |
-
### Basic Usage
|
| 10 |
-
|
| 11 |
-
```bash
|
| 12 |
-
# Run all tests
|
| 13 |
-
pytest
|
| 14 |
-
|
| 15 |
-
# Run specific test file
|
| 16 |
-
pytest tests/test_retrieval_pipeline.py
|
| 17 |
-
|
| 18 |
-
# Run specific test
|
| 19 |
-
pytest tests/test_retrieval_pipeline.py::test_basic_retrieval
|
| 20 |
-
|
| 21 |
-
# Run with verbose output
|
| 22 |
-
pytest -v
|
| 23 |
-
|
| 24 |
-
# Run with coverage
|
| 25 |
-
pytest --cov=ai_agent --cov-report=html
|
| 26 |
-
```
|
| 27 |
-
|
| 28 |
-
### Test Categories
|
| 29 |
-
|
| 30 |
-
Tests are marked by category:
|
| 31 |
-
|
| 32 |
-
```bash
|
| 33 |
-
# Run only unit tests
|
| 34 |
-
pytest -m unit
|
| 35 |
-
|
| 36 |
-
# Run only integration tests
|
| 37 |
-
pytest -m integration
|
| 38 |
-
|
| 39 |
-
# Skip slow tests
|
| 40 |
-
pytest -m "not slow"
|
| 41 |
-
```
|
| 42 |
-
|
| 43 |
-
## Test Organization
|
| 44 |
-
|
| 45 |
-
### Directory Structure
|
| 46 |
-
|
| 47 |
-
```
|
| 48 |
-
tests/
|
| 49 |
-
├── data/
|
| 50 |
-
│ ├── test_data.json # Test cases
|
| 51 |
-
│ └── 0002.DCM # Sample DICOM file
|
| 52 |
-
├── test_retrieval_pipeline.py # Retrieval tests
|
| 53 |
-
├── test_deepwiki_repo_info.py # Repo info tests
|
| 54 |
-
├── test_gpt4o_vision.py # VLM tests (integration)
|
| 55 |
-
└── __pycache__/
|
| 56 |
-
```
|
| 57 |
-
|
| 58 |
-
### Test File Naming
|
| 59 |
-
|
| 60 |
-
- `test_*.py`: Test files
|
| 61 |
-
- `*_test.py`: Alternative naming (less common)
|
| 62 |
-
|
| 63 |
-
### Test Function Naming
|
| 64 |
-
|
| 65 |
-
```python
|
| 66 |
-
def test_basic_retrieval():
|
| 67 |
-
"""Test basic retrieval functionality."""
|
| 68 |
-
pass
|
| 69 |
-
|
| 70 |
-
def test_edge_case_empty_query():
|
| 71 |
-
"""Test handling of empty query."""
|
| 72 |
-
pass
|
| 73 |
-
|
| 74 |
-
def test_integration_full_pipeline():
|
| 75 |
-
"""Integration test for complete pipeline."""
|
| 76 |
-
pass
|
| 77 |
-
```
|
| 78 |
-
|
| 79 |
-
## Writing Tests
|
| 80 |
-
|
| 81 |
-
### Unit Test Example
|
| 82 |
-
|
| 83 |
-
```python
|
| 84 |
-
import pytest
|
| 85 |
-
from ai_agent.retriever.vector_index import VectorIndex
|
| 86 |
-
|
| 87 |
-
def test_vector_index_search():
|
| 88 |
-
"""Test FAISS vector search."""
|
| 89 |
-
# Arrange
|
| 90 |
-
index = VectorIndex()
|
| 91 |
-
index.load("artifacts/rag_index")
|
| 92 |
-
|
| 93 |
-
query = "segment lungs CT"
|
| 94 |
-
|
| 95 |
-
# Act
|
| 96 |
-
results = index.search(query, k=5)
|
| 97 |
-
|
| 98 |
-
# Assert
|
| 99 |
-
assert len(results) == 5
|
| 100 |
-
assert all(r['score'] > 0 for r in results)
|
| 101 |
-
assert 'TotalSegmentator' in [r['name'] for r in results]
|
| 102 |
-
```
|
| 103 |
-
|
| 104 |
-
### Integration Test Example
|
| 105 |
-
|
| 106 |
-
```python
|
| 107 |
-
import pytest
|
| 108 |
-
from ai_agent.api.pipeline import RAGImagingPipeline
|
| 109 |
-
|
| 110 |
-
@pytest.mark.integration
|
| 111 |
-
def test_full_pipeline_with_image():
|
| 112 |
-
"""Integration test with real image and VLM call."""
|
| 113 |
-
# Arrange
|
| 114 |
-
pipeline = RAGImagingPipeline(
|
| 115 |
-
catalog_path="dataset/catalog.jsonl",
|
| 116 |
-
index_dir="artifacts/rag_index"
|
| 117 |
-
)
|
| 118 |
-
|
| 119 |
-
# Act
|
| 120 |
-
result = pipeline.recommend(
|
| 121 |
-
query="segment lungs",
|
| 122 |
-
files=["tests/data/chest_ct.dcm"]
|
| 123 |
-
)
|
| 124 |
-
|
| 125 |
-
# Assert
|
| 126 |
-
assert result.status == "complete"
|
| 127 |
-
assert len(result.recommendations) > 0
|
| 128 |
-
assert result.recommendations[0].accuracy_score > 70
|
| 129 |
-
```
|
| 130 |
-
|
| 131 |
-
### Parametrized Tests
|
| 132 |
-
|
| 133 |
-
```python
|
| 134 |
-
@pytest.mark.parametrize("query,expected_tool", [
|
| 135 |
-
("segment brain MRI", "FreeSurfer"),
|
| 136 |
-
("segment lungs CT", "TotalSegmentator"),
|
| 137 |
-
("classify chest X-ray", "CheXNet"),
|
| 138 |
-
])
|
| 139 |
-
def test_retrieval_for_queries(query, expected_tool):
|
| 140 |
-
"""Test retrieval returns expected tools for various queries."""
|
| 141 |
-
index = VectorIndex()
|
| 142 |
-
index.load("artifacts/rag_index")
|
| 143 |
-
|
| 144 |
-
results = index.search(query, k=10)
|
| 145 |
-
tool_names = [r['name'] for r in results]
|
| 146 |
-
|
| 147 |
-
assert expected_tool in tool_names
|
| 148 |
-
```
|
| 149 |
-
|
| 150 |
-
### Fixtures
|
| 151 |
-
|
| 152 |
-
```python
|
| 153 |
-
import pytest
|
| 154 |
-
|
| 155 |
-
@pytest.fixture
|
| 156 |
-
def pipeline():
|
| 157 |
-
"""Provide initialized pipeline for tests."""
|
| 158 |
-
return RAGImagingPipeline(
|
| 159 |
-
catalog_path="dataset/catalog.jsonl",
|
| 160 |
-
index_dir="artifacts/rag_index"
|
| 161 |
-
)
|
| 162 |
-
|
| 163 |
-
@pytest.fixture
|
| 164 |
-
def sample_dicom():
|
| 165 |
-
"""Provide path to sample DICOM file."""
|
| 166 |
-
return "tests/data/0002.DCM"
|
| 167 |
-
|
| 168 |
-
def test_with_fixtures(pipeline, sample_dicom):
|
| 169 |
-
"""Test using fixtures."""
|
| 170 |
-
result = pipeline.recommend(
|
| 171 |
-
query="analyze DICOM",
|
| 172 |
-
files=[sample_dicom]
|
| 173 |
-
)
|
| 174 |
-
assert result is not None
|
| 175 |
-
```
|
| 176 |
-
|
| 177 |
-
<!-- ## Mocking
|
| 178 |
-
|
| 179 |
-
### Mocking VLM Calls
|
| 180 |
-
|
| 181 |
-
To avoid API costs during testing:
|
| 182 |
-
|
| 183 |
-
```python
|
| 184 |
-
from unittest.mock import Mock, patch
|
| 185 |
-
import pytest
|
| 186 |
-
|
| 187 |
-
@pytest.fixture
|
| 188 |
-
def mock_vlm_response():
|
| 189 |
-
"""Mock VLM response."""
|
| 190 |
-
return {
|
| 191 |
-
"status": "complete",
|
| 192 |
-
"recommendations": [
|
| 193 |
-
{
|
| 194 |
-
"rank": 1,
|
| 195 |
-
"name": "TotalSegmentator",
|
| 196 |
-
"accuracy_score": 95,
|
| 197 |
-
"explanation": "Test explanation",
|
| 198 |
-
"reason": "task_match"
|
| 199 |
-
}
|
| 200 |
-
]
|
| 201 |
-
}
|
| 202 |
-
|
| 203 |
-
def test_with_mocked_vlm(mock_vlm_response):
|
| 204 |
-
"""Test pipeline with mocked VLM."""
|
| 205 |
-
with patch('ai_agent.agent.agent.Agent.run') as mock_run:
|
| 206 |
-
mock_run.return_value = mock_vlm_response
|
| 207 |
-
|
| 208 |
-
# Test code here
|
| 209 |
-
result = pipeline.recommend(query="test", files=[])
|
| 210 |
-
|
| 211 |
-
assert result["status"] == "complete"
|
| 212 |
-
```
|
| 213 |
-
|
| 214 |
-
### Mocking File Operations
|
| 215 |
-
|
| 216 |
-
```python
|
| 217 |
-
def test_file_validation():
|
| 218 |
-
"""Test file validation without real files."""
|
| 219 |
-
with patch('os.path.getsize') as mock_size:
|
| 220 |
-
mock_size.return_value = 1024 * 1024 # 1 MB
|
| 221 |
-
|
| 222 |
-
from ai_agent.utils.file_validator import validate_file
|
| 223 |
-
is_valid = validate_file("fake.dcm")
|
| 224 |
-
|
| 225 |
-
assert is_valid
|
| 226 |
-
``` -->
|
| 227 |
-
|
| 228 |
-
## Test Data
|
| 229 |
-
|
| 230 |
-
### Using Test Cases
|
| 231 |
-
|
| 232 |
-
Load test cases from JSON:
|
| 233 |
-
|
| 234 |
-
```python
|
| 235 |
-
import json
|
| 236 |
-
|
| 237 |
-
def load_test_cases():
|
| 238 |
-
"""Load test cases from data file."""
|
| 239 |
-
with open("tests/data/test_data.json") as f:
|
| 240 |
-
return json.load(f)
|
| 241 |
-
|
| 242 |
-
@pytest.mark.parametrize("test_case", load_test_cases())
|
| 243 |
-
def test_from_json(test_case):
|
| 244 |
-
"""Test using cases from JSON file."""
|
| 245 |
-
query = test_case["query"]
|
| 246 |
-
expected = test_case["expected_tool"]
|
| 247 |
-
|
| 248 |
-
# Test logic here
|
| 249 |
-
assert expected in results
|
| 250 |
-
```
|
| 251 |
-
|
| 252 |
-
### Sample Data Files
|
| 253 |
-
|
| 254 |
-
Keep sample files small:
|
| 255 |
-
|
| 256 |
-
- **DICOM**: Single slice, low resolution
|
| 257 |
-
- **NIfTI**: Small volume (e.g., 64×64×64)
|
| 258 |
-
- **Images**: PNG/JPG under 1 MB
|
| 259 |
-
|
| 260 |
-
<!-- ## Coverage
|
| 261 |
-
|
| 262 |
-
### Measuring Coverage
|
| 263 |
-
|
| 264 |
-
```bash
|
| 265 |
-
# Run with coverage
|
| 266 |
-
pytest --cov=ai_agent
|
| 267 |
-
|
| 268 |
-
# Generate HTML report
|
| 269 |
-
pytest --cov=ai_agent --cov-report=html
|
| 270 |
-
|
| 271 |
-
# Open report
|
| 272 |
-
open htmlcov/index.html # macOS
|
| 273 |
-
# or
|
| 274 |
-
xdg-open htmlcov/index.html # Linux
|
| 275 |
-
```
|
| 276 |
-
|
| 277 |
-
### Coverage Goals
|
| 278 |
-
|
| 279 |
-
Aim for:
|
| 280 |
-
|
| 281 |
-
- **Overall**: >80%
|
| 282 |
-
- **Critical paths**: >90% (retrieval, agent, pipeline)
|
| 283 |
-
- **Utilities**: >70%
|
| 284 |
-
|
| 285 |
-
### Coverage Configuration
|
| 286 |
-
|
| 287 |
-
In `pyproject.toml`:
|
| 288 |
-
|
| 289 |
-
```toml
|
| 290 |
-
[tool.coverage.run]
|
| 291 |
-
source = ["src/ai_agent"]
|
| 292 |
-
omit = ["tests/*", "*/migrations/*"]
|
| 293 |
-
|
| 294 |
-
[tool.coverage.report]
|
| 295 |
-
precision = 2
|
| 296 |
-
show_missing = true
|
| 297 |
-
skip_covered = false
|
| 298 |
-
``` -->
|
| 299 |
-
|
| 300 |
-
## Continuous Integration
|
| 301 |
-
|
| 302 |
-
### GitHub Actions
|
| 303 |
-
|
| 304 |
-
Tests run automatically on:
|
| 305 |
-
|
| 306 |
-
- Pull requests
|
| 307 |
-
- Pushes to main
|
| 308 |
-
|
| 309 |
-
### CI Configuration
|
| 310 |
-
|
| 311 |
-
```yaml
|
| 312 |
-
# .github/workflows/test.yml
|
| 313 |
-
name: Tests
|
| 314 |
-
|
| 315 |
-
on: [push, pull_request]
|
| 316 |
-
|
| 317 |
-
jobs:
|
| 318 |
-
test:
|
| 319 |
-
runs-on: ubuntu-latest
|
| 320 |
-
steps:
|
| 321 |
-
- uses: actions/checkout@v4
|
| 322 |
-
- uses: actions/setup-python@v5
|
| 323 |
-
with:
|
| 324 |
-
python-version: '3.10'
|
| 325 |
-
- run: pip install -e ".[dev]"
|
| 326 |
-
- run: pytest --cov=ai_agent
|
| 327 |
-
```
|
| 328 |
-
|
| 329 |
-
## Best Practices
|
| 330 |
-
|
| 331 |
-
### Do's
|
| 332 |
-
|
| 333 |
-
✅ **Test edge cases**: Empty inputs, invalid data, etc.
|
| 334 |
-
✅ **Test error handling**: Verify exceptions are caught
|
| 335 |
-
✅ **Use descriptive names**: `test_retrieval_with_empty_query` not `test1`
|
| 336 |
-
✅ **Keep tests isolated**: Each test should be independent
|
| 337 |
-
✅ **Use fixtures**: Avoid repeating setup code
|
| 338 |
-
✅ **Mock expensive operations**: VLM calls, network requests
|
| 339 |
-
|
| 340 |
-
### Don'ts
|
| 341 |
-
|
| 342 |
-
❌ **Don't test implementation details**: Test behavior, not internal state
|
| 343 |
-
❌ **Don't make tests depend on each other**: Each should run independently
|
| 344 |
-
❌ **Don't commit large test files**: Keep test data small
|
| 345 |
-
❌ **Don't skip error checking**: Test both success and failure paths
|
| 346 |
-
|
| 347 |
-
## Performance Testing
|
| 348 |
-
|
| 349 |
-
### Benchmarking
|
| 350 |
-
|
| 351 |
-
Use pytest-benchmark:
|
| 352 |
-
|
| 353 |
-
```python
|
| 354 |
-
def test_retrieval_performance(benchmark):
|
| 355 |
-
"""Benchmark retrieval speed."""
|
| 356 |
-
index = VectorIndex()
|
| 357 |
-
index.load("artifacts/rag_index")
|
| 358 |
-
|
| 359 |
-
result = benchmark(index.search, "segment lungs", k=10)
|
| 360 |
-
|
| 361 |
-
assert len(result) == 10
|
| 362 |
-
```
|
| 363 |
-
|
| 364 |
-
### Profiling
|
| 365 |
-
|
| 366 |
-
```bash
|
| 367 |
-
# Profile tests
|
| 368 |
-
pytest --profile
|
| 369 |
-
|
| 370 |
-
# Generate SVG profile
|
| 371 |
-
pytest --profile-svg
|
| 372 |
-
```
|
| 373 |
-
|
| 374 |
-
## Debugging Tests
|
| 375 |
-
|
| 376 |
-
### Running in Debug Mode
|
| 377 |
-
|
| 378 |
-
```python
|
| 379 |
-
# Add to test
|
| 380 |
-
import pdb; pdb.set_trace()
|
| 381 |
-
|
| 382 |
-
# Run pytest
|
| 383 |
-
pytest tests/test_file.py
|
| 384 |
-
```
|
| 385 |
-
|
| 386 |
-
### Verbose Output
|
| 387 |
-
|
| 388 |
-
```bash
|
| 389 |
-
# Show print statements
|
| 390 |
-
pytest -s
|
| 391 |
-
|
| 392 |
-
# Very verbose
|
| 393 |
-
pytest -vv
|
| 394 |
-
|
| 395 |
-
# Show local variables on failure
|
| 396 |
-
pytest -l
|
| 397 |
-
```
|
| 398 |
-
|
| 399 |
-
### Running Single Test
|
| 400 |
-
|
| 401 |
-
```bash
|
| 402 |
-
# Run one test function
|
| 403 |
-
pytest tests/test_file.py::test_function_name -v
|
| 404 |
-
```
|
| 405 |
-
|
| 406 |
-
## Next Steps
|
| 407 |
-
|
| 408 |
-
- Review [Project Structure](structure.md)
|
| 409 |
-
- Read [Contributing Guide](contributing.md)
|
| 410 |
-
- Explore [Architecture](../architecture/overview.md)
|
|
|
|
| 1 |
+
# Testing (still under development)
|
| 2 |
+
|
| 3 |
+
The AI Imaging Agent uses pytest for testing. This guide covers running tests and writing new ones.
|
| 4 |
+
|
| 5 |
+
**Note:** We are still developing some tests for the agent, hence this part is not relevant for now.
|
| 6 |
+
|
| 7 |
+
## Running Tests
|
| 8 |
+
|
| 9 |
+
### Basic Usage
|
| 10 |
+
|
| 11 |
+
```bash
|
| 12 |
+
# Run all tests
|
| 13 |
+
pytest
|
| 14 |
+
|
| 15 |
+
# Run specific test file
|
| 16 |
+
pytest tests/test_retrieval_pipeline.py
|
| 17 |
+
|
| 18 |
+
# Run specific test
|
| 19 |
+
pytest tests/test_retrieval_pipeline.py::test_basic_retrieval
|
| 20 |
+
|
| 21 |
+
# Run with verbose output
|
| 22 |
+
pytest -v
|
| 23 |
+
|
| 24 |
+
# Run with coverage
|
| 25 |
+
pytest --cov=ai_agent --cov-report=html
|
| 26 |
+
```
|
| 27 |
+
|
| 28 |
+
### Test Categories
|
| 29 |
+
|
| 30 |
+
Tests are marked by category:
|
| 31 |
+
|
| 32 |
+
```bash
|
| 33 |
+
# Run only unit tests
|
| 34 |
+
pytest -m unit
|
| 35 |
+
|
| 36 |
+
# Run only integration tests
|
| 37 |
+
pytest -m integration
|
| 38 |
+
|
| 39 |
+
# Skip slow tests
|
| 40 |
+
pytest -m "not slow"
|
| 41 |
+
```
|
| 42 |
+
|
| 43 |
+
## Test Organization
|
| 44 |
+
|
| 45 |
+
### Directory Structure
|
| 46 |
+
|
| 47 |
+
```
|
| 48 |
+
tests/
|
| 49 |
+
├── data/
|
| 50 |
+
│ ├── test_data.json # Test cases
|
| 51 |
+
│ └── 0002.DCM # Sample DICOM file
|
| 52 |
+
├── test_retrieval_pipeline.py # Retrieval tests
|
| 53 |
+
├── test_deepwiki_repo_info.py # Repo info tests
|
| 54 |
+
├── test_gpt4o_vision.py # VLM tests (integration)
|
| 55 |
+
└── __pycache__/
|
| 56 |
+
```
|
| 57 |
+
|
| 58 |
+
### Test File Naming
|
| 59 |
+
|
| 60 |
+
- `test_*.py`: Test files
|
| 61 |
+
- `*_test.py`: Alternative naming (less common)
|
| 62 |
+
|
| 63 |
+
### Test Function Naming
|
| 64 |
+
|
| 65 |
+
```python
|
| 66 |
+
def test_basic_retrieval():
|
| 67 |
+
"""Test basic retrieval functionality."""
|
| 68 |
+
pass
|
| 69 |
+
|
| 70 |
+
def test_edge_case_empty_query():
|
| 71 |
+
"""Test handling of empty query."""
|
| 72 |
+
pass
|
| 73 |
+
|
| 74 |
+
def test_integration_full_pipeline():
|
| 75 |
+
"""Integration test for complete pipeline."""
|
| 76 |
+
pass
|
| 77 |
+
```
|
| 78 |
+
|
| 79 |
+
## Writing Tests
|
| 80 |
+
|
| 81 |
+
### Unit Test Example
|
| 82 |
+
|
| 83 |
+
```python
|
| 84 |
+
import pytest
|
| 85 |
+
from ai_agent.retriever.vector_index import VectorIndex
|
| 86 |
+
|
| 87 |
+
def test_vector_index_search():
|
| 88 |
+
"""Test FAISS vector search."""
|
| 89 |
+
# Arrange
|
| 90 |
+
index = VectorIndex()
|
| 91 |
+
index.load("artifacts/rag_index")
|
| 92 |
+
|
| 93 |
+
query = "segment lungs CT"
|
| 94 |
+
|
| 95 |
+
# Act
|
| 96 |
+
results = index.search(query, k=5)
|
| 97 |
+
|
| 98 |
+
# Assert
|
| 99 |
+
assert len(results) == 5
|
| 100 |
+
assert all(r['score'] > 0 for r in results)
|
| 101 |
+
assert 'TotalSegmentator' in [r['name'] for r in results]
|
| 102 |
+
```
|
| 103 |
+
|
| 104 |
+
### Integration Test Example
|
| 105 |
+
|
| 106 |
+
```python
|
| 107 |
+
import pytest
|
| 108 |
+
from ai_agent.api.pipeline import RAGImagingPipeline
|
| 109 |
+
|
| 110 |
+
@pytest.mark.integration
|
| 111 |
+
def test_full_pipeline_with_image():
|
| 112 |
+
"""Integration test with real image and VLM call."""
|
| 113 |
+
# Arrange
|
| 114 |
+
pipeline = RAGImagingPipeline(
|
| 115 |
+
catalog_path="dataset/catalog.jsonl",
|
| 116 |
+
index_dir="artifacts/rag_index"
|
| 117 |
+
)
|
| 118 |
+
|
| 119 |
+
# Act
|
| 120 |
+
result = pipeline.recommend(
|
| 121 |
+
query="segment lungs",
|
| 122 |
+
files=["tests/data/chest_ct.dcm"]
|
| 123 |
+
)
|
| 124 |
+
|
| 125 |
+
# Assert
|
| 126 |
+
assert result.status == "complete"
|
| 127 |
+
assert len(result.recommendations) > 0
|
| 128 |
+
assert result.recommendations[0].accuracy_score > 70
|
| 129 |
+
```
|
| 130 |
+
|
| 131 |
+
### Parametrized Tests
|
| 132 |
+
|
| 133 |
+
```python
|
| 134 |
+
@pytest.mark.parametrize("query,expected_tool", [
|
| 135 |
+
("segment brain MRI", "FreeSurfer"),
|
| 136 |
+
("segment lungs CT", "TotalSegmentator"),
|
| 137 |
+
("classify chest X-ray", "CheXNet"),
|
| 138 |
+
])
|
| 139 |
+
def test_retrieval_for_queries(query, expected_tool):
|
| 140 |
+
"""Test retrieval returns expected tools for various queries."""
|
| 141 |
+
index = VectorIndex()
|
| 142 |
+
index.load("artifacts/rag_index")
|
| 143 |
+
|
| 144 |
+
results = index.search(query, k=10)
|
| 145 |
+
tool_names = [r['name'] for r in results]
|
| 146 |
+
|
| 147 |
+
assert expected_tool in tool_names
|
| 148 |
+
```
|
| 149 |
+
|
| 150 |
+
### Fixtures
|
| 151 |
+
|
| 152 |
+
```python
|
| 153 |
+
import pytest
|
| 154 |
+
|
| 155 |
+
@pytest.fixture
|
| 156 |
+
def pipeline():
|
| 157 |
+
"""Provide initialized pipeline for tests."""
|
| 158 |
+
return RAGImagingPipeline(
|
| 159 |
+
catalog_path="dataset/catalog.jsonl",
|
| 160 |
+
index_dir="artifacts/rag_index"
|
| 161 |
+
)
|
| 162 |
+
|
| 163 |
+
@pytest.fixture
|
| 164 |
+
def sample_dicom():
|
| 165 |
+
"""Provide path to sample DICOM file."""
|
| 166 |
+
return "tests/data/0002.DCM"
|
| 167 |
+
|
| 168 |
+
def test_with_fixtures(pipeline, sample_dicom):
|
| 169 |
+
"""Test using fixtures."""
|
| 170 |
+
result = pipeline.recommend(
|
| 171 |
+
query="analyze DICOM",
|
| 172 |
+
files=[sample_dicom]
|
| 173 |
+
)
|
| 174 |
+
assert result is not None
|
| 175 |
+
```
|
| 176 |
+
|
| 177 |
+
<!-- ## Mocking
|
| 178 |
+
|
| 179 |
+
### Mocking VLM Calls
|
| 180 |
+
|
| 181 |
+
To avoid API costs during testing:
|
| 182 |
+
|
| 183 |
+
```python
|
| 184 |
+
from unittest.mock import Mock, patch
|
| 185 |
+
import pytest
|
| 186 |
+
|
| 187 |
+
@pytest.fixture
|
| 188 |
+
def mock_vlm_response():
|
| 189 |
+
"""Mock VLM response."""
|
| 190 |
+
return {
|
| 191 |
+
"status": "complete",
|
| 192 |
+
"recommendations": [
|
| 193 |
+
{
|
| 194 |
+
"rank": 1,
|
| 195 |
+
"name": "TotalSegmentator",
|
| 196 |
+
"accuracy_score": 95,
|
| 197 |
+
"explanation": "Test explanation",
|
| 198 |
+
"reason": "task_match"
|
| 199 |
+
}
|
| 200 |
+
]
|
| 201 |
+
}
|
| 202 |
+
|
| 203 |
+
def test_with_mocked_vlm(mock_vlm_response):
|
| 204 |
+
"""Test pipeline with mocked VLM."""
|
| 205 |
+
with patch('ai_agent.agent.agent.Agent.run') as mock_run:
|
| 206 |
+
mock_run.return_value = mock_vlm_response
|
| 207 |
+
|
| 208 |
+
# Test code here
|
| 209 |
+
result = pipeline.recommend(query="test", files=[])
|
| 210 |
+
|
| 211 |
+
assert result["status"] == "complete"
|
| 212 |
+
```
|
| 213 |
+
|
| 214 |
+
### Mocking File Operations
|
| 215 |
+
|
| 216 |
+
```python
|
| 217 |
+
def test_file_validation():
|
| 218 |
+
"""Test file validation without real files."""
|
| 219 |
+
with patch('os.path.getsize') as mock_size:
|
| 220 |
+
mock_size.return_value = 1024 * 1024 # 1 MB
|
| 221 |
+
|
| 222 |
+
from ai_agent.utils.file_validator import validate_file
|
| 223 |
+
is_valid = validate_file("fake.dcm")
|
| 224 |
+
|
| 225 |
+
assert is_valid
|
| 226 |
+
``` -->
|
| 227 |
+
|
| 228 |
+
## Test Data
|
| 229 |
+
|
| 230 |
+
### Using Test Cases
|
| 231 |
+
|
| 232 |
+
Load test cases from JSON:
|
| 233 |
+
|
| 234 |
+
```python
|
| 235 |
+
import json
|
| 236 |
+
|
| 237 |
+
def load_test_cases():
|
| 238 |
+
"""Load test cases from data file."""
|
| 239 |
+
with open("tests/data/test_data.json") as f:
|
| 240 |
+
return json.load(f)
|
| 241 |
+
|
| 242 |
+
@pytest.mark.parametrize("test_case", load_test_cases())
|
| 243 |
+
def test_from_json(test_case):
|
| 244 |
+
"""Test using cases from JSON file."""
|
| 245 |
+
query = test_case["query"]
|
| 246 |
+
expected = test_case["expected_tool"]
|
| 247 |
+
|
| 248 |
+
# Test logic here
|
| 249 |
+
assert expected in results
|
| 250 |
+
```
|
| 251 |
+
|
| 252 |
+
### Sample Data Files
|
| 253 |
+
|
| 254 |
+
Keep sample files small:
|
| 255 |
+
|
| 256 |
+
- **DICOM**: Single slice, low resolution
|
| 257 |
+
- **NIfTI**: Small volume (e.g., 64×64×64)
|
| 258 |
+
- **Images**: PNG/JPG under 1 MB
|
| 259 |
+
|
| 260 |
+
<!-- ## Coverage
|
| 261 |
+
|
| 262 |
+
### Measuring Coverage
|
| 263 |
+
|
| 264 |
+
```bash
|
| 265 |
+
# Run with coverage
|
| 266 |
+
pytest --cov=ai_agent
|
| 267 |
+
|
| 268 |
+
# Generate HTML report
|
| 269 |
+
pytest --cov=ai_agent --cov-report=html
|
| 270 |
+
|
| 271 |
+
# Open report
|
| 272 |
+
open htmlcov/index.html # macOS
|
| 273 |
+
# or
|
| 274 |
+
xdg-open htmlcov/index.html # Linux
|
| 275 |
+
```
|
| 276 |
+
|
| 277 |
+
### Coverage Goals
|
| 278 |
+
|
| 279 |
+
Aim for:
|
| 280 |
+
|
| 281 |
+
- **Overall**: >80%
|
| 282 |
+
- **Critical paths**: >90% (retrieval, agent, pipeline)
|
| 283 |
+
- **Utilities**: >70%
|
| 284 |
+
|
| 285 |
+
### Coverage Configuration
|
| 286 |
+
|
| 287 |
+
In `pyproject.toml`:
|
| 288 |
+
|
| 289 |
+
```toml
|
| 290 |
+
[tool.coverage.run]
|
| 291 |
+
source = ["src/ai_agent"]
|
| 292 |
+
omit = ["tests/*", "*/migrations/*"]
|
| 293 |
+
|
| 294 |
+
[tool.coverage.report]
|
| 295 |
+
precision = 2
|
| 296 |
+
show_missing = true
|
| 297 |
+
skip_covered = false
|
| 298 |
+
``` -->
|
| 299 |
+
|
| 300 |
+
## Continuous Integration
|
| 301 |
+
|
| 302 |
+
### GitHub Actions
|
| 303 |
+
|
| 304 |
+
Tests run automatically on:
|
| 305 |
+
|
| 306 |
+
- Pull requests
|
| 307 |
+
- Pushes to main
|
| 308 |
+
|
| 309 |
+
### CI Configuration
|
| 310 |
+
|
| 311 |
+
```yaml
|
| 312 |
+
# .github/workflows/test.yml
|
| 313 |
+
name: Tests
|
| 314 |
+
|
| 315 |
+
on: [push, pull_request]
|
| 316 |
+
|
| 317 |
+
jobs:
|
| 318 |
+
test:
|
| 319 |
+
runs-on: ubuntu-latest
|
| 320 |
+
steps:
|
| 321 |
+
- uses: actions/checkout@v4
|
| 322 |
+
- uses: actions/setup-python@v5
|
| 323 |
+
with:
|
| 324 |
+
python-version: '3.10'
|
| 325 |
+
- run: pip install -e ".[dev]"
|
| 326 |
+
- run: pytest --cov=ai_agent
|
| 327 |
+
```
|
| 328 |
+
|
| 329 |
+
## Best Practices
|
| 330 |
+
|
| 331 |
+
### Do's
|
| 332 |
+
|
| 333 |
+
✅ **Test edge cases**: Empty inputs, invalid data, etc.
|
| 334 |
+
✅ **Test error handling**: Verify exceptions are caught
|
| 335 |
+
✅ **Use descriptive names**: `test_retrieval_with_empty_query` not `test1`
|
| 336 |
+
✅ **Keep tests isolated**: Each test should be independent
|
| 337 |
+
✅ **Use fixtures**: Avoid repeating setup code
|
| 338 |
+
✅ **Mock expensive operations**: VLM calls, network requests
|
| 339 |
+
|
| 340 |
+
### Don'ts
|
| 341 |
+
|
| 342 |
+
❌ **Don't test implementation details**: Test behavior, not internal state
|
| 343 |
+
❌ **Don't make tests depend on each other**: Each should run independently
|
| 344 |
+
❌ **Don't commit large test files**: Keep test data small
|
| 345 |
+
❌ **Don't skip error checking**: Test both success and failure paths
|
| 346 |
+
|
| 347 |
+
## Performance Testing
|
| 348 |
+
|
| 349 |
+
### Benchmarking
|
| 350 |
+
|
| 351 |
+
Use pytest-benchmark:
|
| 352 |
+
|
| 353 |
+
```python
|
| 354 |
+
def test_retrieval_performance(benchmark):
|
| 355 |
+
"""Benchmark retrieval speed."""
|
| 356 |
+
index = VectorIndex()
|
| 357 |
+
index.load("artifacts/rag_index")
|
| 358 |
+
|
| 359 |
+
result = benchmark(index.search, "segment lungs", k=10)
|
| 360 |
+
|
| 361 |
+
assert len(result) == 10
|
| 362 |
+
```
|
| 363 |
+
|
| 364 |
+
### Profiling
|
| 365 |
+
|
| 366 |
+
```bash
|
| 367 |
+
# Profile tests
|
| 368 |
+
pytest --profile
|
| 369 |
+
|
| 370 |
+
# Generate SVG profile
|
| 371 |
+
pytest --profile-svg
|
| 372 |
+
```
|
| 373 |
+
|
| 374 |
+
## Debugging Tests
|
| 375 |
+
|
| 376 |
+
### Running in Debug Mode
|
| 377 |
+
|
| 378 |
+
```python
|
| 379 |
+
# Add to test
|
| 380 |
+
import pdb; pdb.set_trace()
|
| 381 |
+
|
| 382 |
+
# Run pytest
|
| 383 |
+
pytest tests/test_file.py
|
| 384 |
+
```
|
| 385 |
+
|
| 386 |
+
### Verbose Output
|
| 387 |
+
|
| 388 |
+
```bash
|
| 389 |
+
# Show print statements
|
| 390 |
+
pytest -s
|
| 391 |
+
|
| 392 |
+
# Very verbose
|
| 393 |
+
pytest -vv
|
| 394 |
+
|
| 395 |
+
# Show local variables on failure
|
| 396 |
+
pytest -l
|
| 397 |
+
```
|
| 398 |
+
|
| 399 |
+
### Running Single Test
|
| 400 |
+
|
| 401 |
+
```bash
|
| 402 |
+
# Run one test function
|
| 403 |
+
pytest tests/test_file.py::test_function_name -v
|
| 404 |
+
```
|
| 405 |
+
|
| 406 |
+
## Next Steps
|
| 407 |
+
|
| 408 |
+
- Review [Project Structure](structure.md)
|
| 409 |
+
- Read [Contributing Guide](contributing.md)
|
| 410 |
+
- Explore [Architecture](../architecture/overview.md)
|
docs/getting-started/configuration.md
CHANGED
|
@@ -1,165 +1,165 @@
|
|
| 1 |
-
# Configuration
|
| 2 |
-
|
| 3 |
-
Before running the AI Imaging Agent, you need to configure it with your API keys and preferences.
|
| 4 |
-
|
| 5 |
-
## Environment Variables
|
| 6 |
-
|
| 7 |
-
Create a `.env` file in the repository root with the following configuration:
|
| 8 |
-
|
| 9 |
-
```dotenv
|
| 10 |
-
# Required: OpenAI API key
|
| 11 |
-
OPENAI_API_KEY=sk-xxxx
|
| 12 |
-
|
| 13 |
-
# Optional: GitHub token for repository info tool
|
| 14 |
-
GITHUB_TOKEN=ghp_xxxx
|
| 15 |
-
|
| 16 |
-
# Optional: Alternative model providers
|
| 17 |
-
EPFL_API_KEY=sk-xxxx
|
| 18 |
-
|
| 19 |
-
# Software catalog path
|
| 20 |
-
SOFTWARE_CATALOG=dataset/catalog.jsonl
|
| 21 |
-
|
| 22 |
-
# Logging configuration
|
| 23 |
-
LOGLEVEL_CONSOLE=WARNING
|
| 24 |
-
LOGLEVEL_FILE=INFO
|
| 25 |
-
FILE_LOG=1
|
| 26 |
-
LOG_DIR=logs
|
| 27 |
-
LOG_PROMPTS=0 # Set to 1 to save prompt snapshots for debugging
|
| 28 |
-
|
| 29 |
-
# Custom config path
|
| 30 |
-
CONFIG_PATH=config.yaml
|
| 31 |
-
```
|
| 32 |
-
|
| 33 |
-
## Required Configuration
|
| 34 |
-
|
| 35 |
-
### OpenAI API Key
|
| 36 |
-
|
| 37 |
-
The AI Imaging Agent requires an OpenAI API key for the vision-language model:
|
| 38 |
-
|
| 39 |
-
1. Sign up for an account at [OpenAI](https://platform.openai.com/)
|
| 40 |
-
2. Navigate to [API Keys](https://platform.openai.com/api-keys)
|
| 41 |
-
3. Create a new API key
|
| 42 |
-
4. Add it to your `.env` file:
|
| 43 |
-
|
| 44 |
-
```dotenv
|
| 45 |
-
OPENAI_API_KEY=sk-your-actual-key-here
|
| 46 |
-
```
|
| 47 |
-
|
| 48 |
-
## Model Configuration
|
| 49 |
-
|
| 50 |
-
The agent model can be configured via `config.yaml`:
|
| 51 |
-
|
| 52 |
-
```yaml
|
| 53 |
-
# AI Agent Model Configuration
|
| 54 |
-
|
| 55 |
-
# Default/fallback model (used for CLI and initial startup)
|
| 56 |
-
agent_model:
|
| 57 |
-
name: "gpt-4o-mini"
|
| 58 |
-
base_url: null # null for default OpenAI endpoint
|
| 59 |
-
api_key_env: "OPENAI_API_KEY"
|
| 60 |
-
|
| 61 |
-
# Available models for UI dropdown
|
| 62 |
-
available_models:
|
| 63 |
-
- display_name: "gpt-4o-mini"
|
| 64 |
-
name: "gpt-4o-mini"
|
| 65 |
-
base_url: null
|
| 66 |
-
provider: "OpenAI"
|
| 67 |
-
api_key_env: "OPENAI_API_KEY"
|
| 68 |
-
|
| 69 |
-
- display_name: "gpt-4o"
|
| 70 |
-
name: "gpt-4o"
|
| 71 |
-
base_url: null
|
| 72 |
-
provider: "OpenAI"
|
| 73 |
-
api_key_env: "OPENAI_API_KEY"
|
| 74 |
-
|
| 75 |
-
- display_name: "gpt-5.1"
|
| 76 |
-
name: "gpt-5.1"
|
| 77 |
-
base_url: null
|
| 78 |
-
provider: "OpenAI"
|
| 79 |
-
api_key_env: "OPENAI_API_KEY"
|
| 80 |
-
```
|
| 81 |
-
|
| 82 |
-
### Using Alternative Model Providers
|
| 83 |
-
|
| 84 |
-
You can configure custom OpenAI-compatible endpoints:
|
| 85 |
-
|
| 86 |
-
```yaml
|
| 87 |
-
available_models:
|
| 88 |
-
- display_name: "EPFL Inference"
|
| 89 |
-
name: "gpt-4o-mini"
|
| 90 |
-
base_url: "https://inference.epfl.ch/v1"
|
| 91 |
-
provider: "EPFL"
|
| 92 |
-
api_key_env: "EPFL_API_KEY"
|
| 93 |
-
```
|
| 94 |
-
|
| 95 |
-
Then add the corresponding API key to your `.env`:
|
| 96 |
-
|
| 97 |
-
```dotenv
|
| 98 |
-
EPFL_API_KEY=your-epfl-key
|
| 99 |
-
```
|
| 100 |
-
|
| 101 |
-
## Optional Configuration
|
| 102 |
-
|
| 103 |
-
### GitHub Token
|
| 104 |
-
|
| 105 |
-
For the repository info tool (optional):
|
| 106 |
-
|
| 107 |
-
```dotenv
|
| 108 |
-
GITHUB_TOKEN=ghp_your_github_personal_access_token
|
| 109 |
-
```
|
| 110 |
-
|
| 111 |
-
This enables the agent to fetch detailed information about GitHub repositories.
|
| 112 |
-
|
| 113 |
-
### Pipeline Parameters
|
| 114 |
-
|
| 115 |
-
Adjust retrieval and recommendation settings directly in the app setting. You can change the `TOP_K` and `NUM_CHOICES` parameters.
|
| 116 |
-
|
| 117 |
-
### Logging
|
| 118 |
-
|
| 119 |
-
Configure logging behavior:
|
| 120 |
-
|
| 121 |
-
```dotenv
|
| 122 |
-
# Console log level (DEBUG, INFO, WARNING, ERROR)
|
| 123 |
-
LOGLEVEL_CONSOLE=WARNING
|
| 124 |
-
|
| 125 |
-
# File log level
|
| 126 |
-
LOGLEVEL_FILE=INFO
|
| 127 |
-
|
| 128 |
-
# Enable file logging (0 or 1)
|
| 129 |
-
FILE_LOG=1
|
| 130 |
-
|
| 131 |
-
# Log directory
|
| 132 |
-
LOG_DIR=logs
|
| 133 |
-
|
| 134 |
-
# Save VLM prompts and images for debugging (0 or 1)
|
| 135 |
-
LOG_PROMPTS=0
|
| 136 |
-
```
|
| 137 |
-
|
| 138 |
-
!!! tip "Debug Mode"
|
| 139 |
-
Set `LOG_PROMPTS=1` to save VLM prompts and images to the `logs/` directory. This is useful for debugging but will increase disk usage.
|
| 140 |
-
|
| 141 |
-
### Software Catalog
|
| 142 |
-
|
| 143 |
-
Specify the path to your software catalog:
|
| 144 |
-
|
| 145 |
-
```dotenv
|
| 146 |
-
SOFTWARE_CATALOG=dataset/catalog.jsonl
|
| 147 |
-
```
|
| 148 |
-
|
| 149 |
-
The catalog should be in JSONL format following the schema.org SoftwareSourceCode structure.
|
| 150 |
-
|
| 151 |
-
## Verification
|
| 152 |
-
|
| 153 |
-
After configuring, verify your setup:
|
| 154 |
-
|
| 155 |
-
```bash
|
| 156 |
-
# Check that environment variables are loaded
|
| 157 |
-
python -c "from dotenv import load_dotenv; import os; load_dotenv(); print('API Key:', 'SET' if os.getenv('OPENAI_API_KEY') else 'NOT SET')"
|
| 158 |
-
```
|
| 159 |
-
|
| 160 |
-
## Next Steps
|
| 161 |
-
|
| 162 |
-
With configuration complete, you're ready to:
|
| 163 |
-
|
| 164 |
-
- [Run the Quick Start](quickstart.md)
|
| 165 |
-
- Learn about [Using the Chat Interface](../user-guide/chat-interface.md)
|
|
|
|
| 1 |
+
# Configuration
|
| 2 |
+
|
| 3 |
+
Before running the AI Imaging Agent, you need to configure it with your API keys and preferences.
|
| 4 |
+
|
| 5 |
+
## Environment Variables
|
| 6 |
+
|
| 7 |
+
Create a `.env` file in the repository root with the following configuration:
|
| 8 |
+
|
| 9 |
+
```dotenv
|
| 10 |
+
# Required: OpenAI API key
|
| 11 |
+
OPENAI_API_KEY=sk-xxxx
|
| 12 |
+
|
| 13 |
+
# Optional: GitHub token for repository info tool
|
| 14 |
+
GITHUB_TOKEN=ghp_xxxx
|
| 15 |
+
|
| 16 |
+
# Optional: Alternative model providers
|
| 17 |
+
EPFL_API_KEY=sk-xxxx
|
| 18 |
+
|
| 19 |
+
# Software catalog path
|
| 20 |
+
SOFTWARE_CATALOG=dataset/catalog.jsonl
|
| 21 |
+
|
| 22 |
+
# Logging configuration
|
| 23 |
+
LOGLEVEL_CONSOLE=WARNING
|
| 24 |
+
LOGLEVEL_FILE=INFO
|
| 25 |
+
FILE_LOG=1
|
| 26 |
+
LOG_DIR=logs
|
| 27 |
+
LOG_PROMPTS=0 # Set to 1 to save prompt snapshots for debugging
|
| 28 |
+
|
| 29 |
+
# Custom config path
|
| 30 |
+
CONFIG_PATH=config.yaml
|
| 31 |
+
```
|
| 32 |
+
|
| 33 |
+
## Required Configuration
|
| 34 |
+
|
| 35 |
+
### OpenAI API Key
|
| 36 |
+
|
| 37 |
+
The AI Imaging Agent requires an OpenAI API key for the vision-language model:
|
| 38 |
+
|
| 39 |
+
1. Sign up for an account at [OpenAI](https://platform.openai.com/)
|
| 40 |
+
2. Navigate to [API Keys](https://platform.openai.com/api-keys)
|
| 41 |
+
3. Create a new API key
|
| 42 |
+
4. Add it to your `.env` file:
|
| 43 |
+
|
| 44 |
+
```dotenv
|
| 45 |
+
OPENAI_API_KEY=sk-your-actual-key-here
|
| 46 |
+
```
|
| 47 |
+
|
| 48 |
+
## Model Configuration
|
| 49 |
+
|
| 50 |
+
The agent model can be configured via `config.yaml`:
|
| 51 |
+
|
| 52 |
+
```yaml
|
| 53 |
+
# AI Agent Model Configuration
|
| 54 |
+
|
| 55 |
+
# Default/fallback model (used for CLI and initial startup)
|
| 56 |
+
agent_model:
|
| 57 |
+
name: "gpt-4o-mini"
|
| 58 |
+
base_url: null # null for default OpenAI endpoint
|
| 59 |
+
api_key_env: "OPENAI_API_KEY"
|
| 60 |
+
|
| 61 |
+
# Available models for UI dropdown
|
| 62 |
+
available_models:
|
| 63 |
+
- display_name: "gpt-4o-mini"
|
| 64 |
+
name: "gpt-4o-mini"
|
| 65 |
+
base_url: null
|
| 66 |
+
provider: "OpenAI"
|
| 67 |
+
api_key_env: "OPENAI_API_KEY"
|
| 68 |
+
|
| 69 |
+
- display_name: "gpt-4o"
|
| 70 |
+
name: "gpt-4o"
|
| 71 |
+
base_url: null
|
| 72 |
+
provider: "OpenAI"
|
| 73 |
+
api_key_env: "OPENAI_API_KEY"
|
| 74 |
+
|
| 75 |
+
- display_name: "gpt-5.1"
|
| 76 |
+
name: "gpt-5.1"
|
| 77 |
+
base_url: null
|
| 78 |
+
provider: "OpenAI"
|
| 79 |
+
api_key_env: "OPENAI_API_KEY"
|
| 80 |
+
```
|
| 81 |
+
|
| 82 |
+
### Using Alternative Model Providers
|
| 83 |
+
|
| 84 |
+
You can configure custom OpenAI-compatible endpoints:
|
| 85 |
+
|
| 86 |
+
```yaml
|
| 87 |
+
available_models:
|
| 88 |
+
- display_name: "EPFL Inference"
|
| 89 |
+
name: "gpt-4o-mini"
|
| 90 |
+
base_url: "https://inference.epfl.ch/v1"
|
| 91 |
+
provider: "EPFL"
|
| 92 |
+
api_key_env: "EPFL_API_KEY"
|
| 93 |
+
```
|
| 94 |
+
|
| 95 |
+
Then add the corresponding API key to your `.env`:
|
| 96 |
+
|
| 97 |
+
```dotenv
|
| 98 |
+
EPFL_API_KEY=your-epfl-key
|
| 99 |
+
```
|
| 100 |
+
|
| 101 |
+
## Optional Configuration
|
| 102 |
+
|
| 103 |
+
### GitHub Token
|
| 104 |
+
|
| 105 |
+
For the repository info tool (optional):
|
| 106 |
+
|
| 107 |
+
```dotenv
|
| 108 |
+
GITHUB_TOKEN=ghp_your_github_personal_access_token
|
| 109 |
+
```
|
| 110 |
+
|
| 111 |
+
This enables the agent to fetch detailed information about GitHub repositories.
|
| 112 |
+
|
| 113 |
+
### Pipeline Parameters
|
| 114 |
+
|
| 115 |
+
Adjust retrieval and recommendation settings directly in the app setting. You can change the `TOP_K` and `NUM_CHOICES` parameters.
|
| 116 |
+
|
| 117 |
+
### Logging
|
| 118 |
+
|
| 119 |
+
Configure logging behavior:
|
| 120 |
+
|
| 121 |
+
```dotenv
|
| 122 |
+
# Console log level (DEBUG, INFO, WARNING, ERROR)
|
| 123 |
+
LOGLEVEL_CONSOLE=WARNING
|
| 124 |
+
|
| 125 |
+
# File log level
|
| 126 |
+
LOGLEVEL_FILE=INFO
|
| 127 |
+
|
| 128 |
+
# Enable file logging (0 or 1)
|
| 129 |
+
FILE_LOG=1
|
| 130 |
+
|
| 131 |
+
# Log directory
|
| 132 |
+
LOG_DIR=logs
|
| 133 |
+
|
| 134 |
+
# Save VLM prompts and images for debugging (0 or 1)
|
| 135 |
+
LOG_PROMPTS=0
|
| 136 |
+
```
|
| 137 |
+
|
| 138 |
+
!!! tip "Debug Mode"
|
| 139 |
+
Set `LOG_PROMPTS=1` to save VLM prompts and images to the `logs/` directory. This is useful for debugging but will increase disk usage.
|
| 140 |
+
|
| 141 |
+
### Software Catalog
|
| 142 |
+
|
| 143 |
+
Specify the path to your software catalog:
|
| 144 |
+
|
| 145 |
+
```dotenv
|
| 146 |
+
SOFTWARE_CATALOG=dataset/catalog.jsonl
|
| 147 |
+
```
|
| 148 |
+
|
| 149 |
+
The catalog should be in JSONL format following the schema.org SoftwareSourceCode structure.
|
| 150 |
+
|
| 151 |
+
## Verification
|
| 152 |
+
|
| 153 |
+
After configuring, verify your setup:
|
| 154 |
+
|
| 155 |
+
```bash
|
| 156 |
+
# Check that environment variables are loaded
|
| 157 |
+
python -c "from dotenv import load_dotenv; import os; load_dotenv(); print('API Key:', 'SET' if os.getenv('OPENAI_API_KEY') else 'NOT SET')"
|
| 158 |
+
```
|
| 159 |
+
|
| 160 |
+
## Next Steps
|
| 161 |
+
|
| 162 |
+
With configuration complete, you're ready to:
|
| 163 |
+
|
| 164 |
+
- [Run the Quick Start](quickstart.md)
|
| 165 |
+
- Learn about [Using the Chat Interface](../user-guide/chat-interface.md)
|
docs/getting-started/installation.md
CHANGED
|
@@ -1,143 +1,143 @@
|
|
| 1 |
-
# Installation
|
| 2 |
-
|
| 3 |
-
This guide will help you install and set up the AI Imaging Agent on your system.
|
| 4 |
-
|
| 5 |
-
## Prerequisites
|
| 6 |
-
|
| 7 |
-
Before installing, ensure you have:
|
| 8 |
-
|
| 9 |
-
- **Python 3.10–3.12** installed
|
| 10 |
-
- **pip** (Python package manager)
|
| 11 |
-
- **OpenAI API key** (or compatible API endpoint)
|
| 12 |
-
- Internet connection for model calls
|
| 13 |
-
|
| 14 |
-
## Installation Steps
|
| 15 |
-
|
| 16 |
-
### 1. Clone the Repository
|
| 17 |
-
|
| 18 |
-
```bash
|
| 19 |
-
git clone https://github.com/imaging-plaza/ai-agent.git
|
| 20 |
-
cd ai-agent
|
| 21 |
-
```
|
| 22 |
-
|
| 23 |
-
### 2. Create Virtual Environment
|
| 24 |
-
|
| 25 |
-
It's recommended to use a virtual environment to isolate dependencies:
|
| 26 |
-
|
| 27 |
-
=== "Linux/macOS"
|
| 28 |
-
|
| 29 |
-
```bash
|
| 30 |
-
python -m venv .venv
|
| 31 |
-
source .venv/bin/activate
|
| 32 |
-
```
|
| 33 |
-
|
| 34 |
-
=== "Windows"
|
| 35 |
-
|
| 36 |
-
```bash
|
| 37 |
-
python -m venv .venv
|
| 38 |
-
.venv\Scripts\activate
|
| 39 |
-
```
|
| 40 |
-
|
| 41 |
-
### 3. Install the Package
|
| 42 |
-
|
| 43 |
-
For regular use:
|
| 44 |
-
|
| 45 |
-
```bash
|
| 46 |
-
pip install --upgrade pip
|
| 47 |
-
pip install -e .
|
| 48 |
-
```
|
| 49 |
-
|
| 50 |
-
For development (includes test dependencies):
|
| 51 |
-
|
| 52 |
-
```bash
|
| 53 |
-
pip install -e ".[dev]"
|
| 54 |
-
```
|
| 55 |
-
|
| 56 |
-
## Verify Installation
|
| 57 |
-
|
| 58 |
-
Verify that the installation was successful:
|
| 59 |
-
|
| 60 |
-
```bash
|
| 61 |
-
ai_agent --help
|
| 62 |
-
```
|
| 63 |
-
|
| 64 |
-
You should see the available commands:
|
| 65 |
-
|
| 66 |
-
```
|
| 67 |
-
usage: ai_agent [-h] {chat,sync}
|
| 68 |
-
|
| 69 |
-
AI Agent CLI
|
| 70 |
-
|
| 71 |
-
positional arguments:
|
| 72 |
-
{chat,sync} 'chat' launches the chat UI; 'sync' runs one catalog refresh.
|
| 73 |
-
```
|
| 74 |
-
|
| 75 |
-
## Next Steps
|
| 76 |
-
|
| 77 |
-
Now that you have installed the AI Imaging Agent, proceed to:
|
| 78 |
-
|
| 79 |
-
- [Configuration](configuration.md) - Set up your environment and API keys
|
| 80 |
-
- [Quick Start](quickstart.md) - Run your first query
|
| 81 |
-
|
| 82 |
-
## Troubleshooting
|
| 83 |
-
|
| 84 |
-
### Python Version Issues
|
| 85 |
-
|
| 86 |
-
If you encounter issues with Python version compatibility:
|
| 87 |
-
|
| 88 |
-
```bash
|
| 89 |
-
# Check your Python version
|
| 90 |
-
python --version
|
| 91 |
-
|
| 92 |
-
# Use a specific Python version
|
| 93 |
-
python3.10 -m venv .venv
|
| 94 |
-
```
|
| 95 |
-
|
| 96 |
-
### Installation Errors
|
| 97 |
-
|
| 98 |
-
If you encounter dependency conflicts:
|
| 99 |
-
|
| 100 |
-
```bash
|
| 101 |
-
# Upgrade pip first
|
| 102 |
-
pip install --upgrade pip setuptools wheel
|
| 103 |
-
|
| 104 |
-
# Try installing again
|
| 105 |
-
pip install -e .
|
| 106 |
-
```
|
| 107 |
-
|
| 108 |
-
### Missing System Dependencies
|
| 109 |
-
|
| 110 |
-
Some packages may require system libraries:
|
| 111 |
-
|
| 112 |
-
=== "Ubuntu/Debian"
|
| 113 |
-
|
| 114 |
-
```bash
|
| 115 |
-
sudo apt-get update
|
| 116 |
-
sudo apt-get install python3-dev build-essential
|
| 117 |
-
```
|
| 118 |
-
|
| 119 |
-
=== "macOS"
|
| 120 |
-
|
| 121 |
-
```bash
|
| 122 |
-
# Using Homebrew
|
| 123 |
-
brew install python@3.10
|
| 124 |
-
```
|
| 125 |
-
|
| 126 |
-
=== "Windows"
|
| 127 |
-
|
| 128 |
-
Ensure you have [Microsoft C++ Build Tools](https://visualstudio.microsoft.com/visual-cpp-build-tools/) installed.
|
| 129 |
-
|
| 130 |
-
## Docker Installation (Alternative)
|
| 131 |
-
|
| 132 |
-
A Dockerfile is available for containerized deployment:
|
| 133 |
-
|
| 134 |
-
```bash
|
| 135 |
-
# Build the Docker image
|
| 136 |
-
docker build -t ai-agent -f Dockerfile .
|
| 137 |
-
|
| 138 |
-
# Run the container
|
| 139 |
-
docker run -p 7860:7860 --env-file .env ai-agent
|
| 140 |
-
```
|
| 141 |
-
|
| 142 |
-
!!! note
|
| 143 |
-
Make sure to create a `.env` file with your configuration before running the Docker container.
|
|
|
|
| 1 |
+
# Installation
|
| 2 |
+
|
| 3 |
+
This guide will help you install and set up the AI Imaging Agent on your system.
|
| 4 |
+
|
| 5 |
+
## Prerequisites
|
| 6 |
+
|
| 7 |
+
Before installing, ensure you have:
|
| 8 |
+
|
| 9 |
+
- **Python 3.10–3.12** installed
|
| 10 |
+
- **pip** (Python package manager)
|
| 11 |
+
- **OpenAI API key** (or compatible API endpoint)
|
| 12 |
+
- Internet connection for model calls
|
| 13 |
+
|
| 14 |
+
## Installation Steps
|
| 15 |
+
|
| 16 |
+
### 1. Clone the Repository
|
| 17 |
+
|
| 18 |
+
```bash
|
| 19 |
+
git clone https://github.com/imaging-plaza/ai-agent.git
|
| 20 |
+
cd ai-agent
|
| 21 |
+
```
|
| 22 |
+
|
| 23 |
+
### 2. Create Virtual Environment
|
| 24 |
+
|
| 25 |
+
It's recommended to use a virtual environment to isolate dependencies:
|
| 26 |
+
|
| 27 |
+
=== "Linux/macOS"
|
| 28 |
+
|
| 29 |
+
```bash
|
| 30 |
+
python -m venv .venv
|
| 31 |
+
source .venv/bin/activate
|
| 32 |
+
```
|
| 33 |
+
|
| 34 |
+
=== "Windows"
|
| 35 |
+
|
| 36 |
+
```bash
|
| 37 |
+
python -m venv .venv
|
| 38 |
+
.venv\Scripts\activate
|
| 39 |
+
```
|
| 40 |
+
|
| 41 |
+
### 3. Install the Package
|
| 42 |
+
|
| 43 |
+
For regular use:
|
| 44 |
+
|
| 45 |
+
```bash
|
| 46 |
+
pip install --upgrade pip
|
| 47 |
+
pip install -e .
|
| 48 |
+
```
|
| 49 |
+
|
| 50 |
+
For development (includes test dependencies):
|
| 51 |
+
|
| 52 |
+
```bash
|
| 53 |
+
pip install -e ".[dev]"
|
| 54 |
+
```
|
| 55 |
+
|
| 56 |
+
## Verify Installation
|
| 57 |
+
|
| 58 |
+
Verify that the installation was successful:
|
| 59 |
+
|
| 60 |
+
```bash
|
| 61 |
+
ai_agent --help
|
| 62 |
+
```
|
| 63 |
+
|
| 64 |
+
You should see the available commands:
|
| 65 |
+
|
| 66 |
+
```
|
| 67 |
+
usage: ai_agent [-h] {chat,sync}
|
| 68 |
+
|
| 69 |
+
AI Agent CLI
|
| 70 |
+
|
| 71 |
+
positional arguments:
|
| 72 |
+
{chat,sync} 'chat' launches the chat UI; 'sync' runs one catalog refresh.
|
| 73 |
+
```
|
| 74 |
+
|
| 75 |
+
## Next Steps
|
| 76 |
+
|
| 77 |
+
Now that you have installed the AI Imaging Agent, proceed to:
|
| 78 |
+
|
| 79 |
+
- [Configuration](configuration.md) - Set up your environment and API keys
|
| 80 |
+
- [Quick Start](quickstart.md) - Run your first query
|
| 81 |
+
|
| 82 |
+
## Troubleshooting
|
| 83 |
+
|
| 84 |
+
### Python Version Issues
|
| 85 |
+
|
| 86 |
+
If you encounter issues with Python version compatibility:
|
| 87 |
+
|
| 88 |
+
```bash
|
| 89 |
+
# Check your Python version
|
| 90 |
+
python --version
|
| 91 |
+
|
| 92 |
+
# Use a specific Python version
|
| 93 |
+
python3.10 -m venv .venv
|
| 94 |
+
```
|
| 95 |
+
|
| 96 |
+
### Installation Errors
|
| 97 |
+
|
| 98 |
+
If you encounter dependency conflicts:
|
| 99 |
+
|
| 100 |
+
```bash
|
| 101 |
+
# Upgrade pip first
|
| 102 |
+
pip install --upgrade pip setuptools wheel
|
| 103 |
+
|
| 104 |
+
# Try installing again
|
| 105 |
+
pip install -e .
|
| 106 |
+
```
|
| 107 |
+
|
| 108 |
+
### Missing System Dependencies
|
| 109 |
+
|
| 110 |
+
Some packages may require system libraries:
|
| 111 |
+
|
| 112 |
+
=== "Ubuntu/Debian"
|
| 113 |
+
|
| 114 |
+
```bash
|
| 115 |
+
sudo apt-get update
|
| 116 |
+
sudo apt-get install python3-dev build-essential
|
| 117 |
+
```
|
| 118 |
+
|
| 119 |
+
=== "macOS"
|
| 120 |
+
|
| 121 |
+
```bash
|
| 122 |
+
# Using Homebrew
|
| 123 |
+
brew install python@3.10
|
| 124 |
+
```
|
| 125 |
+
|
| 126 |
+
=== "Windows"
|
| 127 |
+
|
| 128 |
+
Ensure you have [Microsoft C++ Build Tools](https://visualstudio.microsoft.com/visual-cpp-build-tools/) installed.
|
| 129 |
+
|
| 130 |
+
## Docker Installation (Alternative)
|
| 131 |
+
|
| 132 |
+
A Dockerfile is available for containerized deployment:
|
| 133 |
+
|
| 134 |
+
```bash
|
| 135 |
+
# Build the Docker image
|
| 136 |
+
docker build -t ai-agent -f Dockerfile .
|
| 137 |
+
|
| 138 |
+
# Run the container
|
| 139 |
+
docker run -p 7860:7860 --env-file .env ai-agent
|
| 140 |
+
```
|
| 141 |
+
|
| 142 |
+
!!! note
|
| 143 |
+
Make sure to create a `.env` file with your configuration before running the Docker container.
|
docs/getting-started/quickstart.md
CHANGED
|
@@ -1,190 +1,190 @@
|
|
| 1 |
-
# Quick Start
|
| 2 |
-
|
| 3 |
-
This guide will walk you through your first interaction with the AI Imaging Agent.
|
| 4 |
-
|
| 5 |
-
## Starting the Application
|
| 6 |
-
|
| 7 |
-
Once you've [installed](installation.md) and [configured](configuration.md) the agent, start the chat interface:
|
| 8 |
-
|
| 9 |
-
```bash
|
| 10 |
-
ai_agent chat
|
| 11 |
-
```
|
| 12 |
-
|
| 13 |
-
You should see output like:
|
| 14 |
-
|
| 15 |
-
```
|
| 16 |
-
Running on local URL: http://127.0.0.1:7860
|
| 17 |
-
```
|
| 18 |
-
|
| 19 |
-
Open your web browser and navigate to **http://127.0.0.1:7860**
|
| 20 |
-
|
| 21 |
-
## Your First Query
|
| 22 |
-
|
| 23 |
-
### Example 1: Object Segmentation
|
| 24 |
-
|
| 25 |
-
Let's try a simple segmentation task:
|
| 26 |
-
|
| 27 |
-
1. **Upload an Image**: Click the upload area or drag and drop an image (e.g., a photo of a cat)
|
| 28 |
-
|
| 29 |
-
2. **Type Your Request**: In the chat input, type:
|
| 30 |
-
```
|
| 31 |
-
I want to segment the cat from this image
|
| 32 |
-
```
|
| 33 |
-
|
| 34 |
-
3. **Review Recommendations**: The agent will return ranked tool recommendations with:
|
| 35 |
-
- Tool names and descriptions
|
| 36 |
-
- Accuracy scores
|
| 37 |
-
- Explanations for why each tool matches your task
|
| 38 |
-
- Links to runnable demos
|
| 39 |
-
|
| 40 |
-
4. **Run a Demo** (optional): Click the "Run Demo" button to see the result of the tool on your uploaded image
|
| 41 |
-
|
| 42 |
-
### Example 2: Medical Image Analysis
|
| 43 |
-
|
| 44 |
-
For medical imaging tasks:
|
| 45 |
-
|
| 46 |
-
1. **Upload a Medical Image**: Upload a DICOM file, NIfTI volume, or medical image
|
| 47 |
-
|
| 48 |
-
2. **Describe Your Task**:
|
| 49 |
-
```
|
| 50 |
-
Segment the lungs from this CT scan
|
| 51 |
-
```
|
| 52 |
-
|
| 53 |
-
3. **Get Format-Aware Results**: The agent considers:
|
| 54 |
-
- Your image format (DICOM, NIfTI, etc.)
|
| 55 |
-
- Image dimensions (2D, 3D, 4D)
|
| 56 |
-
- Medical imaging modality (CT, MRI, etc.)
|
| 57 |
-
|
| 58 |
-
### Example 3: General Computer Vision
|
| 59 |
-
|
| 60 |
-
For general tasks:
|
| 61 |
-
|
| 62 |
-
```
|
| 63 |
-
Detect all objects in this image
|
| 64 |
-
```
|
| 65 |
-
|
| 66 |
-
```
|
| 67 |
-
Extract text from this document image
|
| 68 |
-
```
|
| 69 |
-
|
| 70 |
-
```
|
| 71 |
-
Classify what type of animal is in this picture
|
| 72 |
-
```
|
| 73 |
-
|
| 74 |
-
## Understanding the Interface
|
| 75 |
-
|
| 76 |
-
### Chat Panel
|
| 77 |
-
|
| 78 |
-
- **Message History**: Scroll to see previous interactions
|
| 79 |
-
- **Rich Media**: Images, files, and tool cards are rendered inline
|
| 80 |
-
- **Code Blocks**: Formatted code and JSON responses
|
| 81 |
-
|
| 82 |
-
### Sidebar
|
| 83 |
-
|
| 84 |
-
- **Uploaded Files**: View all files you've uploaded in the session
|
| 85 |
-
- **Preview Images**: See converted image previews
|
| 86 |
-
- **Debug Info**: View conversation state and excluded tools (if in debug mode)
|
| 87 |
-
|
| 88 |
-
### Tool Recommendation Cards
|
| 89 |
-
|
| 90 |
-
Each recommended tool shows:
|
| 91 |
-
|
| 92 |
-
- **Rank**: Priority order (1 = best match)
|
| 93 |
-
- **Name**: Tool/software name
|
| 94 |
-
- **Accuracy Score**: Confidence level (0-100%)
|
| 95 |
-
- **Description**: What the tool does
|
| 96 |
-
- **Explanation**: Why it matches your request
|
| 97 |
-
- **Metadata**:
|
| 98 |
-
- Supported modalities (CT, MRI, etc.)
|
| 99 |
-
- Dimensions (2D, 3D, etc.)
|
| 100 |
-
- File formats (DICOM, NIfTI, PNG, etc.)
|
| 101 |
-
- License information
|
| 102 |
-
- Tags and categories
|
| 103 |
-
- **Demo Link**: Direct link to runnable example
|
| 104 |
-
|
| 105 |
-
## Advanced Usage
|
| 106 |
-
|
| 107 |
-
### Multi-Turn Conversations
|
| 108 |
-
|
| 109 |
-
The agent maintains conversation context:
|
| 110 |
-
|
| 111 |
-
```
|
| 112 |
-
You: I have a lung CT scan
|
| 113 |
-
Agent: [Provides general information about lung CT analysis tools]
|
| 114 |
-
|
| 115 |
-
You: I want to segment the airways
|
| 116 |
-
Agent: [Provides specific airway segmentation tools]
|
| 117 |
-
|
| 118 |
-
You: Show me alternatives
|
| 119 |
-
Agent: [Provides different tool options]
|
| 120 |
-
```
|
| 121 |
-
|
| 122 |
-
### Excluding Tools
|
| 123 |
-
|
| 124 |
-
Exclude specific tools from results:
|
| 125 |
-
|
| 126 |
-
```
|
| 127 |
-
Find lung segmentation tools [EXCLUDE:totalsegmentator|medicalsam]
|
| 128 |
-
```
|
| 129 |
-
|
| 130 |
-
### Requesting Alternatives
|
| 131 |
-
|
| 132 |
-
If initial results don't match your needs:
|
| 133 |
-
|
| 134 |
-
```
|
| 135 |
-
Show me alternative tools
|
| 136 |
-
|
| 137 |
-
Can you search for other options?
|
| 138 |
-
|
| 139 |
-
What else is available?
|
| 140 |
-
```
|
| 141 |
-
|
| 142 |
-
## CLI Commands
|
| 143 |
-
|
| 144 |
-
The agent provides two main commands:
|
| 145 |
-
|
| 146 |
-
### Launch Chat Interface
|
| 147 |
-
|
| 148 |
-
```bash
|
| 149 |
-
ai_agent chat
|
| 150 |
-
```
|
| 151 |
-
|
| 152 |
-
Starts the Gradio web interface with automatic catalog synchronization.
|
| 153 |
-
|
| 154 |
-
### Sync Catalog
|
| 155 |
-
|
| 156 |
-
```bash
|
| 157 |
-
ai_agent sync
|
| 158 |
-
```
|
| 159 |
-
|
| 160 |
-
Manually synchronize the software catalog without launching the UI.
|
| 161 |
-
|
| 162 |
-
## Tips for Best Results
|
| 163 |
-
|
| 164 |
-
!!! tip "Be Specific"
|
| 165 |
-
The more specific your request, the better the recommendations:
|
| 166 |
-
|
| 167 |
-
- ❌ "Process this image"
|
| 168 |
-
- ✅ "Segment the liver from this abdominal CT scan"
|
| 169 |
-
|
| 170 |
-
!!! tip "Upload First"
|
| 171 |
-
Upload your image before describing the task. The agent can see image content and metadata.
|
| 172 |
-
|
| 173 |
-
!!! tip "Mention Formats"
|
| 174 |
-
If you need specific format support, mention it:
|
| 175 |
-
|
| 176 |
-
"I need a tool that works with DICOM files"
|
| 177 |
-
|
| 178 |
-
!!! tip "Use Natural Language"
|
| 179 |
-
No need to use technical jargon - conversational language works fine:
|
| 180 |
-
|
| 181 |
-
"Help me find tumors in this MRI" works just as well as "Tumor detection in MRI volumes"
|
| 182 |
-
|
| 183 |
-
## Next Steps
|
| 184 |
-
|
| 185 |
-
Now that you've run your first queries:
|
| 186 |
-
|
| 187 |
-
- Learn more about [Using the Chat Interface](../user-guide/chat-interface.md)
|
| 188 |
-
- Explore [Supported File Formats](../user-guide/file-formats.md)
|
| 189 |
-
- Understand [How Recommendations Work](../user-guide/recommendations.md)
|
| 190 |
-
- Dive into the [Architecture Overview](../architecture/overview.md)
|
|
|
|
| 1 |
+
# Quick Start
|
| 2 |
+
|
| 3 |
+
This guide will walk you through your first interaction with the AI Imaging Agent.
|
| 4 |
+
|
| 5 |
+
## Starting the Application
|
| 6 |
+
|
| 7 |
+
Once you've [installed](installation.md) and [configured](configuration.md) the agent, start the chat interface:
|
| 8 |
+
|
| 9 |
+
```bash
|
| 10 |
+
ai_agent chat
|
| 11 |
+
```
|
| 12 |
+
|
| 13 |
+
You should see output like:
|
| 14 |
+
|
| 15 |
+
```
|
| 16 |
+
Running on local URL: http://127.0.0.1:7860
|
| 17 |
+
```
|
| 18 |
+
|
| 19 |
+
Open your web browser and navigate to **http://127.0.0.1:7860**
|
| 20 |
+
|
| 21 |
+
## Your First Query
|
| 22 |
+
|
| 23 |
+
### Example 1: Object Segmentation
|
| 24 |
+
|
| 25 |
+
Let's try a simple segmentation task:
|
| 26 |
+
|
| 27 |
+
1. **Upload an Image**: Click the upload area or drag and drop an image (e.g., a photo of a cat)
|
| 28 |
+
|
| 29 |
+
2. **Type Your Request**: In the chat input, type:
|
| 30 |
+
```
|
| 31 |
+
I want to segment the cat from this image
|
| 32 |
+
```
|
| 33 |
+
|
| 34 |
+
3. **Review Recommendations**: The agent will return ranked tool recommendations with:
|
| 35 |
+
- Tool names and descriptions
|
| 36 |
+
- Accuracy scores
|
| 37 |
+
- Explanations for why each tool matches your task
|
| 38 |
+
- Links to runnable demos
|
| 39 |
+
|
| 40 |
+
4. **Run a Demo** (optional): Click the "Run Demo" button to see the result of the tool on your uploaded image
|
| 41 |
+
|
| 42 |
+
### Example 2: Medical Image Analysis
|
| 43 |
+
|
| 44 |
+
For medical imaging tasks:
|
| 45 |
+
|
| 46 |
+
1. **Upload a Medical Image**: Upload a DICOM file, NIfTI volume, or medical image
|
| 47 |
+
|
| 48 |
+
2. **Describe Your Task**:
|
| 49 |
+
```
|
| 50 |
+
Segment the lungs from this CT scan
|
| 51 |
+
```
|
| 52 |
+
|
| 53 |
+
3. **Get Format-Aware Results**: The agent considers:
|
| 54 |
+
- Your image format (DICOM, NIfTI, etc.)
|
| 55 |
+
- Image dimensions (2D, 3D, 4D)
|
| 56 |
+
- Medical imaging modality (CT, MRI, etc.)
|
| 57 |
+
|
| 58 |
+
### Example 3: General Computer Vision
|
| 59 |
+
|
| 60 |
+
For general tasks:
|
| 61 |
+
|
| 62 |
+
```
|
| 63 |
+
Detect all objects in this image
|
| 64 |
+
```
|
| 65 |
+
|
| 66 |
+
```
|
| 67 |
+
Extract text from this document image
|
| 68 |
+
```
|
| 69 |
+
|
| 70 |
+
```
|
| 71 |
+
Classify what type of animal is in this picture
|
| 72 |
+
```
|
| 73 |
+
|
| 74 |
+
## Understanding the Interface
|
| 75 |
+
|
| 76 |
+
### Chat Panel
|
| 77 |
+
|
| 78 |
+
- **Message History**: Scroll to see previous interactions
|
| 79 |
+
- **Rich Media**: Images, files, and tool cards are rendered inline
|
| 80 |
+
- **Code Blocks**: Formatted code and JSON responses
|
| 81 |
+
|
| 82 |
+
### Sidebar
|
| 83 |
+
|
| 84 |
+
- **Uploaded Files**: View all files you've uploaded in the session
|
| 85 |
+
- **Preview Images**: See converted image previews
|
| 86 |
+
- **Debug Info**: View conversation state and excluded tools (if in debug mode)
|
| 87 |
+
|
| 88 |
+
### Tool Recommendation Cards
|
| 89 |
+
|
| 90 |
+
Each recommended tool shows:
|
| 91 |
+
|
| 92 |
+
- **Rank**: Priority order (1 = best match)
|
| 93 |
+
- **Name**: Tool/software name
|
| 94 |
+
- **Accuracy Score**: Confidence level (0-100%)
|
| 95 |
+
- **Description**: What the tool does
|
| 96 |
+
- **Explanation**: Why it matches your request
|
| 97 |
+
- **Metadata**:
|
| 98 |
+
- Supported modalities (CT, MRI, etc.)
|
| 99 |
+
- Dimensions (2D, 3D, etc.)
|
| 100 |
+
- File formats (DICOM, NIfTI, PNG, etc.)
|
| 101 |
+
- License information
|
| 102 |
+
- Tags and categories
|
| 103 |
+
- **Demo Link**: Direct link to runnable example
|
| 104 |
+
|
| 105 |
+
## Advanced Usage
|
| 106 |
+
|
| 107 |
+
### Multi-Turn Conversations
|
| 108 |
+
|
| 109 |
+
The agent maintains conversation context:
|
| 110 |
+
|
| 111 |
+
```
|
| 112 |
+
You: I have a lung CT scan
|
| 113 |
+
Agent: [Provides general information about lung CT analysis tools]
|
| 114 |
+
|
| 115 |
+
You: I want to segment the airways
|
| 116 |
+
Agent: [Provides specific airway segmentation tools]
|
| 117 |
+
|
| 118 |
+
You: Show me alternatives
|
| 119 |
+
Agent: [Provides different tool options]
|
| 120 |
+
```
|
| 121 |
+
|
| 122 |
+
### Excluding Tools
|
| 123 |
+
|
| 124 |
+
Exclude specific tools from results:
|
| 125 |
+
|
| 126 |
+
```
|
| 127 |
+
Find lung segmentation tools [EXCLUDE:totalsegmentator|medicalsam]
|
| 128 |
+
```
|
| 129 |
+
|
| 130 |
+
### Requesting Alternatives
|
| 131 |
+
|
| 132 |
+
If initial results don't match your needs:
|
| 133 |
+
|
| 134 |
+
```
|
| 135 |
+
Show me alternative tools
|
| 136 |
+
|
| 137 |
+
Can you search for other options?
|
| 138 |
+
|
| 139 |
+
What else is available?
|
| 140 |
+
```
|
| 141 |
+
|
| 142 |
+
## CLI Commands
|
| 143 |
+
|
| 144 |
+
The agent provides two main commands:
|
| 145 |
+
|
| 146 |
+
### Launch Chat Interface
|
| 147 |
+
|
| 148 |
+
```bash
|
| 149 |
+
ai_agent chat
|
| 150 |
+
```
|
| 151 |
+
|
| 152 |
+
Starts the Gradio web interface with automatic catalog synchronization.
|
| 153 |
+
|
| 154 |
+
### Sync Catalog
|
| 155 |
+
|
| 156 |
+
```bash
|
| 157 |
+
ai_agent sync
|
| 158 |
+
```
|
| 159 |
+
|
| 160 |
+
Manually synchronize the software catalog without launching the UI.
|
| 161 |
+
|
| 162 |
+
## Tips for Best Results
|
| 163 |
+
|
| 164 |
+
!!! tip "Be Specific"
|
| 165 |
+
The more specific your request, the better the recommendations:
|
| 166 |
+
|
| 167 |
+
- ❌ "Process this image"
|
| 168 |
+
- ✅ "Segment the liver from this abdominal CT scan"
|
| 169 |
+
|
| 170 |
+
!!! tip "Upload First"
|
| 171 |
+
Upload your image before describing the task. The agent can see image content and metadata.
|
| 172 |
+
|
| 173 |
+
!!! tip "Mention Formats"
|
| 174 |
+
If you need specific format support, mention it:
|
| 175 |
+
|
| 176 |
+
"I need a tool that works with DICOM files"
|
| 177 |
+
|
| 178 |
+
!!! tip "Use Natural Language"
|
| 179 |
+
No need to use technical jargon - conversational language works fine:
|
| 180 |
+
|
| 181 |
+
"Help me find tumors in this MRI" works just as well as "Tumor detection in MRI volumes"
|
| 182 |
+
|
| 183 |
+
## Next Steps
|
| 184 |
+
|
| 185 |
+
Now that you've run your first queries:
|
| 186 |
+
|
| 187 |
+
- Learn more about [Using the Chat Interface](../user-guide/chat-interface.md)
|
| 188 |
+
- Explore [Supported File Formats](../user-guide/file-formats.md)
|
| 189 |
+
- Understand [How Recommendations Work](../user-guide/recommendations.md)
|
| 190 |
+
- Dive into the [Architecture Overview](../architecture/overview.md)
|
docs/guide.md
CHANGED
|
@@ -1,277 +1,277 @@
|
|
| 1 |
-
# Project Guide
|
| 2 |
-
|
| 3 |
-
This guide is a practical map of the entire repository for contributors and maintainers.
|
| 4 |
-
|
| 5 |
-
It focuses on:
|
| 6 |
-
- What each folder is responsible for
|
| 7 |
-
- Which Python environment and package workflow are the defaults
|
| 8 |
-
- Which commands are currently valid
|
| 9 |
-
- What to improve next in architecture, testing, performance, and developer experience
|
| 10 |
-
|
| 11 |
-
## 1) System Summary
|
| 12 |
-
|
| 13 |
-
AI Imaging Agent is a RAG plus VLM recommender for imaging software.
|
| 14 |
-
|
| 15 |
-
High-level flow:
|
| 16 |
-
1. User uploads file(s) and enters a task.
|
| 17 |
-
2. Retrieval stage finds candidate tools (BGE-M3 + FAISS + reranker).
|
| 18 |
-
3. Agent/VLM stage ranks candidates with image-aware reasoning.
|
| 19 |
-
4. UI renders ranked recommendations and optional demo links.
|
| 20 |
-
|
| 21 |
-
Primary orchestrator: [src/ai_agent/api/pipeline.py](src/ai_agent/api/pipeline.py)
|
| 22 |
-
|
| 23 |
-
## 2) Default Python Environment And Packages (Dev Container Canonical)
|
| 24 |
-
|
| 25 |
-
Assume development is done inside the dev container.
|
| 26 |
-
|
| 27 |
-
Source of truth:
|
| 28 |
-
- Dev container: [.devcontainer/devcontainer.json](../.devcontainer/devcontainer.json)
|
| 29 |
-
- Package metadata and pinned dependencies: [pyproject.toml](../pyproject.toml)
|
| 30 |
-
- Secondary dependency list: [requirements.txt](../requirements.txt)
|
| 31 |
-
|
| 32 |
-
Default environment:
|
| 33 |
-
- OS: Debian Bookworm (dev container)
|
| 34 |
-
- Python: 3.12
|
| 35 |
-
- Environment manager: uv
|
| 36 |
-
- Virtual environment path: .venv
|
| 37 |
-
|
| 38 |
-
Recommended commands:
|
| 39 |
-
|
| 40 |
-
```bash
|
| 41 |
-
uv venv
|
| 42 |
-
uv pip install -e .
|
| 43 |
-
uv pip install -e ".[dev]"
|
| 44 |
-
```
|
| 45 |
-
|
| 46 |
-
Run and test:
|
| 47 |
-
|
| 48 |
-
```bash
|
| 49 |
-
ai_agent chat
|
| 50 |
-
ai_agent sync
|
| 51 |
-
pytest tests/
|
| 52 |
-
```
|
| 53 |
-
|
| 54 |
-
Important note on command drift:
|
| 55 |
-
- CLI officially supports `chat` and `sync` in [src/ai_agent/cli.py](../src/ai_agent/cli.py).
|
| 56 |
-
- [justfile](../justfile) currently references `ai_agent ui`, which does not match current CLI modes.
|
| 57 |
-
- Documentation in this guide follows the actual CLI implementation.
|
| 58 |
-
|
| 59 |
-
## 3) Repository Top-Level Map
|
| 60 |
-
|
| 61 |
-
- [.github/](../.github/): automation and agent instructions
|
| 62 |
-
- [.devcontainer/](../.devcontainer/): dev container build and editor defaults
|
| 63 |
-
- [docs/](.): MkDocs source pages
|
| 64 |
-
- [src/](../src/): application source code
|
| 65 |
-
- [tests/](../tests/): test suite
|
| 66 |
-
- [data/](../data/): sample data assets
|
| 67 |
-
- [tools/](../tools/): container/tooling helpers
|
| 68 |
-
- [CHANGELOG.md](../CHANGELOG.md): release history
|
| 69 |
-
- [config.yaml](../config.yaml): model/provider configuration
|
| 70 |
-
- [mkdocs.yml](../mkdocs.yml): docs site navigation and theme
|
| 71 |
-
- [pyproject.toml](../pyproject.toml): package metadata, dependencies, entrypoints
|
| 72 |
-
|
| 73 |
-
## 4) Detailed Source Folder Responsibilities
|
| 74 |
-
|
| 75 |
-
Package root: [src/ai_agent/](../src/ai_agent)
|
| 76 |
-
|
| 77 |
-
### 4.1 [src/ai_agent/agent/](../src/ai_agent/agent)
|
| 78 |
-
|
| 79 |
-
Purpose: conversational orchestration using PydanticAI.
|
| 80 |
-
|
| 81 |
-
Key files:
|
| 82 |
-
- [src/ai_agent/agent/agent.py](../src/ai_agent/agent/agent.py): agent setup, tool wiring, response flow
|
| 83 |
-
- [src/ai_agent/agent/models.py](../src/ai_agent/agent/models.py): state/output models
|
| 84 |
-
- [src/ai_agent/agent/utils.py](../src/ai_agent/agent/utils.py): helper utilities and guardrails
|
| 85 |
-
- [src/ai_agent/agent/tools/](../src/ai_agent/agent/tools): concrete tool implementations
|
| 86 |
-
- [src/ai_agent/agent/tools/mcp/](../src/ai_agent/agent/tools/mcp): MCP adapters
|
| 87 |
-
|
| 88 |
-
Boundary:
|
| 89 |
-
- Should orchestrate tools and policy, not own retrieval internals.
|
| 90 |
-
|
| 91 |
-
### 4.2 [src/ai_agent/api/](../src/ai_agent/api)
|
| 92 |
-
|
| 93 |
-
Purpose: pipeline orchestration between inputs, retrieval, and selection.
|
| 94 |
-
|
| 95 |
-
Key file:
|
| 96 |
-
- [src/ai_agent/api/pipeline.py](../src/ai_agent/api/pipeline.py)
|
| 97 |
-
|
| 98 |
-
Responsibilities:
|
| 99 |
-
- validate files
|
| 100 |
-
- extract metadata
|
| 101 |
-
- build retrieval query
|
| 102 |
-
- call retrieval and selection stages
|
| 103 |
-
- manage index refresh/reload behavior
|
| 104 |
-
|
| 105 |
-
Boundary:
|
| 106 |
-
- Keep UI concerns out of this module.
|
| 107 |
-
|
| 108 |
-
### 4.3 [src/ai_agent/retriever/](../src/ai_agent/retriever)
|
| 109 |
-
|
| 110 |
-
Purpose: deterministic retrieval stack (no LLM calls).
|
| 111 |
-
|
| 112 |
-
Key files:
|
| 113 |
-
- [src/ai_agent/retriever/text_embedder.py](../src/ai_agent/retriever/text_embedder.py)
|
| 114 |
-
- [src/ai_agent/retriever/vector_index.py](../src/ai_agent/retriever/vector_index.py)
|
| 115 |
-
- [src/ai_agent/retriever/reranker.py](../src/ai_agent/retriever/reranker.py)
|
| 116 |
-
- [src/ai_agent/retriever/software_doc.py](../src/ai_agent/retriever/software_doc.py)
|
| 117 |
-
|
| 118 |
-
Boundary:
|
| 119 |
-
- Retrieval quality logic should stay here.
|
| 120 |
-
|
| 121 |
-
### 4.4 [src/ai_agent/generator/](../src/ai_agent/generator)
|
| 122 |
-
|
| 123 |
-
Purpose: selection schema and prompting primitives.
|
| 124 |
-
|
| 125 |
-
Key files:
|
| 126 |
-
- [src/ai_agent/generator/prompts.py](../src/ai_agent/generator/prompts.py)
|
| 127 |
-
- [src/ai_agent/generator/schema.py](../src/ai_agent/generator/schema.py)
|
| 128 |
-
|
| 129 |
-
Boundary:
|
| 130 |
-
- Keep this layer focused on schema and prompt contracts, not transport/UI concerns.
|
| 131 |
-
|
| 132 |
-
### 4.5 [src/ai_agent/ui/](../src/ai_agent/ui)
|
| 133 |
-
|
| 134 |
-
Purpose: Gradio app and interaction handling.
|
| 135 |
-
|
| 136 |
-
Key files:
|
| 137 |
-
- [src/ai_agent/ui/app.py](../src/ai_agent/ui/app.py)
|
| 138 |
-
- [src/ai_agent/ui/handlers.py](../src/ai_agent/ui/handlers.py)
|
| 139 |
-
- [src/ai_agent/ui/components.py](../src/ai_agent/ui/components.py)
|
| 140 |
-
- [src/ai_agent/ui/formatters.py](../src/ai_agent/ui/formatters.py)
|
| 141 |
-
- [src/ai_agent/ui/state.py](../src/ai_agent/ui/state.py)
|
| 142 |
-
- [src/ai_agent/ui/visualizations.py](../src/ai_agent/ui/visualizations.py)
|
| 143 |
-
|
| 144 |
-
Boundary:
|
| 145 |
-
- UI should call orchestrators, not reimplement retrieval/selection decisions.
|
| 146 |
-
|
| 147 |
-
### 4.6 [src/ai_agent/utils/](../src/ai_agent/utils)
|
| 148 |
-
|
| 149 |
-
Purpose: cross-cutting utility functions.
|
| 150 |
-
|
| 151 |
-
Key files:
|
| 152 |
-
- [src/ai_agent/utils/config.py](../src/ai_agent/utils/config.py)
|
| 153 |
-
- [src/ai_agent/utils/file_validator.py](../src/ai_agent/utils/file_validator.py)
|
| 154 |
-
- [src/ai_agent/utils/image_meta.py](../src/ai_agent/utils/image_meta.py)
|
| 155 |
-
- [src/ai_agent/utils/image_io.py](../src/ai_agent/utils/image_io.py)
|
| 156 |
-
- [src/ai_agent/utils/previews.py](../src/ai_agent/utils/previews.py)
|
| 157 |
-
- [src/ai_agent/utils/tags.py](../src/ai_agent/utils/tags.py)
|
| 158 |
-
- [src/ai_agent/utils/temp_file_manager.py](../src/ai_agent/utils/temp_file_manager.py)
|
| 159 |
-
|
| 160 |
-
Boundary:
|
| 161 |
-
- Keep utilities reusable and independent from UI-specific logic.
|
| 162 |
-
|
| 163 |
-
### 4.7 [src/ai_agent/catalog/](../src/ai_agent/catalog)
|
| 164 |
-
|
| 165 |
-
Purpose: catalog synchronization and refresh helpers.
|
| 166 |
-
|
| 167 |
-
Key file:
|
| 168 |
-
- [src/ai_agent/catalog/sync.py](../src/ai_agent/catalog/sync.py)
|
| 169 |
-
|
| 170 |
-
Boundary:
|
| 171 |
-
- Catalog IO and sync logic should stay isolated from ranking logic.
|
| 172 |
-
|
| 173 |
-
### 4.8 [src/ai_agent/core/](../src/ai_agent/core)
|
| 174 |
-
|
| 175 |
-
Purpose: shared core coordination such as pipeline registry.
|
| 176 |
-
|
| 177 |
-
Key file:
|
| 178 |
-
- [src/ai_agent/core/pipeline_registry.py](../src/ai_agent/core/pipeline_registry.py)
|
| 179 |
-
|
| 180 |
-
Boundary:
|
| 181 |
-
- Keep core primitives minimal and dependency-light.
|
| 182 |
-
|
| 183 |
-
### 4.9 [src/ai_agent/queries/](../src/ai_agent/queries)
|
| 184 |
-
|
| 185 |
-
Purpose: query assets used by catalog sync/retrieval support.
|
| 186 |
-
|
| 187 |
-
Key file:
|
| 188 |
-
- [src/ai_agent/queries/get_relevant_software.rq](../src/ai_agent/queries/get_relevant_software.rq)
|
| 189 |
-
|
| 190 |
-
Boundary:
|
| 191 |
-
- Keep query definitions versioned and testable.
|
| 192 |
-
|
| 193 |
-
### 4.10 [src/ai_agent/cli.py](../src/ai_agent/cli.py)
|
| 194 |
-
|
| 195 |
-
Purpose: command entry point and mode dispatch.
|
| 196 |
-
|
| 197 |
-
Current modes:
|
| 198 |
-
- `chat`
|
| 199 |
-
- `sync`
|
| 200 |
-
|
| 201 |
-
This is the command contract docs should follow.
|
| 202 |
-
|
| 203 |
-
## 5) Supporting Folders
|
| 204 |
-
|
| 205 |
-
### 5.1 [tests/](../tests)
|
| 206 |
-
|
| 207 |
-
Contains unit/integration tests and test fixtures under [tests/data/](../tests/data).
|
| 208 |
-
|
| 209 |
-
Improvement target:
|
| 210 |
-
- add more focused tests for UI handler edge cases and tool failure handling.
|
| 211 |
-
|
| 212 |
-
### 5.2 [tools/](../tools)
|
| 213 |
-
|
| 214 |
-
Container and deployment support assets.
|
| 215 |
-
|
| 216 |
-
Notable file:
|
| 217 |
-
- [tools/image/Dockerfile](../tools/image/Dockerfile) (uv + Python 3.12 baseline)
|
| 218 |
-
|
| 219 |
-
### 5.3 [docs/](.)
|
| 220 |
-
|
| 221 |
-
Documentation source for MkDocs.
|
| 222 |
-
|
| 223 |
-
Add new pages to [mkdocs.yml](../mkdocs.yml) nav to keep docs discoverable.
|
| 224 |
-
|
| 225 |
-
## 6) Known Inconsistencies To Track
|
| 226 |
-
|
| 227 |
-
1. [justfile](../justfile) uses `ai_agent ui`, while [src/ai_agent/cli.py](../src/ai_agent/cli.py) defines `chat` and `sync`.
|
| 228 |
-
2. Installation docs often show pip-first flow, while dev container bootstrap is uv-first.
|
| 229 |
-
3. [requirements.txt](../requirements.txt) is looser than [pyproject.toml](../pyproject.toml), which contains current pinned/runtime dependencies.
|
| 230 |
-
|
| 231 |
-
## 7) Codebase Improvement Guidelines
|
| 232 |
-
|
| 233 |
-
### 7.1 Architecture And Modularity
|
| 234 |
-
|
| 235 |
-
1. Keep strict stage boundaries: retrieval logic in `retriever`, selection contracts in `generator`, orchestration in `api`.
|
| 236 |
-
2. Minimize cross-layer imports from `ui` to low-level modules.
|
| 237 |
-
3. Introduce lightweight interface contracts for tool adapters to reduce coupling in `agent/tools`.
|
| 238 |
-
4. Centralize shared constants/env defaults to reduce duplicated configuration behavior.
|
| 239 |
-
|
| 240 |
-
### 7.2 Testing And Quality Gates
|
| 241 |
-
|
| 242 |
-
1. Add regression tests for format-token query construction and retry broadening behavior.
|
| 243 |
-
2. Add failure-path tests for image preview generation and graceful degradation.
|
| 244 |
-
3. Add contract tests for agent tool outputs (search, alternative search, repo info).
|
| 245 |
-
4. Enforce formatting/lint/type checks in CI (`ruff`, `black --check`, `mypy`, `pytest`).
|
| 246 |
-
|
| 247 |
-
### 7.3 Performance And Retrieval Quality
|
| 248 |
-
|
| 249 |
-
1. Add benchmark fixtures for retrieval latency and reranker throughput.
|
| 250 |
-
2. Track retrieval quality with a small fixed evaluation set (top-k recall, MRR).
|
| 251 |
-
3. Cache expensive metadata extraction where safe for repeated files in a session.
|
| 252 |
-
4. Make index reload behavior observable with structured counters in logs.
|
| 253 |
-
|
| 254 |
-
### 7.4 Developer Experience And CI
|
| 255 |
-
|
| 256 |
-
1. Align `just` tasks with real CLI contract (`chat`/`sync`).
|
| 257 |
-
2. Add a docs link checker in CI to prevent markdown drift.
|
| 258 |
-
3. Document one canonical local workflow (dev container first, optional local pip fallback).
|
| 259 |
-
4. Add a short maintainer checklist for release prep and changelog updates.
|
| 260 |
-
|
| 261 |
-
## 8) Practical Contributor Checklist
|
| 262 |
-
|
| 263 |
-
Before opening a PR:
|
| 264 |
-
1. Install/update in editable mode in the active environment.
|
| 265 |
-
2. Run tests relevant to changed modules.
|
| 266 |
-
3. Validate docs links if docs were touched.
|
| 267 |
-
4. Update [CHANGELOG.md](../CHANGELOG.md) for user-visible changes.
|
| 268 |
-
5. Confirm command and environment docs still match real behavior.
|
| 269 |
-
|
| 270 |
-
## 9) Related References
|
| 271 |
-
|
| 272 |
-
- [README.md](../README.md)
|
| 273 |
-
- [docs/index.md](index.md)
|
| 274 |
-
- [docs/architecture/overview.md](architecture/overview.md)
|
| 275 |
-
- [docs/development/structure.md](development/structure.md)
|
| 276 |
-
- [AGENTS.md](../AGENTS.md)
|
| 277 |
-
- [.github/copilot-instructions.md](../.github/copilot-instructions.md)
|
|
|
|
| 1 |
+
# Project Guide
|
| 2 |
+
|
| 3 |
+
This guide is a practical map of the entire repository for contributors and maintainers.
|
| 4 |
+
|
| 5 |
+
It focuses on:
|
| 6 |
+
- What each folder is responsible for
|
| 7 |
+
- Which Python environment and package workflow are the defaults
|
| 8 |
+
- Which commands are currently valid
|
| 9 |
+
- What to improve next in architecture, testing, performance, and developer experience
|
| 10 |
+
|
| 11 |
+
## 1) System Summary
|
| 12 |
+
|
| 13 |
+
AI Imaging Agent is a RAG plus VLM recommender for imaging software.
|
| 14 |
+
|
| 15 |
+
High-level flow:
|
| 16 |
+
1. User uploads file(s) and enters a task.
|
| 17 |
+
2. Retrieval stage finds candidate tools (BGE-M3 + FAISS + reranker).
|
| 18 |
+
3. Agent/VLM stage ranks candidates with image-aware reasoning.
|
| 19 |
+
4. UI renders ranked recommendations and optional demo links.
|
| 20 |
+
|
| 21 |
+
Primary orchestrator: [src/ai_agent/api/pipeline.py](src/ai_agent/api/pipeline.py)
|
| 22 |
+
|
| 23 |
+
## 2) Default Python Environment And Packages (Dev Container Canonical)
|
| 24 |
+
|
| 25 |
+
Assume development is done inside the dev container.
|
| 26 |
+
|
| 27 |
+
Source of truth:
|
| 28 |
+
- Dev container: [.devcontainer/devcontainer.json](../.devcontainer/devcontainer.json)
|
| 29 |
+
- Package metadata and pinned dependencies: [pyproject.toml](../pyproject.toml)
|
| 30 |
+
- Secondary dependency list: [requirements.txt](../requirements.txt)
|
| 31 |
+
|
| 32 |
+
Default environment:
|
| 33 |
+
- OS: Debian Bookworm (dev container)
|
| 34 |
+
- Python: 3.12
|
| 35 |
+
- Environment manager: uv
|
| 36 |
+
- Virtual environment path: .venv
|
| 37 |
+
|
| 38 |
+
Recommended commands:
|
| 39 |
+
|
| 40 |
+
```bash
|
| 41 |
+
uv venv
|
| 42 |
+
uv pip install -e .
|
| 43 |
+
uv pip install -e ".[dev]"
|
| 44 |
+
```
|
| 45 |
+
|
| 46 |
+
Run and test:
|
| 47 |
+
|
| 48 |
+
```bash
|
| 49 |
+
ai_agent chat
|
| 50 |
+
ai_agent sync
|
| 51 |
+
pytest tests/
|
| 52 |
+
```
|
| 53 |
+
|
| 54 |
+
Important note on command drift:
|
| 55 |
+
- CLI officially supports `chat` and `sync` in [src/ai_agent/cli.py](../src/ai_agent/cli.py).
|
| 56 |
+
- [justfile](../justfile) currently references `ai_agent ui`, which does not match current CLI modes.
|
| 57 |
+
- Documentation in this guide follows the actual CLI implementation.
|
| 58 |
+
|
| 59 |
+
## 3) Repository Top-Level Map
|
| 60 |
+
|
| 61 |
+
- [.github/](../.github/): automation and agent instructions
|
| 62 |
+
- [.devcontainer/](../.devcontainer/): dev container build and editor defaults
|
| 63 |
+
- [docs/](.): MkDocs source pages
|
| 64 |
+
- [src/](../src/): application source code
|
| 65 |
+
- [tests/](../tests/): test suite
|
| 66 |
+
- [data/](../data/): sample data assets
|
| 67 |
+
- [tools/](../tools/): container/tooling helpers
|
| 68 |
+
- [CHANGELOG.md](../CHANGELOG.md): release history
|
| 69 |
+
- [config.yaml](../config.yaml): model/provider configuration
|
| 70 |
+
- [mkdocs.yml](../mkdocs.yml): docs site navigation and theme
|
| 71 |
+
- [pyproject.toml](../pyproject.toml): package metadata, dependencies, entrypoints
|
| 72 |
+
|
| 73 |
+
## 4) Detailed Source Folder Responsibilities
|
| 74 |
+
|
| 75 |
+
Package root: [src/ai_agent/](../src/ai_agent)
|
| 76 |
+
|
| 77 |
+
### 4.1 [src/ai_agent/agent/](../src/ai_agent/agent)
|
| 78 |
+
|
| 79 |
+
Purpose: conversational orchestration using PydanticAI.
|
| 80 |
+
|
| 81 |
+
Key files:
|
| 82 |
+
- [src/ai_agent/agent/agent.py](../src/ai_agent/agent/agent.py): agent setup, tool wiring, response flow
|
| 83 |
+
- [src/ai_agent/agent/models.py](../src/ai_agent/agent/models.py): state/output models
|
| 84 |
+
- [src/ai_agent/agent/utils.py](../src/ai_agent/agent/utils.py): helper utilities and guardrails
|
| 85 |
+
- [src/ai_agent/agent/tools/](../src/ai_agent/agent/tools): concrete tool implementations
|
| 86 |
+
- [src/ai_agent/agent/tools/mcp/](../src/ai_agent/agent/tools/mcp): MCP adapters
|
| 87 |
+
|
| 88 |
+
Boundary:
|
| 89 |
+
- Should orchestrate tools and policy, not own retrieval internals.
|
| 90 |
+
|
| 91 |
+
### 4.2 [src/ai_agent/api/](../src/ai_agent/api)
|
| 92 |
+
|
| 93 |
+
Purpose: pipeline orchestration between inputs, retrieval, and selection.
|
| 94 |
+
|
| 95 |
+
Key file:
|
| 96 |
+
- [src/ai_agent/api/pipeline.py](../src/ai_agent/api/pipeline.py)
|
| 97 |
+
|
| 98 |
+
Responsibilities:
|
| 99 |
+
- validate files
|
| 100 |
+
- extract metadata
|
| 101 |
+
- build retrieval query
|
| 102 |
+
- call retrieval and selection stages
|
| 103 |
+
- manage index refresh/reload behavior
|
| 104 |
+
|
| 105 |
+
Boundary:
|
| 106 |
+
- Keep UI concerns out of this module.
|
| 107 |
+
|
| 108 |
+
### 4.3 [src/ai_agent/retriever/](../src/ai_agent/retriever)
|
| 109 |
+
|
| 110 |
+
Purpose: deterministic retrieval stack (no LLM calls).
|
| 111 |
+
|
| 112 |
+
Key files:
|
| 113 |
+
- [src/ai_agent/retriever/text_embedder.py](../src/ai_agent/retriever/text_embedder.py)
|
| 114 |
+
- [src/ai_agent/retriever/vector_index.py](../src/ai_agent/retriever/vector_index.py)
|
| 115 |
+
- [src/ai_agent/retriever/reranker.py](../src/ai_agent/retriever/reranker.py)
|
| 116 |
+
- [src/ai_agent/retriever/software_doc.py](../src/ai_agent/retriever/software_doc.py)
|
| 117 |
+
|
| 118 |
+
Boundary:
|
| 119 |
+
- Retrieval quality logic should stay here.
|
| 120 |
+
|
| 121 |
+
### 4.4 [src/ai_agent/generator/](../src/ai_agent/generator)
|
| 122 |
+
|
| 123 |
+
Purpose: selection schema and prompting primitives.
|
| 124 |
+
|
| 125 |
+
Key files:
|
| 126 |
+
- [src/ai_agent/generator/prompts.py](../src/ai_agent/generator/prompts.py)
|
| 127 |
+
- [src/ai_agent/generator/schema.py](../src/ai_agent/generator/schema.py)
|
| 128 |
+
|
| 129 |
+
Boundary:
|
| 130 |
+
- Keep this layer focused on schema and prompt contracts, not transport/UI concerns.
|
| 131 |
+
|
| 132 |
+
### 4.5 [src/ai_agent/ui/](../src/ai_agent/ui)
|
| 133 |
+
|
| 134 |
+
Purpose: Gradio app and interaction handling.
|
| 135 |
+
|
| 136 |
+
Key files:
|
| 137 |
+
- [src/ai_agent/ui/app.py](../src/ai_agent/ui/app.py)
|
| 138 |
+
- [src/ai_agent/ui/handlers.py](../src/ai_agent/ui/handlers.py)
|
| 139 |
+
- [src/ai_agent/ui/components.py](../src/ai_agent/ui/components.py)
|
| 140 |
+
- [src/ai_agent/ui/formatters.py](../src/ai_agent/ui/formatters.py)
|
| 141 |
+
- [src/ai_agent/ui/state.py](../src/ai_agent/ui/state.py)
|
| 142 |
+
- [src/ai_agent/ui/visualizations.py](../src/ai_agent/ui/visualizations.py)
|
| 143 |
+
|
| 144 |
+
Boundary:
|
| 145 |
+
- UI should call orchestrators, not reimplement retrieval/selection decisions.
|
| 146 |
+
|
| 147 |
+
### 4.6 [src/ai_agent/utils/](../src/ai_agent/utils)
|
| 148 |
+
|
| 149 |
+
Purpose: cross-cutting utility functions.
|
| 150 |
+
|
| 151 |
+
Key files:
|
| 152 |
+
- [src/ai_agent/utils/config.py](../src/ai_agent/utils/config.py)
|
| 153 |
+
- [src/ai_agent/utils/file_validator.py](../src/ai_agent/utils/file_validator.py)
|
| 154 |
+
- [src/ai_agent/utils/image_meta.py](../src/ai_agent/utils/image_meta.py)
|
| 155 |
+
- [src/ai_agent/utils/image_io.py](../src/ai_agent/utils/image_io.py)
|
| 156 |
+
- [src/ai_agent/utils/previews.py](../src/ai_agent/utils/previews.py)
|
| 157 |
+
- [src/ai_agent/utils/tags.py](../src/ai_agent/utils/tags.py)
|
| 158 |
+
- [src/ai_agent/utils/temp_file_manager.py](../src/ai_agent/utils/temp_file_manager.py)
|
| 159 |
+
|
| 160 |
+
Boundary:
|
| 161 |
+
- Keep utilities reusable and independent from UI-specific logic.
|
| 162 |
+
|
| 163 |
+
### 4.7 [src/ai_agent/catalog/](../src/ai_agent/catalog)
|
| 164 |
+
|
| 165 |
+
Purpose: catalog synchronization and refresh helpers.
|
| 166 |
+
|
| 167 |
+
Key file:
|
| 168 |
+
- [src/ai_agent/catalog/sync.py](../src/ai_agent/catalog/sync.py)
|
| 169 |
+
|
| 170 |
+
Boundary:
|
| 171 |
+
- Catalog IO and sync logic should stay isolated from ranking logic.
|
| 172 |
+
|
| 173 |
+
### 4.8 [src/ai_agent/core/](../src/ai_agent/core)
|
| 174 |
+
|
| 175 |
+
Purpose: shared core coordination such as pipeline registry.
|
| 176 |
+
|
| 177 |
+
Key file:
|
| 178 |
+
- [src/ai_agent/core/pipeline_registry.py](../src/ai_agent/core/pipeline_registry.py)
|
| 179 |
+
|
| 180 |
+
Boundary:
|
| 181 |
+
- Keep core primitives minimal and dependency-light.
|
| 182 |
+
|
| 183 |
+
### 4.9 [src/ai_agent/queries/](../src/ai_agent/queries)
|
| 184 |
+
|
| 185 |
+
Purpose: query assets used by catalog sync/retrieval support.
|
| 186 |
+
|
| 187 |
+
Key file:
|
| 188 |
+
- [src/ai_agent/queries/get_relevant_software.rq](../src/ai_agent/queries/get_relevant_software.rq)
|
| 189 |
+
|
| 190 |
+
Boundary:
|
| 191 |
+
- Keep query definitions versioned and testable.
|
| 192 |
+
|
| 193 |
+
### 4.10 [src/ai_agent/cli.py](../src/ai_agent/cli.py)
|
| 194 |
+
|
| 195 |
+
Purpose: command entry point and mode dispatch.
|
| 196 |
+
|
| 197 |
+
Current modes:
|
| 198 |
+
- `chat`
|
| 199 |
+
- `sync`
|
| 200 |
+
|
| 201 |
+
This is the command contract docs should follow.
|
| 202 |
+
|
| 203 |
+
## 5) Supporting Folders
|
| 204 |
+
|
| 205 |
+
### 5.1 [tests/](../tests)
|
| 206 |
+
|
| 207 |
+
Contains unit/integration tests and test fixtures under [tests/data/](../tests/data).
|
| 208 |
+
|
| 209 |
+
Improvement target:
|
| 210 |
+
- add more focused tests for UI handler edge cases and tool failure handling.
|
| 211 |
+
|
| 212 |
+
### 5.2 [tools/](../tools)
|
| 213 |
+
|
| 214 |
+
Container and deployment support assets.
|
| 215 |
+
|
| 216 |
+
Notable file:
|
| 217 |
+
- [tools/image/Dockerfile](../tools/image/Dockerfile) (uv + Python 3.12 baseline)
|
| 218 |
+
|
| 219 |
+
### 5.3 [docs/](.)
|
| 220 |
+
|
| 221 |
+
Documentation source for MkDocs.
|
| 222 |
+
|
| 223 |
+
Add new pages to [mkdocs.yml](../mkdocs.yml) nav to keep docs discoverable.
|
| 224 |
+
|
| 225 |
+
## 6) Known Inconsistencies To Track
|
| 226 |
+
|
| 227 |
+
1. [justfile](../justfile) uses `ai_agent ui`, while [src/ai_agent/cli.py](../src/ai_agent/cli.py) defines `chat` and `sync`.
|
| 228 |
+
2. Installation docs often show pip-first flow, while dev container bootstrap is uv-first.
|
| 229 |
+
3. [requirements.txt](../requirements.txt) is looser than [pyproject.toml](../pyproject.toml), which contains current pinned/runtime dependencies.
|
| 230 |
+
|
| 231 |
+
## 7) Codebase Improvement Guidelines
|
| 232 |
+
|
| 233 |
+
### 7.1 Architecture And Modularity
|
| 234 |
+
|
| 235 |
+
1. Keep strict stage boundaries: retrieval logic in `retriever`, selection contracts in `generator`, orchestration in `api`.
|
| 236 |
+
2. Minimize cross-layer imports from `ui` to low-level modules.
|
| 237 |
+
3. Introduce lightweight interface contracts for tool adapters to reduce coupling in `agent/tools`.
|
| 238 |
+
4. Centralize shared constants/env defaults to reduce duplicated configuration behavior.
|
| 239 |
+
|
| 240 |
+
### 7.2 Testing And Quality Gates
|
| 241 |
+
|
| 242 |
+
1. Add regression tests for format-token query construction and retry broadening behavior.
|
| 243 |
+
2. Add failure-path tests for image preview generation and graceful degradation.
|
| 244 |
+
3. Add contract tests for agent tool outputs (search, alternative search, repo info).
|
| 245 |
+
4. Enforce formatting/lint/type checks in CI (`ruff`, `black --check`, `mypy`, `pytest`).
|
| 246 |
+
|
| 247 |
+
### 7.3 Performance And Retrieval Quality
|
| 248 |
+
|
| 249 |
+
1. Add benchmark fixtures for retrieval latency and reranker throughput.
|
| 250 |
+
2. Track retrieval quality with a small fixed evaluation set (top-k recall, MRR).
|
| 251 |
+
3. Cache expensive metadata extraction where safe for repeated files in a session.
|
| 252 |
+
4. Make index reload behavior observable with structured counters in logs.
|
| 253 |
+
|
| 254 |
+
### 7.4 Developer Experience And CI
|
| 255 |
+
|
| 256 |
+
1. Align `just` tasks with real CLI contract (`chat`/`sync`).
|
| 257 |
+
2. Add a docs link checker in CI to prevent markdown drift.
|
| 258 |
+
3. Document one canonical local workflow (dev container first, optional local pip fallback).
|
| 259 |
+
4. Add a short maintainer checklist for release prep and changelog updates.
|
| 260 |
+
|
| 261 |
+
## 8) Practical Contributor Checklist
|
| 262 |
+
|
| 263 |
+
Before opening a PR:
|
| 264 |
+
1. Install/update in editable mode in the active environment.
|
| 265 |
+
2. Run tests relevant to changed modules.
|
| 266 |
+
3. Validate docs links if docs were touched.
|
| 267 |
+
4. Update [CHANGELOG.md](../CHANGELOG.md) for user-visible changes.
|
| 268 |
+
5. Confirm command and environment docs still match real behavior.
|
| 269 |
+
|
| 270 |
+
## 9) Related References
|
| 271 |
+
|
| 272 |
+
- [README.md](../README.md)
|
| 273 |
+
- [docs/index.md](index.md)
|
| 274 |
+
- [docs/architecture/overview.md](architecture/overview.md)
|
| 275 |
+
- [docs/development/structure.md](development/structure.md)
|
| 276 |
+
- [AGENTS.md](../AGENTS.md)
|
| 277 |
+
- [.github/copilot-instructions.md](../.github/copilot-instructions.md)
|
docs/index.md
CHANGED
|
@@ -1,81 +1,81 @@
|
|
| 1 |
-
# AI Imaging Agent
|
| 2 |
-
|
| 3 |
-
**An intelligent RAG + AI agent system that helps users discover the right imaging software for their images and tasks.**
|
| 4 |
-
|
| 5 |
-
[](https://www.python.org/downloads/)
|
| 6 |
-
[](https://github.com/imaging-plaza/ai-agent/blob/main/LICENSE)
|
| 7 |
-
|
| 8 |
-
---
|
| 9 |
-
|
| 10 |
-
## What is AI Imaging Agent?
|
| 11 |
-
|
| 12 |
-
AI Imaging Agent (also known as **Imaging Plaza**) is a conversational AI assistant that helps researchers and practitioners find the right imaging analysis tools for their specific needs. Simply upload an image, describe what you want to do, and get ranked software recommendations with links to runnable demos.
|
| 13 |
-
|
| 14 |
-
## ✨ Key Features
|
| 15 |
-
|
| 16 |
-
- **🤖 Conversational AI Agent**: Natural language interaction with multi-turn context
|
| 17 |
-
- **🔍 Smart Retrieval**: BGE-M3 embeddings + FAISS + CrossEncoder reranking
|
| 18 |
-
- **👁️ Vision-Aware Selection**: VLM-based tool selection considering both image content and metadata
|
| 19 |
-
- **🏥 Medical Imaging Focus**: Specialized support for CT, MRI, DICOM, NIfTI, and other medical formats
|
| 20 |
-
- **🎯 Format-Aware Matching**: IO compatibility scoring based on file formats and dimensions
|
| 21 |
-
- **🚀 Demo Integration**: Direct execution of Gradio Space demos on your images
|
| 22 |
-
- **📊 Rich UI**: Chat interface with image previews, file management, and execution traces
|
| 23 |
-
|
| 24 |
-
## Quick Example
|
| 25 |
-
|
| 26 |
-
```bash
|
| 27 |
-
# Install and run
|
| 28 |
-
pip install -e .
|
| 29 |
-
ai_agent chat
|
| 30 |
-
```
|
| 31 |
-
|
| 32 |
-
Then in the web interface:
|
| 33 |
-
|
| 34 |
-
1. Upload an image (e.g., a CT scan, or a PNG)
|
| 35 |
-
2. Type your request (e.g., _"I want to segment the lungs from this image"_ or _"I want to deblur this image"_)
|
| 36 |
-
3. Get ranked tool recommendations with accuracy scores
|
| 37 |
-
4. Click "Run demo" to execute tools directly
|
| 38 |
-
|
| 39 |
-
## Use Cases
|
| 40 |
-
|
| 41 |
-
### Medical Imaging
|
| 42 |
-
- Segment organs from CT/MRI scans
|
| 43 |
-
- Register brain images
|
| 44 |
-
- Detect tumors and anomalies
|
| 45 |
-
- Analyze DICOM files
|
| 46 |
-
|
| 47 |
-
### Scientific Imaging
|
| 48 |
-
- Process microscopy images
|
| 49 |
-
- Analyze multidimensional TIFF stacks
|
| 50 |
-
- Extract features from scientific images
|
| 51 |
-
|
| 52 |
-
### General Computer Vision
|
| 53 |
-
- Object detection and segmentation
|
| 54 |
-
- Image classification
|
| 55 |
-
- OCR and text extraction
|
| 56 |
-
- Image enhancement
|
| 57 |
-
|
| 58 |
-
## How It Works
|
| 59 |
-
|
| 60 |
-
The system uses a **two-stage pipeline**:
|
| 61 |
-
|
| 62 |
-
1. **Retrieval Stage**: Fast text search using BGE-M3 embeddings and FAISS to find candidate tools from a curated catalog
|
| 63 |
-
2. **Agent Selection**: Vision-language model (GPT-4o) analyzes your image and task to rank the best tools with explanations
|
| 64 |
-
|
| 65 |
-
Learn more in the [Architecture Overview](architecture/overview.md).
|
| 66 |
-
|
| 67 |
-
## Getting Started
|
| 68 |
-
|
| 69 |
-
Ready to try it out? Head over to the [Installation Guide](getting-started/installation.md) to get started!
|
| 70 |
-
|
| 71 |
-
## Project Guide
|
| 72 |
-
|
| 73 |
-
For maintainers and contributors, see the [Project Guide](guide.md) for a detailed repository map, dev-container environment defaults, and practical codebase improvement guidelines.
|
| 74 |
-
|
| 75 |
-
## Project Status
|
| 76 |
-
|
| 77 |
-
This project is actively developed and maintained by the Imaging Plaza team. Check the [Changelog](reference/changelog.md) for recent updates.
|
| 78 |
-
|
| 79 |
-
## License
|
| 80 |
-
|
| 81 |
-
This project is licensed under the Apache 2.0 License - see the [LICENSE](https://github.com/imaging-plaza/ai-agent/blob/main/LICENSE) file for details.
|
|
|
|
| 1 |
+
# AI Imaging Agent
|
| 2 |
+
|
| 3 |
+
**An intelligent RAG + AI agent system that helps users discover the right imaging software for their images and tasks.**
|
| 4 |
+
|
| 5 |
+
[](https://www.python.org/downloads/)
|
| 6 |
+
[](https://github.com/imaging-plaza/ai-agent/blob/main/LICENSE)
|
| 7 |
+
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
## What is AI Imaging Agent?
|
| 11 |
+
|
| 12 |
+
AI Imaging Agent (also known as **Imaging Plaza**) is a conversational AI assistant that helps researchers and practitioners find the right imaging analysis tools for their specific needs. Simply upload an image, describe what you want to do, and get ranked software recommendations with links to runnable demos.
|
| 13 |
+
|
| 14 |
+
## ✨ Key Features
|
| 15 |
+
|
| 16 |
+
- **🤖 Conversational AI Agent**: Natural language interaction with multi-turn context
|
| 17 |
+
- **🔍 Smart Retrieval**: BGE-M3 embeddings + FAISS + CrossEncoder reranking
|
| 18 |
+
- **👁️ Vision-Aware Selection**: VLM-based tool selection considering both image content and metadata
|
| 19 |
+
- **🏥 Medical Imaging Focus**: Specialized support for CT, MRI, DICOM, NIfTI, and other medical formats
|
| 20 |
+
- **🎯 Format-Aware Matching**: IO compatibility scoring based on file formats and dimensions
|
| 21 |
+
- **🚀 Demo Integration**: Direct execution of Gradio Space demos on your images
|
| 22 |
+
- **📊 Rich UI**: Chat interface with image previews, file management, and execution traces
|
| 23 |
+
|
| 24 |
+
## Quick Example
|
| 25 |
+
|
| 26 |
+
```bash
|
| 27 |
+
# Install and run
|
| 28 |
+
pip install -e .
|
| 29 |
+
ai_agent chat
|
| 30 |
+
```
|
| 31 |
+
|
| 32 |
+
Then in the web interface:
|
| 33 |
+
|
| 34 |
+
1. Upload an image (e.g., a CT scan, or a PNG)
|
| 35 |
+
2. Type your request (e.g., _"I want to segment the lungs from this image"_ or _"I want to deblur this image"_)
|
| 36 |
+
3. Get ranked tool recommendations with accuracy scores
|
| 37 |
+
4. Click "Run demo" to execute tools directly
|
| 38 |
+
|
| 39 |
+
## Use Cases
|
| 40 |
+
|
| 41 |
+
### Medical Imaging
|
| 42 |
+
- Segment organs from CT/MRI scans
|
| 43 |
+
- Register brain images
|
| 44 |
+
- Detect tumors and anomalies
|
| 45 |
+
- Analyze DICOM files
|
| 46 |
+
|
| 47 |
+
### Scientific Imaging
|
| 48 |
+
- Process microscopy images
|
| 49 |
+
- Analyze multidimensional TIFF stacks
|
| 50 |
+
- Extract features from scientific images
|
| 51 |
+
|
| 52 |
+
### General Computer Vision
|
| 53 |
+
- Object detection and segmentation
|
| 54 |
+
- Image classification
|
| 55 |
+
- OCR and text extraction
|
| 56 |
+
- Image enhancement
|
| 57 |
+
|
| 58 |
+
## How It Works
|
| 59 |
+
|
| 60 |
+
The system uses a **two-stage pipeline**:
|
| 61 |
+
|
| 62 |
+
1. **Retrieval Stage**: Fast text search using BGE-M3 embeddings and FAISS to find candidate tools from a curated catalog
|
| 63 |
+
2. **Agent Selection**: Vision-language model (GPT-4o) analyzes your image and task to rank the best tools with explanations
|
| 64 |
+
|
| 65 |
+
Learn more in the [Architecture Overview](architecture/overview.md).
|
| 66 |
+
|
| 67 |
+
## Getting Started
|
| 68 |
+
|
| 69 |
+
Ready to try it out? Head over to the [Installation Guide](getting-started/installation.md) to get started!
|
| 70 |
+
|
| 71 |
+
## Project Guide
|
| 72 |
+
|
| 73 |
+
For maintainers and contributors, see the [Project Guide](guide.md) for a detailed repository map, dev-container environment defaults, and practical codebase improvement guidelines.
|
| 74 |
+
|
| 75 |
+
## Project Status
|
| 76 |
+
|
| 77 |
+
This project is actively developed and maintained by the Imaging Plaza team. Check the [Changelog](reference/changelog.md) for recent updates.
|
| 78 |
+
|
| 79 |
+
## License
|
| 80 |
+
|
| 81 |
+
This project is licensed under the Apache 2.0 License - see the [LICENSE](https://github.com/imaging-plaza/ai-agent/blob/main/LICENSE) file for details.
|
docs/reference/changelog.md
CHANGED
|
@@ -1,79 +1,79 @@
|
|
| 1 |
-
# Changelog
|
| 2 |
-
|
| 3 |
-
All notable changes to the AI Imaging Agent are documented here.
|
| 4 |
-
|
| 5 |
-
For the complete, detailed changelog, see [CHANGELOG.md](https://github.com/imaging-plaza/ai-agent/blob/main/CHANGELOG.md) in the repository.
|
| 6 |
-
|
| 7 |
-
## Recent Releases
|
| 8 |
-
|
| 9 |
-
### [1.0.0]
|
| 10 |
-
|
| 11 |
-
#### 🚀 Added
|
| 12 |
-
|
| 13 |
-
- **Chat-based interface** (`ai_agent chat`) with conversational AI assistant and tool integration
|
| 14 |
-
- **Imaging Plaza UI**: Custom branding, theme, and improved layout
|
| 15 |
-
- **Iterative retrieval with automatic retry** for low-result queries
|
| 16 |
-
- **Alternative search tool** for agent-driven query refinement
|
| 17 |
-
- **YAML configuration** (`config.yaml`) for flexible model and backend setup
|
| 18 |
-
- **DeepWiki MCP integration** for fast GitHub repository documentation access
|
| 19 |
-
|
| 20 |
-
---
|
| 21 |
-
|
| 22 |
-
#### 🔄 Changed
|
| 23 |
-
|
| 24 |
-
- CLI updated: `ai_agent chat` replaces deprecated `ai_agent ui`
|
| 25 |
-
- Retrieval pipeline enhanced with smarter expansion and retry logic
|
| 26 |
-
- UI state management simplified
|
| 27 |
-
- Agent-based architecture fully replaces legacy pipelines
|
| 28 |
-
|
| 29 |
-
---
|
| 30 |
-
|
| 31 |
-
#### 🧹 Removed
|
| 32 |
-
|
| 33 |
-
- `VLMToolSelector` (replaced by agent-based tool selection)
|
| 34 |
-
- Legacy refine intent detection system
|
| 35 |
-
- Deprecated UI command (`ai_agent ui`)
|
| 36 |
-
- Outdated tests and unused code paths
|
| 37 |
-
|
| 38 |
-
### [0.1.3] - 2025-10-22
|
| 39 |
-
|
| 40 |
-
#### Added
|
| 41 |
-
- Gradio Space runner tool
|
| 42 |
-
- Repository info tool
|
| 43 |
-
|
| 44 |
-
#### Fixed
|
| 45 |
-
- Gradio UI context binding
|
| 46 |
-
- Chatbot message format migration
|
| 47 |
-
- Cache cleaning
|
| 48 |
-
- PNG preview handling
|
| 49 |
-
|
| 50 |
-
### [0.1.2] - 2025-10-07
|
| 51 |
-
|
| 52 |
-
#### Added
|
| 53 |
-
- Pydantic AI pipeline with tools
|
| 54 |
-
- Better runnable example handling
|
| 55 |
-
|
| 56 |
-
### [0.1.1] - 2025-10-02
|
| 57 |
-
|
| 58 |
-
#### Added
|
| 59 |
-
- Experimental Pydantic AI agent skeleton
|
| 60 |
-
- Multimodal agent pathway
|
| 61 |
-
|
| 62 |
-
### [0.1.0] - 2025-09-30
|
| 63 |
-
|
| 64 |
-
#### Added
|
| 65 |
-
- Initial chat functionality
|
| 66 |
-
|
| 67 |
-
## Versioning
|
| 68 |
-
|
| 69 |
-
This project follows [Semantic Versioning](https://semver.org/):
|
| 70 |
-
|
| 71 |
-
- **MAJOR**: Incompatible API changes
|
| 72 |
-
- **MINOR**: New functionality (backwards-compatible)
|
| 73 |
-
- **PATCH**: Bug fixes (backwards-compatible)
|
| 74 |
-
|
| 75 |
-
## Contributing
|
| 76 |
-
|
| 77 |
-
All notable changes should be documented in [CHANGELOG.md](https://github.com/imaging-plaza/ai-agent/blob/main/CHANGELOG.md) following the [Keep a Changelog](https://keepachangelog.com/) format.
|
| 78 |
-
|
| 79 |
-
See [Contributing Guide](../development/contributing.md) for details.
|
|
|
|
| 1 |
+
# Changelog
|
| 2 |
+
|
| 3 |
+
All notable changes to the AI Imaging Agent are documented here.
|
| 4 |
+
|
| 5 |
+
For the complete, detailed changelog, see [CHANGELOG.md](https://github.com/imaging-plaza/ai-agent/blob/main/CHANGELOG.md) in the repository.
|
| 6 |
+
|
| 7 |
+
## Recent Releases
|
| 8 |
+
|
| 9 |
+
### [1.0.0]
|
| 10 |
+
|
| 11 |
+
#### 🚀 Added
|
| 12 |
+
|
| 13 |
+
- **Chat-based interface** (`ai_agent chat`) with conversational AI assistant and tool integration
|
| 14 |
+
- **Imaging Plaza UI**: Custom branding, theme, and improved layout
|
| 15 |
+
- **Iterative retrieval with automatic retry** for low-result queries
|
| 16 |
+
- **Alternative search tool** for agent-driven query refinement
|
| 17 |
+
- **YAML configuration** (`config.yaml`) for flexible model and backend setup
|
| 18 |
+
- **DeepWiki MCP integration** for fast GitHub repository documentation access
|
| 19 |
+
|
| 20 |
+
---
|
| 21 |
+
|
| 22 |
+
#### 🔄 Changed
|
| 23 |
+
|
| 24 |
+
- CLI updated: `ai_agent chat` replaces deprecated `ai_agent ui`
|
| 25 |
+
- Retrieval pipeline enhanced with smarter expansion and retry logic
|
| 26 |
+
- UI state management simplified
|
| 27 |
+
- Agent-based architecture fully replaces legacy pipelines
|
| 28 |
+
|
| 29 |
+
---
|
| 30 |
+
|
| 31 |
+
#### 🧹 Removed
|
| 32 |
+
|
| 33 |
+
- `VLMToolSelector` (replaced by agent-based tool selection)
|
| 34 |
+
- Legacy refine intent detection system
|
| 35 |
+
- Deprecated UI command (`ai_agent ui`)
|
| 36 |
+
- Outdated tests and unused code paths
|
| 37 |
+
|
| 38 |
+
### [0.1.3] - 2025-10-22
|
| 39 |
+
|
| 40 |
+
#### Added
|
| 41 |
+
- Gradio Space runner tool
|
| 42 |
+
- Repository info tool
|
| 43 |
+
|
| 44 |
+
#### Fixed
|
| 45 |
+
- Gradio UI context binding
|
| 46 |
+
- Chatbot message format migration
|
| 47 |
+
- Cache cleaning
|
| 48 |
+
- PNG preview handling
|
| 49 |
+
|
| 50 |
+
### [0.1.2] - 2025-10-07
|
| 51 |
+
|
| 52 |
+
#### Added
|
| 53 |
+
- Pydantic AI pipeline with tools
|
| 54 |
+
- Better runnable example handling
|
| 55 |
+
|
| 56 |
+
### [0.1.1] - 2025-10-02
|
| 57 |
+
|
| 58 |
+
#### Added
|
| 59 |
+
- Experimental Pydantic AI agent skeleton
|
| 60 |
+
- Multimodal agent pathway
|
| 61 |
+
|
| 62 |
+
### [0.1.0] - 2025-09-30
|
| 63 |
+
|
| 64 |
+
#### Added
|
| 65 |
+
- Initial chat functionality
|
| 66 |
+
|
| 67 |
+
## Versioning
|
| 68 |
+
|
| 69 |
+
This project follows [Semantic Versioning](https://semver.org/):
|
| 70 |
+
|
| 71 |
+
- **MAJOR**: Incompatible API changes
|
| 72 |
+
- **MINOR**: New functionality (backwards-compatible)
|
| 73 |
+
- **PATCH**: Bug fixes (backwards-compatible)
|
| 74 |
+
|
| 75 |
+
## Contributing
|
| 76 |
+
|
| 77 |
+
All notable changes should be documented in [CHANGELOG.md](https://github.com/imaging-plaza/ai-agent/blob/main/CHANGELOG.md) following the [Keep a Changelog](https://keepachangelog.com/) format.
|
| 78 |
+
|
| 79 |
+
See [Contributing Guide](../development/contributing.md) for details.
|
docs/reference/cli.md
CHANGED
|
@@ -1,197 +1,197 @@
|
|
| 1 |
-
# CLI Commands
|
| 2 |
-
|
| 3 |
-
The AI Imaging Agent provides a command-line interface for starting the application and managing the software catalog.
|
| 4 |
-
|
| 5 |
-
## Available Commands
|
| 6 |
-
|
| 7 |
-
### ai_agent chat
|
| 8 |
-
|
| 9 |
-
Launch the chat-based user interface.
|
| 10 |
-
|
| 11 |
-
```bash
|
| 12 |
-
ai_agent chat
|
| 13 |
-
```
|
| 14 |
-
|
| 15 |
-
**What it does**:
|
| 16 |
-
|
| 17 |
-
1. Performs startup catalog synchronization
|
| 18 |
-
2. Loads the FAISS index
|
| 19 |
-
3. Initializes the retrieval and agent pipelines
|
| 20 |
-
4. Launches the Gradio web interface on `http://127.0.0.1:7860`
|
| 21 |
-
5. Starts background catalog refresh (if configured)
|
| 22 |
-
|
| 23 |
-
**Options**: None (all configuration via `.env` and `config.yaml`)
|
| 24 |
-
|
| 25 |
-
**Example**:
|
| 26 |
-
|
| 27 |
-
```bash
|
| 28 |
-
$ ai_agent chat
|
| 29 |
-
[startup-sync] 150 → dataset/catalog.jsonl
|
| 30 |
-
[startup-refresh] catalog unchanged; keeping existing FAISS index
|
| 31 |
-
Running on local URL: http://127.0.0.1:7860
|
| 32 |
-
|
| 33 |
-
To create a public link, set `share=True` in `launch()`.
|
| 34 |
-
```
|
| 35 |
-
|
| 36 |
-
**Background Refresh**:
|
| 37 |
-
|
| 38 |
-
If `SYNC_EVERY_HOURS` is set in `.env`, the catalog will auto-refresh in the background:
|
| 39 |
-
|
| 40 |
-
```dotenv
|
| 41 |
-
SYNC_EVERY_HOURS=24 # Check every 24 hours
|
| 42 |
-
```
|
| 43 |
-
|
| 44 |
-
### ai_agent sync
|
| 45 |
-
|
| 46 |
-
Manually synchronize the software catalog and rebuild the index.
|
| 47 |
-
|
| 48 |
-
```bash
|
| 49 |
-
ai_agent sync
|
| 50 |
-
```
|
| 51 |
-
|
| 52 |
-
**What it does**:
|
| 53 |
-
|
| 54 |
-
1. Loads the software catalog from `SOFTWARE_CATALOG` path
|
| 55 |
-
2. Embeds all tool descriptions using BGE-M3
|
| 56 |
-
3. Builds FAISS vector index
|
| 57 |
-
4. Saves artifacts to `artifacts/rag_index/`
|
| 58 |
-
|
| 59 |
-
**When to use**:
|
| 60 |
-
|
| 61 |
-
- After editing `catalog.jsonl`
|
| 62 |
-
- After adding new tools
|
| 63 |
-
- To force index rebuild
|
| 64 |
-
- For testing catalog changes
|
| 65 |
-
|
| 66 |
-
**Example**:
|
| 67 |
-
|
| 68 |
-
```bash
|
| 69 |
-
$ ai_agent sync
|
| 70 |
-
[sync] 150 → dataset/catalog.jsonl
|
| 71 |
-
[sync] Embedding 150 tools... (5.2s)
|
| 72 |
-
[sync] Building FAISS index...
|
| 73 |
-
[sync] Saved to artifacts/rag_index/
|
| 74 |
-
[sync] Sync complete.
|
| 75 |
-
```
|
| 76 |
-
|
| 77 |
-
## Command Aliases
|
| 78 |
-
|
| 79 |
-
Both commands are available with either `ai_agent` or `ai-agent`:
|
| 80 |
-
|
| 81 |
-
```bash
|
| 82 |
-
ai_agent chat # Works
|
| 83 |
-
ai-agent chat # Also works
|
| 84 |
-
|
| 85 |
-
ai_agent sync # Works
|
| 86 |
-
ai-agent sync # Also works
|
| 87 |
-
```
|
| 88 |
-
|
| 89 |
-
## Common Usage Patterns
|
| 90 |
-
|
| 91 |
-
### Development Workflow
|
| 92 |
-
|
| 93 |
-
```bash
|
| 94 |
-
# Edit catalog
|
| 95 |
-
vim dataset/catalog.jsonl
|
| 96 |
-
|
| 97 |
-
# Sync catalog
|
| 98 |
-
ai_agent sync
|
| 99 |
-
|
| 100 |
-
# Test changes
|
| 101 |
-
ai_agent chat
|
| 102 |
-
```
|
| 103 |
-
|
| 104 |
-
<!-- ### Production Deployment
|
| 105 |
-
|
| 106 |
-
```bash
|
| 107 |
-
# In your deployment script:
|
| 108 |
-
ai_agent sync # Ensure index is built
|
| 109 |
-
nohup ai_agent chat & # Run in background
|
| 110 |
-
```
|
| 111 |
-
|
| 112 |
-
Or use environment variable control:
|
| 113 |
-
|
| 114 |
-
```bash
|
| 115 |
-
export SYNC_EVERY_HOURS=0 # Disable auto-refresh in production
|
| 116 |
-
ai_agent chat
|
| 117 |
-
``` -->
|
| 118 |
-
|
| 119 |
-
### Testing & Development
|
| 120 |
-
|
| 121 |
-
```bash
|
| 122 |
-
# Enable debug logging
|
| 123 |
-
export LOGLEVEL_CONSOLE=DEBUG
|
| 124 |
-
export LOG_PROMPTS=1
|
| 125 |
-
ai_agent chat
|
| 126 |
-
```
|
| 127 |
-
|
| 128 |
-
## Environment Variables
|
| 129 |
-
|
| 130 |
-
All configuration is via environment variables (see [Environment Variables Reference](environment.md)).
|
| 131 |
-
|
| 132 |
-
## Exit Codes
|
| 133 |
-
|
| 134 |
-
- **0**: Success
|
| 135 |
-
- **1**: General error (see logs)
|
| 136 |
-
|
| 137 |
-
## Troubleshooting
|
| 138 |
-
|
| 139 |
-
### Command Not Found
|
| 140 |
-
|
| 141 |
-
If you see `command not found: ai_agent`:
|
| 142 |
-
|
| 143 |
-
```bash
|
| 144 |
-
# Ensure package is installed
|
| 145 |
-
pip install -e .
|
| 146 |
-
|
| 147 |
-
# Check installation
|
| 148 |
-
pip list | grep ai-agent
|
| 149 |
-
|
| 150 |
-
# Try with python -m
|
| 151 |
-
python -m ai_agent.cli chat
|
| 152 |
-
```
|
| 153 |
-
|
| 154 |
-
### Port Already in Use
|
| 155 |
-
|
| 156 |
-
If port 7860 is occupied:
|
| 157 |
-
|
| 158 |
-
```bash
|
| 159 |
-
# Find and kill process
|
| 160 |
-
lsof -ti:7860 | xargs kill -9
|
| 161 |
-
|
| 162 |
-
# Or change port in code (ui/app.py)
|
| 163 |
-
```
|
| 164 |
-
|
| 165 |
-
### Catalog Load Error
|
| 166 |
-
|
| 167 |
-
If catalog fails to load:
|
| 168 |
-
|
| 169 |
-
```bash
|
| 170 |
-
# Verify catalog exists
|
| 171 |
-
ls -lh dataset/catalog.jsonl
|
| 172 |
-
|
| 173 |
-
# Verify JSONL syntax
|
| 174 |
-
python -c "import json; [json.loads(l) for l in open('dataset/catalog.jsonl')]"
|
| 175 |
-
|
| 176 |
-
# Check environment variable
|
| 177 |
-
echo $SOFTWARE_CATALOG
|
| 178 |
-
```
|
| 179 |
-
|
| 180 |
-
### Index Build Error
|
| 181 |
-
|
| 182 |
-
If FAISS index building fails:
|
| 183 |
-
|
| 184 |
-
```bash
|
| 185 |
-
# Check artifacts directory
|
| 186 |
-
ls -lh artifacts/rag_index/
|
| 187 |
-
|
| 188 |
-
# Rebuild manually
|
| 189 |
-
rm -rf artifacts/rag_index/
|
| 190 |
-
ai_agent sync
|
| 191 |
-
```
|
| 192 |
-
|
| 193 |
-
## Next Steps
|
| 194 |
-
|
| 195 |
-
- Configure [Environment Variables](environment.md)
|
| 196 |
-
- Review the [Changelog](changelog.md)
|
| 197 |
-
- Return to [Getting Started](../getting-started/quickstart.md)
|
|
|
|
| 1 |
+
# CLI Commands
|
| 2 |
+
|
| 3 |
+
The AI Imaging Agent provides a command-line interface for starting the application and managing the software catalog.
|
| 4 |
+
|
| 5 |
+
## Available Commands
|
| 6 |
+
|
| 7 |
+
### ai_agent chat
|
| 8 |
+
|
| 9 |
+
Launch the chat-based user interface.
|
| 10 |
+
|
| 11 |
+
```bash
|
| 12 |
+
ai_agent chat
|
| 13 |
+
```
|
| 14 |
+
|
| 15 |
+
**What it does**:
|
| 16 |
+
|
| 17 |
+
1. Performs startup catalog synchronization
|
| 18 |
+
2. Loads the FAISS index
|
| 19 |
+
3. Initializes the retrieval and agent pipelines
|
| 20 |
+
4. Launches the Gradio web interface on `http://127.0.0.1:7860`
|
| 21 |
+
5. Starts background catalog refresh (if configured)
|
| 22 |
+
|
| 23 |
+
**Options**: None (all configuration via `.env` and `config.yaml`)
|
| 24 |
+
|
| 25 |
+
**Example**:
|
| 26 |
+
|
| 27 |
+
```bash
|
| 28 |
+
$ ai_agent chat
|
| 29 |
+
[startup-sync] 150 → dataset/catalog.jsonl
|
| 30 |
+
[startup-refresh] catalog unchanged; keeping existing FAISS index
|
| 31 |
+
Running on local URL: http://127.0.0.1:7860
|
| 32 |
+
|
| 33 |
+
To create a public link, set `share=True` in `launch()`.
|
| 34 |
+
```
|
| 35 |
+
|
| 36 |
+
**Background Refresh**:
|
| 37 |
+
|
| 38 |
+
If `SYNC_EVERY_HOURS` is set in `.env`, the catalog will auto-refresh in the background:
|
| 39 |
+
|
| 40 |
+
```dotenv
|
| 41 |
+
SYNC_EVERY_HOURS=24 # Check every 24 hours
|
| 42 |
+
```
|
| 43 |
+
|
| 44 |
+
### ai_agent sync
|
| 45 |
+
|
| 46 |
+
Manually synchronize the software catalog and rebuild the index.
|
| 47 |
+
|
| 48 |
+
```bash
|
| 49 |
+
ai_agent sync
|
| 50 |
+
```
|
| 51 |
+
|
| 52 |
+
**What it does**:
|
| 53 |
+
|
| 54 |
+
1. Loads the software catalog from `SOFTWARE_CATALOG` path
|
| 55 |
+
2. Embeds all tool descriptions using BGE-M3
|
| 56 |
+
3. Builds FAISS vector index
|
| 57 |
+
4. Saves artifacts to `artifacts/rag_index/`
|
| 58 |
+
|
| 59 |
+
**When to use**:
|
| 60 |
+
|
| 61 |
+
- After editing `catalog.jsonl`
|
| 62 |
+
- After adding new tools
|
| 63 |
+
- To force index rebuild
|
| 64 |
+
- For testing catalog changes
|
| 65 |
+
|
| 66 |
+
**Example**:
|
| 67 |
+
|
| 68 |
+
```bash
|
| 69 |
+
$ ai_agent sync
|
| 70 |
+
[sync] 150 → dataset/catalog.jsonl
|
| 71 |
+
[sync] Embedding 150 tools... (5.2s)
|
| 72 |
+
[sync] Building FAISS index...
|
| 73 |
+
[sync] Saved to artifacts/rag_index/
|
| 74 |
+
[sync] Sync complete.
|
| 75 |
+
```
|
| 76 |
+
|
| 77 |
+
## Command Aliases
|
| 78 |
+
|
| 79 |
+
Both commands are available with either `ai_agent` or `ai-agent`:
|
| 80 |
+
|
| 81 |
+
```bash
|
| 82 |
+
ai_agent chat # Works
|
| 83 |
+
ai-agent chat # Also works
|
| 84 |
+
|
| 85 |
+
ai_agent sync # Works
|
| 86 |
+
ai-agent sync # Also works
|
| 87 |
+
```
|
| 88 |
+
|
| 89 |
+
## Common Usage Patterns
|
| 90 |
+
|
| 91 |
+
### Development Workflow
|
| 92 |
+
|
| 93 |
+
```bash
|
| 94 |
+
# Edit catalog
|
| 95 |
+
vim dataset/catalog.jsonl
|
| 96 |
+
|
| 97 |
+
# Sync catalog
|
| 98 |
+
ai_agent sync
|
| 99 |
+
|
| 100 |
+
# Test changes
|
| 101 |
+
ai_agent chat
|
| 102 |
+
```
|
| 103 |
+
|
| 104 |
+
<!-- ### Production Deployment
|
| 105 |
+
|
| 106 |
+
```bash
|
| 107 |
+
# In your deployment script:
|
| 108 |
+
ai_agent sync # Ensure index is built
|
| 109 |
+
nohup ai_agent chat & # Run in background
|
| 110 |
+
```
|
| 111 |
+
|
| 112 |
+
Or use environment variable control:
|
| 113 |
+
|
| 114 |
+
```bash
|
| 115 |
+
export SYNC_EVERY_HOURS=0 # Disable auto-refresh in production
|
| 116 |
+
ai_agent chat
|
| 117 |
+
``` -->
|
| 118 |
+
|
| 119 |
+
### Testing & Development
|
| 120 |
+
|
| 121 |
+
```bash
|
| 122 |
+
# Enable debug logging
|
| 123 |
+
export LOGLEVEL_CONSOLE=DEBUG
|
| 124 |
+
export LOG_PROMPTS=1
|
| 125 |
+
ai_agent chat
|
| 126 |
+
```
|
| 127 |
+
|
| 128 |
+
## Environment Variables
|
| 129 |
+
|
| 130 |
+
All configuration is via environment variables (see [Environment Variables Reference](environment.md)).
|
| 131 |
+
|
| 132 |
+
## Exit Codes
|
| 133 |
+
|
| 134 |
+
- **0**: Success
|
| 135 |
+
- **1**: General error (see logs)
|
| 136 |
+
|
| 137 |
+
## Troubleshooting
|
| 138 |
+
|
| 139 |
+
### Command Not Found
|
| 140 |
+
|
| 141 |
+
If you see `command not found: ai_agent`:
|
| 142 |
+
|
| 143 |
+
```bash
|
| 144 |
+
# Ensure package is installed
|
| 145 |
+
pip install -e .
|
| 146 |
+
|
| 147 |
+
# Check installation
|
| 148 |
+
pip list | grep ai-agent
|
| 149 |
+
|
| 150 |
+
# Try with python -m
|
| 151 |
+
python -m ai_agent.cli chat
|
| 152 |
+
```
|
| 153 |
+
|
| 154 |
+
### Port Already in Use
|
| 155 |
+
|
| 156 |
+
If port 7860 is occupied:
|
| 157 |
+
|
| 158 |
+
```bash
|
| 159 |
+
# Find and kill process
|
| 160 |
+
lsof -ti:7860 | xargs kill -9
|
| 161 |
+
|
| 162 |
+
# Or change port in code (ui/app.py)
|
| 163 |
+
```
|
| 164 |
+
|
| 165 |
+
### Catalog Load Error
|
| 166 |
+
|
| 167 |
+
If catalog fails to load:
|
| 168 |
+
|
| 169 |
+
```bash
|
| 170 |
+
# Verify catalog exists
|
| 171 |
+
ls -lh dataset/catalog.jsonl
|
| 172 |
+
|
| 173 |
+
# Verify JSONL syntax
|
| 174 |
+
python -c "import json; [json.loads(l) for l in open('dataset/catalog.jsonl')]"
|
| 175 |
+
|
| 176 |
+
# Check environment variable
|
| 177 |
+
echo $SOFTWARE_CATALOG
|
| 178 |
+
```
|
| 179 |
+
|
| 180 |
+
### Index Build Error
|
| 181 |
+
|
| 182 |
+
If FAISS index building fails:
|
| 183 |
+
|
| 184 |
+
```bash
|
| 185 |
+
# Check artifacts directory
|
| 186 |
+
ls -lh artifacts/rag_index/
|
| 187 |
+
|
| 188 |
+
# Rebuild manually
|
| 189 |
+
rm -rf artifacts/rag_index/
|
| 190 |
+
ai_agent sync
|
| 191 |
+
```
|
| 192 |
+
|
| 193 |
+
## Next Steps
|
| 194 |
+
|
| 195 |
+
- Configure [Environment Variables](environment.md)
|
| 196 |
+
- Review the [Changelog](changelog.md)
|
| 197 |
+
- Return to [Getting Started](../getting-started/quickstart.md)
|
docs/reference/environment.md
CHANGED
|
@@ -1,309 +1,309 @@
|
|
| 1 |
-
# Environment Variables
|
| 2 |
-
|
| 3 |
-
Configuration for the AI Imaging Agent is managed via environment variables, typically defined in a `.env` file.
|
| 4 |
-
|
| 5 |
-
## Required Variables
|
| 6 |
-
|
| 7 |
-
### OPENAI_API_KEY
|
| 8 |
-
|
| 9 |
-
OpenAI API key for vision-language model calls.
|
| 10 |
-
|
| 11 |
-
```dotenv
|
| 12 |
-
OPENAI_API_KEY=sk-xxxx
|
| 13 |
-
```
|
| 14 |
-
|
| 15 |
-
**Where to get it**: [OpenAI API Keys](https://platform.openai.com/api-keys)
|
| 16 |
-
|
| 17 |
-
**Required**: Yes (unless using alternative model provider)
|
| 18 |
-
|
| 19 |
-
**Used by**: Agent VLM calls, tool selection
|
| 20 |
-
|
| 21 |
-
## Optional Variables
|
| 22 |
-
|
| 23 |
-
### SOFTWARE_CATALOG
|
| 24 |
-
|
| 25 |
-
Path to the software catalog JSONL file.
|
| 26 |
-
|
| 27 |
-
```dotenv
|
| 28 |
-
SOFTWARE_CATALOG=dataset/catalog.jsonl
|
| 29 |
-
```
|
| 30 |
-
|
| 31 |
-
**Default**: `dataset/catalog.jsonl`
|
| 32 |
-
|
| 33 |
-
**Required**: No (uses default)
|
| 34 |
-
|
| 35 |
-
### TOP_K
|
| 36 |
-
|
| 37 |
-
Number of candidate tools to retrieve from FAISS search.
|
| 38 |
-
|
| 39 |
-
```dotenv
|
| 40 |
-
TOP_K=8
|
| 41 |
-
```
|
| 42 |
-
|
| 43 |
-
**Default**: `8`
|
| 44 |
-
|
| 45 |
-
**Range**: 1-50 (recommended: 5-10)
|
| 46 |
-
|
| 47 |
-
**Impact**: More candidates = better recall but slower VLM calls
|
| 48 |
-
|
| 49 |
-
### NUM_CHOICES
|
| 50 |
-
|
| 51 |
-
Number of final tool recommendations to return to user.
|
| 52 |
-
|
| 53 |
-
```dotenv
|
| 54 |
-
NUM_CHOICES=3
|
| 55 |
-
```
|
| 56 |
-
|
| 57 |
-
**Default**: `3`
|
| 58 |
-
|
| 59 |
-
**Range**: 1-10 (recommended: 3-5)
|
| 60 |
-
|
| 61 |
-
**Impact**: Too many recommendations can overwhelm users
|
| 62 |
-
|
| 63 |
-
### GITHUB_TOKEN
|
| 64 |
-
|
| 65 |
-
GitHub personal access token for repository info tool.
|
| 66 |
-
|
| 67 |
-
```dotenv
|
| 68 |
-
GITHUB_TOKEN=ghp_xxxx
|
| 69 |
-
```
|
| 70 |
-
|
| 71 |
-
**Where to get it**: [GitHub Tokens](https://github.com/settings/tokens)
|
| 72 |
-
|
| 73 |
-
**Permissions needed**: `public_repo` (read access)
|
| 74 |
-
|
| 75 |
-
**Required**: No (tool gracefully degrades without it)
|
| 76 |
-
|
| 77 |
-
**Benefits**: Higher API rate limits, access to private repos
|
| 78 |
-
|
| 79 |
-
### SYNC_EVERY_HOURS
|
| 80 |
-
|
| 81 |
-
Auto-refresh catalog interval in hours.
|
| 82 |
-
|
| 83 |
-
```dotenv
|
| 84 |
-
SYNC_EVERY_HOURS=24
|
| 85 |
-
```
|
| 86 |
-
|
| 87 |
-
**Default**: `0` (disabled)
|
| 88 |
-
|
| 89 |
-
**Range**: 0 (disabled) or ≥1
|
| 90 |
-
|
| 91 |
-
**Behavior**: Background thread checks catalog every N hours and rebuilds index if changed
|
| 92 |
-
|
| 93 |
-
## Logging Configuration
|
| 94 |
-
|
| 95 |
-
### LOGLEVEL_CONSOLE
|
| 96 |
-
|
| 97 |
-
Console logging level.
|
| 98 |
-
|
| 99 |
-
```dotenv
|
| 100 |
-
LOGLEVEL_CONSOLE=WARNING
|
| 101 |
-
```
|
| 102 |
-
|
| 103 |
-
**Options**: `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL`
|
| 104 |
-
|
| 105 |
-
**Default**: `WARNING`
|
| 106 |
-
|
| 107 |
-
**Recommendation**:
|
| 108 |
-
- Development: `DEBUG` or `INFO`
|
| 109 |
-
- Production: `WARNING`
|
| 110 |
-
|
| 111 |
-
### LOGLEVEL_FILE
|
| 112 |
-
|
| 113 |
-
File logging level.
|
| 114 |
-
|
| 115 |
-
```dotenv
|
| 116 |
-
LOGLEVEL_FILE=INFO
|
| 117 |
-
```
|
| 118 |
-
|
| 119 |
-
**Options**: `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL`
|
| 120 |
-
|
| 121 |
-
**Default**: `INFO`
|
| 122 |
-
|
| 123 |
-
**Files written to**: `logs/app_YYYYMMDD.log`
|
| 124 |
-
|
| 125 |
-
### FILE_LOG
|
| 126 |
-
|
| 127 |
-
Enable file logging.
|
| 128 |
-
|
| 129 |
-
```dotenv
|
| 130 |
-
FILE_LOG=1
|
| 131 |
-
```
|
| 132 |
-
|
| 133 |
-
**Options**: `0` (disabled), `1` (enabled)
|
| 134 |
-
|
| 135 |
-
**Default**: `1`
|
| 136 |
-
|
| 137 |
-
### LOG_DIR
|
| 138 |
-
|
| 139 |
-
Directory for log files.
|
| 140 |
-
|
| 141 |
-
```dotenv
|
| 142 |
-
LOG_DIR=logs
|
| 143 |
-
```
|
| 144 |
-
|
| 145 |
-
**Default**: `logs`
|
| 146 |
-
|
| 147 |
-
**Created automatically** if it doesn't exist
|
| 148 |
-
|
| 149 |
-
### LOG_PROMPTS
|
| 150 |
-
|
| 151 |
-
Save VLM prompts and images for debugging.
|
| 152 |
-
|
| 153 |
-
```dotenv
|
| 154 |
-
LOG_PROMPTS=1
|
| 155 |
-
```
|
| 156 |
-
|
| 157 |
-
**Options**: `0` (disabled), `1` (enabled)
|
| 158 |
-
|
| 159 |
-
**Default**: `0`
|
| 160 |
-
|
| 161 |
-
**Writes to**: `logs/prompts/YYYYMMDD_HHMMSS/`
|
| 162 |
-
|
| 163 |
-
**Contents**:
|
| 164 |
-
- `prompt.txt`: Text prompt sent to VLM
|
| 165 |
-
- `image_*.png`: Images included in prompt
|
| 166 |
-
- `response.json`: VLM response
|
| 167 |
-
- `metadata.json`: Request metadata
|
| 168 |
-
|
| 169 |
-
**Warning**: Can consume significant disk space over time
|
| 170 |
-
|
| 171 |
-
## Model Configuration
|
| 172 |
-
|
| 173 |
-
### CONFIG_PATH
|
| 174 |
-
|
| 175 |
-
Path to YAML model configuration file.
|
| 176 |
-
|
| 177 |
-
```dotenv
|
| 178 |
-
CONFIG_PATH=config.yaml
|
| 179 |
-
```
|
| 180 |
-
|
| 181 |
-
**Default**: `config.yaml`
|
| 182 |
-
|
| 183 |
-
**See**: `config.yaml` for model configuration details
|
| 184 |
-
|
| 185 |
-
### Alternative Model Providers
|
| 186 |
-
|
| 187 |
-
For custom OpenAI-compatible endpoints, configure in `config.yaml`:
|
| 188 |
-
|
| 189 |
-
```yaml
|
| 190 |
-
agent_model:
|
| 191 |
-
name: "model-name"
|
| 192 |
-
base_url: "https://api.example.com/v1"
|
| 193 |
-
api_key_env: "CUSTOM_API_KEY"
|
| 194 |
-
```
|
| 195 |
-
|
| 196 |
-
Then in `.env`:
|
| 197 |
-
|
| 198 |
-
```dotenv
|
| 199 |
-
CUSTOM_API_KEY=your-key-here
|
| 200 |
-
```
|
| 201 |
-
|
| 202 |
-
<!-- ## Advanced Configuration
|
| 203 |
-
|
| 204 |
-
### RERANK_TOP_N
|
| 205 |
-
|
| 206 |
-
Number of candidates to retrieve before reranking.
|
| 207 |
-
|
| 208 |
-
```dotenv
|
| 209 |
-
RERANK_TOP_N=20
|
| 210 |
-
```
|
| 211 |
-
|
| 212 |
-
**Default**: `20`
|
| 213 |
-
|
| 214 |
-
**Interaction with TOP_K**:
|
| 215 |
-
- FAISS retrieves `RERANK_TOP_N` candidates (e.g., 20)
|
| 216 |
-
- CrossEncoder reranks them
|
| 217 |
-
- Top `TOP_K` (e.g., 8) passed to VLM
|
| 218 |
-
|
| 219 |
-
**Recommendation**: 2-3x `TOP_K` value
|
| 220 |
-
|
| 221 |
-
-->
|
| 222 |
-
|
| 223 |
-
## .env File Example
|
| 224 |
-
|
| 225 |
-
Complete example `.env` file:
|
| 226 |
-
|
| 227 |
-
```dotenv
|
| 228 |
-
# Required
|
| 229 |
-
OPENAI_API_KEY=sk-xxxx
|
| 230 |
-
|
| 231 |
-
# Optional: Alternative providers
|
| 232 |
-
EPFL_API_KEY=sk-xxxx
|
| 233 |
-
GITHUB_TOKEN=ghp_xxxx
|
| 234 |
-
|
| 235 |
-
# Catalog
|
| 236 |
-
SOFTWARE_CATALOG=dataset/catalog.jsonl
|
| 237 |
-
SYNC_EVERY_HOURS=24
|
| 238 |
-
|
| 239 |
-
# Pipeline
|
| 240 |
-
TOP_K=8
|
| 241 |
-
NUM_CHOICES=3
|
| 242 |
-
|
| 243 |
-
# Logging
|
| 244 |
-
LOGLEVEL_CONSOLE=WARNING
|
| 245 |
-
LOGLEVEL_FILE=INFO
|
| 246 |
-
FILE_LOG=1
|
| 247 |
-
LOG_DIR=logs
|
| 248 |
-
LOG_PROMPTS=0 # Set to 1 for debugging
|
| 249 |
-
|
| 250 |
-
# Model configuration
|
| 251 |
-
CONFIG_PATH=config.yaml
|
| 252 |
-
```
|
| 253 |
-
|
| 254 |
-
## Loading Environment Variables
|
| 255 |
-
|
| 256 |
-
### Automatic Loading
|
| 257 |
-
|
| 258 |
-
The application automatically loads `.env` from the repository root:
|
| 259 |
-
|
| 260 |
-
```python
|
| 261 |
-
from dotenv import load_dotenv
|
| 262 |
-
load_dotenv() # Loads .env automatically
|
| 263 |
-
```
|
| 264 |
-
|
| 265 |
-
### Manual Loading
|
| 266 |
-
|
| 267 |
-
```bash
|
| 268 |
-
# Export manually
|
| 269 |
-
export OPENAI_API_KEY=sk-xxxx
|
| 270 |
-
export TOP_K=8
|
| 271 |
-
|
| 272 |
-
# Or source .env
|
| 273 |
-
set -a
|
| 274 |
-
source .env
|
| 275 |
-
set +a
|
| 276 |
-
```
|
| 277 |
-
|
| 278 |
-
### Docker
|
| 279 |
-
|
| 280 |
-
Pass environment variables to Docker:
|
| 281 |
-
|
| 282 |
-
```bash
|
| 283 |
-
docker run --env-file .env ai-agent
|
| 284 |
-
```
|
| 285 |
-
|
| 286 |
-
## Security Best Practices
|
| 287 |
-
|
| 288 |
-
!!! warning "Never commit .env files"
|
| 289 |
-
Add `.env` to `.gitignore` to prevent accidental commits
|
| 290 |
-
|
| 291 |
-
!!! warning "Protect API keys"
|
| 292 |
-
Treat API keys as sensitive credentials:
|
| 293 |
-
- Never share in public repositories
|
| 294 |
-
- Rotate keys if exposed
|
| 295 |
-
- Use environment-specific keys (dev/prod)
|
| 296 |
-
|
| 297 |
-
!!! tip "Use .env.example"
|
| 298 |
-
Create `.env.example` with dummy values for documentation:
|
| 299 |
-
|
| 300 |
-
```dotenv
|
| 301 |
-
OPENAI_API_KEY=sk-your-key-here
|
| 302 |
-
GITHUB_TOKEN=ghp-your-token-here
|
| 303 |
-
```
|
| 304 |
-
|
| 305 |
-
## Next Steps
|
| 306 |
-
|
| 307 |
-
- Review [CLI Commands](cli.md)
|
| 308 |
-
- Check [Configuration Guide](../getting-started/configuration.md)
|
| 309 |
-
- See [Changelog](changelog.md)
|
|
|
|
| 1 |
+
# Environment Variables
|
| 2 |
+
|
| 3 |
+
Configuration for the AI Imaging Agent is managed via environment variables, typically defined in a `.env` file.
|
| 4 |
+
|
| 5 |
+
## Required Variables
|
| 6 |
+
|
| 7 |
+
### OPENAI_API_KEY
|
| 8 |
+
|
| 9 |
+
OpenAI API key for vision-language model calls.
|
| 10 |
+
|
| 11 |
+
```dotenv
|
| 12 |
+
OPENAI_API_KEY=sk-xxxx
|
| 13 |
+
```
|
| 14 |
+
|
| 15 |
+
**Where to get it**: [OpenAI API Keys](https://platform.openai.com/api-keys)
|
| 16 |
+
|
| 17 |
+
**Required**: Yes (unless using alternative model provider)
|
| 18 |
+
|
| 19 |
+
**Used by**: Agent VLM calls, tool selection
|
| 20 |
+
|
| 21 |
+
## Optional Variables
|
| 22 |
+
|
| 23 |
+
### SOFTWARE_CATALOG
|
| 24 |
+
|
| 25 |
+
Path to the software catalog JSONL file.
|
| 26 |
+
|
| 27 |
+
```dotenv
|
| 28 |
+
SOFTWARE_CATALOG=dataset/catalog.jsonl
|
| 29 |
+
```
|
| 30 |
+
|
| 31 |
+
**Default**: `dataset/catalog.jsonl`
|
| 32 |
+
|
| 33 |
+
**Required**: No (uses default)
|
| 34 |
+
|
| 35 |
+
### TOP_K
|
| 36 |
+
|
| 37 |
+
Number of candidate tools to retrieve from FAISS search.
|
| 38 |
+
|
| 39 |
+
```dotenv
|
| 40 |
+
TOP_K=8
|
| 41 |
+
```
|
| 42 |
+
|
| 43 |
+
**Default**: `8`
|
| 44 |
+
|
| 45 |
+
**Range**: 1-50 (recommended: 5-10)
|
| 46 |
+
|
| 47 |
+
**Impact**: More candidates = better recall but slower VLM calls
|
| 48 |
+
|
| 49 |
+
### NUM_CHOICES
|
| 50 |
+
|
| 51 |
+
Number of final tool recommendations to return to user.
|
| 52 |
+
|
| 53 |
+
```dotenv
|
| 54 |
+
NUM_CHOICES=3
|
| 55 |
+
```
|
| 56 |
+
|
| 57 |
+
**Default**: `3`
|
| 58 |
+
|
| 59 |
+
**Range**: 1-10 (recommended: 3-5)
|
| 60 |
+
|
| 61 |
+
**Impact**: Too many recommendations can overwhelm users
|
| 62 |
+
|
| 63 |
+
### GITHUB_TOKEN
|
| 64 |
+
|
| 65 |
+
GitHub personal access token for repository info tool.
|
| 66 |
+
|
| 67 |
+
```dotenv
|
| 68 |
+
GITHUB_TOKEN=ghp_xxxx
|
| 69 |
+
```
|
| 70 |
+
|
| 71 |
+
**Where to get it**: [GitHub Tokens](https://github.com/settings/tokens)
|
| 72 |
+
|
| 73 |
+
**Permissions needed**: `public_repo` (read access)
|
| 74 |
+
|
| 75 |
+
**Required**: No (tool gracefully degrades without it)
|
| 76 |
+
|
| 77 |
+
**Benefits**: Higher API rate limits, access to private repos
|
| 78 |
+
|
| 79 |
+
### SYNC_EVERY_HOURS
|
| 80 |
+
|
| 81 |
+
Auto-refresh catalog interval in hours.
|
| 82 |
+
|
| 83 |
+
```dotenv
|
| 84 |
+
SYNC_EVERY_HOURS=24
|
| 85 |
+
```
|
| 86 |
+
|
| 87 |
+
**Default**: `0` (disabled)
|
| 88 |
+
|
| 89 |
+
**Range**: 0 (disabled) or ≥1
|
| 90 |
+
|
| 91 |
+
**Behavior**: Background thread checks catalog every N hours and rebuilds index if changed
|
| 92 |
+
|
| 93 |
+
## Logging Configuration
|
| 94 |
+
|
| 95 |
+
### LOGLEVEL_CONSOLE
|
| 96 |
+
|
| 97 |
+
Console logging level.
|
| 98 |
+
|
| 99 |
+
```dotenv
|
| 100 |
+
LOGLEVEL_CONSOLE=WARNING
|
| 101 |
+
```
|
| 102 |
+
|
| 103 |
+
**Options**: `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL`
|
| 104 |
+
|
| 105 |
+
**Default**: `WARNING`
|
| 106 |
+
|
| 107 |
+
**Recommendation**:
|
| 108 |
+
- Development: `DEBUG` or `INFO`
|
| 109 |
+
- Production: `WARNING`
|
| 110 |
+
|
| 111 |
+
### LOGLEVEL_FILE
|
| 112 |
+
|
| 113 |
+
File logging level.
|
| 114 |
+
|
| 115 |
+
```dotenv
|
| 116 |
+
LOGLEVEL_FILE=INFO
|
| 117 |
+
```
|
| 118 |
+
|
| 119 |
+
**Options**: `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL`
|
| 120 |
+
|
| 121 |
+
**Default**: `INFO`
|
| 122 |
+
|
| 123 |
+
**Files written to**: `logs/app_YYYYMMDD.log`
|
| 124 |
+
|
| 125 |
+
### FILE_LOG
|
| 126 |
+
|
| 127 |
+
Enable file logging.
|
| 128 |
+
|
| 129 |
+
```dotenv
|
| 130 |
+
FILE_LOG=1
|
| 131 |
+
```
|
| 132 |
+
|
| 133 |
+
**Options**: `0` (disabled), `1` (enabled)
|
| 134 |
+
|
| 135 |
+
**Default**: `1`
|
| 136 |
+
|
| 137 |
+
### LOG_DIR
|
| 138 |
+
|
| 139 |
+
Directory for log files.
|
| 140 |
+
|
| 141 |
+
```dotenv
|
| 142 |
+
LOG_DIR=logs
|
| 143 |
+
```
|
| 144 |
+
|
| 145 |
+
**Default**: `logs`
|
| 146 |
+
|
| 147 |
+
**Created automatically** if it doesn't exist
|
| 148 |
+
|
| 149 |
+
### LOG_PROMPTS
|
| 150 |
+
|
| 151 |
+
Save VLM prompts and images for debugging.
|
| 152 |
+
|
| 153 |
+
```dotenv
|
| 154 |
+
LOG_PROMPTS=1
|
| 155 |
+
```
|
| 156 |
+
|
| 157 |
+
**Options**: `0` (disabled), `1` (enabled)
|
| 158 |
+
|
| 159 |
+
**Default**: `0`
|
| 160 |
+
|
| 161 |
+
**Writes to**: `logs/prompts/YYYYMMDD_HHMMSS/`
|
| 162 |
+
|
| 163 |
+
**Contents**:
|
| 164 |
+
- `prompt.txt`: Text prompt sent to VLM
|
| 165 |
+
- `image_*.png`: Images included in prompt
|
| 166 |
+
- `response.json`: VLM response
|
| 167 |
+
- `metadata.json`: Request metadata
|
| 168 |
+
|
| 169 |
+
**Warning**: Can consume significant disk space over time
|
| 170 |
+
|
| 171 |
+
## Model Configuration
|
| 172 |
+
|
| 173 |
+
### CONFIG_PATH
|
| 174 |
+
|
| 175 |
+
Path to YAML model configuration file.
|
| 176 |
+
|
| 177 |
+
```dotenv
|
| 178 |
+
CONFIG_PATH=config.yaml
|
| 179 |
+
```
|
| 180 |
+
|
| 181 |
+
**Default**: `config.yaml`
|
| 182 |
+
|
| 183 |
+
**See**: `config.yaml` for model configuration details
|
| 184 |
+
|
| 185 |
+
### Alternative Model Providers
|
| 186 |
+
|
| 187 |
+
For custom OpenAI-compatible endpoints, configure in `config.yaml`:
|
| 188 |
+
|
| 189 |
+
```yaml
|
| 190 |
+
agent_model:
|
| 191 |
+
name: "model-name"
|
| 192 |
+
base_url: "https://api.example.com/v1"
|
| 193 |
+
api_key_env: "CUSTOM_API_KEY"
|
| 194 |
+
```
|
| 195 |
+
|
| 196 |
+
Then in `.env`:
|
| 197 |
+
|
| 198 |
+
```dotenv
|
| 199 |
+
CUSTOM_API_KEY=your-key-here
|
| 200 |
+
```
|
| 201 |
+
|
| 202 |
+
<!-- ## Advanced Configuration
|
| 203 |
+
|
| 204 |
+
### RERANK_TOP_N
|
| 205 |
+
|
| 206 |
+
Number of candidates to retrieve before reranking.
|
| 207 |
+
|
| 208 |
+
```dotenv
|
| 209 |
+
RERANK_TOP_N=20
|
| 210 |
+
```
|
| 211 |
+
|
| 212 |
+
**Default**: `20`
|
| 213 |
+
|
| 214 |
+
**Interaction with TOP_K**:
|
| 215 |
+
- FAISS retrieves `RERANK_TOP_N` candidates (e.g., 20)
|
| 216 |
+
- CrossEncoder reranks them
|
| 217 |
+
- Top `TOP_K` (e.g., 8) passed to VLM
|
| 218 |
+
|
| 219 |
+
**Recommendation**: 2-3x `TOP_K` value
|
| 220 |
+
|
| 221 |
+
-->
|
| 222 |
+
|
| 223 |
+
## .env File Example
|
| 224 |
+
|
| 225 |
+
Complete example `.env` file:
|
| 226 |
+
|
| 227 |
+
```dotenv
|
| 228 |
+
# Required
|
| 229 |
+
OPENAI_API_KEY=sk-xxxx
|
| 230 |
+
|
| 231 |
+
# Optional: Alternative providers
|
| 232 |
+
EPFL_API_KEY=sk-xxxx
|
| 233 |
+
GITHUB_TOKEN=ghp_xxxx
|
| 234 |
+
|
| 235 |
+
# Catalog
|
| 236 |
+
SOFTWARE_CATALOG=dataset/catalog.jsonl
|
| 237 |
+
SYNC_EVERY_HOURS=24
|
| 238 |
+
|
| 239 |
+
# Pipeline
|
| 240 |
+
TOP_K=8
|
| 241 |
+
NUM_CHOICES=3
|
| 242 |
+
|
| 243 |
+
# Logging
|
| 244 |
+
LOGLEVEL_CONSOLE=WARNING
|
| 245 |
+
LOGLEVEL_FILE=INFO
|
| 246 |
+
FILE_LOG=1
|
| 247 |
+
LOG_DIR=logs
|
| 248 |
+
LOG_PROMPTS=0 # Set to 1 for debugging
|
| 249 |
+
|
| 250 |
+
# Model configuration
|
| 251 |
+
CONFIG_PATH=config.yaml
|
| 252 |
+
```
|
| 253 |
+
|
| 254 |
+
## Loading Environment Variables
|
| 255 |
+
|
| 256 |
+
### Automatic Loading
|
| 257 |
+
|
| 258 |
+
The application automatically loads `.env` from the repository root:
|
| 259 |
+
|
| 260 |
+
```python
|
| 261 |
+
from dotenv import load_dotenv
|
| 262 |
+
load_dotenv() # Loads .env automatically
|
| 263 |
+
```
|
| 264 |
+
|
| 265 |
+
### Manual Loading
|
| 266 |
+
|
| 267 |
+
```bash
|
| 268 |
+
# Export manually
|
| 269 |
+
export OPENAI_API_KEY=sk-xxxx
|
| 270 |
+
export TOP_K=8
|
| 271 |
+
|
| 272 |
+
# Or source .env
|
| 273 |
+
set -a
|
| 274 |
+
source .env
|
| 275 |
+
set +a
|
| 276 |
+
```
|
| 277 |
+
|
| 278 |
+
### Docker
|
| 279 |
+
|
| 280 |
+
Pass environment variables to Docker:
|
| 281 |
+
|
| 282 |
+
```bash
|
| 283 |
+
docker run --env-file .env ai-agent
|
| 284 |
+
```
|
| 285 |
+
|
| 286 |
+
## Security Best Practices
|
| 287 |
+
|
| 288 |
+
!!! warning "Never commit .env files"
|
| 289 |
+
Add `.env` to `.gitignore` to prevent accidental commits
|
| 290 |
+
|
| 291 |
+
!!! warning "Protect API keys"
|
| 292 |
+
Treat API keys as sensitive credentials:
|
| 293 |
+
- Never share in public repositories
|
| 294 |
+
- Rotate keys if exposed
|
| 295 |
+
- Use environment-specific keys (dev/prod)
|
| 296 |
+
|
| 297 |
+
!!! tip "Use .env.example"
|
| 298 |
+
Create `.env.example` with dummy values for documentation:
|
| 299 |
+
|
| 300 |
+
```dotenv
|
| 301 |
+
OPENAI_API_KEY=sk-your-key-here
|
| 302 |
+
GITHUB_TOKEN=ghp-your-token-here
|
| 303 |
+
```
|
| 304 |
+
|
| 305 |
+
## Next Steps
|
| 306 |
+
|
| 307 |
+
- Review [CLI Commands](cli.md)
|
| 308 |
+
- Check [Configuration Guide](../getting-started/configuration.md)
|
| 309 |
+
- See [Changelog](changelog.md)
|
docs/user-guide/advanced-features.md
CHANGED
|
@@ -1,387 +1,387 @@
|
|
| 1 |
-
# Advanced Features (not tested for now..)
|
| 2 |
-
|
| 3 |
-
The AI Imaging Agent includes several advanced features for power users and specialized use cases.
|
| 4 |
-
|
| 5 |
-
## Control Tags
|
| 6 |
-
|
| 7 |
-
Control tags modify agent behavior using special syntax in your queries.
|
| 8 |
-
|
| 9 |
-
### Exclude Tools
|
| 10 |
-
|
| 11 |
-
Filter out specific tools from results:
|
| 12 |
-
|
| 13 |
-
```
|
| 14 |
-
Find lung segmentation tools [EXCLUDE:totalsegmentator|medicalsam]
|
| 15 |
-
```
|
| 16 |
-
|
| 17 |
-
**Syntax**: `[EXCLUDE:tool1|tool2|tool3]`
|
| 18 |
-
|
| 19 |
-
**Use cases**:
|
| 20 |
-
|
| 21 |
-
- You've already tried certain tools
|
| 22 |
-
- Exclude tools you don't have access to
|
| 23 |
-
- Filter by licensing (exclude proprietary tools)
|
| 24 |
-
- Remove tools with specific limitations
|
| 25 |
-
|
| 26 |
-
**Example**:
|
| 27 |
-
```
|
| 28 |
-
You: Segment kidneys [EXCLUDE:totalsegmentator]
|
| 29 |
-
Agent: [Returns kidney segmentation tools except TotalSegmentator]
|
| 30 |
-
|
| 31 |
-
You: Find open-source options [EXCLUDE:proprietarytool1|proprietarytool2]
|
| 32 |
-
Agent: [Returns only open-source tools]
|
| 33 |
-
```
|
| 34 |
-
|
| 35 |
-
### Notes
|
| 36 |
-
|
| 37 |
-
Only `[EXCLUDE:...]` is currently interpreted as a control tag for retrieval filtering.
|
| 38 |
-
|
| 39 |
-
## Alternative Searches
|
| 40 |
-
|
| 41 |
-
Request the agent to search with different strategies.
|
| 42 |
-
|
| 43 |
-
### Requesting Alternatives
|
| 44 |
-
|
| 45 |
-
Use natural language:
|
| 46 |
-
|
| 47 |
-
```
|
| 48 |
-
Can you search for alternatives?
|
| 49 |
-
Show me other options
|
| 50 |
-
Find different tools
|
| 51 |
-
What else is available?
|
| 52 |
-
```
|
| 53 |
-
|
| 54 |
-
**What happens**:
|
| 55 |
-
|
| 56 |
-
- Agent formulates alternative query
|
| 57 |
-
- Uses different phrasing/keywords for broader coverage
|
| 58 |
-
- Searches with different emphasis
|
| 59 |
-
- Returns new set of recommendations
|
| 60 |
-
|
| 61 |
-
**Limit**: Up to 3 alternative searches per conversation
|
| 62 |
-
|
| 63 |
-
### When to Use
|
| 64 |
-
|
| 65 |
-
- Initial results don't quite match
|
| 66 |
-
- Want to see different approaches
|
| 67 |
-
- Exploring the catalog
|
| 68 |
-
- Looking for specialized tools
|
| 69 |
-
|
| 70 |
-
**Example conversation**:
|
| 71 |
-
```
|
| 72 |
-
You: Segment lungs from this CT
|
| 73 |
-
Agent: [Provides general lung segmentation tools]
|
| 74 |
-
|
| 75 |
-
You: Can you search for alternatives?
|
| 76 |
-
Agent: [Searches with emphasis on "airway segmentation", "pulmonary analysis"]
|
| 77 |
-
|
| 78 |
-
You: Show me other options
|
| 79 |
-
Agent: [Searches with emphasis on "CT thorax processing", "respiratory imaging"]
|
| 80 |
-
```
|
| 81 |
-
|
| 82 |
-
## Multi-Model Support
|
| 83 |
-
|
| 84 |
-
### Selecting Different Models
|
| 85 |
-
|
| 86 |
-
The UI provides a model selector dropdown:
|
| 87 |
-
|
| 88 |
-
Available models (configurable in `config.yaml`):
|
| 89 |
-
|
| 90 |
-
- **gpt-4o-mini**: Faster, lower cost
|
| 91 |
-
- **gpt-4o**: Higher accuracy, multimodal
|
| 92 |
-
- **gpt-5.1**: Latest capabilities (if available)
|
| 93 |
-
- **Custom endpoints**: EPFL, local servers, etc.
|
| 94 |
-
|
| 95 |
-
### Model Trade-offs
|
| 96 |
-
|
| 97 |
-
| Model | Speed | Cost | Accuracy | Vision |
|
| 98 |
-
|-------|-------|------|----------|--------|
|
| 99 |
-
| gpt-4o-mini | ⚡⚡⚡ | 💰 | ⭐⭐⭐ | ✅ |
|
| 100 |
-
| gpt-4o | ⚡⚡ | 💰💰 | ⭐⭐⭐⭐ | ✅✅ |
|
| 101 |
-
| gpt-5.1 | ⚡ | 💰💰💰 | ⭐⭐⭐⭐⭐ | ✅✅✅ |
|
| 102 |
-
|
| 103 |
-
### When to Switch Models
|
| 104 |
-
|
| 105 |
-
**Use gpt-4o-mini when**:
|
| 106 |
-
|
| 107 |
-
- Doing quick explorations
|
| 108 |
-
- Cost is a concern
|
| 109 |
-
- Tasks are straightforward
|
| 110 |
-
- Query is well-specified
|
| 111 |
-
|
| 112 |
-
**Use gpt-4o when**:
|
| 113 |
-
|
| 114 |
-
- Complex visual analysis needed
|
| 115 |
-
- Accuracy is critical
|
| 116 |
-
- Ambiguous queries
|
| 117 |
-
- Multi-step reasoning required
|
| 118 |
-
|
| 119 |
-
**Use gpt-5.1 when**:
|
| 120 |
-
|
| 121 |
-
- Maximum accuracy needed
|
| 122 |
-
- Complex multi-modal tasks
|
| 123 |
-
- Research/publication work
|
| 124 |
-
|
| 125 |
-
## Repository Info Tool
|
| 126 |
-
|
| 127 |
-
### What It Does
|
| 128 |
-
|
| 129 |
-
The agent can fetch detailed information about GitHub repositories:
|
| 130 |
-
|
| 131 |
-
```
|
| 132 |
-
You: Tell me about TotalSegmentator
|
| 133 |
-
Agent: [Fetches repo info from GitHub via DeepWiki or repocards]
|
| 134 |
-
|
| 135 |
-
Repository: wasserth/TotalSegmentator
|
| 136 |
-
Description: Automated multi-organ segmentation in CT and MR images
|
| 137 |
-
Stars: 1.2k
|
| 138 |
-
Language: Python
|
| 139 |
-
Topics: segmentation, medical-imaging, deep-learning
|
| 140 |
-
Last Updated: 2024-03-15
|
| 141 |
-
License: Apache-2.0
|
| 142 |
-
```
|
| 143 |
-
|
| 144 |
-
### Data Sources
|
| 145 |
-
|
| 146 |
-
1. **DeepWiki MCP** (primary): Fast, pre-indexed repository documentation
|
| 147 |
-
2. **Repocards** (fallback): Direct library-based fetch
|
| 148 |
-
|
| 149 |
-
### Usage
|
| 150 |
-
|
| 151 |
-
Ask about tools naturally:
|
| 152 |
-
|
| 153 |
-
```
|
| 154 |
-
What is [tool name]?
|
| 155 |
-
Tell me more about [repository]
|
| 156 |
-
Show me details for [tool]
|
| 157 |
-
```
|
| 158 |
-
|
| 159 |
-
## Conversation State Management
|
| 160 |
-
|
| 161 |
-
### State Tracking
|
| 162 |
-
|
| 163 |
-
The agent maintains state across conversation:
|
| 164 |
-
|
| 165 |
-
- **Uploaded files**: All files in session
|
| 166 |
-
- **Preview images**: Converted images for VLM
|
| 167 |
-
- **Excluded tools**: Tools filtered via `[EXCLUDE:]`
|
| 168 |
-
- **Conversation history**: Previous messages and context
|
| 169 |
-
- **Turn counter**: Current conversation turn
|
| 170 |
-
|
| 171 |
-
### Viewing State
|
| 172 |
-
|
| 173 |
-
In the sidebar (debug mode):
|
| 174 |
-
|
| 175 |
-
```json
|
| 176 |
-
{
|
| 177 |
-
"conversation_turn": 3,
|
| 178 |
-
"uploaded_files": ["scan.dcm", "brain.nii"],
|
| 179 |
-
"excluded_tools": ["tool1", "tool2"],
|
| 180 |
-
"preview_images": ["/tmp/scan_preview.png"]
|
| 181 |
-
}
|
| 182 |
-
```
|
| 183 |
-
|
| 184 |
-
### Resetting State
|
| 185 |
-
|
| 186 |
-
To start fresh:
|
| 187 |
-
- Refresh the page
|
| 188 |
-
- Clear uploaded files
|
| 189 |
-
- Start new conversation
|
| 190 |
-
|
| 191 |
-
## Retrieval Query Behavior
|
| 192 |
-
|
| 193 |
-
### How It Works
|
| 194 |
-
|
| 195 |
-
The retrieval pipeline currently does not add semantic neighbor terms.
|
| 196 |
-
Instead, it builds retrieval queries from:
|
| 197 |
-
|
| 198 |
-
```
|
| 199 |
-
User text: "segment brain"
|
| 200 |
-
+ format hints from files: format:DICOM / format:NIfTI
|
| 201 |
-
+ compact image metadata: modality, anatomy, dimensions (when available)
|
| 202 |
-
```
|
| 203 |
-
|
| 204 |
-
**Based on**:
|
| 205 |
-
|
| 206 |
-
- BGE-M3 embeddings
|
| 207 |
-
- Format-aware hinting from uploaded files
|
| 208 |
-
- Metadata-aware context from image inspection
|
| 209 |
-
|
| 210 |
-
### Benefits
|
| 211 |
-
|
| 212 |
-
- ✅ Stronger format compatibility matching
|
| 213 |
-
- ✅ Better ranking for modality/dimension-specific tasks
|
| 214 |
-
- ✅ More predictable retrieval behavior
|
| 215 |
-
|
| 216 |
-
### Customization
|
| 217 |
-
|
| 218 |
-
If initial results are too sparse, the pipeline retries with a broader query
|
| 219 |
-
formulation automatically.
|
| 220 |
-
|
| 221 |
-
## Format-Aware Matching
|
| 222 |
-
|
| 223 |
-
### Input Format Tokens
|
| 224 |
-
|
| 225 |
-
File uploads add format tokens to queries:
|
| 226 |
-
|
| 227 |
-
```
|
| 228 |
-
Uploaded: scan.dcm (DICOM)
|
| 229 |
-
Query enhancement: "segment lungs format:DICOM format:CT format:3D"
|
| 230 |
-
```
|
| 231 |
-
|
| 232 |
-
### How It Helps
|
| 233 |
-
|
| 234 |
-
- **Narrows results**: Shows compatible tools first
|
| 235 |
-
- **Boosts relevance**: DICOM tools rank higher for DICOM
|
| 236 |
-
- **Compatibility check**: Agent verifies format support
|
| 237 |
-
|
| 238 |
-
### Supported Formats
|
| 239 |
-
|
| 240 |
-
Tokens added for:
|
| 241 |
-
- File extension (`.dcm`, `.nii`, `.png`)
|
| 242 |
-
- Detected format (DICOM, NIfTI, TIFF)
|
| 243 |
-
- Modality for medical images (CT, MRI, XR)
|
| 244 |
-
- Dimensions (2D, 3D, 4D)
|
| 245 |
-
|
| 246 |
-
## Iterative Retrieval
|
| 247 |
-
|
| 248 |
-
### Auto-Retry on Low Results
|
| 249 |
-
|
| 250 |
-
If initial search returns <5 candidates:
|
| 251 |
-
|
| 252 |
-
1. **Retry #1**: Alternative query with semantic expansion
|
| 253 |
-
2. **Retry #2**: Further expansion with broader terms
|
| 254 |
-
3. **Max 2 retries**: Then return best available
|
| 255 |
-
|
| 256 |
-
### Why It Matters
|
| 257 |
-
|
| 258 |
-
- Handles rare/specialized queries
|
| 259 |
-
- Finds tools even with limited matches
|
| 260 |
-
- Automatic - no user action needed
|
| 261 |
-
|
| 262 |
-
### Example
|
| 263 |
-
|
| 264 |
-
```
|
| 265 |
-
Query: "segment rare anatomical structure"
|
| 266 |
-
Initial: 2 candidates found
|
| 267 |
-
Retry 1: Expanded to "segment anatomy structure region organ"
|
| 268 |
-
Result: 7 candidates found ✓
|
| 269 |
-
```
|
| 270 |
-
|
| 271 |
-
## Debug Features
|
| 272 |
-
|
| 273 |
-
### Prompt Logging
|
| 274 |
-
|
| 275 |
-
Enable in `.env`:
|
| 276 |
-
|
| 277 |
-
```dotenv
|
| 278 |
-
LOG_PROMPTS=1
|
| 279 |
-
```
|
| 280 |
-
|
| 281 |
-
**Saves**:
|
| 282 |
-
|
| 283 |
-
- VLM prompts sent to API
|
| 284 |
-
- Images included in prompts
|
| 285 |
-
- Response JSON
|
| 286 |
-
- Timestamp and metadata
|
| 287 |
-
|
| 288 |
-
**Location**: `logs/prompts/YYYYMMDD_HHMMSS/`
|
| 289 |
-
|
| 290 |
-
**Contents**:
|
| 291 |
-
```
|
| 292 |
-
logs/prompts/20240315_143022/
|
| 293 |
-
├── prompt.txt # Text prompt
|
| 294 |
-
├── image_0.png # Uploaded image
|
| 295 |
-
├── response.json # API response
|
| 296 |
-
└── metadata.json # Request metadata
|
| 297 |
-
```
|
| 298 |
-
|
| 299 |
-
### Execution Traces
|
| 300 |
-
|
| 301 |
-
Always shown in chat (expandable):
|
| 302 |
-
|
| 303 |
-
```html
|
| 304 |
-
<details>
|
| 305 |
-
<summary>🔧 Execution Trace</summary>
|
| 306 |
-
...detailed logs...
|
| 307 |
-
</details>
|
| 308 |
-
```
|
| 309 |
-
|
| 310 |
-
Shows:
|
| 311 |
-
|
| 312 |
-
- Tool calls made
|
| 313 |
-
- Parameters used
|
| 314 |
-
- API responses
|
| 315 |
-
- Timing information
|
| 316 |
-
|
| 317 |
-
## Catalog Synchronization
|
| 318 |
-
|
| 319 |
-
### Auto-Refresh
|
| 320 |
-
|
| 321 |
-
Configured via `.env`:
|
| 322 |
-
|
| 323 |
-
```dotenv
|
| 324 |
-
SYNC_EVERY_HOURS=24
|
| 325 |
-
```
|
| 326 |
-
|
| 327 |
-
**Behavior**:
|
| 328 |
-
|
| 329 |
-
- Background thread checks for catalog updates
|
| 330 |
-
- Reloads FAISS index if changed
|
| 331 |
-
- No UI interruption
|
| 332 |
-
- Logs refresh activity
|
| 333 |
-
|
| 334 |
-
### Manual Sync
|
| 335 |
-
|
| 336 |
-
Force synchronization:
|
| 337 |
-
|
| 338 |
-
```bash
|
| 339 |
-
ai_agent sync
|
| 340 |
-
```
|
| 341 |
-
|
| 342 |
-
Updates:
|
| 343 |
-
|
| 344 |
-
- Software catalog
|
| 345 |
-
- Embeddings
|
| 346 |
-
- FAISS index
|
| 347 |
-
- Vocabulary for expansion
|
| 348 |
-
|
| 349 |
-
## Advanced Configuration
|
| 350 |
-
|
| 351 |
-
### Custom Catalog
|
| 352 |
-
|
| 353 |
-
Use your own tool catalog:
|
| 354 |
-
|
| 355 |
-
```dotenv
|
| 356 |
-
SOFTWARE_CATALOG=/path/to/custom_catalog.jsonl
|
| 357 |
-
```
|
| 358 |
-
|
| 359 |
-
**Format**: JSONL with schema.org SoftwareSourceCode
|
| 360 |
-
|
| 361 |
-
### API Endpoints
|
| 362 |
-
|
| 363 |
-
Configure custom OpenAI-compatible endpoints in `config.yaml`:
|
| 364 |
-
|
| 365 |
-
```yaml
|
| 366 |
-
available_models:
|
| 367 |
-
- display_name: "Local LLM"
|
| 368 |
-
name: "llama-3.1"
|
| 369 |
-
base_url: "http://localhost:8000/v1"
|
| 370 |
-
api_key_env: "LOCAL_API_KEY"
|
| 371 |
-
```
|
| 372 |
-
|
| 373 |
-
### Pipeline Parameters
|
| 374 |
-
|
| 375 |
-
Fine-tune retrieval:
|
| 376 |
-
|
| 377 |
-
```dotenv
|
| 378 |
-
TOP_K=8 # Candidates to retrieve
|
| 379 |
-
NUM_CHOICES=3 # Final recommendations
|
| 380 |
-
RERANK_TOP_N=20 # Candidates before reranking
|
| 381 |
-
```
|
| 382 |
-
|
| 383 |
-
## Next Steps
|
| 384 |
-
|
| 385 |
-
- Dive into [Architecture Overview](../architecture/overview.md)
|
| 386 |
-
- Learn about [Development and Contributing](../development/contributing.md)
|
| 387 |
-
- Check [Environment Variables Reference](../reference/environment.md)
|
|
|
|
| 1 |
+
# Advanced Features (not tested for now..)
|
| 2 |
+
|
| 3 |
+
The AI Imaging Agent includes several advanced features for power users and specialized use cases.
|
| 4 |
+
|
| 5 |
+
## Control Tags
|
| 6 |
+
|
| 7 |
+
Control tags modify agent behavior using special syntax in your queries.
|
| 8 |
+
|
| 9 |
+
### Exclude Tools
|
| 10 |
+
|
| 11 |
+
Filter out specific tools from results:
|
| 12 |
+
|
| 13 |
+
```
|
| 14 |
+
Find lung segmentation tools [EXCLUDE:totalsegmentator|medicalsam]
|
| 15 |
+
```
|
| 16 |
+
|
| 17 |
+
**Syntax**: `[EXCLUDE:tool1|tool2|tool3]`
|
| 18 |
+
|
| 19 |
+
**Use cases**:
|
| 20 |
+
|
| 21 |
+
- You've already tried certain tools
|
| 22 |
+
- Exclude tools you don't have access to
|
| 23 |
+
- Filter by licensing (exclude proprietary tools)
|
| 24 |
+
- Remove tools with specific limitations
|
| 25 |
+
|
| 26 |
+
**Example**:
|
| 27 |
+
```
|
| 28 |
+
You: Segment kidneys [EXCLUDE:totalsegmentator]
|
| 29 |
+
Agent: [Returns kidney segmentation tools except TotalSegmentator]
|
| 30 |
+
|
| 31 |
+
You: Find open-source options [EXCLUDE:proprietarytool1|proprietarytool2]
|
| 32 |
+
Agent: [Returns only open-source tools]
|
| 33 |
+
```
|
| 34 |
+
|
| 35 |
+
### Notes
|
| 36 |
+
|
| 37 |
+
Only `[EXCLUDE:...]` is currently interpreted as a control tag for retrieval filtering.
|
| 38 |
+
|
| 39 |
+
## Alternative Searches
|
| 40 |
+
|
| 41 |
+
Request the agent to search with different strategies.
|
| 42 |
+
|
| 43 |
+
### Requesting Alternatives
|
| 44 |
+
|
| 45 |
+
Use natural language:
|
| 46 |
+
|
| 47 |
+
```
|
| 48 |
+
Can you search for alternatives?
|
| 49 |
+
Show me other options
|
| 50 |
+
Find different tools
|
| 51 |
+
What else is available?
|
| 52 |
+
```
|
| 53 |
+
|
| 54 |
+
**What happens**:
|
| 55 |
+
|
| 56 |
+
- Agent formulates alternative query
|
| 57 |
+
- Uses different phrasing/keywords for broader coverage
|
| 58 |
+
- Searches with different emphasis
|
| 59 |
+
- Returns new set of recommendations
|
| 60 |
+
|
| 61 |
+
**Limit**: Up to 3 alternative searches per conversation
|
| 62 |
+
|
| 63 |
+
### When to Use
|
| 64 |
+
|
| 65 |
+
- Initial results don't quite match
|
| 66 |
+
- Want to see different approaches
|
| 67 |
+
- Exploring the catalog
|
| 68 |
+
- Looking for specialized tools
|
| 69 |
+
|
| 70 |
+
**Example conversation**:
|
| 71 |
+
```
|
| 72 |
+
You: Segment lungs from this CT
|
| 73 |
+
Agent: [Provides general lung segmentation tools]
|
| 74 |
+
|
| 75 |
+
You: Can you search for alternatives?
|
| 76 |
+
Agent: [Searches with emphasis on "airway segmentation", "pulmonary analysis"]
|
| 77 |
+
|
| 78 |
+
You: Show me other options
|
| 79 |
+
Agent: [Searches with emphasis on "CT thorax processing", "respiratory imaging"]
|
| 80 |
+
```
|
| 81 |
+
|
| 82 |
+
## Multi-Model Support
|
| 83 |
+
|
| 84 |
+
### Selecting Different Models
|
| 85 |
+
|
| 86 |
+
The UI provides a model selector dropdown:
|
| 87 |
+
|
| 88 |
+
Available models (configurable in `config.yaml`):
|
| 89 |
+
|
| 90 |
+
- **gpt-4o-mini**: Faster, lower cost
|
| 91 |
+
- **gpt-4o**: Higher accuracy, multimodal
|
| 92 |
+
- **gpt-5.1**: Latest capabilities (if available)
|
| 93 |
+
- **Custom endpoints**: EPFL, local servers, etc.
|
| 94 |
+
|
| 95 |
+
### Model Trade-offs
|
| 96 |
+
|
| 97 |
+
| Model | Speed | Cost | Accuracy | Vision |
|
| 98 |
+
|-------|-------|------|----------|--------|
|
| 99 |
+
| gpt-4o-mini | ⚡⚡⚡ | 💰 | ⭐⭐⭐ | ✅ |
|
| 100 |
+
| gpt-4o | ⚡⚡ | 💰💰 | ⭐⭐⭐⭐ | ✅✅ |
|
| 101 |
+
| gpt-5.1 | ⚡ | 💰💰💰 | ⭐⭐⭐⭐⭐ | ✅✅✅ |
|
| 102 |
+
|
| 103 |
+
### When to Switch Models
|
| 104 |
+
|
| 105 |
+
**Use gpt-4o-mini when**:
|
| 106 |
+
|
| 107 |
+
- Doing quick explorations
|
| 108 |
+
- Cost is a concern
|
| 109 |
+
- Tasks are straightforward
|
| 110 |
+
- Query is well-specified
|
| 111 |
+
|
| 112 |
+
**Use gpt-4o when**:
|
| 113 |
+
|
| 114 |
+
- Complex visual analysis needed
|
| 115 |
+
- Accuracy is critical
|
| 116 |
+
- Ambiguous queries
|
| 117 |
+
- Multi-step reasoning required
|
| 118 |
+
|
| 119 |
+
**Use gpt-5.1 when**:
|
| 120 |
+
|
| 121 |
+
- Maximum accuracy needed
|
| 122 |
+
- Complex multi-modal tasks
|
| 123 |
+
- Research/publication work
|
| 124 |
+
|
| 125 |
+
## Repository Info Tool
|
| 126 |
+
|
| 127 |
+
### What It Does
|
| 128 |
+
|
| 129 |
+
The agent can fetch detailed information about GitHub repositories:
|
| 130 |
+
|
| 131 |
+
```
|
| 132 |
+
You: Tell me about TotalSegmentator
|
| 133 |
+
Agent: [Fetches repo info from GitHub via DeepWiki or repocards]
|
| 134 |
+
|
| 135 |
+
Repository: wasserth/TotalSegmentator
|
| 136 |
+
Description: Automated multi-organ segmentation in CT and MR images
|
| 137 |
+
Stars: 1.2k
|
| 138 |
+
Language: Python
|
| 139 |
+
Topics: segmentation, medical-imaging, deep-learning
|
| 140 |
+
Last Updated: 2024-03-15
|
| 141 |
+
License: Apache-2.0
|
| 142 |
+
```
|
| 143 |
+
|
| 144 |
+
### Data Sources
|
| 145 |
+
|
| 146 |
+
1. **DeepWiki MCP** (primary): Fast, pre-indexed repository documentation
|
| 147 |
+
2. **Repocards** (fallback): Direct library-based fetch
|
| 148 |
+
|
| 149 |
+
### Usage
|
| 150 |
+
|
| 151 |
+
Ask about tools naturally:
|
| 152 |
+
|
| 153 |
+
```
|
| 154 |
+
What is [tool name]?
|
| 155 |
+
Tell me more about [repository]
|
| 156 |
+
Show me details for [tool]
|
| 157 |
+
```
|
| 158 |
+
|
| 159 |
+
## Conversation State Management
|
| 160 |
+
|
| 161 |
+
### State Tracking
|
| 162 |
+
|
| 163 |
+
The agent maintains state across conversation:
|
| 164 |
+
|
| 165 |
+
- **Uploaded files**: All files in session
|
| 166 |
+
- **Preview images**: Converted images for VLM
|
| 167 |
+
- **Excluded tools**: Tools filtered via `[EXCLUDE:]`
|
| 168 |
+
- **Conversation history**: Previous messages and context
|
| 169 |
+
- **Turn counter**: Current conversation turn
|
| 170 |
+
|
| 171 |
+
### Viewing State
|
| 172 |
+
|
| 173 |
+
In the sidebar (debug mode):
|
| 174 |
+
|
| 175 |
+
```json
|
| 176 |
+
{
|
| 177 |
+
"conversation_turn": 3,
|
| 178 |
+
"uploaded_files": ["scan.dcm", "brain.nii"],
|
| 179 |
+
"excluded_tools": ["tool1", "tool2"],
|
| 180 |
+
"preview_images": ["/tmp/scan_preview.png"]
|
| 181 |
+
}
|
| 182 |
+
```
|
| 183 |
+
|
| 184 |
+
### Resetting State
|
| 185 |
+
|
| 186 |
+
To start fresh:
|
| 187 |
+
- Refresh the page
|
| 188 |
+
- Clear uploaded files
|
| 189 |
+
- Start new conversation
|
| 190 |
+
|
| 191 |
+
## Retrieval Query Behavior
|
| 192 |
+
|
| 193 |
+
### How It Works
|
| 194 |
+
|
| 195 |
+
The retrieval pipeline currently does not add semantic neighbor terms.
|
| 196 |
+
Instead, it builds retrieval queries from:
|
| 197 |
+
|
| 198 |
+
```
|
| 199 |
+
User text: "segment brain"
|
| 200 |
+
+ format hints from files: format:DICOM / format:NIfTI
|
| 201 |
+
+ compact image metadata: modality, anatomy, dimensions (when available)
|
| 202 |
+
```
|
| 203 |
+
|
| 204 |
+
**Based on**:
|
| 205 |
+
|
| 206 |
+
- BGE-M3 embeddings
|
| 207 |
+
- Format-aware hinting from uploaded files
|
| 208 |
+
- Metadata-aware context from image inspection
|
| 209 |
+
|
| 210 |
+
### Benefits
|
| 211 |
+
|
| 212 |
+
- ✅ Stronger format compatibility matching
|
| 213 |
+
- ✅ Better ranking for modality/dimension-specific tasks
|
| 214 |
+
- ✅ More predictable retrieval behavior
|
| 215 |
+
|
| 216 |
+
### Customization
|
| 217 |
+
|
| 218 |
+
If initial results are too sparse, the pipeline retries with a broader query
|
| 219 |
+
formulation automatically.
|
| 220 |
+
|
| 221 |
+
## Format-Aware Matching
|
| 222 |
+
|
| 223 |
+
### Input Format Tokens
|
| 224 |
+
|
| 225 |
+
File uploads add format tokens to queries:
|
| 226 |
+
|
| 227 |
+
```
|
| 228 |
+
Uploaded: scan.dcm (DICOM)
|
| 229 |
+
Query enhancement: "segment lungs format:DICOM format:CT format:3D"
|
| 230 |
+
```
|
| 231 |
+
|
| 232 |
+
### How It Helps
|
| 233 |
+
|
| 234 |
+
- **Narrows results**: Shows compatible tools first
|
| 235 |
+
- **Boosts relevance**: DICOM tools rank higher for DICOM
|
| 236 |
+
- **Compatibility check**: Agent verifies format support
|
| 237 |
+
|
| 238 |
+
### Supported Formats
|
| 239 |
+
|
| 240 |
+
Tokens added for:
|
| 241 |
+
- File extension (`.dcm`, `.nii`, `.png`)
|
| 242 |
+
- Detected format (DICOM, NIfTI, TIFF)
|
| 243 |
+
- Modality for medical images (CT, MRI, XR)
|
| 244 |
+
- Dimensions (2D, 3D, 4D)
|
| 245 |
+
|
| 246 |
+
## Iterative Retrieval
|
| 247 |
+
|
| 248 |
+
### Auto-Retry on Low Results
|
| 249 |
+
|
| 250 |
+
If initial search returns <5 candidates:
|
| 251 |
+
|
| 252 |
+
1. **Retry #1**: Alternative query with semantic expansion
|
| 253 |
+
2. **Retry #2**: Further expansion with broader terms
|
| 254 |
+
3. **Max 2 retries**: Then return best available
|
| 255 |
+
|
| 256 |
+
### Why It Matters
|
| 257 |
+
|
| 258 |
+
- Handles rare/specialized queries
|
| 259 |
+
- Finds tools even with limited matches
|
| 260 |
+
- Automatic - no user action needed
|
| 261 |
+
|
| 262 |
+
### Example
|
| 263 |
+
|
| 264 |
+
```
|
| 265 |
+
Query: "segment rare anatomical structure"
|
| 266 |
+
Initial: 2 candidates found
|
| 267 |
+
Retry 1: Expanded to "segment anatomy structure region organ"
|
| 268 |
+
Result: 7 candidates found ✓
|
| 269 |
+
```
|
| 270 |
+
|
| 271 |
+
## Debug Features
|
| 272 |
+
|
| 273 |
+
### Prompt Logging
|
| 274 |
+
|
| 275 |
+
Enable in `.env`:
|
| 276 |
+
|
| 277 |
+
```dotenv
|
| 278 |
+
LOG_PROMPTS=1
|
| 279 |
+
```
|
| 280 |
+
|
| 281 |
+
**Saves**:
|
| 282 |
+
|
| 283 |
+
- VLM prompts sent to API
|
| 284 |
+
- Images included in prompts
|
| 285 |
+
- Response JSON
|
| 286 |
+
- Timestamp and metadata
|
| 287 |
+
|
| 288 |
+
**Location**: `logs/prompts/YYYYMMDD_HHMMSS/`
|
| 289 |
+
|
| 290 |
+
**Contents**:
|
| 291 |
+
```
|
| 292 |
+
logs/prompts/20240315_143022/
|
| 293 |
+
├── prompt.txt # Text prompt
|
| 294 |
+
├── image_0.png # Uploaded image
|
| 295 |
+
├── response.json # API response
|
| 296 |
+
└── metadata.json # Request metadata
|
| 297 |
+
```
|
| 298 |
+
|
| 299 |
+
### Execution Traces
|
| 300 |
+
|
| 301 |
+
Always shown in chat (expandable):
|
| 302 |
+
|
| 303 |
+
```html
|
| 304 |
+
<details>
|
| 305 |
+
<summary>🔧 Execution Trace</summary>
|
| 306 |
+
...detailed logs...
|
| 307 |
+
</details>
|
| 308 |
+
```
|
| 309 |
+
|
| 310 |
+
Shows:
|
| 311 |
+
|
| 312 |
+
- Tool calls made
|
| 313 |
+
- Parameters used
|
| 314 |
+
- API responses
|
| 315 |
+
- Timing information
|
| 316 |
+
|
| 317 |
+
## Catalog Synchronization
|
| 318 |
+
|
| 319 |
+
### Auto-Refresh
|
| 320 |
+
|
| 321 |
+
Configured via `.env`:
|
| 322 |
+
|
| 323 |
+
```dotenv
|
| 324 |
+
SYNC_EVERY_HOURS=24
|
| 325 |
+
```
|
| 326 |
+
|
| 327 |
+
**Behavior**:
|
| 328 |
+
|
| 329 |
+
- Background thread checks for catalog updates
|
| 330 |
+
- Reloads FAISS index if changed
|
| 331 |
+
- No UI interruption
|
| 332 |
+
- Logs refresh activity
|
| 333 |
+
|
| 334 |
+
### Manual Sync
|
| 335 |
+
|
| 336 |
+
Force synchronization:
|
| 337 |
+
|
| 338 |
+
```bash
|
| 339 |
+
ai_agent sync
|
| 340 |
+
```
|
| 341 |
+
|
| 342 |
+
Updates:
|
| 343 |
+
|
| 344 |
+
- Software catalog
|
| 345 |
+
- Embeddings
|
| 346 |
+
- FAISS index
|
| 347 |
+
- Vocabulary for expansion
|
| 348 |
+
|
| 349 |
+
## Advanced Configuration
|
| 350 |
+
|
| 351 |
+
### Custom Catalog
|
| 352 |
+
|
| 353 |
+
Use your own tool catalog:
|
| 354 |
+
|
| 355 |
+
```dotenv
|
| 356 |
+
SOFTWARE_CATALOG=/path/to/custom_catalog.jsonl
|
| 357 |
+
```
|
| 358 |
+
|
| 359 |
+
**Format**: JSONL with schema.org SoftwareSourceCode
|
| 360 |
+
|
| 361 |
+
### API Endpoints
|
| 362 |
+
|
| 363 |
+
Configure custom OpenAI-compatible endpoints in `config.yaml`:
|
| 364 |
+
|
| 365 |
+
```yaml
|
| 366 |
+
available_models:
|
| 367 |
+
- display_name: "Local LLM"
|
| 368 |
+
name: "llama-3.1"
|
| 369 |
+
base_url: "http://localhost:8000/v1"
|
| 370 |
+
api_key_env: "LOCAL_API_KEY"
|
| 371 |
+
```
|
| 372 |
+
|
| 373 |
+
### Pipeline Parameters
|
| 374 |
+
|
| 375 |
+
Fine-tune retrieval:
|
| 376 |
+
|
| 377 |
+
```dotenv
|
| 378 |
+
TOP_K=8 # Candidates to retrieve
|
| 379 |
+
NUM_CHOICES=3 # Final recommendations
|
| 380 |
+
RERANK_TOP_N=20 # Candidates before reranking
|
| 381 |
+
```
|
| 382 |
+
|
| 383 |
+
## Next Steps
|
| 384 |
+
|
| 385 |
+
- Dive into [Architecture Overview](../architecture/overview.md)
|
| 386 |
+
- Learn about [Development and Contributing](../development/contributing.md)
|
| 387 |
+
- Check [Environment Variables Reference](../reference/environment.md)
|
docs/user-guide/chat-interface.md
CHANGED
|
@@ -1,275 +1,275 @@
|
|
| 1 |
-
# Using the Chat Interface
|
| 2 |
-
|
| 3 |
-
The AI Imaging Agent provides a conversational interface for discovering and using imaging software. This guide explains how to interact with the chat interface effectively.
|
| 4 |
-
|
| 5 |
-
## Interface Layout
|
| 6 |
-
|
| 7 |
-
The interface consists of three main areas:
|
| 8 |
-
|
| 9 |
-
### Left Panel: Chat Conversation
|
| 10 |
-
- **Message History**: Your conversation with the agent
|
| 11 |
-
- **Rich Media Rendering**: Images, tool cards, and files are displayed inline
|
| 12 |
-
- **Input Box**: Type your messages at the bottom
|
| 13 |
-
- **File Upload**: Attach files via the paperclip icon or drag-and-drop
|
| 14 |
-
|
| 15 |
-
### Right Panel: Sidebar
|
| 16 |
-
- **Files Tab**: View uploaded files with format information
|
| 17 |
-
- **State Tab**: Debug information showing conversation state
|
| 18 |
-
|
| 19 |
-
### Header
|
| 20 |
-
- **Model Selector**: Choose which AI model to use
|
| 21 |
-
- **Settings**: Access configuration options
|
| 22 |
-
|
| 23 |
-
## Basic Workflow
|
| 24 |
-
|
| 25 |
-
### 1. Upload Files
|
| 26 |
-
|
| 27 |
-
Upload images or other files in several ways:
|
| 28 |
-
|
| 29 |
-
- **Drag and Drop**: Drag files directly onto the upload area
|
| 30 |
-
- **Click to Browse**: Click the upload area to select files
|
| 31 |
-
- **Attach to Message**: Use the paperclip icon in the input box
|
| 32 |
-
|
| 33 |
-
Files are automatically processed and metadata is extracted.
|
| 34 |
-
|
| 35 |
-
### 2. Describe Your Task
|
| 36 |
-
|
| 37 |
-
Use natural language to describe what you want to do:
|
| 38 |
-
|
| 39 |
-
!!! example "Good Task Descriptions"
|
| 40 |
-
- "I want to segment the lungs from this CT scan"
|
| 41 |
-
- "Help me detect tumors in this MRI"
|
| 42 |
-
- "I need to register these two brain images"
|
| 43 |
-
- "Extract text from this medical report"
|
| 44 |
-
- "Classify the organ shown in this ultrasound"
|
| 45 |
-
|
| 46 |
-
### 3. Review Recommendations
|
| 47 |
-
|
| 48 |
-
The agent returns ranked tool recommendations with:
|
| 49 |
-
|
| 50 |
-
- **Tool Cards**: Each tool is presented in a card format
|
| 51 |
-
- **Accuracy Scores**: Confidence levels for each recommendation
|
| 52 |
-
- **Explanations**: Why each tool matches your request
|
| 53 |
-
- **Metadata**: Technical details about compatibility
|
| 54 |
-
|
| 55 |
-
### 4. Run Demos (Optional)
|
| 56 |
-
|
| 57 |
-
The agent may offer to run demos:
|
| 58 |
-
|
| 59 |
-
```
|
| 60 |
-
Agent: Would you like me to run the demo with your image?
|
| 61 |
-
```
|
| 62 |
-
|
| 63 |
-
Respond with affirmative language:
|
| 64 |
-
- "yes"
|
| 65 |
-
- "sure"
|
| 66 |
-
- "ok"
|
| 67 |
-
- "please"
|
| 68 |
-
- "go ahead"
|
| 69 |
-
|
| 70 |
-
The agent will execute the tool and show results.
|
| 71 |
-
|
| 72 |
-
## Multi-Turn Conversations
|
| 73 |
-
|
| 74 |
-
The agent maintains context across multiple messages:
|
| 75 |
-
|
| 76 |
-
!!! example "Multi-Turn Example"
|
| 77 |
-
```
|
| 78 |
-
You: I have a lung CT scan [uploads file]
|
| 79 |
-
|
| 80 |
-
Agent: I can see you have a DICOM CT image. What would you like to do with it?
|
| 81 |
-
|
| 82 |
-
You: Segment the airways
|
| 83 |
-
|
| 84 |
-
Agent: [Provides airway segmentation tool recommendations]
|
| 85 |
-
|
| 86 |
-
You: What about segmenting the whole lung?
|
| 87 |
-
|
| 88 |
-
Agent: [Provides lung segmentation tools, remembering you're working with CT]
|
| 89 |
-
|
| 90 |
-
You: Show me alternatives
|
| 91 |
-
|
| 92 |
-
Agent: [Provides additional options]
|
| 93 |
-
```
|
| 94 |
-
|
| 95 |
-
## Advanced Features
|
| 96 |
-
|
| 97 |
-
### Excluding Tools
|
| 98 |
-
|
| 99 |
-
Exclude specific tools using the `[EXCLUDE:...]` tag:
|
| 100 |
-
|
| 101 |
-
```
|
| 102 |
-
Find segmentation tools [EXCLUDE:totalsegmentator|medicalsam]
|
| 103 |
-
```
|
| 104 |
-
|
| 105 |
-
You can exclude multiple tools separated by `|`.
|
| 106 |
-
|
| 107 |
-
### Requesting Alternatives
|
| 108 |
-
|
| 109 |
-
Ask the agent to search with different strategies:
|
| 110 |
-
|
| 111 |
-
```
|
| 112 |
-
Can you search for alternatives?
|
| 113 |
-
|
| 114 |
-
Show me other options
|
| 115 |
-
|
| 116 |
-
Find different tools for this task
|
| 117 |
-
```
|
| 118 |
-
|
| 119 |
-
The agent can perform up to 3 alternative searches per conversation.
|
| 120 |
-
|
| 121 |
-
## Understanding Agent Responses
|
| 122 |
-
|
| 123 |
-
### Recommendation Cards
|
| 124 |
-
|
| 125 |
-
Each recommendation includes:
|
| 126 |
-
|
| 127 |
-
#### Header
|
| 128 |
-
- **Rank Number**: 1, 2, 3 (1 = best match)
|
| 129 |
-
- **Tool Name**: Software/tool identifier
|
| 130 |
-
- **Accuracy Score**: 0-100% confidence
|
| 131 |
-
|
| 132 |
-
#### Body
|
| 133 |
-
- **Description**: What the tool does
|
| 134 |
-
- **Explanation**: Why it matches your task
|
| 135 |
-
- **Demo Link**: Click to visit runnable example
|
| 136 |
-
|
| 137 |
-
#### Footer Metadata
|
| 138 |
-
- **Modalities**: CT, MRI, X-ray, etc.
|
| 139 |
-
- **Dimensions**: 2D, 3D, 4D
|
| 140 |
-
- **Formats**: Supported file formats (DICOM, NIfTI, etc.)
|
| 141 |
-
- **License**: Software license information
|
| 142 |
-
- **Tags**: Categorization and keywords
|
| 143 |
-
|
| 144 |
-
### Execution Traces
|
| 145 |
-
|
| 146 |
-
When demos run, you'll see execution details:
|
| 147 |
-
|
| 148 |
-
```
|
| 149 |
-
<details>
|
| 150 |
-
<summary>Tool Execution Trace</summary>
|
| 151 |
-
|
| 152 |
-
Image uploaded to Gradio Space
|
| 153 |
-
Processing started...
|
| 154 |
-
Result: Success
|
| 155 |
-
Output saved to: result.png
|
| 156 |
-
</details>
|
| 157 |
-
```
|
| 158 |
-
|
| 159 |
-
Click to expand and see full execution logs.
|
| 160 |
-
|
| 161 |
-
### Clarification Questions
|
| 162 |
-
|
| 163 |
-
Sometimes the agent needs more information:
|
| 164 |
-
|
| 165 |
-
```
|
| 166 |
-
Agent: I found several segmentation tools. Which organ are you trying to segment?
|
| 167 |
-
|
| 168 |
-
You: The liver
|
| 169 |
-
|
| 170 |
-
Agent: [Provides liver-specific segmentation tools]
|
| 171 |
-
```
|
| 172 |
-
|
| 173 |
-
## File Management
|
| 174 |
-
|
| 175 |
-
### Uploaded Files List
|
| 176 |
-
|
| 177 |
-
The sidebar shows all uploaded files with:
|
| 178 |
-
|
| 179 |
-
- **Filename**: Original file name
|
| 180 |
-
- **Format**: File type/extension
|
| 181 |
-
- **Size**: File size
|
| 182 |
-
- **Preview**: Thumbnail (for images)
|
| 183 |
-
|
| 184 |
-
### Image Previews
|
| 185 |
-
|
| 186 |
-
Medical images are automatically converted:
|
| 187 |
-
|
| 188 |
-
- **DICOM**: PNG previews; 3D series use orthogonal composite views (MIPs + central slices) rather than a single slice
|
| 189 |
-
- **NIfTI**: PNG previews built from orthogonal composite views of the volume
|
| 190 |
-
- **TIFF Stacks**: PNG previews built from orthogonal composite views of the stack
|
| 191 |
-
- **Standard 2D Images**: Resized PNG preview of the original image
|
| 192 |
-
|
| 193 |
-
Previews are used for VLM analysis while preserving original format metadata.
|
| 194 |
-
|
| 195 |
-
### Removing Files
|
| 196 |
-
|
| 197 |
-
Click the 'X' button next to a file to remove it from the current session.
|
| 198 |
-
|
| 199 |
-
## Conversation State
|
| 200 |
-
|
| 201 |
-
The debug sidebar shows:
|
| 202 |
-
|
| 203 |
-
### Current State
|
| 204 |
-
- **Status**: idle, processing, waiting
|
| 205 |
-
- **Conversation Turn**: Current turn number
|
| 206 |
-
- **Excluded Tools**: Tools filtered from results
|
| 207 |
-
|
| 208 |
-
### Preview Images
|
| 209 |
-
- Images prepared for VLM analysis
|
| 210 |
-
- Format conversions applied
|
| 211 |
-
|
| 212 |
-
## Tips for Effective Interaction
|
| 213 |
-
|
| 214 |
-
!!! tip "Be Specific About Requirements"
|
| 215 |
-
Mention specific needs:
|
| 216 |
-
|
| 217 |
-
- "I need a tool that works with NIfTI files"
|
| 218 |
-
- "Must support 3D volumes"
|
| 219 |
-
- "Looking for open-source options"
|
| 220 |
-
|
| 221 |
-
!!! tip "Use Conversational Language"
|
| 222 |
-
Natural language works best:
|
| 223 |
-
|
| 224 |
-
- ✅ "Help me find tool that segments kidneys"
|
| 225 |
-
- ❌ "kidney_segmentation_tool filter:3D"
|
| 226 |
-
|
| 227 |
-
!!! tip "Iterate Based on Results"
|
| 228 |
-
If initial results aren't perfect, refine:
|
| 229 |
-
|
| 230 |
-
- "Can you find tools with higher accuracy?"
|
| 231 |
-
- "Show me open-source alternatives"
|
| 232 |
-
- "What about tools that support DICOM?"
|
| 233 |
-
|
| 234 |
-
!!! tip "Ask Follow-Up Questions"
|
| 235 |
-
The agent maintains context:
|
| 236 |
-
|
| 237 |
-
- "What about the second recommendation?"
|
| 238 |
-
- "Can you compare these two tools?"
|
| 239 |
-
- "Which one is fastest?"
|
| 240 |
-
|
| 241 |
-
## Troubleshooting
|
| 242 |
-
|
| 243 |
-
### No Recommendations
|
| 244 |
-
|
| 245 |
-
If the agent can't find suitable tools:
|
| 246 |
-
|
| 247 |
-
- Try rephrasing your query
|
| 248 |
-
- Be more specific about the task
|
| 249 |
-
- Check that your file uploaded successfully
|
| 250 |
-
- Ensure your task matches the catalog domain (imaging/medical)
|
| 251 |
-
|
| 252 |
-
### Wrong Recommendations
|
| 253 |
-
|
| 254 |
-
If recommendations don't match:
|
| 255 |
-
|
| 256 |
-
- Provide more context about your specific needs
|
| 257 |
-
- Mention required file format support
|
| 258 |
-
- Specify modality or domain
|
| 259 |
-
- Use the exclude feature to filter out irrelevant tools
|
| 260 |
-
|
| 261 |
-
### Demo Execution Fails
|
| 262 |
-
|
| 263 |
-
If a demo doesn't run:
|
| 264 |
-
|
| 265 |
-
- Check your internet connection
|
| 266 |
-
- Verify the demo link is still active
|
| 267 |
-
- Try a different recommended tool
|
| 268 |
-
- Check file format compatibility
|
| 269 |
-
|
| 270 |
-
## Next Steps
|
| 271 |
-
|
| 272 |
-
- Learn about [Supported File Formats](file-formats.md)
|
| 273 |
-
- Understand [How Recommendations Work](recommendations.md)
|
| 274 |
-
- Explore [Running Demos](running-demos.md)
|
| 275 |
-
- Check out [Advanced Features](advanced-features.md)
|
|
|
|
| 1 |
+
# Using the Chat Interface
|
| 2 |
+
|
| 3 |
+
The AI Imaging Agent provides a conversational interface for discovering and using imaging software. This guide explains how to interact with the chat interface effectively.
|
| 4 |
+
|
| 5 |
+
## Interface Layout
|
| 6 |
+
|
| 7 |
+
The interface consists of three main areas:
|
| 8 |
+
|
| 9 |
+
### Left Panel: Chat Conversation
|
| 10 |
+
- **Message History**: Your conversation with the agent
|
| 11 |
+
- **Rich Media Rendering**: Images, tool cards, and files are displayed inline
|
| 12 |
+
- **Input Box**: Type your messages at the bottom
|
| 13 |
+
- **File Upload**: Attach files via the paperclip icon or drag-and-drop
|
| 14 |
+
|
| 15 |
+
### Right Panel: Sidebar
|
| 16 |
+
- **Files Tab**: View uploaded files with format information
|
| 17 |
+
- **State Tab**: Debug information showing conversation state
|
| 18 |
+
|
| 19 |
+
### Header
|
| 20 |
+
- **Model Selector**: Choose which AI model to use
|
| 21 |
+
- **Settings**: Access configuration options
|
| 22 |
+
|
| 23 |
+
## Basic Workflow
|
| 24 |
+
|
| 25 |
+
### 1. Upload Files
|
| 26 |
+
|
| 27 |
+
Upload images or other files in several ways:
|
| 28 |
+
|
| 29 |
+
- **Drag and Drop**: Drag files directly onto the upload area
|
| 30 |
+
- **Click to Browse**: Click the upload area to select files
|
| 31 |
+
- **Attach to Message**: Use the paperclip icon in the input box
|
| 32 |
+
|
| 33 |
+
Files are automatically processed and metadata is extracted.
|
| 34 |
+
|
| 35 |
+
### 2. Describe Your Task
|
| 36 |
+
|
| 37 |
+
Use natural language to describe what you want to do:
|
| 38 |
+
|
| 39 |
+
!!! example "Good Task Descriptions"
|
| 40 |
+
- "I want to segment the lungs from this CT scan"
|
| 41 |
+
- "Help me detect tumors in this MRI"
|
| 42 |
+
- "I need to register these two brain images"
|
| 43 |
+
- "Extract text from this medical report"
|
| 44 |
+
- "Classify the organ shown in this ultrasound"
|
| 45 |
+
|
| 46 |
+
### 3. Review Recommendations
|
| 47 |
+
|
| 48 |
+
The agent returns ranked tool recommendations with:
|
| 49 |
+
|
| 50 |
+
- **Tool Cards**: Each tool is presented in a card format
|
| 51 |
+
- **Accuracy Scores**: Confidence levels for each recommendation
|
| 52 |
+
- **Explanations**: Why each tool matches your request
|
| 53 |
+
- **Metadata**: Technical details about compatibility
|
| 54 |
+
|
| 55 |
+
### 4. Run Demos (Optional)
|
| 56 |
+
|
| 57 |
+
The agent may offer to run demos:
|
| 58 |
+
|
| 59 |
+
```
|
| 60 |
+
Agent: Would you like me to run the demo with your image?
|
| 61 |
+
```
|
| 62 |
+
|
| 63 |
+
Respond with affirmative language:
|
| 64 |
+
- "yes"
|
| 65 |
+
- "sure"
|
| 66 |
+
- "ok"
|
| 67 |
+
- "please"
|
| 68 |
+
- "go ahead"
|
| 69 |
+
|
| 70 |
+
The agent will execute the tool and show results.
|
| 71 |
+
|
| 72 |
+
## Multi-Turn Conversations
|
| 73 |
+
|
| 74 |
+
The agent maintains context across multiple messages:
|
| 75 |
+
|
| 76 |
+
!!! example "Multi-Turn Example"
|
| 77 |
+
```
|
| 78 |
+
You: I have a lung CT scan [uploads file]
|
| 79 |
+
|
| 80 |
+
Agent: I can see you have a DICOM CT image. What would you like to do with it?
|
| 81 |
+
|
| 82 |
+
You: Segment the airways
|
| 83 |
+
|
| 84 |
+
Agent: [Provides airway segmentation tool recommendations]
|
| 85 |
+
|
| 86 |
+
You: What about segmenting the whole lung?
|
| 87 |
+
|
| 88 |
+
Agent: [Provides lung segmentation tools, remembering you're working with CT]
|
| 89 |
+
|
| 90 |
+
You: Show me alternatives
|
| 91 |
+
|
| 92 |
+
Agent: [Provides additional options]
|
| 93 |
+
```
|
| 94 |
+
|
| 95 |
+
## Advanced Features
|
| 96 |
+
|
| 97 |
+
### Excluding Tools
|
| 98 |
+
|
| 99 |
+
Exclude specific tools using the `[EXCLUDE:...]` tag:
|
| 100 |
+
|
| 101 |
+
```
|
| 102 |
+
Find segmentation tools [EXCLUDE:totalsegmentator|medicalsam]
|
| 103 |
+
```
|
| 104 |
+
|
| 105 |
+
You can exclude multiple tools separated by `|`.
|
| 106 |
+
|
| 107 |
+
### Requesting Alternatives
|
| 108 |
+
|
| 109 |
+
Ask the agent to search with different strategies:
|
| 110 |
+
|
| 111 |
+
```
|
| 112 |
+
Can you search for alternatives?
|
| 113 |
+
|
| 114 |
+
Show me other options
|
| 115 |
+
|
| 116 |
+
Find different tools for this task
|
| 117 |
+
```
|
| 118 |
+
|
| 119 |
+
The agent can perform up to 3 alternative searches per conversation.
|
| 120 |
+
|
| 121 |
+
## Understanding Agent Responses
|
| 122 |
+
|
| 123 |
+
### Recommendation Cards
|
| 124 |
+
|
| 125 |
+
Each recommendation includes:
|
| 126 |
+
|
| 127 |
+
#### Header
|
| 128 |
+
- **Rank Number**: 1, 2, 3 (1 = best match)
|
| 129 |
+
- **Tool Name**: Software/tool identifier
|
| 130 |
+
- **Accuracy Score**: 0-100% confidence
|
| 131 |
+
|
| 132 |
+
#### Body
|
| 133 |
+
- **Description**: What the tool does
|
| 134 |
+
- **Explanation**: Why it matches your task
|
| 135 |
+
- **Demo Link**: Click to visit runnable example
|
| 136 |
+
|
| 137 |
+
#### Footer Metadata
|
| 138 |
+
- **Modalities**: CT, MRI, X-ray, etc.
|
| 139 |
+
- **Dimensions**: 2D, 3D, 4D
|
| 140 |
+
- **Formats**: Supported file formats (DICOM, NIfTI, etc.)
|
| 141 |
+
- **License**: Software license information
|
| 142 |
+
- **Tags**: Categorization and keywords
|
| 143 |
+
|
| 144 |
+
### Execution Traces
|
| 145 |
+
|
| 146 |
+
When demos run, you'll see execution details:
|
| 147 |
+
|
| 148 |
+
```
|
| 149 |
+
<details>
|
| 150 |
+
<summary>Tool Execution Trace</summary>
|
| 151 |
+
|
| 152 |
+
Image uploaded to Gradio Space
|
| 153 |
+
Processing started...
|
| 154 |
+
Result: Success
|
| 155 |
+
Output saved to: result.png
|
| 156 |
+
</details>
|
| 157 |
+
```
|
| 158 |
+
|
| 159 |
+
Click to expand and see full execution logs.
|
| 160 |
+
|
| 161 |
+
### Clarification Questions
|
| 162 |
+
|
| 163 |
+
Sometimes the agent needs more information:
|
| 164 |
+
|
| 165 |
+
```
|
| 166 |
+
Agent: I found several segmentation tools. Which organ are you trying to segment?
|
| 167 |
+
|
| 168 |
+
You: The liver
|
| 169 |
+
|
| 170 |
+
Agent: [Provides liver-specific segmentation tools]
|
| 171 |
+
```
|
| 172 |
+
|
| 173 |
+
## File Management
|
| 174 |
+
|
| 175 |
+
### Uploaded Files List
|
| 176 |
+
|
| 177 |
+
The sidebar shows all uploaded files with:
|
| 178 |
+
|
| 179 |
+
- **Filename**: Original file name
|
| 180 |
+
- **Format**: File type/extension
|
| 181 |
+
- **Size**: File size
|
| 182 |
+
- **Preview**: Thumbnail (for images)
|
| 183 |
+
|
| 184 |
+
### Image Previews
|
| 185 |
+
|
| 186 |
+
Medical images are automatically converted:
|
| 187 |
+
|
| 188 |
+
- **DICOM**: PNG previews; 3D series use orthogonal composite views (MIPs + central slices) rather than a single slice
|
| 189 |
+
- **NIfTI**: PNG previews built from orthogonal composite views of the volume
|
| 190 |
+
- **TIFF Stacks**: PNG previews built from orthogonal composite views of the stack
|
| 191 |
+
- **Standard 2D Images**: Resized PNG preview of the original image
|
| 192 |
+
|
| 193 |
+
Previews are used for VLM analysis while preserving original format metadata.
|
| 194 |
+
|
| 195 |
+
### Removing Files
|
| 196 |
+
|
| 197 |
+
Click the 'X' button next to a file to remove it from the current session.
|
| 198 |
+
|
| 199 |
+
## Conversation State
|
| 200 |
+
|
| 201 |
+
The debug sidebar shows:
|
| 202 |
+
|
| 203 |
+
### Current State
|
| 204 |
+
- **Status**: idle, processing, waiting
|
| 205 |
+
- **Conversation Turn**: Current turn number
|
| 206 |
+
- **Excluded Tools**: Tools filtered from results
|
| 207 |
+
|
| 208 |
+
### Preview Images
|
| 209 |
+
- Images prepared for VLM analysis
|
| 210 |
+
- Format conversions applied
|
| 211 |
+
|
| 212 |
+
## Tips for Effective Interaction
|
| 213 |
+
|
| 214 |
+
!!! tip "Be Specific About Requirements"
|
| 215 |
+
Mention specific needs:
|
| 216 |
+
|
| 217 |
+
- "I need a tool that works with NIfTI files"
|
| 218 |
+
- "Must support 3D volumes"
|
| 219 |
+
- "Looking for open-source options"
|
| 220 |
+
|
| 221 |
+
!!! tip "Use Conversational Language"
|
| 222 |
+
Natural language works best:
|
| 223 |
+
|
| 224 |
+
- ✅ "Help me find tool that segments kidneys"
|
| 225 |
+
- ❌ "kidney_segmentation_tool filter:3D"
|
| 226 |
+
|
| 227 |
+
!!! tip "Iterate Based on Results"
|
| 228 |
+
If initial results aren't perfect, refine:
|
| 229 |
+
|
| 230 |
+
- "Can you find tools with higher accuracy?"
|
| 231 |
+
- "Show me open-source alternatives"
|
| 232 |
+
- "What about tools that support DICOM?"
|
| 233 |
+
|
| 234 |
+
!!! tip "Ask Follow-Up Questions"
|
| 235 |
+
The agent maintains context:
|
| 236 |
+
|
| 237 |
+
- "What about the second recommendation?"
|
| 238 |
+
- "Can you compare these two tools?"
|
| 239 |
+
- "Which one is fastest?"
|
| 240 |
+
|
| 241 |
+
## Troubleshooting
|
| 242 |
+
|
| 243 |
+
### No Recommendations
|
| 244 |
+
|
| 245 |
+
If the agent can't find suitable tools:
|
| 246 |
+
|
| 247 |
+
- Try rephrasing your query
|
| 248 |
+
- Be more specific about the task
|
| 249 |
+
- Check that your file uploaded successfully
|
| 250 |
+
- Ensure your task matches the catalog domain (imaging/medical)
|
| 251 |
+
|
| 252 |
+
### Wrong Recommendations
|
| 253 |
+
|
| 254 |
+
If recommendations don't match:
|
| 255 |
+
|
| 256 |
+
- Provide more context about your specific needs
|
| 257 |
+
- Mention required file format support
|
| 258 |
+
- Specify modality or domain
|
| 259 |
+
- Use the exclude feature to filter out irrelevant tools
|
| 260 |
+
|
| 261 |
+
### Demo Execution Fails
|
| 262 |
+
|
| 263 |
+
If a demo doesn't run:
|
| 264 |
+
|
| 265 |
+
- Check your internet connection
|
| 266 |
+
- Verify the demo link is still active
|
| 267 |
+
- Try a different recommended tool
|
| 268 |
+
- Check file format compatibility
|
| 269 |
+
|
| 270 |
+
## Next Steps
|
| 271 |
+
|
| 272 |
+
- Learn about [Supported File Formats](file-formats.md)
|
| 273 |
+
- Understand [How Recommendations Work](recommendations.md)
|
| 274 |
+
- Explore [Running Demos](running-demos.md)
|
| 275 |
+
- Check out [Advanced Features](advanced-features.md)
|
docs/user-guide/file-formats.md
CHANGED
|
@@ -1,282 +1,282 @@
|
|
| 1 |
-
# Supported File Formats
|
| 2 |
-
|
| 3 |
-
The AI Imaging Agent supports a wide range of file formats for medical and scientific imaging, as well as general data files.
|
| 4 |
-
|
| 5 |
-
## Image Formats
|
| 6 |
-
|
| 7 |
-
### Standard Images
|
| 8 |
-
|
| 9 |
-
| Format | Extensions | Description |
|
| 10 |
-
|--------|-----------|-------------|
|
| 11 |
-
| PNG | `.png` | Portable Network Graphics - lossless compression |
|
| 12 |
-
| JPEG | `.jpg`, `.jpeg` | Joint Photographic Experts Group - lossy compression |
|
| 13 |
-
|
| 14 |
-
Currently, only PNG and JPEG are accepted for standard images. Other web formats (e.g. WebP, BMP, GIF) should be converted to PNG or JPEG before upload.
|
| 15 |
-
**Best for**: General photographs, screenshots, web images
|
| 16 |
-
|
| 17 |
-
### Medical Imaging Formats
|
| 18 |
-
|
| 19 |
-
#### DICOM
|
| 20 |
-
|
| 21 |
-
| Format | Extensions | Description |
|
| 22 |
-
|--------|-----------|-------------|
|
| 23 |
-
| DICOM | `.dcm`, `.dicom` | Digital Imaging and Communications in Medicine |
|
| 24 |
-
|
| 25 |
-
**Features**:
|
| 26 |
-
|
| 27 |
-
- Industry standard for medical imaging
|
| 28 |
-
- Contains rich metadata (patient info, acquisition parameters)
|
| 29 |
-
- Supports multiple modalities (CT, MRI, X-ray, etc.)
|
| 30 |
-
- Can store 2D images or 3D volumes
|
| 31 |
-
|
| 32 |
-
**Metadata Extracted**:
|
| 33 |
-
|
| 34 |
-
- Patient ID, Study Instance UID
|
| 35 |
-
- Modality (CT, MR, CR, DX, etc.)
|
| 36 |
-
- Image dimensions and spacing
|
| 37 |
-
- Acquisition date/time
|
| 38 |
-
- Manufacturer and model
|
| 39 |
-
|
| 40 |
-
**Example Usage**:
|
| 41 |
-
```
|
| 42 |
-
Upload a CT DICOM file and ask:
|
| 43 |
-
"Segment the lungs from this scan"
|
| 44 |
-
```
|
| 45 |
-
|
| 46 |
-
#### NIfTI
|
| 47 |
-
|
| 48 |
-
| Format | Extensions | Description |
|
| 49 |
-
|--------|-----------|-------------|
|
| 50 |
-
| NIfTI | `.nii`, `.nii.gz` | Neuroimaging Informatics Technology Initiative |
|
| 51 |
-
|
| 52 |
-
**Features**:
|
| 53 |
-
|
| 54 |
-
- Standard for neuroimaging research
|
| 55 |
-
- Supports 3D and 4D (time-series) volumes
|
| 56 |
-
- Compact storage with optional gzip compression
|
| 57 |
-
- Contains spatial orientation information
|
| 58 |
-
|
| 59 |
-
**Metadata Extracted**:
|
| 60 |
-
|
| 61 |
-
- Volume dimensions (x, y, z, time)
|
| 62 |
-
- Voxel spacing
|
| 63 |
-
- Data type and bit depth
|
| 64 |
-
- Orientation matrix
|
| 65 |
-
|
| 66 |
-
**Example Usage**:
|
| 67 |
-
```
|
| 68 |
-
Upload a brain MRI NIfTI file:
|
| 69 |
-
"Register this brain scan to MNI space"
|
| 70 |
-
```
|
| 71 |
-
|
| 72 |
-
### Scientific Imaging Formats
|
| 73 |
-
|
| 74 |
-
#### TIFF/TIFF Stacks
|
| 75 |
-
|
| 76 |
-
| Format | Extensions | Description |
|
| 77 |
-
|--------|-----------|-------------|
|
| 78 |
-
| TIFF | `.tif`, `.tiff` | Tagged Image File Format |
|
| 79 |
-
|
| 80 |
-
**Features**:
|
| 81 |
-
|
| 82 |
-
- Supports multi-page/multi-frame images
|
| 83 |
-
- Common in microscopy and scientific imaging
|
| 84 |
-
- Can store extensive metadata
|
| 85 |
-
- Lossless compression options
|
| 86 |
-
|
| 87 |
-
**Metadata Extracted**:
|
| 88 |
-
|
| 89 |
-
- Number of pages/frames (for stacks)
|
| 90 |
-
- Dimensions (width, height, channels)
|
| 91 |
-
- Color mode (RGB, grayscale, etc.)
|
| 92 |
-
- Compression method
|
| 93 |
-
- DPI/resolution information
|
| 94 |
-
|
| 95 |
-
**Example Usage**:
|
| 96 |
-
```
|
| 97 |
-
Upload a microscopy TIFF stack:
|
| 98 |
-
"Analyze cell structures in this z-stack"
|
| 99 |
-
```
|
| 100 |
-
|
| 101 |
-
## Data Formats
|
| 102 |
-
|
| 103 |
-
<!-- ### Structured Data
|
| 104 |
-
|
| 105 |
-
| Format | Extensions | Description |
|
| 106 |
-
|--------|-----------|-------------|
|
| 107 |
-
| CSV | `.csv` | Comma-separated values |
|
| 108 |
-
| JSON | `.json` | JavaScript Object Notation |
|
| 109 |
-
| XML | `.xml` | Extensible Markup Language |
|
| 110 |
-
|
| 111 |
-
**Best for**: Metadata, annotations, measurements, structured results
|
| 112 |
-
|
| 113 |
-
## Media Formats
|
| 114 |
-
|
| 115 |
-
| Format | Extensions | Description |
|
| 116 |
-
|--------|-----------|-------------|
|
| 117 |
-
| Audio | `.mp3` | MPEG Audio Layer 3 |
|
| 118 |
-
| Video | `.mp4` | MPEG-4 video |
|
| 119 |
-
|
| 120 |
-
**Note**: Currently supported for upload but limited analysis capabilities. -->
|
| 121 |
-
|
| 122 |
-
## Format Detection
|
| 123 |
-
|
| 124 |
-
The agent automatically detects file formats using:
|
| 125 |
-
|
| 126 |
-
1. **File Extension**: Primary detection method
|
| 127 |
-
2. **Magic Bytes**: Header inspection for validation
|
| 128 |
-
3. **Content Analysis**: Fallback for ambiguous cases
|
| 129 |
-
|
| 130 |
-
## Metadata Extraction
|
| 131 |
-
|
| 132 |
-
### What Gets Extracted
|
| 133 |
-
|
| 134 |
-
For each uploaded file, the agent extracts:
|
| 135 |
-
|
| 136 |
-
#### Image Metadata
|
| 137 |
-
- **Dimensions**: Width, height, depth (for volumes)
|
| 138 |
-
- **Channels**: Grayscale, RGB, RGBA
|
| 139 |
-
- **Data Type**: uint8, int16, float32, etc.
|
| 140 |
-
- **File Size**: Storage size
|
| 141 |
-
|
| 142 |
-
#### Medical Image Metadata
|
| 143 |
-
- **Modality**: CT, MRI, X-ray, Ultrasound, PET, etc.
|
| 144 |
-
- **Patient Info**: Anonymized IDs
|
| 145 |
-
- **Study Info**: Study UID, dates
|
| 146 |
-
- **Acquisition Parameters**: Slice thickness, spacing, orientation
|
| 147 |
-
- **Equipment**: Manufacturer, model, software version
|
| 148 |
-
|
| 149 |
-
#### Format-Specific Metadata
|
| 150 |
-
- **DICOM Tags**: Full DICOM header information
|
| 151 |
-
- **NIfTI Header**: Spatial orientation, timing information
|
| 152 |
-
- **TIFF Tags**: IFD entries, compression, photometric interpretation
|
| 153 |
-
|
| 154 |
-
### Why Metadata Matters
|
| 155 |
-
|
| 156 |
-
Metadata is used for:
|
| 157 |
-
|
| 158 |
-
1. **Format Matching**: Recommend tools that support your file format
|
| 159 |
-
2. **Compatibility Scoring**: Prioritize tools that work with your specific format
|
| 160 |
-
3. **Context Understanding**: Help VLM understand image characteristics
|
| 161 |
-
4. **Demo Execution**: Ensure tools can process your data
|
| 162 |
-
|
| 163 |
-
## Preview Generation
|
| 164 |
-
|
| 165 |
-
### Automatic Conversion
|
| 166 |
-
|
| 167 |
-
Medical and scientific images are converted to PNG previews for VLM analysis:
|
| 168 |
-
|
| 169 |
-
| Original Format | Preview Generation |
|
| 170 |
-
|----------------|-------------------|
|
| 171 |
-
| DICOM (2D) | Single-frame converted to PNG |
|
| 172 |
-
| DICOM / NIfTI 3D volumes | Orthogonal 3‑view composite PNG (axial, sagittal, coronal) using middle slices and/or maximum intensity projections (MIPs) |
|
| 173 |
-
| NIfTI 4D (time series) | Middle timepoint volume rendered as an orthogonal 3‑view composite (middle slices and/or MIPs) |
|
| 174 |
-
| TIFF Stack | Orthogonal 3‑view composite for 3D stacks; otherwise contact sheet or animated GIF preview when appropriate |
|
| 175 |
-
| Standard Images | Single-view PNG (content preserved; may be resized/normalized) |
|
| 176 |
-
|
| 177 |
-
**Important**: Preview generation is for visual analysis only. Original format metadata is preserved and used for compatibility matching.
|
| 178 |
-
|
| 179 |
-
### Multi-Slice Handling
|
| 180 |
-
|
| 181 |
-
For 3D volumes, the agent typically builds an orthogonal 3‑view composite preview:
|
| 182 |
-
|
| 183 |
-
- **Axial**: Horizontal slices (z-axis)
|
| 184 |
-
- **Sagittal**: Side view (x-axis)
|
| 185 |
-
- **Coronal**: Front view (y-axis)
|
| 186 |
-
|
| 187 |
-
Each view may combine the middle slice with a maximum intensity projection (MIP) to capture both anatomical context and bright structures. When a 3‑view composite cannot be generated (e.g., unusual stack layout), the agent may fall back to a contact sheet or an animated GIF preview of multiple slices.
|
| 188 |
-
|
| 189 |
-
## Format Compatibility Matching
|
| 190 |
-
|
| 191 |
-
### How It Works
|
| 192 |
-
|
| 193 |
-
The retrieval system adds format tokens to your query:
|
| 194 |
-
|
| 195 |
-
```
|
| 196 |
-
Original query: "segment lungs"
|
| 197 |
-
Enhanced query: "segment lungs format:DICOM format:3D"
|
| 198 |
-
```
|
| 199 |
-
|
| 200 |
-
Tools are matched based on:
|
| 201 |
-
|
| 202 |
-
1. **Direct Format Support**: Tool explicitly supports your format
|
| 203 |
-
2. **Format Category**: Tool supports format family (e.g., medical imaging)
|
| 204 |
-
3. **Conversion Capability**: Tool can convert from your format
|
| 205 |
-
|
| 206 |
-
### IO Compatibility Scoring
|
| 207 |
-
|
| 208 |
-
The VLM considers:
|
| 209 |
-
|
| 210 |
-
- **Input Format Match**: Can the tool read your file?
|
| 211 |
-
- **Output Format**: What format does the tool produce?
|
| 212 |
-
- **Dimension Compatibility**: 2D tool for 2D images, 3D for volumes
|
| 213 |
-
- **Modality Specificity**: CT tools for CT images, MRI for MRI
|
| 214 |
-
|
| 215 |
-
<!-- ## File Size Limits
|
| 216 |
-
|
| 217 |
-
Default limits (configurable):
|
| 218 |
-
|
| 219 |
-
| Category | Limit | Notes |
|
| 220 |
-
|----------|-------|-------|
|
| 221 |
-
| Images | 100 MB | Per file |
|
| 222 |
-
| DICOM | 200 MB | Medical images can be larger |
|
| 223 |
-
| NIfTI | 500 MB | Volumes can be very large |
|
| 224 |
-
| TIFF Stacks | 200 MB | Multi-frame images |
|
| 225 |
-
| Other Files | 50 MB | General limit |
|
| 226 |
-
|
| 227 |
-
!!! warning "Large Files"
|
| 228 |
-
Very large files may take longer to process. Consider downsampling or cropping if possible. -->
|
| 229 |
-
|
| 230 |
-
## Unsupported Formats
|
| 231 |
-
|
| 232 |
-
Currently not supported:
|
| 233 |
-
|
| 234 |
-
- **Proprietary Formats**: Manufacturer-specific formats (e.g., .PAR/.REC)
|
| 235 |
-
- **Video Processing**: Limited video analysis capability
|
| 236 |
-
- **Raw Data**: Unformatted binary dumps without headers
|
| 237 |
-
|
| 238 |
-
## Format Best Practices
|
| 239 |
-
|
| 240 |
-
!!! tip "Use Standard Formats"
|
| 241 |
-
Stick to standard formats (DICOM, NIfTI, PNG, TIFF) for best tool compatibility.
|
| 242 |
-
|
| 243 |
-
!!! tip "Include Metadata"
|
| 244 |
-
Use formats that preserve metadata (DICOM, NIfTI) rather than exporting to PNG/JPEG.
|
| 245 |
-
|
| 246 |
-
!!! tip "Check Compatibility"
|
| 247 |
-
If a tool doesn't work, check the format compatibility in the recommendation metadata.
|
| 248 |
-
|
| 249 |
-
!!! tip "Convert When Needed"
|
| 250 |
-
Some tools prefer specific formats. Convert using standard tools (ITK-SNAP, 3D Slicer) before upload.
|
| 251 |
-
|
| 252 |
-
## Example Workflows by Format
|
| 253 |
-
|
| 254 |
-
### DICOM Workflow
|
| 255 |
-
```
|
| 256 |
-
1. Upload: chest_ct.dcm
|
| 257 |
-
2. Query: "Segment lungs"
|
| 258 |
-
3. Agent detects: DICOM, CT modality, 3D volume
|
| 259 |
-
4. Results: CT-compatible lung segmentation tools
|
| 260 |
-
```
|
| 261 |
-
|
| 262 |
-
### NIfTI Workflow
|
| 263 |
-
```
|
| 264 |
-
1. Upload: brain_mri.nii.gz
|
| 265 |
-
2. Query: "Skull stripping"
|
| 266 |
-
3. Agent detects: NIfTI, 3D volume, likely MRI
|
| 267 |
-
4. Results: Brain extraction tools supporting NIfTI
|
| 268 |
-
```
|
| 269 |
-
|
| 270 |
-
### TIFF Stack Workflow
|
| 271 |
-
```
|
| 272 |
-
1. Upload: microscopy_stack.tif
|
| 273 |
-
2. Query: "Cell counting"
|
| 274 |
-
3. Agent detects: Multi-frame TIFF, 3D stack
|
| 275 |
-
4. Results: Microscopy analysis tools
|
| 276 |
-
```
|
| 277 |
-
|
| 278 |
-
## Next Steps
|
| 279 |
-
|
| 280 |
-
- Learn about [Understanding Recommendations](recommendations.md)
|
| 281 |
-
- Explore [Running Demos](running-demos.md)
|
| 282 |
-
- Check [Advanced Features](advanced-features.md)
|
|
|
|
| 1 |
+
# Supported File Formats
|
| 2 |
+
|
| 3 |
+
The AI Imaging Agent supports a wide range of file formats for medical and scientific imaging, as well as general data files.
|
| 4 |
+
|
| 5 |
+
## Image Formats
|
| 6 |
+
|
| 7 |
+
### Standard Images
|
| 8 |
+
|
| 9 |
+
| Format | Extensions | Description |
|
| 10 |
+
|--------|-----------|-------------|
|
| 11 |
+
| PNG | `.png` | Portable Network Graphics - lossless compression |
|
| 12 |
+
| JPEG | `.jpg`, `.jpeg` | Joint Photographic Experts Group - lossy compression |
|
| 13 |
+
|
| 14 |
+
Currently, only PNG and JPEG are accepted for standard images. Other web formats (e.g. WebP, BMP, GIF) should be converted to PNG or JPEG before upload.
|
| 15 |
+
**Best for**: General photographs, screenshots, web images
|
| 16 |
+
|
| 17 |
+
### Medical Imaging Formats
|
| 18 |
+
|
| 19 |
+
#### DICOM
|
| 20 |
+
|
| 21 |
+
| Format | Extensions | Description |
|
| 22 |
+
|--------|-----------|-------------|
|
| 23 |
+
| DICOM | `.dcm`, `.dicom` | Digital Imaging and Communications in Medicine |
|
| 24 |
+
|
| 25 |
+
**Features**:
|
| 26 |
+
|
| 27 |
+
- Industry standard for medical imaging
|
| 28 |
+
- Contains rich metadata (patient info, acquisition parameters)
|
| 29 |
+
- Supports multiple modalities (CT, MRI, X-ray, etc.)
|
| 30 |
+
- Can store 2D images or 3D volumes
|
| 31 |
+
|
| 32 |
+
**Metadata Extracted**:
|
| 33 |
+
|
| 34 |
+
- Patient ID, Study Instance UID
|
| 35 |
+
- Modality (CT, MR, CR, DX, etc.)
|
| 36 |
+
- Image dimensions and spacing
|
| 37 |
+
- Acquisition date/time
|
| 38 |
+
- Manufacturer and model
|
| 39 |
+
|
| 40 |
+
**Example Usage**:
|
| 41 |
+
```
|
| 42 |
+
Upload a CT DICOM file and ask:
|
| 43 |
+
"Segment the lungs from this scan"
|
| 44 |
+
```
|
| 45 |
+
|
| 46 |
+
#### NIfTI
|
| 47 |
+
|
| 48 |
+
| Format | Extensions | Description |
|
| 49 |
+
|--------|-----------|-------------|
|
| 50 |
+
| NIfTI | `.nii`, `.nii.gz` | Neuroimaging Informatics Technology Initiative |
|
| 51 |
+
|
| 52 |
+
**Features**:
|
| 53 |
+
|
| 54 |
+
- Standard for neuroimaging research
|
| 55 |
+
- Supports 3D and 4D (time-series) volumes
|
| 56 |
+
- Compact storage with optional gzip compression
|
| 57 |
+
- Contains spatial orientation information
|
| 58 |
+
|
| 59 |
+
**Metadata Extracted**:
|
| 60 |
+
|
| 61 |
+
- Volume dimensions (x, y, z, time)
|
| 62 |
+
- Voxel spacing
|
| 63 |
+
- Data type and bit depth
|
| 64 |
+
- Orientation matrix
|
| 65 |
+
|
| 66 |
+
**Example Usage**:
|
| 67 |
+
```
|
| 68 |
+
Upload a brain MRI NIfTI file:
|
| 69 |
+
"Register this brain scan to MNI space"
|
| 70 |
+
```
|
| 71 |
+
|
| 72 |
+
### Scientific Imaging Formats
|
| 73 |
+
|
| 74 |
+
#### TIFF/TIFF Stacks
|
| 75 |
+
|
| 76 |
+
| Format | Extensions | Description |
|
| 77 |
+
|--------|-----------|-------------|
|
| 78 |
+
| TIFF | `.tif`, `.tiff` | Tagged Image File Format |
|
| 79 |
+
|
| 80 |
+
**Features**:
|
| 81 |
+
|
| 82 |
+
- Supports multi-page/multi-frame images
|
| 83 |
+
- Common in microscopy and scientific imaging
|
| 84 |
+
- Can store extensive metadata
|
| 85 |
+
- Lossless compression options
|
| 86 |
+
|
| 87 |
+
**Metadata Extracted**:
|
| 88 |
+
|
| 89 |
+
- Number of pages/frames (for stacks)
|
| 90 |
+
- Dimensions (width, height, channels)
|
| 91 |
+
- Color mode (RGB, grayscale, etc.)
|
| 92 |
+
- Compression method
|
| 93 |
+
- DPI/resolution information
|
| 94 |
+
|
| 95 |
+
**Example Usage**:
|
| 96 |
+
```
|
| 97 |
+
Upload a microscopy TIFF stack:
|
| 98 |
+
"Analyze cell structures in this z-stack"
|
| 99 |
+
```
|
| 100 |
+
|
| 101 |
+
## Data Formats
|
| 102 |
+
|
| 103 |
+
<!-- ### Structured Data
|
| 104 |
+
|
| 105 |
+
| Format | Extensions | Description |
|
| 106 |
+
|--------|-----------|-------------|
|
| 107 |
+
| CSV | `.csv` | Comma-separated values |
|
| 108 |
+
| JSON | `.json` | JavaScript Object Notation |
|
| 109 |
+
| XML | `.xml` | Extensible Markup Language |
|
| 110 |
+
|
| 111 |
+
**Best for**: Metadata, annotations, measurements, structured results
|
| 112 |
+
|
| 113 |
+
## Media Formats
|
| 114 |
+
|
| 115 |
+
| Format | Extensions | Description |
|
| 116 |
+
|--------|-----------|-------------|
|
| 117 |
+
| Audio | `.mp3` | MPEG Audio Layer 3 |
|
| 118 |
+
| Video | `.mp4` | MPEG-4 video |
|
| 119 |
+
|
| 120 |
+
**Note**: Currently supported for upload but limited analysis capabilities. -->
|
| 121 |
+
|
| 122 |
+
## Format Detection
|
| 123 |
+
|
| 124 |
+
The agent automatically detects file formats using:
|
| 125 |
+
|
| 126 |
+
1. **File Extension**: Primary detection method
|
| 127 |
+
2. **Magic Bytes**: Header inspection for validation
|
| 128 |
+
3. **Content Analysis**: Fallback for ambiguous cases
|
| 129 |
+
|
| 130 |
+
## Metadata Extraction
|
| 131 |
+
|
| 132 |
+
### What Gets Extracted
|
| 133 |
+
|
| 134 |
+
For each uploaded file, the agent extracts:
|
| 135 |
+
|
| 136 |
+
#### Image Metadata
|
| 137 |
+
- **Dimensions**: Width, height, depth (for volumes)
|
| 138 |
+
- **Channels**: Grayscale, RGB, RGBA
|
| 139 |
+
- **Data Type**: uint8, int16, float32, etc.
|
| 140 |
+
- **File Size**: Storage size
|
| 141 |
+
|
| 142 |
+
#### Medical Image Metadata
|
| 143 |
+
- **Modality**: CT, MRI, X-ray, Ultrasound, PET, etc.
|
| 144 |
+
- **Patient Info**: Anonymized IDs
|
| 145 |
+
- **Study Info**: Study UID, dates
|
| 146 |
+
- **Acquisition Parameters**: Slice thickness, spacing, orientation
|
| 147 |
+
- **Equipment**: Manufacturer, model, software version
|
| 148 |
+
|
| 149 |
+
#### Format-Specific Metadata
|
| 150 |
+
- **DICOM Tags**: Full DICOM header information
|
| 151 |
+
- **NIfTI Header**: Spatial orientation, timing information
|
| 152 |
+
- **TIFF Tags**: IFD entries, compression, photometric interpretation
|
| 153 |
+
|
| 154 |
+
### Why Metadata Matters
|
| 155 |
+
|
| 156 |
+
Metadata is used for:
|
| 157 |
+
|
| 158 |
+
1. **Format Matching**: Recommend tools that support your file format
|
| 159 |
+
2. **Compatibility Scoring**: Prioritize tools that work with your specific format
|
| 160 |
+
3. **Context Understanding**: Help VLM understand image characteristics
|
| 161 |
+
4. **Demo Execution**: Ensure tools can process your data
|
| 162 |
+
|
| 163 |
+
## Preview Generation
|
| 164 |
+
|
| 165 |
+
### Automatic Conversion
|
| 166 |
+
|
| 167 |
+
Medical and scientific images are converted to PNG previews for VLM analysis:
|
| 168 |
+
|
| 169 |
+
| Original Format | Preview Generation |
|
| 170 |
+
|----------------|-------------------|
|
| 171 |
+
| DICOM (2D) | Single-frame converted to PNG |
|
| 172 |
+
| DICOM / NIfTI 3D volumes | Orthogonal 3‑view composite PNG (axial, sagittal, coronal) using middle slices and/or maximum intensity projections (MIPs) |
|
| 173 |
+
| NIfTI 4D (time series) | Middle timepoint volume rendered as an orthogonal 3‑view composite (middle slices and/or MIPs) |
|
| 174 |
+
| TIFF Stack | Orthogonal 3‑view composite for 3D stacks; otherwise contact sheet or animated GIF preview when appropriate |
|
| 175 |
+
| Standard Images | Single-view PNG (content preserved; may be resized/normalized) |
|
| 176 |
+
|
| 177 |
+
**Important**: Preview generation is for visual analysis only. Original format metadata is preserved and used for compatibility matching.
|
| 178 |
+
|
| 179 |
+
### Multi-Slice Handling
|
| 180 |
+
|
| 181 |
+
For 3D volumes, the agent typically builds an orthogonal 3‑view composite preview:
|
| 182 |
+
|
| 183 |
+
- **Axial**: Horizontal slices (z-axis)
|
| 184 |
+
- **Sagittal**: Side view (x-axis)
|
| 185 |
+
- **Coronal**: Front view (y-axis)
|
| 186 |
+
|
| 187 |
+
Each view may combine the middle slice with a maximum intensity projection (MIP) to capture both anatomical context and bright structures. When a 3‑view composite cannot be generated (e.g., unusual stack layout), the agent may fall back to a contact sheet or an animated GIF preview of multiple slices.
|
| 188 |
+
|
| 189 |
+
## Format Compatibility Matching
|
| 190 |
+
|
| 191 |
+
### How It Works
|
| 192 |
+
|
| 193 |
+
The retrieval system adds format tokens to your query:
|
| 194 |
+
|
| 195 |
+
```
|
| 196 |
+
Original query: "segment lungs"
|
| 197 |
+
Enhanced query: "segment lungs format:DICOM format:3D"
|
| 198 |
+
```
|
| 199 |
+
|
| 200 |
+
Tools are matched based on:
|
| 201 |
+
|
| 202 |
+
1. **Direct Format Support**: Tool explicitly supports your format
|
| 203 |
+
2. **Format Category**: Tool supports format family (e.g., medical imaging)
|
| 204 |
+
3. **Conversion Capability**: Tool can convert from your format
|
| 205 |
+
|
| 206 |
+
### IO Compatibility Scoring
|
| 207 |
+
|
| 208 |
+
The VLM considers:
|
| 209 |
+
|
| 210 |
+
- **Input Format Match**: Can the tool read your file?
|
| 211 |
+
- **Output Format**: What format does the tool produce?
|
| 212 |
+
- **Dimension Compatibility**: 2D tool for 2D images, 3D for volumes
|
| 213 |
+
- **Modality Specificity**: CT tools for CT images, MRI for MRI
|
| 214 |
+
|
| 215 |
+
<!-- ## File Size Limits
|
| 216 |
+
|
| 217 |
+
Default limits (configurable):
|
| 218 |
+
|
| 219 |
+
| Category | Limit | Notes |
|
| 220 |
+
|----------|-------|-------|
|
| 221 |
+
| Images | 100 MB | Per file |
|
| 222 |
+
| DICOM | 200 MB | Medical images can be larger |
|
| 223 |
+
| NIfTI | 500 MB | Volumes can be very large |
|
| 224 |
+
| TIFF Stacks | 200 MB | Multi-frame images |
|
| 225 |
+
| Other Files | 50 MB | General limit |
|
| 226 |
+
|
| 227 |
+
!!! warning "Large Files"
|
| 228 |
+
Very large files may take longer to process. Consider downsampling or cropping if possible. -->
|
| 229 |
+
|
| 230 |
+
## Unsupported Formats
|
| 231 |
+
|
| 232 |
+
Currently not supported:
|
| 233 |
+
|
| 234 |
+
- **Proprietary Formats**: Manufacturer-specific formats (e.g., .PAR/.REC)
|
| 235 |
+
- **Video Processing**: Limited video analysis capability
|
| 236 |
+
- **Raw Data**: Unformatted binary dumps without headers
|
| 237 |
+
|
| 238 |
+
## Format Best Practices
|
| 239 |
+
|
| 240 |
+
!!! tip "Use Standard Formats"
|
| 241 |
+
Stick to standard formats (DICOM, NIfTI, PNG, TIFF) for best tool compatibility.
|
| 242 |
+
|
| 243 |
+
!!! tip "Include Metadata"
|
| 244 |
+
Use formats that preserve metadata (DICOM, NIfTI) rather than exporting to PNG/JPEG.
|
| 245 |
+
|
| 246 |
+
!!! tip "Check Compatibility"
|
| 247 |
+
If a tool doesn't work, check the format compatibility in the recommendation metadata.
|
| 248 |
+
|
| 249 |
+
!!! tip "Convert When Needed"
|
| 250 |
+
Some tools prefer specific formats. Convert using standard tools (ITK-SNAP, 3D Slicer) before upload.
|
| 251 |
+
|
| 252 |
+
## Example Workflows by Format
|
| 253 |
+
|
| 254 |
+
### DICOM Workflow
|
| 255 |
+
```
|
| 256 |
+
1. Upload: chest_ct.dcm
|
| 257 |
+
2. Query: "Segment lungs"
|
| 258 |
+
3. Agent detects: DICOM, CT modality, 3D volume
|
| 259 |
+
4. Results: CT-compatible lung segmentation tools
|
| 260 |
+
```
|
| 261 |
+
|
| 262 |
+
### NIfTI Workflow
|
| 263 |
+
```
|
| 264 |
+
1. Upload: brain_mri.nii.gz
|
| 265 |
+
2. Query: "Skull stripping"
|
| 266 |
+
3. Agent detects: NIfTI, 3D volume, likely MRI
|
| 267 |
+
4. Results: Brain extraction tools supporting NIfTI
|
| 268 |
+
```
|
| 269 |
+
|
| 270 |
+
### TIFF Stack Workflow
|
| 271 |
+
```
|
| 272 |
+
1. Upload: microscopy_stack.tif
|
| 273 |
+
2. Query: "Cell counting"
|
| 274 |
+
3. Agent detects: Multi-frame TIFF, 3D stack
|
| 275 |
+
4. Results: Microscopy analysis tools
|
| 276 |
+
```
|
| 277 |
+
|
| 278 |
+
## Next Steps
|
| 279 |
+
|
| 280 |
+
- Learn about [Understanding Recommendations](recommendations.md)
|
| 281 |
+
- Explore [Running Demos](running-demos.md)
|
| 282 |
+
- Check [Advanced Features](advanced-features.md)
|
docs/user-guide/recommendations.md
CHANGED
|
@@ -1,329 +1,329 @@
|
|
| 1 |
-
# Understanding Recommendations
|
| 2 |
-
|
| 3 |
-
The AI Imaging Agent uses a sophisticated two-stage pipeline to provide ranked tool recommendations. This guide explains how recommendations are generated and how to interpret them.
|
| 4 |
-
|
| 5 |
-
## How Recommendations Work
|
| 6 |
-
|
| 7 |
-
### Two-Stage Pipeline
|
| 8 |
-
|
| 9 |
-
```mermaid
|
| 10 |
-
graph TD
|
| 11 |
-
A[User Input: Image + Query] --> B[Stage 1: Retrieval]
|
| 12 |
-
B --> C[Candidate Tools]
|
| 13 |
-
C --> D[Stage 2: Agent Selection]
|
| 14 |
-
D --> E[Ranked Recommendations]
|
| 15 |
-
```
|
| 16 |
-
|
| 17 |
-
#### Stage 1: Retrieval (Fast Text Search)
|
| 18 |
-
|
| 19 |
-
The retrieval stage quickly narrows down candidates:
|
| 20 |
-
|
| 21 |
-
1. **Query Enhancement**: Your query is enriched with format tokens
|
| 22 |
-
```
|
| 23 |
-
Original: "segment lungs"
|
| 24 |
-
Enhanced: "segment lungs format:DICOM format:CT format:3D"
|
| 25 |
-
```
|
| 26 |
-
|
| 27 |
-
2. **Embedding Search**: BGE-M3 model converts query to vector
|
| 28 |
-
3. **FAISS Vector Search**: Finds semantically similar tools
|
| 29 |
-
4. **CrossEncoder Reranking**: Re-scores candidates for better relevance
|
| 30 |
-
5. **Result**: Top-K candidates (default: 8)
|
| 31 |
-
|
| 32 |
-
**No LLM calls** - this stage is fast and deterministic.
|
| 33 |
-
|
| 34 |
-
#### Stage 2: Agent Selection (VLM-Powered)
|
| 35 |
-
|
| 36 |
-
The agent analyzes candidates with full context:
|
| 37 |
-
|
| 38 |
-
1. **Vision Analysis (only for VLM)**: GPT-4o/4o-mini (or your custom model) sees your image preview
|
| 39 |
-
2. **Context Integration**: Considers query + metadata + candidates
|
| 40 |
-
3. **Reasoning**: Explains why each tool matches
|
| 41 |
-
4. **Scoring**: Assigns accuracy scores (0-100%)
|
| 42 |
-
5. **Ranking**: Orders tools by relevance
|
| 43 |
-
|
| 44 |
-
**Single VLM call** - comprehensive analysis with explanations.
|
| 45 |
-
|
| 46 |
-
## Recommendation Format
|
| 47 |
-
|
| 48 |
-
Each recommendation includes several components:
|
| 49 |
-
|
| 50 |
-
### Header Information
|
| 51 |
-
|
| 52 |
-
#### Rank Number
|
| 53 |
-
Position in the ranked list (1 = best match).
|
| 54 |
-
|
| 55 |
-
```
|
| 56 |
-
1️⃣ TotalSegmentator
|
| 57 |
-
2️⃣ MedSAM
|
| 58 |
-
3️⃣ nnU-Net
|
| 59 |
-
```
|
| 60 |
-
|
| 61 |
-
#### Tool Name
|
| 62 |
-
The software or tool identifier, typically matching:
|
| 63 |
-
- GitHub repository name
|
| 64 |
-
- Published tool name
|
| 65 |
-
- Common community name
|
| 66 |
-
|
| 67 |
-
#### Accuracy Score
|
| 68 |
-
Confidence level from 0-100%:
|
| 69 |
-
|
| 70 |
-
- **90-100%**: Excellent match, highly confident
|
| 71 |
-
- **70-89%**: Good match, suitable for task
|
| 72 |
-
- **50-69%**: Moderate match, may need adaptation
|
| 73 |
-
- **Below 50%**: Weak match, alternative approach
|
| 74 |
-
|
| 75 |
-
!!! note "Score Interpretation"
|
| 76 |
-
Scores reflect match quality for **your specific task and image**, not overall tool quality.
|
| 77 |
-
|
| 78 |
-
### Body Content
|
| 79 |
-
|
| 80 |
-
#### Description
|
| 81 |
-
Brief explanation of what the tool does:
|
| 82 |
-
|
| 83 |
-
```
|
| 84 |
-
TotalSegmentator: Automated multi-organ segmentation for CT scans supporting 104 anatomical structures.
|
| 85 |
-
```
|
| 86 |
-
|
| 87 |
-
#### Explanation
|
| 88 |
-
Why this tool matches your request:
|
| 89 |
-
|
| 90 |
-
```
|
| 91 |
-
Explanation: TotalSegmentator is specifically designed for whole-body CT segmentation including lung structures. It supports DICOM input and provides automated, accurate lung segmentation without manual intervention.
|
| 92 |
-
```
|
| 93 |
-
|
| 94 |
-
Key points in explanations:
|
| 95 |
-
|
| 96 |
-
- **Task Alignment**: How well it matches your goal
|
| 97 |
-
- **Format Compatibility**: Support for your file format
|
| 98 |
-
- **Relevant Features**: Specific capabilities that help
|
| 99 |
-
- **Known Limitations**: Caveats or requirements
|
| 100 |
-
|
| 101 |
-
#### Demo Link
|
| 102 |
-
Direct link to a runnable example:
|
| 103 |
-
|
| 104 |
-
```
|
| 105 |
-
🚀 Demo: https://huggingface.co/spaces/example/totalsegmentator
|
| 106 |
-
```
|
| 107 |
-
|
| 108 |
-
Types of demos:
|
| 109 |
-
|
| 110 |
-
- **HuggingFace Spaces**: Interactive Gradio/Streamlit apps
|
| 111 |
-
- **Colab Notebooks**: Jupyter notebooks you can run
|
| 112 |
-
- **Web Demos**: Hosted web interfaces
|
| 113 |
-
- **Documentation**: GitHub README with examples
|
| 114 |
-
|
| 115 |
-
### Metadata Footer
|
| 116 |
-
|
| 117 |
-
Technical details about the tool:
|
| 118 |
-
|
| 119 |
-
#### Modality Support
|
| 120 |
-
Medical imaging modalities the tool works with:
|
| 121 |
-
|
| 122 |
-
```
|
| 123 |
-
Modalities: CT, MRI, X-ray
|
| 124 |
-
```
|
| 125 |
-
|
| 126 |
-
Common modalities:
|
| 127 |
-
|
| 128 |
-
- **CT**: Computed Tomography
|
| 129 |
-
- **MRI**: Magnetic Resonance Imaging
|
| 130 |
-
- **XR**: X-ray radiography
|
| 131 |
-
- **US**: Ultrasound
|
| 132 |
-
- **PET**: Positron Emission Tomography
|
| 133 |
-
- **OCT**: Optical Coherence Tomography
|
| 134 |
-
- **Microscopy**: Various microscopy types
|
| 135 |
-
|
| 136 |
-
#### Dimension Support
|
| 137 |
-
Image/volume dimensions supported:
|
| 138 |
-
|
| 139 |
-
```
|
| 140 |
-
Dimensions: 2D, 3D
|
| 141 |
-
```
|
| 142 |
-
|
| 143 |
-
- **2D**: Single slice images
|
| 144 |
-
- **3D**: Volumetric data
|
| 145 |
-
- **4D**: Time-series volumes
|
| 146 |
-
|
| 147 |
-
#### Format Support
|
| 148 |
-
File formats the tool can process:
|
| 149 |
-
|
| 150 |
-
```
|
| 151 |
-
Formats: DICOM, NIfTI, PNG, JPEG
|
| 152 |
-
```
|
| 153 |
-
|
| 154 |
-
!!! tip "Format Importance"
|
| 155 |
-
Tools that support your **exact format** are prioritized in ranking.
|
| 156 |
-
|
| 157 |
-
#### Tags
|
| 158 |
-
Categorization and keywords:
|
| 159 |
-
|
| 160 |
-
```
|
| 161 |
-
Tags: segmentation, medical-imaging, deep-learning, pytorch
|
| 162 |
-
```
|
| 163 |
-
|
| 164 |
-
Used for:
|
| 165 |
-
- Task categorization
|
| 166 |
-
- Technology stack
|
| 167 |
-
- Domain specificity
|
| 168 |
-
- Feature indicators
|
| 169 |
-
|
| 170 |
-
## Scoring Factors
|
| 171 |
-
|
| 172 |
-
The agent considers multiple factors when scoring:
|
| 173 |
-
|
| 174 |
-
### Primary Factors (High Weight)
|
| 175 |
-
|
| 176 |
-
1. **Task Match**: How well the tool's purpose aligns with your request
|
| 177 |
-
2. **Format Compatibility**: Support for your input format
|
| 178 |
-
3. **Image Content**: Visual analysis of what's in your image
|
| 179 |
-
4. **Dimension Match**: 2D tool for 2D images, 3D for volumes
|
| 180 |
-
|
| 181 |
-
### Secondary Factors (Medium Weight)
|
| 182 |
-
|
| 183 |
-
5. **Modality Specificity**: Tool designed for your imaging modality
|
| 184 |
-
6. **Feature Coverage**: Breadth of capabilities
|
| 185 |
-
7. **Stated Requirements**: Meets any specific requirements you mentioned
|
| 186 |
-
8. **Quality Indicators**: Stars, citations, community adoption
|
| 187 |
-
|
| 188 |
-
### Tertiary Factors (Low Weight)
|
| 189 |
-
|
| 190 |
-
9. **License**: Open-source vs. proprietary
|
| 191 |
-
10. **Recency**: Recently updated tools
|
| 192 |
-
11. **Documentation Quality**: Demo availability, examples
|
| 193 |
-
12. **Popularity**: Community usage and validation
|
| 194 |
-
|
| 195 |
-
## Interpreting Results
|
| 196 |
-
|
| 197 |
-
### High-Scoring Recommendations
|
| 198 |
-
|
| 199 |
-
When you see scores above 85%:
|
| 200 |
-
|
| 201 |
-
✅ **Strong match** - Tool is designed for this task
|
| 202 |
-
✅ **Format compatible** - Handles your file type
|
| 203 |
-
✅ **Proven capability** - Demonstrated results in this domain
|
| 204 |
-
|
| 205 |
-
**Action**: These are your best options. Try the top recommendation first.
|
| 206 |
-
|
| 207 |
-
### Medium-Scoring Recommendations
|
| 208 |
-
|
| 209 |
-
Scores 60-85%:
|
| 210 |
-
|
| 211 |
-
⚠️ **Good match** - Suitable but may need adaptation
|
| 212 |
-
⚠️ **Possible format conversion** - Might require preprocessing
|
| 213 |
-
⚠️ **Partial capability** - Covers some but not all requirements
|
| 214 |
-
|
| 215 |
-
**Action**: Worth trying, especially if top choices don't work. Read explanations carefully.
|
| 216 |
-
|
| 217 |
-
### Low-Scoring Recommendations
|
| 218 |
-
|
| 219 |
-
Scores below 60%:
|
| 220 |
-
|
| 221 |
-
❌ **Weak match** - Limited alignment with task
|
| 222 |
-
❌ **Format issues** - May not support your format
|
| 223 |
-
❌ **Alternative approach** - Different methodology
|
| 224 |
-
|
| 225 |
-
**Action**: Consider as fallback or for exploring alternative approaches.
|
| 226 |
-
|
| 227 |
-
## Why Rankings Change
|
| 228 |
-
|
| 229 |
-
Rankings depend on your specific context:
|
| 230 |
-
|
| 231 |
-
### Same Tool, Different Queries
|
| 232 |
-
|
| 233 |
-
"Segment lungs" vs "Detect tumors":
|
| 234 |
-
- Different tools excel at each task
|
| 235 |
-
- Rankings change based on task specificity
|
| 236 |
-
|
| 237 |
-
### Same Task, Different Formats
|
| 238 |
-
|
| 239 |
-
DICOM input vs PNG input:
|
| 240 |
-
- DICOM-compatible tools rank higher for DICOM
|
| 241 |
-
- General tools rank higher for standard images
|
| 242 |
-
|
| 243 |
-
### Same Task, Different Images
|
| 244 |
-
|
| 245 |
-
CT scan vs X-ray:
|
| 246 |
-
- Modality-specific tools get boosted
|
| 247 |
-
- Visual content influences selection
|
| 248 |
-
|
| 249 |
-
## Common Patterns
|
| 250 |
-
|
| 251 |
-
### All High Scores
|
| 252 |
-
Most recommendations >80%:
|
| 253 |
-
|
| 254 |
-
- **Good news!** Multiple excellent options
|
| 255 |
-
- **Strategy**: Try top recommendation, then compare
|
| 256 |
-
|
| 257 |
-
### Mixed Scores
|
| 258 |
-
Wide range (e.g., 90%, 65%, 45%):
|
| 259 |
-
|
| 260 |
-
- **Top choice clear** - Focus on highest scorer
|
| 261 |
-
- **Strategy**: Try #1, fall back to #2 if needed
|
| 262 |
-
|
| 263 |
-
### All Low Scores
|
| 264 |
-
All recommendations <60%:
|
| 265 |
-
|
| 266 |
-
- **Limited options** - Task may be specialized
|
| 267 |
-
- **Strategy**: Try anyway, or rephrase query
|
| 268 |
-
- **Alternative**: Ask for suggestions
|
| 269 |
-
|
| 270 |
-
## Acting on Recommendations
|
| 271 |
-
|
| 272 |
-
### First Time with a Tool
|
| 273 |
-
|
| 274 |
-
1. **Read the explanation** - Understand why it was recommended
|
| 275 |
-
2. **Check format compatibility** - Verify it supports your format
|
| 276 |
-
3. **Click demo link** - See it in action
|
| 277 |
-
4. **Try on your data** - Run if agent offers
|
| 278 |
-
|
| 279 |
-
### Comparing Tools
|
| 280 |
-
|
| 281 |
-
When choosing between similar scores:
|
| 282 |
-
|
| 283 |
-
- **Check licenses** if redistribution matters
|
| 284 |
-
- **Compare formats** - prefer exact format match
|
| 285 |
-
- **Review tags** - match technology preferences
|
| 286 |
-
- **Demo availability** - easier to try
|
| 287 |
-
|
| 288 |
-
### When Results Don't Match
|
| 289 |
-
|
| 290 |
-
If recommendations seem wrong:
|
| 291 |
-
|
| 292 |
-
1. **Provide more context**: "I need 3D volume support"
|
| 293 |
-
2. **Mention specific requirements**: "Must work with DICOM"
|
| 294 |
-
3. **Exclude irrelevant tools**: `[EXCLUDE:toolname]`
|
| 295 |
-
4. **Request alternatives**: "Can you search differently?"
|
| 296 |
-
|
| 297 |
-
## Explanation Analysis
|
| 298 |
-
|
| 299 |
-
Read explanations to understand:
|
| 300 |
-
|
| 301 |
-
### Positive Indicators
|
| 302 |
-
|
| 303 |
-
Look for phrases like:
|
| 304 |
-
- "Specifically designed for..."
|
| 305 |
-
- "Supports your exact format..."
|
| 306 |
-
- "Demonstrated accuracy on..."
|
| 307 |
-
- "Active development and maintained"
|
| 308 |
-
|
| 309 |
-
### Caveats
|
| 310 |
-
|
| 311 |
-
Watch for:
|
| 312 |
-
- "May require preprocessing..."
|
| 313 |
-
- "Limited to 2D images..."
|
| 314 |
-
- "Experimental feature..."
|
| 315 |
-
- "Requires specific environment..."
|
| 316 |
-
|
| 317 |
-
### Requirements
|
| 318 |
-
|
| 319 |
-
Note when explanations mention:
|
| 320 |
-
- "Needs GPU for inference"
|
| 321 |
-
- "Requires Python 3.8+"
|
| 322 |
-
- "DICOM headers must include..."
|
| 323 |
-
- "Minimum image resolution..."
|
| 324 |
-
|
| 325 |
-
## Next Steps
|
| 326 |
-
|
| 327 |
-
- Learn about [Running Demos](running-demos.md)
|
| 328 |
-
- Explore [Advanced Features](advanced-features.md)
|
| 329 |
-
- Understand the [Architecture Overview](../architecture/overview.md)
|
|
|
|
| 1 |
+
# Understanding Recommendations
|
| 2 |
+
|
| 3 |
+
The AI Imaging Agent uses a sophisticated two-stage pipeline to provide ranked tool recommendations. This guide explains how recommendations are generated and how to interpret them.
|
| 4 |
+
|
| 5 |
+
## How Recommendations Work
|
| 6 |
+
|
| 7 |
+
### Two-Stage Pipeline
|
| 8 |
+
|
| 9 |
+
```mermaid
|
| 10 |
+
graph TD
|
| 11 |
+
A[User Input: Image + Query] --> B[Stage 1: Retrieval]
|
| 12 |
+
B --> C[Candidate Tools]
|
| 13 |
+
C --> D[Stage 2: Agent Selection]
|
| 14 |
+
D --> E[Ranked Recommendations]
|
| 15 |
+
```
|
| 16 |
+
|
| 17 |
+
#### Stage 1: Retrieval (Fast Text Search)
|
| 18 |
+
|
| 19 |
+
The retrieval stage quickly narrows down candidates:
|
| 20 |
+
|
| 21 |
+
1. **Query Enhancement**: Your query is enriched with format tokens
|
| 22 |
+
```
|
| 23 |
+
Original: "segment lungs"
|
| 24 |
+
Enhanced: "segment lungs format:DICOM format:CT format:3D"
|
| 25 |
+
```
|
| 26 |
+
|
| 27 |
+
2. **Embedding Search**: BGE-M3 model converts query to vector
|
| 28 |
+
3. **FAISS Vector Search**: Finds semantically similar tools
|
| 29 |
+
4. **CrossEncoder Reranking**: Re-scores candidates for better relevance
|
| 30 |
+
5. **Result**: Top-K candidates (default: 8)
|
| 31 |
+
|
| 32 |
+
**No LLM calls** - this stage is fast and deterministic.
|
| 33 |
+
|
| 34 |
+
#### Stage 2: Agent Selection (VLM-Powered)
|
| 35 |
+
|
| 36 |
+
The agent analyzes candidates with full context:
|
| 37 |
+
|
| 38 |
+
1. **Vision Analysis (only for VLM)**: GPT-4o/4o-mini (or your custom model) sees your image preview
|
| 39 |
+
2. **Context Integration**: Considers query + metadata + candidates
|
| 40 |
+
3. **Reasoning**: Explains why each tool matches
|
| 41 |
+
4. **Scoring**: Assigns accuracy scores (0-100%)
|
| 42 |
+
5. **Ranking**: Orders tools by relevance
|
| 43 |
+
|
| 44 |
+
**Single VLM call** - comprehensive analysis with explanations.
|
| 45 |
+
|
| 46 |
+
## Recommendation Format
|
| 47 |
+
|
| 48 |
+
Each recommendation includes several components:
|
| 49 |
+
|
| 50 |
+
### Header Information
|
| 51 |
+
|
| 52 |
+
#### Rank Number
|
| 53 |
+
Position in the ranked list (1 = best match).
|
| 54 |
+
|
| 55 |
+
```
|
| 56 |
+
1️⃣ TotalSegmentator
|
| 57 |
+
2️⃣ MedSAM
|
| 58 |
+
3️⃣ nnU-Net
|
| 59 |
+
```
|
| 60 |
+
|
| 61 |
+
#### Tool Name
|
| 62 |
+
The software or tool identifier, typically matching:
|
| 63 |
+
- GitHub repository name
|
| 64 |
+
- Published tool name
|
| 65 |
+
- Common community name
|
| 66 |
+
|
| 67 |
+
#### Accuracy Score
|
| 68 |
+
Confidence level from 0-100%:
|
| 69 |
+
|
| 70 |
+
- **90-100%**: Excellent match, highly confident
|
| 71 |
+
- **70-89%**: Good match, suitable for task
|
| 72 |
+
- **50-69%**: Moderate match, may need adaptation
|
| 73 |
+
- **Below 50%**: Weak match, alternative approach
|
| 74 |
+
|
| 75 |
+
!!! note "Score Interpretation"
|
| 76 |
+
Scores reflect match quality for **your specific task and image**, not overall tool quality.
|
| 77 |
+
|
| 78 |
+
### Body Content
|
| 79 |
+
|
| 80 |
+
#### Description
|
| 81 |
+
Brief explanation of what the tool does:
|
| 82 |
+
|
| 83 |
+
```
|
| 84 |
+
TotalSegmentator: Automated multi-organ segmentation for CT scans supporting 104 anatomical structures.
|
| 85 |
+
```
|
| 86 |
+
|
| 87 |
+
#### Explanation
|
| 88 |
+
Why this tool matches your request:
|
| 89 |
+
|
| 90 |
+
```
|
| 91 |
+
Explanation: TotalSegmentator is specifically designed for whole-body CT segmentation including lung structures. It supports DICOM input and provides automated, accurate lung segmentation without manual intervention.
|
| 92 |
+
```
|
| 93 |
+
|
| 94 |
+
Key points in explanations:
|
| 95 |
+
|
| 96 |
+
- **Task Alignment**: How well it matches your goal
|
| 97 |
+
- **Format Compatibility**: Support for your file format
|
| 98 |
+
- **Relevant Features**: Specific capabilities that help
|
| 99 |
+
- **Known Limitations**: Caveats or requirements
|
| 100 |
+
|
| 101 |
+
#### Demo Link
|
| 102 |
+
Direct link to a runnable example:
|
| 103 |
+
|
| 104 |
+
```
|
| 105 |
+
🚀 Demo: https://huggingface.co/spaces/example/totalsegmentator
|
| 106 |
+
```
|
| 107 |
+
|
| 108 |
+
Types of demos:
|
| 109 |
+
|
| 110 |
+
- **HuggingFace Spaces**: Interactive Gradio/Streamlit apps
|
| 111 |
+
- **Colab Notebooks**: Jupyter notebooks you can run
|
| 112 |
+
- **Web Demos**: Hosted web interfaces
|
| 113 |
+
- **Documentation**: GitHub README with examples
|
| 114 |
+
|
| 115 |
+
### Metadata Footer
|
| 116 |
+
|
| 117 |
+
Technical details about the tool:
|
| 118 |
+
|
| 119 |
+
#### Modality Support
|
| 120 |
+
Medical imaging modalities the tool works with:
|
| 121 |
+
|
| 122 |
+
```
|
| 123 |
+
Modalities: CT, MRI, X-ray
|
| 124 |
+
```
|
| 125 |
+
|
| 126 |
+
Common modalities:
|
| 127 |
+
|
| 128 |
+
- **CT**: Computed Tomography
|
| 129 |
+
- **MRI**: Magnetic Resonance Imaging
|
| 130 |
+
- **XR**: X-ray radiography
|
| 131 |
+
- **US**: Ultrasound
|
| 132 |
+
- **PET**: Positron Emission Tomography
|
| 133 |
+
- **OCT**: Optical Coherence Tomography
|
| 134 |
+
- **Microscopy**: Various microscopy types
|
| 135 |
+
|
| 136 |
+
#### Dimension Support
|
| 137 |
+
Image/volume dimensions supported:
|
| 138 |
+
|
| 139 |
+
```
|
| 140 |
+
Dimensions: 2D, 3D
|
| 141 |
+
```
|
| 142 |
+
|
| 143 |
+
- **2D**: Single slice images
|
| 144 |
+
- **3D**: Volumetric data
|
| 145 |
+
- **4D**: Time-series volumes
|
| 146 |
+
|
| 147 |
+
#### Format Support
|
| 148 |
+
File formats the tool can process:
|
| 149 |
+
|
| 150 |
+
```
|
| 151 |
+
Formats: DICOM, NIfTI, PNG, JPEG
|
| 152 |
+
```
|
| 153 |
+
|
| 154 |
+
!!! tip "Format Importance"
|
| 155 |
+
Tools that support your **exact format** are prioritized in ranking.
|
| 156 |
+
|
| 157 |
+
#### Tags
|
| 158 |
+
Categorization and keywords:
|
| 159 |
+
|
| 160 |
+
```
|
| 161 |
+
Tags: segmentation, medical-imaging, deep-learning, pytorch
|
| 162 |
+
```
|
| 163 |
+
|
| 164 |
+
Used for:
|
| 165 |
+
- Task categorization
|
| 166 |
+
- Technology stack
|
| 167 |
+
- Domain specificity
|
| 168 |
+
- Feature indicators
|
| 169 |
+
|
| 170 |
+
## Scoring Factors
|
| 171 |
+
|
| 172 |
+
The agent considers multiple factors when scoring:
|
| 173 |
+
|
| 174 |
+
### Primary Factors (High Weight)
|
| 175 |
+
|
| 176 |
+
1. **Task Match**: How well the tool's purpose aligns with your request
|
| 177 |
+
2. **Format Compatibility**: Support for your input format
|
| 178 |
+
3. **Image Content**: Visual analysis of what's in your image
|
| 179 |
+
4. **Dimension Match**: 2D tool for 2D images, 3D for volumes
|
| 180 |
+
|
| 181 |
+
### Secondary Factors (Medium Weight)
|
| 182 |
+
|
| 183 |
+
5. **Modality Specificity**: Tool designed for your imaging modality
|
| 184 |
+
6. **Feature Coverage**: Breadth of capabilities
|
| 185 |
+
7. **Stated Requirements**: Meets any specific requirements you mentioned
|
| 186 |
+
8. **Quality Indicators**: Stars, citations, community adoption
|
| 187 |
+
|
| 188 |
+
### Tertiary Factors (Low Weight)
|
| 189 |
+
|
| 190 |
+
9. **License**: Open-source vs. proprietary
|
| 191 |
+
10. **Recency**: Recently updated tools
|
| 192 |
+
11. **Documentation Quality**: Demo availability, examples
|
| 193 |
+
12. **Popularity**: Community usage and validation
|
| 194 |
+
|
| 195 |
+
## Interpreting Results
|
| 196 |
+
|
| 197 |
+
### High-Scoring Recommendations
|
| 198 |
+
|
| 199 |
+
When you see scores above 85%:
|
| 200 |
+
|
| 201 |
+
✅ **Strong match** - Tool is designed for this task
|
| 202 |
+
✅ **Format compatible** - Handles your file type
|
| 203 |
+
✅ **Proven capability** - Demonstrated results in this domain
|
| 204 |
+
|
| 205 |
+
**Action**: These are your best options. Try the top recommendation first.
|
| 206 |
+
|
| 207 |
+
### Medium-Scoring Recommendations
|
| 208 |
+
|
| 209 |
+
Scores 60-85%:
|
| 210 |
+
|
| 211 |
+
⚠️ **Good match** - Suitable but may need adaptation
|
| 212 |
+
⚠️ **Possible format conversion** - Might require preprocessing
|
| 213 |
+
⚠️ **Partial capability** - Covers some but not all requirements
|
| 214 |
+
|
| 215 |
+
**Action**: Worth trying, especially if top choices don't work. Read explanations carefully.
|
| 216 |
+
|
| 217 |
+
### Low-Scoring Recommendations
|
| 218 |
+
|
| 219 |
+
Scores below 60%:
|
| 220 |
+
|
| 221 |
+
❌ **Weak match** - Limited alignment with task
|
| 222 |
+
❌ **Format issues** - May not support your format
|
| 223 |
+
❌ **Alternative approach** - Different methodology
|
| 224 |
+
|
| 225 |
+
**Action**: Consider as fallback or for exploring alternative approaches.
|
| 226 |
+
|
| 227 |
+
## Why Rankings Change
|
| 228 |
+
|
| 229 |
+
Rankings depend on your specific context:
|
| 230 |
+
|
| 231 |
+
### Same Tool, Different Queries
|
| 232 |
+
|
| 233 |
+
"Segment lungs" vs "Detect tumors":
|
| 234 |
+
- Different tools excel at each task
|
| 235 |
+
- Rankings change based on task specificity
|
| 236 |
+
|
| 237 |
+
### Same Task, Different Formats
|
| 238 |
+
|
| 239 |
+
DICOM input vs PNG input:
|
| 240 |
+
- DICOM-compatible tools rank higher for DICOM
|
| 241 |
+
- General tools rank higher for standard images
|
| 242 |
+
|
| 243 |
+
### Same Task, Different Images
|
| 244 |
+
|
| 245 |
+
CT scan vs X-ray:
|
| 246 |
+
- Modality-specific tools get boosted
|
| 247 |
+
- Visual content influences selection
|
| 248 |
+
|
| 249 |
+
## Common Patterns
|
| 250 |
+
|
| 251 |
+
### All High Scores
|
| 252 |
+
Most recommendations >80%:
|
| 253 |
+
|
| 254 |
+
- **Good news!** Multiple excellent options
|
| 255 |
+
- **Strategy**: Try top recommendation, then compare
|
| 256 |
+
|
| 257 |
+
### Mixed Scores
|
| 258 |
+
Wide range (e.g., 90%, 65%, 45%):
|
| 259 |
+
|
| 260 |
+
- **Top choice clear** - Focus on highest scorer
|
| 261 |
+
- **Strategy**: Try #1, fall back to #2 if needed
|
| 262 |
+
|
| 263 |
+
### All Low Scores
|
| 264 |
+
All recommendations <60%:
|
| 265 |
+
|
| 266 |
+
- **Limited options** - Task may be specialized
|
| 267 |
+
- **Strategy**: Try anyway, or rephrase query
|
| 268 |
+
- **Alternative**: Ask for suggestions
|
| 269 |
+
|
| 270 |
+
## Acting on Recommendations
|
| 271 |
+
|
| 272 |
+
### First Time with a Tool
|
| 273 |
+
|
| 274 |
+
1. **Read the explanation** - Understand why it was recommended
|
| 275 |
+
2. **Check format compatibility** - Verify it supports your format
|
| 276 |
+
3. **Click demo link** - See it in action
|
| 277 |
+
4. **Try on your data** - Run if agent offers
|
| 278 |
+
|
| 279 |
+
### Comparing Tools
|
| 280 |
+
|
| 281 |
+
When choosing between similar scores:
|
| 282 |
+
|
| 283 |
+
- **Check licenses** if redistribution matters
|
| 284 |
+
- **Compare formats** - prefer exact format match
|
| 285 |
+
- **Review tags** - match technology preferences
|
| 286 |
+
- **Demo availability** - easier to try
|
| 287 |
+
|
| 288 |
+
### When Results Don't Match
|
| 289 |
+
|
| 290 |
+
If recommendations seem wrong:
|
| 291 |
+
|
| 292 |
+
1. **Provide more context**: "I need 3D volume support"
|
| 293 |
+
2. **Mention specific requirements**: "Must work with DICOM"
|
| 294 |
+
3. **Exclude irrelevant tools**: `[EXCLUDE:toolname]`
|
| 295 |
+
4. **Request alternatives**: "Can you search differently?"
|
| 296 |
+
|
| 297 |
+
## Explanation Analysis
|
| 298 |
+
|
| 299 |
+
Read explanations to understand:
|
| 300 |
+
|
| 301 |
+
### Positive Indicators
|
| 302 |
+
|
| 303 |
+
Look for phrases like:
|
| 304 |
+
- "Specifically designed for..."
|
| 305 |
+
- "Supports your exact format..."
|
| 306 |
+
- "Demonstrated accuracy on..."
|
| 307 |
+
- "Active development and maintained"
|
| 308 |
+
|
| 309 |
+
### Caveats
|
| 310 |
+
|
| 311 |
+
Watch for:
|
| 312 |
+
- "May require preprocessing..."
|
| 313 |
+
- "Limited to 2D images..."
|
| 314 |
+
- "Experimental feature..."
|
| 315 |
+
- "Requires specific environment..."
|
| 316 |
+
|
| 317 |
+
### Requirements
|
| 318 |
+
|
| 319 |
+
Note when explanations mention:
|
| 320 |
+
- "Needs GPU for inference"
|
| 321 |
+
- "Requires Python 3.8+"
|
| 322 |
+
- "DICOM headers must include..."
|
| 323 |
+
- "Minimum image resolution..."
|
| 324 |
+
|
| 325 |
+
## Next Steps
|
| 326 |
+
|
| 327 |
+
- Learn about [Running Demos](running-demos.md)
|
| 328 |
+
- Explore [Advanced Features](advanced-features.md)
|
| 329 |
+
- Understand the [Architecture Overview](../architecture/overview.md)
|
docs/user-guide/running-demos.md
CHANGED
|
@@ -1,372 +1,372 @@
|
|
| 1 |
-
# Running Demos (This area is still under construction..)
|
| 2 |
-
|
| 3 |
-
The AI Imaging Agent can execute tool demos directly on your uploaded images. This guide explains how demo execution works and how to use it effectively.
|
| 4 |
-
|
| 5 |
-
## What Are Demos?
|
| 6 |
-
|
| 7 |
-
Demos are **runnable examples** of imaging tools, typically hosted as:
|
| 8 |
-
|
| 9 |
-
- **HuggingFace Spaces**: Interactive Gradio or Streamlit applications
|
| 10 |
-
- **Jupyter Notebooks**: Google Colab or similar notebook environments
|
| 11 |
-
- **Web Applications**: Hosted web interfaces
|
| 12 |
-
- **GitHub Examples**: Code repositories with example scripts
|
| 13 |
-
|
| 14 |
-
## Demo Execution Flow
|
| 15 |
-
|
| 16 |
-
### 1. Agent Offers to Run Demo
|
| 17 |
-
|
| 18 |
-
After providing recommendations, the agent may offer:
|
| 19 |
-
|
| 20 |
-
```
|
| 21 |
-
Agent: Would you like me to run the demo with your image?
|
| 22 |
-
```
|
| 23 |
-
|
| 24 |
-
This appears when:
|
| 25 |
-
|
| 26 |
-
- Tool has a compatible Gradio Space demo
|
| 27 |
-
- Your image format is compatible
|
| 28 |
-
- Demo's API is accessible
|
| 29 |
-
|
| 30 |
-
### 2. You Confirm
|
| 31 |
-
|
| 32 |
-
Respond with affirmative language:
|
| 33 |
-
|
| 34 |
-
- "yes"
|
| 35 |
-
- "sure"
|
| 36 |
-
- "ok"
|
| 37 |
-
- "please"
|
| 38 |
-
- "go ahead"
|
| 39 |
-
- "run it"
|
| 40 |
-
|
| 41 |
-
The agent detects these patterns and proceeds.
|
| 42 |
-
|
| 43 |
-
### 3. Execution Happens
|
| 44 |
-
|
| 45 |
-
The agent:
|
| 46 |
-
|
| 47 |
-
1. Uploads your image to the demo space
|
| 48 |
-
2. Configures any required parameters
|
| 49 |
-
3. Triggers execution
|
| 50 |
-
4. Monitors progress
|
| 51 |
-
5. Retrieves results
|
| 52 |
-
|
| 53 |
-
### 4. Results Display
|
| 54 |
-
|
| 55 |
-
You receive:
|
| 56 |
-
|
| 57 |
-
- **Success message** with output
|
| 58 |
-
- **Result images** or files
|
| 59 |
-
- **Execution trace** showing what happened
|
| 60 |
-
|
| 61 |
-
## Demo Types
|
| 62 |
-
|
| 63 |
-
### Gradio Space Demos
|
| 64 |
-
|
| 65 |
-
**Best supported** - Direct API integration:
|
| 66 |
-
|
| 67 |
-
```
|
| 68 |
-
🚀 Demo: https://huggingface.co/spaces/username/toolname
|
| 69 |
-
```
|
| 70 |
-
|
| 71 |
-
**Features**:
|
| 72 |
-
|
| 73 |
-
- ✅ Automatic execution
|
| 74 |
-
- ✅ Progress monitoring
|
| 75 |
-
- ✅ Result retrieval
|
| 76 |
-
- ✅ Error handling
|
| 77 |
-
|
| 78 |
-
**Example**:
|
| 79 |
-
```
|
| 80 |
-
Running TotalSegmentator on your CT scan...
|
| 81 |
-
✓ Image uploaded
|
| 82 |
-
✓ Processing started
|
| 83 |
-
✓ Segmentation complete
|
| 84 |
-
✓ Results downloaded
|
| 85 |
-
```
|
| 86 |
-
|
| 87 |
-
### Notebook Demos
|
| 88 |
-
|
| 89 |
-
**Partially supported** - Links provided for manual execution:
|
| 90 |
-
|
| 91 |
-
```
|
| 92 |
-
📓 Notebook: https://colab.research.google.com/...
|
| 93 |
-
```
|
| 94 |
-
|
| 95 |
-
**Process**:
|
| 96 |
-
|
| 97 |
-
1. Click the notebook link
|
| 98 |
-
2. Open in Google Colab
|
| 99 |
-
3. Upload your image to the notebook
|
| 100 |
-
4. Run cells sequentially
|
| 101 |
-
5. Download results
|
| 102 |
-
|
| 103 |
-
### Web Application Demos
|
| 104 |
-
|
| 105 |
-
**Manual execution** - Opens in browser:
|
| 106 |
-
|
| 107 |
-
```
|
| 108 |
-
🌐 Web Demo: https://example.com/tool
|
| 109 |
-
```
|
| 110 |
-
|
| 111 |
-
**Process**:
|
| 112 |
-
|
| 113 |
-
1. Click the demo link
|
| 114 |
-
2. Web app opens in new tab
|
| 115 |
-
3. Upload your image via the web UI
|
| 116 |
-
4. Configure settings
|
| 117 |
-
5. Run and download results
|
| 118 |
-
|
| 119 |
-
### GitHub Repository Examples
|
| 120 |
-
|
| 121 |
-
**Code-based** - Requires local setup:
|
| 122 |
-
|
| 123 |
-
```
|
| 124 |
-
💻 Repository: https://github.com/user/repo
|
| 125 |
-
```
|
| 126 |
-
|
| 127 |
-
**Process**:
|
| 128 |
-
|
| 129 |
-
1. Clone the repository
|
| 130 |
-
2. Install dependencies
|
| 131 |
-
3. Run example scripts
|
| 132 |
-
4. Adapt for your data
|
| 133 |
-
|
| 134 |
-
## Execution Traces
|
| 135 |
-
|
| 136 |
-
When demos run, you see detailed traces:
|
| 137 |
-
|
| 138 |
-
```html
|
| 139 |
-
<details>
|
| 140 |
-
<summary>🔧 Tool Execution Trace</summary>
|
| 141 |
-
|
| 142 |
-
Step 1: Uploading image to Gradio Space
|
| 143 |
-
✓ Connected to space: username/toolname
|
| 144 |
-
✓ Image uploaded: 2.3 MB
|
| 145 |
-
|
| 146 |
-
Step 2: Configuring parameters
|
| 147 |
-
✓ Set task: lung-segmentation
|
| 148 |
-
✓ Set format: DICOM
|
| 149 |
-
|
| 150 |
-
Step 3: Running inference
|
| 151 |
-
⏳ Processing... (estimated 30s)
|
| 152 |
-
✓ Completed in 28s
|
| 153 |
-
|
| 154 |
-
Step 4: Retrieving results
|
| 155 |
-
✓ Downloaded segmentation mask: 1.1 MB
|
| 156 |
-
✓ Downloaded visualization: 0.8 MB
|
| 157 |
-
|
| 158 |
-
Status: ✅ Success
|
| 159 |
-
</details>
|
| 160 |
-
```
|
| 161 |
-
|
| 162 |
-
Click to expand and see full details.
|
| 163 |
-
|
| 164 |
-
## Supported Gradio Spaces
|
| 165 |
-
|
| 166 |
-
### Auto-Detected Parameters
|
| 167 |
-
|
| 168 |
-
The agent automatically configures:
|
| 169 |
-
|
| 170 |
-
#### Image Input
|
| 171 |
-
- Detects image input component(s)
|
| 172 |
-
- Uploads your file
|
| 173 |
-
- Converts format if needed
|
| 174 |
-
|
| 175 |
-
#### Task Selection
|
| 176 |
-
Common task parameters:
|
| 177 |
-
|
| 178 |
-
- **Task dropdown**: Matches your query to task option
|
| 179 |
-
- **Model selection**: Chooses appropriate model
|
| 180 |
-
- **Mode**: Inference, predict, analyze, etc.
|
| 181 |
-
|
| 182 |
-
#### Format Options
|
| 183 |
-
- **Input format**: DICOM, NIfTI, PNG, etc.
|
| 184 |
-
- **Output format**: Segmentation mask, visualization, etc.
|
| 185 |
-
- **Data type**: 2D, 3D, specific modality
|
| 186 |
-
|
| 187 |
-
### Manual Parameters
|
| 188 |
-
|
| 189 |
-
Some demos require manual interaction:
|
| 190 |
-
|
| 191 |
-
```
|
| 192 |
-
Agent: This demo has additional parameters. Please visit the link to configure:
|
| 193 |
-
- Segmentation threshold: 0.5
|
| 194 |
-
- Post-processing: enabled
|
| 195 |
-
```
|
| 196 |
-
|
| 197 |
-
## Demo Execution Best Practices
|
| 198 |
-
|
| 199 |
-
!!! tip "Check Compatibility First"
|
| 200 |
-
Verify the tool supports your file format in the recommendation metadata.
|
| 201 |
-
|
| 202 |
-
!!! tip "Use Standard Formats"
|
| 203 |
-
Demos work best with standard formats (PNG, JPEG for general; DICOM, NIfTI for medical).
|
| 204 |
-
|
| 205 |
-
!!! tip "Be Patient"
|
| 206 |
-
Some demos take time, especially for:
|
| 207 |
-
- Large images or volumes
|
| 208 |
-
- Deep learning models
|
| 209 |
-
- 3D processing
|
| 210 |
-
|
| 211 |
-
Typical times: 10 seconds to 2 minutes.
|
| 212 |
-
|
| 213 |
-
!!! tip "Save Results Immediately"
|
| 214 |
-
Download result files promptly - they may not persist after closing the browser.
|
| 215 |
-
|
| 216 |
-
!!! warning "Rate Limits"
|
| 217 |
-
Public Gradio Spaces may have rate limits or queue systems during high usage.
|
| 218 |
-
|
| 219 |
-
## Troubleshooting Demo Execution
|
| 220 |
-
|
| 221 |
-
### Demo Fails to Run
|
| 222 |
-
|
| 223 |
-
**Error**: Connection timeout or failed upload
|
| 224 |
-
|
| 225 |
-
**Solutions**:
|
| 226 |
-
|
| 227 |
-
- Check internet connection
|
| 228 |
-
- Try again (server may be busy)
|
| 229 |
-
- Visit demo link manually
|
| 230 |
-
- Try alternative recommendation
|
| 231 |
-
|
| 232 |
-
### Wrong Results
|
| 233 |
-
|
| 234 |
-
**Error**: Output doesn't match expectations
|
| 235 |
-
|
| 236 |
-
**Solutions**:
|
| 237 |
-
|
| 238 |
-
- Check if correct parameters were used
|
| 239 |
-
- Verify image uploaded correctly
|
| 240 |
-
- Try adjusting task settings manually
|
| 241 |
-
- Compare with demo's example images
|
| 242 |
-
|
| 243 |
-
### Incompatible Format
|
| 244 |
-
|
| 245 |
-
**Error**: "Format not supported"
|
| 246 |
-
|
| 247 |
-
**Solutions**:
|
| 248 |
-
|
| 249 |
-
- Convert image to supported format
|
| 250 |
-
- Use tool that accepts your format
|
| 251 |
-
- Try alternative recommendation
|
| 252 |
-
|
| 253 |
-
### Demo Link Broken
|
| 254 |
-
|
| 255 |
-
**Error**: 404 or space not found
|
| 256 |
-
|
| 257 |
-
**Solutions**:
|
| 258 |
-
|
| 259 |
-
- Space may be temporarily down
|
| 260 |
-
- Check GitHub repo for alternative demo
|
| 261 |
-
- Try different tool recommendation
|
| 262 |
-
- Report broken link
|
| 263 |
-
|
| 264 |
-
## Manual Demo Execution
|
| 265 |
-
|
| 266 |
-
If automatic execution isn't available:
|
| 267 |
-
|
| 268 |
-
### For Gradio Spaces
|
| 269 |
-
|
| 270 |
-
1. Click the demo link
|
| 271 |
-
2. The space opens in your browser
|
| 272 |
-
3. Upload your image via the UI
|
| 273 |
-
4. Select appropriate options
|
| 274 |
-
5. Click "Submit" or "Run"
|
| 275 |
-
6. Download results
|
| 276 |
-
|
| 277 |
-
### For Colab Notebooks
|
| 278 |
-
|
| 279 |
-
1. Click the notebook link
|
| 280 |
-
2. Open in Google Colab
|
| 281 |
-
3. Run setup cells (install dependencies)
|
| 282 |
-
4. Upload your image when prompted:
|
| 283 |
-
```python
|
| 284 |
-
from google.colab import files
|
| 285 |
-
uploaded = files.upload()
|
| 286 |
-
```
|
| 287 |
-
5. Run processing cells
|
| 288 |
-
6. Download results:
|
| 289 |
-
```python
|
| 290 |
-
files.download('result.png')
|
| 291 |
-
```
|
| 292 |
-
|
| 293 |
-
### For Local Execution
|
| 294 |
-
|
| 295 |
-
1. Clone the repository:
|
| 296 |
-
```bash
|
| 297 |
-
git clone https://github.com/user/repo
|
| 298 |
-
cd repo
|
| 299 |
-
```
|
| 300 |
-
|
| 301 |
-
2. Install dependencies:
|
| 302 |
-
```bash
|
| 303 |
-
pip install -r requirements.txt
|
| 304 |
-
```
|
| 305 |
-
|
| 306 |
-
3. Run example script:
|
| 307 |
-
```bash
|
| 308 |
-
python run_demo.py --input your_image.png --output result.png
|
| 309 |
-
```
|
| 310 |
-
|
| 311 |
-
4. Check output directory for results
|
| 312 |
-
|
| 313 |
-
## Understanding Results
|
| 314 |
-
|
| 315 |
-
### Segmentation Results
|
| 316 |
-
|
| 317 |
-
Typically includes:
|
| 318 |
-
|
| 319 |
-
- **Segmentation mask**: Binary or multi-class mask
|
| 320 |
-
- **Overlay visualization**: Mask overlaid on original image
|
| 321 |
-
- **Statistics**: Volume, area, counts
|
| 322 |
-
|
| 323 |
-
### Detection Results
|
| 324 |
-
|
| 325 |
-
Usually provides:
|
| 326 |
-
|
| 327 |
-
- **Bounding boxes**: Coordinates of detected objects
|
| 328 |
-
- **Annotated image**: Visual with boxes/labels
|
| 329 |
-
- **Confidence scores**: Detection confidence
|
| 330 |
-
|
| 331 |
-
### Registration Results
|
| 332 |
-
|
| 333 |
-
Common outputs:
|
| 334 |
-
|
| 335 |
-
- **Transformed image**: Registered/aligned image
|
| 336 |
-
- **Transformation matrix**: Spatial transform parameters
|
| 337 |
-
- **Quality metrics**: Similarity scores
|
| 338 |
-
|
| 339 |
-
### Classification Results
|
| 340 |
-
|
| 341 |
-
Typical outputs:
|
| 342 |
-
|
| 343 |
-
- **Class labels**: Predicted categories
|
| 344 |
-
- **Probabilities**: Confidence per class
|
| 345 |
-
- **Visualization**: Class activation maps
|
| 346 |
-
|
| 347 |
-
## Demo Feedback
|
| 348 |
-
|
| 349 |
-
Help improve the agent by reporting:
|
| 350 |
-
|
| 351 |
-
### Successful Demos
|
| 352 |
-
When demos work well, this validates:
|
| 353 |
-
|
| 354 |
-
- Tool compatibility
|
| 355 |
-
- Parameter auto-configuration
|
| 356 |
-
- Format handling
|
| 357 |
-
|
| 358 |
-
### Issues
|
| 359 |
-
Report when:
|
| 360 |
-
|
| 361 |
-
- Demo fails unexpectedly
|
| 362 |
-
- Results are incorrect
|
| 363 |
-
- Parameters were misconfigured
|
| 364 |
-
- Format conversion was wrong
|
| 365 |
-
|
| 366 |
-
Feedback helps refine the agent's demo execution capabilities.
|
| 367 |
-
|
| 368 |
-
## Next Steps
|
| 369 |
-
|
| 370 |
-
- Explore [Advanced Features](advanced-features.md)
|
| 371 |
-
- Learn about the [Architecture](../architecture/overview.md)
|
| 372 |
-
- Check [CLI Commands](../reference/cli.md)
|
|
|
|
| 1 |
+
# Running Demos (This area is still under construction..)
|
| 2 |
+
|
| 3 |
+
The AI Imaging Agent can execute tool demos directly on your uploaded images. This guide explains how demo execution works and how to use it effectively.
|
| 4 |
+
|
| 5 |
+
## What Are Demos?
|
| 6 |
+
|
| 7 |
+
Demos are **runnable examples** of imaging tools, typically hosted as:
|
| 8 |
+
|
| 9 |
+
- **HuggingFace Spaces**: Interactive Gradio or Streamlit applications
|
| 10 |
+
- **Jupyter Notebooks**: Google Colab or similar notebook environments
|
| 11 |
+
- **Web Applications**: Hosted web interfaces
|
| 12 |
+
- **GitHub Examples**: Code repositories with example scripts
|
| 13 |
+
|
| 14 |
+
## Demo Execution Flow
|
| 15 |
+
|
| 16 |
+
### 1. Agent Offers to Run Demo
|
| 17 |
+
|
| 18 |
+
After providing recommendations, the agent may offer:
|
| 19 |
+
|
| 20 |
+
```
|
| 21 |
+
Agent: Would you like me to run the demo with your image?
|
| 22 |
+
```
|
| 23 |
+
|
| 24 |
+
This appears when:
|
| 25 |
+
|
| 26 |
+
- Tool has a compatible Gradio Space demo
|
| 27 |
+
- Your image format is compatible
|
| 28 |
+
- Demo's API is accessible
|
| 29 |
+
|
| 30 |
+
### 2. You Confirm
|
| 31 |
+
|
| 32 |
+
Respond with affirmative language:
|
| 33 |
+
|
| 34 |
+
- "yes"
|
| 35 |
+
- "sure"
|
| 36 |
+
- "ok"
|
| 37 |
+
- "please"
|
| 38 |
+
- "go ahead"
|
| 39 |
+
- "run it"
|
| 40 |
+
|
| 41 |
+
The agent detects these patterns and proceeds.
|
| 42 |
+
|
| 43 |
+
### 3. Execution Happens
|
| 44 |
+
|
| 45 |
+
The agent:
|
| 46 |
+
|
| 47 |
+
1. Uploads your image to the demo space
|
| 48 |
+
2. Configures any required parameters
|
| 49 |
+
3. Triggers execution
|
| 50 |
+
4. Monitors progress
|
| 51 |
+
5. Retrieves results
|
| 52 |
+
|
| 53 |
+
### 4. Results Display
|
| 54 |
+
|
| 55 |
+
You receive:
|
| 56 |
+
|
| 57 |
+
- **Success message** with output
|
| 58 |
+
- **Result images** or files
|
| 59 |
+
- **Execution trace** showing what happened
|
| 60 |
+
|
| 61 |
+
## Demo Types
|
| 62 |
+
|
| 63 |
+
### Gradio Space Demos
|
| 64 |
+
|
| 65 |
+
**Best supported** - Direct API integration:
|
| 66 |
+
|
| 67 |
+
```
|
| 68 |
+
🚀 Demo: https://huggingface.co/spaces/username/toolname
|
| 69 |
+
```
|
| 70 |
+
|
| 71 |
+
**Features**:
|
| 72 |
+
|
| 73 |
+
- ✅ Automatic execution
|
| 74 |
+
- ✅ Progress monitoring
|
| 75 |
+
- ✅ Result retrieval
|
| 76 |
+
- ✅ Error handling
|
| 77 |
+
|
| 78 |
+
**Example**:
|
| 79 |
+
```
|
| 80 |
+
Running TotalSegmentator on your CT scan...
|
| 81 |
+
✓ Image uploaded
|
| 82 |
+
✓ Processing started
|
| 83 |
+
✓ Segmentation complete
|
| 84 |
+
✓ Results downloaded
|
| 85 |
+
```
|
| 86 |
+
|
| 87 |
+
### Notebook Demos
|
| 88 |
+
|
| 89 |
+
**Partially supported** - Links provided for manual execution:
|
| 90 |
+
|
| 91 |
+
```
|
| 92 |
+
📓 Notebook: https://colab.research.google.com/...
|
| 93 |
+
```
|
| 94 |
+
|
| 95 |
+
**Process**:
|
| 96 |
+
|
| 97 |
+
1. Click the notebook link
|
| 98 |
+
2. Open in Google Colab
|
| 99 |
+
3. Upload your image to the notebook
|
| 100 |
+
4. Run cells sequentially
|
| 101 |
+
5. Download results
|
| 102 |
+
|
| 103 |
+
### Web Application Demos
|
| 104 |
+
|
| 105 |
+
**Manual execution** - Opens in browser:
|
| 106 |
+
|
| 107 |
+
```
|
| 108 |
+
🌐 Web Demo: https://example.com/tool
|
| 109 |
+
```
|
| 110 |
+
|
| 111 |
+
**Process**:
|
| 112 |
+
|
| 113 |
+
1. Click the demo link
|
| 114 |
+
2. Web app opens in new tab
|
| 115 |
+
3. Upload your image via the web UI
|
| 116 |
+
4. Configure settings
|
| 117 |
+
5. Run and download results
|
| 118 |
+
|
| 119 |
+
### GitHub Repository Examples
|
| 120 |
+
|
| 121 |
+
**Code-based** - Requires local setup:
|
| 122 |
+
|
| 123 |
+
```
|
| 124 |
+
💻 Repository: https://github.com/user/repo
|
| 125 |
+
```
|
| 126 |
+
|
| 127 |
+
**Process**:
|
| 128 |
+
|
| 129 |
+
1. Clone the repository
|
| 130 |
+
2. Install dependencies
|
| 131 |
+
3. Run example scripts
|
| 132 |
+
4. Adapt for your data
|
| 133 |
+
|
| 134 |
+
## Execution Traces
|
| 135 |
+
|
| 136 |
+
When demos run, you see detailed traces:
|
| 137 |
+
|
| 138 |
+
```html
|
| 139 |
+
<details>
|
| 140 |
+
<summary>🔧 Tool Execution Trace</summary>
|
| 141 |
+
|
| 142 |
+
Step 1: Uploading image to Gradio Space
|
| 143 |
+
✓ Connected to space: username/toolname
|
| 144 |
+
✓ Image uploaded: 2.3 MB
|
| 145 |
+
|
| 146 |
+
Step 2: Configuring parameters
|
| 147 |
+
✓ Set task: lung-segmentation
|
| 148 |
+
✓ Set format: DICOM
|
| 149 |
+
|
| 150 |
+
Step 3: Running inference
|
| 151 |
+
⏳ Processing... (estimated 30s)
|
| 152 |
+
✓ Completed in 28s
|
| 153 |
+
|
| 154 |
+
Step 4: Retrieving results
|
| 155 |
+
✓ Downloaded segmentation mask: 1.1 MB
|
| 156 |
+
✓ Downloaded visualization: 0.8 MB
|
| 157 |
+
|
| 158 |
+
Status: ✅ Success
|
| 159 |
+
</details>
|
| 160 |
+
```
|
| 161 |
+
|
| 162 |
+
Click to expand and see full details.
|
| 163 |
+
|
| 164 |
+
## Supported Gradio Spaces
|
| 165 |
+
|
| 166 |
+
### Auto-Detected Parameters
|
| 167 |
+
|
| 168 |
+
The agent automatically configures:
|
| 169 |
+
|
| 170 |
+
#### Image Input
|
| 171 |
+
- Detects image input component(s)
|
| 172 |
+
- Uploads your file
|
| 173 |
+
- Converts format if needed
|
| 174 |
+
|
| 175 |
+
#### Task Selection
|
| 176 |
+
Common task parameters:
|
| 177 |
+
|
| 178 |
+
- **Task dropdown**: Matches your query to task option
|
| 179 |
+
- **Model selection**: Chooses appropriate model
|
| 180 |
+
- **Mode**: Inference, predict, analyze, etc.
|
| 181 |
+
|
| 182 |
+
#### Format Options
|
| 183 |
+
- **Input format**: DICOM, NIfTI, PNG, etc.
|
| 184 |
+
- **Output format**: Segmentation mask, visualization, etc.
|
| 185 |
+
- **Data type**: 2D, 3D, specific modality
|
| 186 |
+
|
| 187 |
+
### Manual Parameters
|
| 188 |
+
|
| 189 |
+
Some demos require manual interaction:
|
| 190 |
+
|
| 191 |
+
```
|
| 192 |
+
Agent: This demo has additional parameters. Please visit the link to configure:
|
| 193 |
+
- Segmentation threshold: 0.5
|
| 194 |
+
- Post-processing: enabled
|
| 195 |
+
```
|
| 196 |
+
|
| 197 |
+
## Demo Execution Best Practices
|
| 198 |
+
|
| 199 |
+
!!! tip "Check Compatibility First"
|
| 200 |
+
Verify the tool supports your file format in the recommendation metadata.
|
| 201 |
+
|
| 202 |
+
!!! tip "Use Standard Formats"
|
| 203 |
+
Demos work best with standard formats (PNG, JPEG for general; DICOM, NIfTI for medical).
|
| 204 |
+
|
| 205 |
+
!!! tip "Be Patient"
|
| 206 |
+
Some demos take time, especially for:
|
| 207 |
+
- Large images or volumes
|
| 208 |
+
- Deep learning models
|
| 209 |
+
- 3D processing
|
| 210 |
+
|
| 211 |
+
Typical times: 10 seconds to 2 minutes.
|
| 212 |
+
|
| 213 |
+
!!! tip "Save Results Immediately"
|
| 214 |
+
Download result files promptly - they may not persist after closing the browser.
|
| 215 |
+
|
| 216 |
+
!!! warning "Rate Limits"
|
| 217 |
+
Public Gradio Spaces may have rate limits or queue systems during high usage.
|
| 218 |
+
|
| 219 |
+
## Troubleshooting Demo Execution
|
| 220 |
+
|
| 221 |
+
### Demo Fails to Run
|
| 222 |
+
|
| 223 |
+
**Error**: Connection timeout or failed upload
|
| 224 |
+
|
| 225 |
+
**Solutions**:
|
| 226 |
+
|
| 227 |
+
- Check internet connection
|
| 228 |
+
- Try again (server may be busy)
|
| 229 |
+
- Visit demo link manually
|
| 230 |
+
- Try alternative recommendation
|
| 231 |
+
|
| 232 |
+
### Wrong Results
|
| 233 |
+
|
| 234 |
+
**Error**: Output doesn't match expectations
|
| 235 |
+
|
| 236 |
+
**Solutions**:
|
| 237 |
+
|
| 238 |
+
- Check if correct parameters were used
|
| 239 |
+
- Verify image uploaded correctly
|
| 240 |
+
- Try adjusting task settings manually
|
| 241 |
+
- Compare with demo's example images
|
| 242 |
+
|
| 243 |
+
### Incompatible Format
|
| 244 |
+
|
| 245 |
+
**Error**: "Format not supported"
|
| 246 |
+
|
| 247 |
+
**Solutions**:
|
| 248 |
+
|
| 249 |
+
- Convert image to supported format
|
| 250 |
+
- Use tool that accepts your format
|
| 251 |
+
- Try alternative recommendation
|
| 252 |
+
|
| 253 |
+
### Demo Link Broken
|
| 254 |
+
|
| 255 |
+
**Error**: 404 or space not found
|
| 256 |
+
|
| 257 |
+
**Solutions**:
|
| 258 |
+
|
| 259 |
+
- Space may be temporarily down
|
| 260 |
+
- Check GitHub repo for alternative demo
|
| 261 |
+
- Try different tool recommendation
|
| 262 |
+
- Report broken link
|
| 263 |
+
|
| 264 |
+
## Manual Demo Execution
|
| 265 |
+
|
| 266 |
+
If automatic execution isn't available:
|
| 267 |
+
|
| 268 |
+
### For Gradio Spaces
|
| 269 |
+
|
| 270 |
+
1. Click the demo link
|
| 271 |
+
2. The space opens in your browser
|
| 272 |
+
3. Upload your image via the UI
|
| 273 |
+
4. Select appropriate options
|
| 274 |
+
5. Click "Submit" or "Run"
|
| 275 |
+
6. Download results
|
| 276 |
+
|
| 277 |
+
### For Colab Notebooks
|
| 278 |
+
|
| 279 |
+
1. Click the notebook link
|
| 280 |
+
2. Open in Google Colab
|
| 281 |
+
3. Run setup cells (install dependencies)
|
| 282 |
+
4. Upload your image when prompted:
|
| 283 |
+
```python
|
| 284 |
+
from google.colab import files
|
| 285 |
+
uploaded = files.upload()
|
| 286 |
+
```
|
| 287 |
+
5. Run processing cells
|
| 288 |
+
6. Download results:
|
| 289 |
+
```python
|
| 290 |
+
files.download('result.png')
|
| 291 |
+
```
|
| 292 |
+
|
| 293 |
+
### For Local Execution
|
| 294 |
+
|
| 295 |
+
1. Clone the repository:
|
| 296 |
+
```bash
|
| 297 |
+
git clone https://github.com/user/repo
|
| 298 |
+
cd repo
|
| 299 |
+
```
|
| 300 |
+
|
| 301 |
+
2. Install dependencies:
|
| 302 |
+
```bash
|
| 303 |
+
pip install -r requirements.txt
|
| 304 |
+
```
|
| 305 |
+
|
| 306 |
+
3. Run example script:
|
| 307 |
+
```bash
|
| 308 |
+
python run_demo.py --input your_image.png --output result.png
|
| 309 |
+
```
|
| 310 |
+
|
| 311 |
+
4. Check output directory for results
|
| 312 |
+
|
| 313 |
+
## Understanding Results
|
| 314 |
+
|
| 315 |
+
### Segmentation Results
|
| 316 |
+
|
| 317 |
+
Typically includes:
|
| 318 |
+
|
| 319 |
+
- **Segmentation mask**: Binary or multi-class mask
|
| 320 |
+
- **Overlay visualization**: Mask overlaid on original image
|
| 321 |
+
- **Statistics**: Volume, area, counts
|
| 322 |
+
|
| 323 |
+
### Detection Results
|
| 324 |
+
|
| 325 |
+
Usually provides:
|
| 326 |
+
|
| 327 |
+
- **Bounding boxes**: Coordinates of detected objects
|
| 328 |
+
- **Annotated image**: Visual with boxes/labels
|
| 329 |
+
- **Confidence scores**: Detection confidence
|
| 330 |
+
|
| 331 |
+
### Registration Results
|
| 332 |
+
|
| 333 |
+
Common outputs:
|
| 334 |
+
|
| 335 |
+
- **Transformed image**: Registered/aligned image
|
| 336 |
+
- **Transformation matrix**: Spatial transform parameters
|
| 337 |
+
- **Quality metrics**: Similarity scores
|
| 338 |
+
|
| 339 |
+
### Classification Results
|
| 340 |
+
|
| 341 |
+
Typical outputs:
|
| 342 |
+
|
| 343 |
+
- **Class labels**: Predicted categories
|
| 344 |
+
- **Probabilities**: Confidence per class
|
| 345 |
+
- **Visualization**: Class activation maps
|
| 346 |
+
|
| 347 |
+
## Demo Feedback
|
| 348 |
+
|
| 349 |
+
Help improve the agent by reporting:
|
| 350 |
+
|
| 351 |
+
### Successful Demos
|
| 352 |
+
When demos work well, this validates:
|
| 353 |
+
|
| 354 |
+
- Tool compatibility
|
| 355 |
+
- Parameter auto-configuration
|
| 356 |
+
- Format handling
|
| 357 |
+
|
| 358 |
+
### Issues
|
| 359 |
+
Report when:
|
| 360 |
+
|
| 361 |
+
- Demo fails unexpectedly
|
| 362 |
+
- Results are incorrect
|
| 363 |
+
- Parameters were misconfigured
|
| 364 |
+
- Format conversion was wrong
|
| 365 |
+
|
| 366 |
+
Feedback helps refine the agent's demo execution capabilities.
|
| 367 |
+
|
| 368 |
+
## Next Steps
|
| 369 |
+
|
| 370 |
+
- Explore [Advanced Features](advanced-features.md)
|
| 371 |
+
- Learn about the [Architecture](../architecture/overview.md)
|
| 372 |
+
- Check [CLI Commands](../reference/cli.md)
|
mkdocs.yml
CHANGED
|
@@ -1,108 +1,108 @@
|
|
| 1 |
-
site_name: AI Imaging Agent
|
| 2 |
-
site_description: Intelligent RAG + AI agent for discovering imaging software
|
| 3 |
-
site_author: Imaging Plaza
|
| 4 |
-
site_url: https://imaging-plaza.github.io/ai-agent/
|
| 5 |
-
|
| 6 |
-
repo_name: imaging-plaza/ai-agent
|
| 7 |
-
repo_url: https://github.com/imaging-plaza/ai-agent
|
| 8 |
-
edit_uri: edit/main/docs/
|
| 9 |
-
|
| 10 |
-
theme:
|
| 11 |
-
name: material
|
| 12 |
-
palette:
|
| 13 |
-
# Palette toggle for light mode
|
| 14 |
-
- scheme: default
|
| 15 |
-
primary: teal
|
| 16 |
-
accent: green
|
| 17 |
-
toggle:
|
| 18 |
-
icon: material/brightness-7
|
| 19 |
-
name: Switch to dark mode
|
| 20 |
-
# Palette toggle for dark mode
|
| 21 |
-
- scheme: slate
|
| 22 |
-
primary: teal
|
| 23 |
-
accent: green
|
| 24 |
-
toggle:
|
| 25 |
-
icon: material/brightness-4
|
| 26 |
-
name: Switch to light mode
|
| 27 |
-
|
| 28 |
-
font:
|
| 29 |
-
text: Roboto
|
| 30 |
-
code: Roboto Mono
|
| 31 |
-
|
| 32 |
-
features:
|
| 33 |
-
- navigation.instant
|
| 34 |
-
- navigation.tracking
|
| 35 |
-
- navigation.tabs
|
| 36 |
-
- navigation.sections
|
| 37 |
-
- navigation.top
|
| 38 |
-
- navigation.footer
|
| 39 |
-
- search.suggest
|
| 40 |
-
- search.highlight
|
| 41 |
-
- content.code.copy
|
| 42 |
-
- content.code.annotate
|
| 43 |
-
- content.tabs.link
|
| 44 |
-
|
| 45 |
-
icon:
|
| 46 |
-
repo: fontawesome/brands/github
|
| 47 |
-
|
| 48 |
-
plugins:
|
| 49 |
-
- search
|
| 50 |
-
- tags
|
| 51 |
-
|
| 52 |
-
markdown_extensions:
|
| 53 |
-
- admonition
|
| 54 |
-
- pymdownx.details
|
| 55 |
-
- pymdownx.superfences:
|
| 56 |
-
custom_fences:
|
| 57 |
-
- name: mermaid
|
| 58 |
-
class: mermaid
|
| 59 |
-
format: !!python/name:pymdownx.superfences.fence_code_format
|
| 60 |
-
- pymdownx.tabbed:
|
| 61 |
-
alternate_style: true
|
| 62 |
-
- pymdownx.highlight:
|
| 63 |
-
anchor_linenums: true
|
| 64 |
-
- pymdownx.inlinehilite
|
| 65 |
-
- pymdownx.snippets
|
| 66 |
-
- attr_list
|
| 67 |
-
- md_in_html
|
| 68 |
-
- pymdownx.emoji:
|
| 69 |
-
emoji_index: !!python/name:material.extensions.emoji.twemoji
|
| 70 |
-
emoji_generator: !!python/name:material.extensions.emoji.to_svg
|
| 71 |
-
- tables
|
| 72 |
-
- footnotes
|
| 73 |
-
- toc:
|
| 74 |
-
permalink: true
|
| 75 |
-
|
| 76 |
-
nav:
|
| 77 |
-
- Home: index.md
|
| 78 |
-
- Getting Started:
|
| 79 |
-
- Installation: getting-started/installation.md
|
| 80 |
-
- Quick Start: getting-started/quickstart.md
|
| 81 |
-
- Configuration: getting-started/configuration.md
|
| 82 |
-
- User Guide:
|
| 83 |
-
- Using the Chat Interface: user-guide/chat-interface.md
|
| 84 |
-
- Supported File Formats: user-guide/file-formats.md
|
| 85 |
-
- Understanding Recommendations: user-guide/recommendations.md
|
| 86 |
-
- Running Demos: user-guide/running-demos.md
|
| 87 |
-
- Advanced Features: user-guide/advanced-features.md
|
| 88 |
-
- Architecture:
|
| 89 |
-
- Overview: architecture/overview.md
|
| 90 |
-
- Retrieval Pipeline: architecture/retrieval.md
|
| 91 |
-
- Agent & VLM Selection: architecture/agent.md
|
| 92 |
-
- Software Catalog: architecture/catalog.md
|
| 93 |
-
- Development:
|
| 94 |
-
- Project Guide: guide.md
|
| 95 |
-
- Project Structure: development/structure.md
|
| 96 |
-
- Contributing: development/contributing.md
|
| 97 |
-
- Testing: development/testing.md
|
| 98 |
-
- Reference:
|
| 99 |
-
- CLI Commands: reference/cli.md
|
| 100 |
-
- Environment Variables: reference/environment.md
|
| 101 |
-
- Changelog: reference/changelog.md
|
| 102 |
-
|
| 103 |
-
extra:
|
| 104 |
-
social:
|
| 105 |
-
- icon: fontawesome/brands/github
|
| 106 |
-
link: https://github.com/imaging-plaza/ai-agent
|
| 107 |
-
|
| 108 |
-
copyright: Copyright © 2024-2026 Imaging Plaza
|
|
|
|
| 1 |
+
site_name: AI Imaging Agent
|
| 2 |
+
site_description: Intelligent RAG + AI agent for discovering imaging software
|
| 3 |
+
site_author: Imaging Plaza
|
| 4 |
+
site_url: https://imaging-plaza.github.io/ai-agent/
|
| 5 |
+
|
| 6 |
+
repo_name: imaging-plaza/ai-agent
|
| 7 |
+
repo_url: https://github.com/imaging-plaza/ai-agent
|
| 8 |
+
edit_uri: edit/main/docs/
|
| 9 |
+
|
| 10 |
+
theme:
|
| 11 |
+
name: material
|
| 12 |
+
palette:
|
| 13 |
+
# Palette toggle for light mode
|
| 14 |
+
- scheme: default
|
| 15 |
+
primary: teal
|
| 16 |
+
accent: green
|
| 17 |
+
toggle:
|
| 18 |
+
icon: material/brightness-7
|
| 19 |
+
name: Switch to dark mode
|
| 20 |
+
# Palette toggle for dark mode
|
| 21 |
+
- scheme: slate
|
| 22 |
+
primary: teal
|
| 23 |
+
accent: green
|
| 24 |
+
toggle:
|
| 25 |
+
icon: material/brightness-4
|
| 26 |
+
name: Switch to light mode
|
| 27 |
+
|
| 28 |
+
font:
|
| 29 |
+
text: Roboto
|
| 30 |
+
code: Roboto Mono
|
| 31 |
+
|
| 32 |
+
features:
|
| 33 |
+
- navigation.instant
|
| 34 |
+
- navigation.tracking
|
| 35 |
+
- navigation.tabs
|
| 36 |
+
- navigation.sections
|
| 37 |
+
- navigation.top
|
| 38 |
+
- navigation.footer
|
| 39 |
+
- search.suggest
|
| 40 |
+
- search.highlight
|
| 41 |
+
- content.code.copy
|
| 42 |
+
- content.code.annotate
|
| 43 |
+
- content.tabs.link
|
| 44 |
+
|
| 45 |
+
icon:
|
| 46 |
+
repo: fontawesome/brands/github
|
| 47 |
+
|
| 48 |
+
plugins:
|
| 49 |
+
- search
|
| 50 |
+
- tags
|
| 51 |
+
|
| 52 |
+
markdown_extensions:
|
| 53 |
+
- admonition
|
| 54 |
+
- pymdownx.details
|
| 55 |
+
- pymdownx.superfences:
|
| 56 |
+
custom_fences:
|
| 57 |
+
- name: mermaid
|
| 58 |
+
class: mermaid
|
| 59 |
+
format: !!python/name:pymdownx.superfences.fence_code_format
|
| 60 |
+
- pymdownx.tabbed:
|
| 61 |
+
alternate_style: true
|
| 62 |
+
- pymdownx.highlight:
|
| 63 |
+
anchor_linenums: true
|
| 64 |
+
- pymdownx.inlinehilite
|
| 65 |
+
- pymdownx.snippets
|
| 66 |
+
- attr_list
|
| 67 |
+
- md_in_html
|
| 68 |
+
- pymdownx.emoji:
|
| 69 |
+
emoji_index: !!python/name:material.extensions.emoji.twemoji
|
| 70 |
+
emoji_generator: !!python/name:material.extensions.emoji.to_svg
|
| 71 |
+
- tables
|
| 72 |
+
- footnotes
|
| 73 |
+
- toc:
|
| 74 |
+
permalink: true
|
| 75 |
+
|
| 76 |
+
nav:
|
| 77 |
+
- Home: index.md
|
| 78 |
+
- Getting Started:
|
| 79 |
+
- Installation: getting-started/installation.md
|
| 80 |
+
- Quick Start: getting-started/quickstart.md
|
| 81 |
+
- Configuration: getting-started/configuration.md
|
| 82 |
+
- User Guide:
|
| 83 |
+
- Using the Chat Interface: user-guide/chat-interface.md
|
| 84 |
+
- Supported File Formats: user-guide/file-formats.md
|
| 85 |
+
- Understanding Recommendations: user-guide/recommendations.md
|
| 86 |
+
- Running Demos: user-guide/running-demos.md
|
| 87 |
+
- Advanced Features: user-guide/advanced-features.md
|
| 88 |
+
- Architecture:
|
| 89 |
+
- Overview: architecture/overview.md
|
| 90 |
+
- Retrieval Pipeline: architecture/retrieval.md
|
| 91 |
+
- Agent & VLM Selection: architecture/agent.md
|
| 92 |
+
- Software Catalog: architecture/catalog.md
|
| 93 |
+
- Development:
|
| 94 |
+
- Project Guide: guide.md
|
| 95 |
+
- Project Structure: development/structure.md
|
| 96 |
+
- Contributing: development/contributing.md
|
| 97 |
+
- Testing: development/testing.md
|
| 98 |
+
- Reference:
|
| 99 |
+
- CLI Commands: reference/cli.md
|
| 100 |
+
- Environment Variables: reference/environment.md
|
| 101 |
+
- Changelog: reference/changelog.md
|
| 102 |
+
|
| 103 |
+
extra:
|
| 104 |
+
social:
|
| 105 |
+
- icon: fontawesome/brands/github
|
| 106 |
+
link: https://github.com/imaging-plaza/ai-agent
|
| 107 |
+
|
| 108 |
+
copyright: Copyright © 2024-2026 Imaging Plaza
|
pyproject.toml
CHANGED
|
@@ -15,6 +15,10 @@ dependencies = [
|
|
| 15 |
"requests==2.32.4",
|
| 16 |
"python-dotenv==1.1.1",
|
| 17 |
"gradio==5.42.0",
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
"json5==0.12.0",
|
| 19 |
"pillow==11.3.0",
|
| 20 |
"nibabel==5.3.2",
|
|
|
|
| 15 |
"requests==2.32.4",
|
| 16 |
"python-dotenv==1.1.1",
|
| 17 |
"gradio==5.42.0",
|
| 18 |
+
"fastapi==0.118.0",
|
| 19 |
+
"uvicorn[standard]==0.36.0",
|
| 20 |
+
"sse-starlette==2.1.3",
|
| 21 |
+
"python-multipart==0.0.20",
|
| 22 |
"json5==0.12.0",
|
| 23 |
"pillow==11.3.0",
|
| 24 |
"nibabel==5.3.2",
|
src/ai_agent/agent/agent.py
CHANGED
|
@@ -1,611 +1,650 @@
|
|
| 1 |
-
from __future__ import annotations
|
| 2 |
-
|
| 3 |
-
import os
|
| 4 |
-
import logging
|
| 5 |
-
import time
|
| 6 |
-
import asyncio
|
| 7 |
-
import threading
|
| 8 |
-
from collections import OrderedDict
|
| 9 |
-
from datetime import datetime
|
| 10 |
-
from typing import List
|
| 11 |
-
|
| 12 |
-
from pydantic_ai import Agent, RunContext
|
| 13 |
-
from pydantic_ai.usage import UsageLimits
|
| 14 |
-
from pydantic_ai.models.openai import OpenAIResponsesModel, OpenAIChatModel
|
| 15 |
-
from pydantic_ai.providers.openai import OpenAIProvider
|
| 16 |
-
from pydantic_ai.messages import BinaryContent
|
| 17 |
-
|
| 18 |
-
from ai_agent.generator.prompts import get_agent_system_prompt
|
| 19 |
-
from ai_agent.generator.schema import ToolSelection, Conversation, ConversationStatus
|
| 20 |
-
from ai_agent.utils.config import get_config
|
| 21 |
-
from .models import AgentToolSelection, ToolRunLog, UsageStats
|
| 22 |
-
from .tools.repo_info_tool import tool_repo_summary, RepoSummaryInput
|
| 23 |
-
from ai_agent.agent.utils import coerce_github_url_or_none
|
| 24 |
-
from .tools.search_tool import tool_search_tools, SearchToolsInput
|
| 25 |
-
from .tools.search_alternative_tool import (
|
| 26 |
-
tool_search_alternative,
|
| 27 |
-
SearchAlternativeInput,
|
| 28 |
-
)
|
| 29 |
-
from .tools.
|
| 30 |
-
from .
|
| 31 |
-
from
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
#
|
| 39 |
-
#
|
| 40 |
-
#
|
| 41 |
-
#
|
| 42 |
-
#
|
| 43 |
-
#
|
| 44 |
-
#
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
#
|
| 51 |
-
#
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
log.info("Using
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
#
|
| 83 |
-
#
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
)
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
#
|
| 93 |
-
#
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
@
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
"
|
| 139 |
-
"
|
| 140 |
-
"
|
| 141 |
-
"
|
| 142 |
-
"
|
| 143 |
-
"
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
@
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
"
|
| 183 |
-
"
|
| 184 |
-
"
|
| 185 |
-
"
|
| 186 |
-
"
|
| 187 |
-
"
|
| 188 |
-
"
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
@
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
|
| 310 |
-
|
| 311 |
-
|
| 312 |
-
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
)
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
else
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
|
| 350 |
-
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
|
| 355 |
-
|
| 356 |
-
|
| 357 |
-
|
| 358 |
-
|
| 359 |
-
|
| 360 |
-
|
| 361 |
-
|
| 362 |
-
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
|
| 369 |
-
|
| 370 |
-
|
| 371 |
-
|
| 372 |
-
|
| 373 |
-
|
| 374 |
-
|
| 375 |
-
|
| 376 |
-
|
| 377 |
-
|
| 378 |
-
|
| 379 |
-
|
| 380 |
-
|
| 381 |
-
|
| 382 |
-
|
| 383 |
-
|
| 384 |
-
|
| 385 |
-
|
| 386 |
-
|
| 387 |
-
|
| 388 |
-
|
| 389 |
-
|
| 390 |
-
|
| 391 |
-
|
| 392 |
-
|
| 393 |
-
|
| 394 |
-
|
| 395 |
-
|
| 396 |
-
|
| 397 |
-
|
| 398 |
-
|
| 399 |
-
|
| 400 |
-
|
| 401 |
-
|
| 402 |
-
|
| 403 |
-
|
| 404 |
-
|
| 405 |
-
|
| 406 |
-
|
| 407 |
-
|
| 408 |
-
|
| 409 |
-
|
| 410 |
-
|
| 411 |
-
|
| 412 |
-
|
| 413 |
-
|
| 414 |
-
|
| 415 |
-
|
| 416 |
-
|
| 417 |
-
|
| 418 |
-
|
| 419 |
-
|
| 420 |
-
|
| 421 |
-
|
| 422 |
-
|
| 423 |
-
|
| 424 |
-
|
| 425 |
-
|
| 426 |
-
|
| 427 |
-
|
| 428 |
-
|
| 429 |
-
|
| 430 |
-
|
| 431 |
-
|
| 432 |
-
|
| 433 |
-
|
| 434 |
-
|
| 435 |
-
|
| 436 |
-
|
| 437 |
-
|
| 438 |
-
|
| 439 |
-
|
| 440 |
-
|
| 441 |
-
|
| 442 |
-
|
| 443 |
-
|
| 444 |
-
|
| 445 |
-
|
| 446 |
-
|
| 447 |
-
|
| 448 |
-
|
| 449 |
-
|
| 450 |
-
|
| 451 |
-
|
| 452 |
-
|
| 453 |
-
|
| 454 |
-
|
| 455 |
-
|
| 456 |
-
|
| 457 |
-
|
| 458 |
-
|
| 459 |
-
|
| 460 |
-
|
| 461 |
-
|
| 462 |
-
|
| 463 |
-
|
| 464 |
-
|
| 465 |
-
|
| 466 |
-
|
| 467 |
-
|
| 468 |
-
|
| 469 |
-
|
| 470 |
-
|
| 471 |
-
|
| 472 |
-
|
| 473 |
-
|
| 474 |
-
|
| 475 |
-
|
| 476 |
-
|
| 477 |
-
|
| 478 |
-
|
| 479 |
-
|
| 480 |
-
|
| 481 |
-
|
| 482 |
-
|
| 483 |
-
|
| 484 |
-
|
| 485 |
-
|
| 486 |
-
|
| 487 |
-
|
| 488 |
-
|
| 489 |
-
|
| 490 |
-
|
| 491 |
-
|
| 492 |
-
|
| 493 |
-
|
| 494 |
-
|
| 495 |
-
|
| 496 |
-
|
| 497 |
-
|
| 498 |
-
|
| 499 |
-
|
| 500 |
-
|
| 501 |
-
|
| 502 |
-
|
| 503 |
-
|
| 504 |
-
|
| 505 |
-
|
| 506 |
-
|
| 507 |
-
|
| 508 |
-
|
| 509 |
-
|
| 510 |
-
|
| 511 |
-
|
| 512 |
-
|
| 513 |
-
|
| 514 |
-
|
| 515 |
-
|
| 516 |
-
|
| 517 |
-
|
| 518 |
-
|
| 519 |
-
|
| 520 |
-
|
| 521 |
-
|
| 522 |
-
|
| 523 |
-
|
| 524 |
-
|
| 525 |
-
|
| 526 |
-
|
| 527 |
-
|
| 528 |
-
log.warning(
|
| 529 |
-
|
| 530 |
-
|
| 531 |
-
|
| 532 |
-
|
| 533 |
-
|
| 534 |
-
|
| 535 |
-
)
|
| 536 |
-
|
| 537 |
-
|
| 538 |
-
|
| 539 |
-
|
| 540 |
-
|
| 541 |
-
|
| 542 |
-
|
| 543 |
-
|
| 544 |
-
|
| 545 |
-
|
| 546 |
-
|
| 547 |
-
|
| 548 |
-
|
| 549 |
-
|
| 550 |
-
|
| 551 |
-
|
| 552 |
-
|
| 553 |
-
|
| 554 |
-
|
| 555 |
-
|
| 556 |
-
|
| 557 |
-
|
| 558 |
-
|
| 559 |
-
|
| 560 |
-
|
| 561 |
-
|
| 562 |
-
|
| 563 |
-
|
| 564 |
-
|
| 565 |
-
|
| 566 |
-
|
| 567 |
-
|
| 568 |
-
|
| 569 |
-
|
| 570 |
-
|
| 571 |
-
|
| 572 |
-
|
| 573 |
-
|
| 574 |
-
|
| 575 |
-
|
| 576 |
-
|
| 577 |
-
|
| 578 |
-
|
| 579 |
-
|
| 580 |
-
|
| 581 |
-
|
| 582 |
-
|
| 583 |
-
|
| 584 |
-
|
| 585 |
-
|
| 586 |
-
|
| 587 |
-
|
| 588 |
-
|
| 589 |
-
|
| 590 |
-
|
| 591 |
-
|
| 592 |
-
|
| 593 |
-
|
| 594 |
-
|
| 595 |
-
|
| 596 |
-
|
| 597 |
-
|
| 598 |
-
|
| 599 |
-
|
| 600 |
-
|
| 601 |
-
|
| 602 |
-
|
| 603 |
-
|
| 604 |
-
|
| 605 |
-
|
| 606 |
-
|
| 607 |
-
|
| 608 |
-
|
| 609 |
-
|
| 610 |
-
|
| 611 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import os
|
| 4 |
+
import logging
|
| 5 |
+
import time
|
| 6 |
+
import asyncio
|
| 7 |
+
import threading
|
| 8 |
+
from collections import OrderedDict
|
| 9 |
+
from datetime import datetime
|
| 10 |
+
from typing import List
|
| 11 |
+
|
| 12 |
+
from pydantic_ai import Agent, RunContext
|
| 13 |
+
from pydantic_ai.usage import UsageLimits
|
| 14 |
+
from pydantic_ai.models.openai import OpenAIResponsesModel, OpenAIChatModel
|
| 15 |
+
from pydantic_ai.providers.openai import OpenAIProvider
|
| 16 |
+
from pydantic_ai.messages import BinaryContent
|
| 17 |
+
|
| 18 |
+
from ai_agent.generator.prompts import get_agent_system_prompt
|
| 19 |
+
from ai_agent.generator.schema import ToolSelection, Conversation, ConversationStatus
|
| 20 |
+
from ai_agent.utils.config import get_config
|
| 21 |
+
from .models import AgentToolSelection, ToolRunLog, UsageStats
|
| 22 |
+
from .tools.repo_info_tool import tool_repo_summary, RepoSummaryInput
|
| 23 |
+
from ai_agent.agent.utils import coerce_github_url_or_none
|
| 24 |
+
from .tools.search_tool import tool_search_tools, SearchToolsInput
|
| 25 |
+
from .tools.search_alternative_tool import (
|
| 26 |
+
tool_search_alternative,
|
| 27 |
+
SearchAlternativeInput,
|
| 28 |
+
)
|
| 29 |
+
from .tools.sparql_tool import tool_sparql_query, SparqlQueryInput
|
| 30 |
+
from .tools.query_utils import sanitize_retrieval_query
|
| 31 |
+
from .utils import AgentState, limit_tool_calls, cap_prepare
|
| 32 |
+
from ai_agent.utils.image_meta import summarize_image_metadata, detect_ext_token
|
| 33 |
+
|
| 34 |
+
log = logging.getLogger("agent.core")
|
| 35 |
+
|
| 36 |
+
DEFAULT_NUM_CHOICES = int(os.getenv("NUM_CHOICES", "3"))
|
| 37 |
+
|
| 38 |
+
# ---------------------------------------------------------------------------
|
| 39 |
+
# Dynamic agent instance cache
|
| 40 |
+
# Key: (model_name, base_url, api_key_env, num_choices)
|
| 41 |
+
# Avoids rebuilding Agent/OpenAIProvider/model objects on every request when
|
| 42 |
+
# the UI repeatedly uses the same custom endpoint + model combination.
|
| 43 |
+
# Bounded LRU (max AGENT_CACHE_MAX entries); protected by a lock so that
|
| 44 |
+
# concurrent requests cannot race while creating/inserting agents.
|
| 45 |
+
# ---------------------------------------------------------------------------
|
| 46 |
+
_AGENT_CACHE_MAX: int = int(os.getenv("AGENT_CACHE_MAX", "16"))
|
| 47 |
+
_AGENT_CACHE_LOCK: threading.Lock = threading.Lock()
|
| 48 |
+
_AGENT_CACHE: OrderedDict[tuple, "Agent"] = OrderedDict()
|
| 49 |
+
|
| 50 |
+
# ---------------------------------------------------------------------------
|
| 51 |
+
# Model / provider setup
|
| 52 |
+
# ---------------------------------------------------------------------------
|
| 53 |
+
config = get_config()
|
| 54 |
+
agent_model_config = config.agent_model
|
| 55 |
+
|
| 56 |
+
try:
|
| 57 |
+
api_key = agent_model_config.get_api_key()
|
| 58 |
+
except ValueError as e:
|
| 59 |
+
log.error(f"Failed to get API key for agent model: {e}")
|
| 60 |
+
raise
|
| 61 |
+
|
| 62 |
+
log.info(f"Initializing agent model: {agent_model_config.name}")
|
| 63 |
+
|
| 64 |
+
if agent_model_config.base_url:
|
| 65 |
+
log.info(f"Using custom OpenAI base URL: {agent_model_config.base_url}")
|
| 66 |
+
log.info("Using OpenAIChatModel (chat/completions API) for custom endpoint")
|
| 67 |
+
provider = OpenAIProvider(
|
| 68 |
+
base_url=agent_model_config.base_url,
|
| 69 |
+
api_key=api_key,
|
| 70 |
+
)
|
| 71 |
+
openai_model = OpenAIChatModel(
|
| 72 |
+
model_name=agent_model_config.name,
|
| 73 |
+
provider=provider,
|
| 74 |
+
)
|
| 75 |
+
else:
|
| 76 |
+
provider = OpenAIProvider(api_key=api_key)
|
| 77 |
+
openai_model = OpenAIResponsesModel(
|
| 78 |
+
model_name=agent_model_config.name,
|
| 79 |
+
provider=provider,
|
| 80 |
+
)
|
| 81 |
+
|
| 82 |
+
# ---------------------------------------------------------------------------
|
| 83 |
+
# Agent definition
|
| 84 |
+
# ---------------------------------------------------------------------------
|
| 85 |
+
agent = Agent(
|
| 86 |
+
model=openai_model,
|
| 87 |
+
system_prompt=get_agent_system_prompt(DEFAULT_NUM_CHOICES),
|
| 88 |
+
deps_type=AgentState,
|
| 89 |
+
output_retries=int(os.getenv("AGENT_OUTPUT_RETRIES", "3")),
|
| 90 |
+
)
|
| 91 |
+
|
| 92 |
+
# ---------------------------------------------------------------------------
|
| 93 |
+
# Tool adapters for the agent
|
| 94 |
+
# ---------------------------------------------------------------------------
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
@agent.tool(retries=2, prepare=cap_prepare)
|
| 98 |
+
@limit_tool_calls("search_tools", cap=1)
|
| 99 |
+
async def search_tools(
|
| 100 |
+
ctx: RunContext[AgentState],
|
| 101 |
+
query: str,
|
| 102 |
+
excluded: List[str] | None = None,
|
| 103 |
+
top_k: int = 12,
|
| 104 |
+
) -> List[dict]:
|
| 105 |
+
"""
|
| 106 |
+
Agent-facing search tool.
|
| 107 |
+
|
| 108 |
+
Delegates to tools.search_tool.tool_search_tools(), but automatically
|
| 109 |
+
injects:
|
| 110 |
+
- globally excluded tools (from ctx.deps.excluded_tools)
|
| 111 |
+
- image_paths and original_formats (from ctx.deps, set in run_agent)
|
| 112 |
+
so the language model never has to reason about file paths directly.
|
| 113 |
+
"""
|
| 114 |
+
# Merge explicit exclusions with global exclusions from AgentState
|
| 115 |
+
explicit_excluded = excluded or []
|
| 116 |
+
global_excluded = getattr(ctx.deps, "excluded_tools", []) or []
|
| 117 |
+
all_excluded = sorted(set(explicit_excluded + list(global_excluded)))
|
| 118 |
+
|
| 119 |
+
original_formats = getattr(ctx.deps, "original_formats", []) or []
|
| 120 |
+
image_paths = getattr(ctx.deps, "image_paths", []) or []
|
| 121 |
+
|
| 122 |
+
effective_top_k = (
|
| 123 |
+
ctx.deps.override_top_k if ctx.deps.override_top_k is not None else top_k
|
| 124 |
+
)
|
| 125 |
+
|
| 126 |
+
started = time.perf_counter()
|
| 127 |
+
inp = SearchToolsInput(
|
| 128 |
+
query=sanitize_retrieval_query(query),
|
| 129 |
+
excluded=all_excluded,
|
| 130 |
+
top_k=effective_top_k,
|
| 131 |
+
original_formats=original_formats,
|
| 132 |
+
image_paths=image_paths,
|
| 133 |
+
)
|
| 134 |
+
out = tool_search_tools(inp)
|
| 135 |
+
|
| 136 |
+
ctx.deps.tool_calls.append(
|
| 137 |
+
{
|
| 138 |
+
"tool": "search_tools",
|
| 139 |
+
"query": query,
|
| 140 |
+
"count": len(out.candidates),
|
| 141 |
+
"duration_ms": round((time.perf_counter() - started) * 1000, 1),
|
| 142 |
+
"original_formats": original_formats,
|
| 143 |
+
"excluded": all_excluded,
|
| 144 |
+
"timestamp": datetime.now().isoformat(),
|
| 145 |
+
}
|
| 146 |
+
)
|
| 147 |
+
|
| 148 |
+
# Return plain dicts so the LLM sees a simple JSON-like structure.
|
| 149 |
+
return [c.model_dump(mode="python") for c in out.candidates]
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
@agent.tool(retries=2, prepare=cap_prepare)
|
| 153 |
+
@limit_tool_calls("search_alternative", cap=3)
|
| 154 |
+
async def search_alternative(
|
| 155 |
+
ctx: RunContext[AgentState],
|
| 156 |
+
alternative_query: str,
|
| 157 |
+
excluded: List[str] | None = None,
|
| 158 |
+
top_k: int = 12,
|
| 159 |
+
) -> List[dict]:
|
| 160 |
+
"""
|
| 161 |
+
Search with an alternative query formulation (includes automatic reranking).
|
| 162 |
+
"""
|
| 163 |
+
explicit_excluded = excluded or []
|
| 164 |
+
global_excluded = getattr(ctx.deps, "excluded_tools", []) or []
|
| 165 |
+
all_excluded = sorted(set(explicit_excluded + list(global_excluded)))
|
| 166 |
+
|
| 167 |
+
original_formats = getattr(ctx.deps, "original_formats", []) or []
|
| 168 |
+
image_paths = getattr(ctx.deps, "image_paths", []) or []
|
| 169 |
+
|
| 170 |
+
started = time.perf_counter()
|
| 171 |
+
inp = SearchAlternativeInput(
|
| 172 |
+
alternative_query=alternative_query,
|
| 173 |
+
excluded=all_excluded,
|
| 174 |
+
top_k=top_k,
|
| 175 |
+
original_formats=original_formats,
|
| 176 |
+
image_paths=image_paths,
|
| 177 |
+
)
|
| 178 |
+
out = tool_search_alternative(inp)
|
| 179 |
+
|
| 180 |
+
ctx.deps.tool_calls.append(
|
| 181 |
+
{
|
| 182 |
+
"tool": "search_alternative",
|
| 183 |
+
"alternative_query": alternative_query,
|
| 184 |
+
"query_used": out.query_used,
|
| 185 |
+
"count": len(out.candidates),
|
| 186 |
+
"duration_ms": round((time.perf_counter() - started) * 1000, 1),
|
| 187 |
+
"original_formats": original_formats,
|
| 188 |
+
"excluded": all_excluded,
|
| 189 |
+
"timestamp": datetime.now().isoformat(),
|
| 190 |
+
}
|
| 191 |
+
)
|
| 192 |
+
|
| 193 |
+
return [c.model_dump(mode="python") for c in out.candidates]
|
| 194 |
+
|
| 195 |
+
|
| 196 |
+
@agent.tool(retries=1, prepare=cap_prepare)
|
| 197 |
+
@limit_tool_calls("sparql_query", cap=3)
|
| 198 |
+
async def sparql_query(
|
| 199 |
+
ctx: RunContext[AgentState],
|
| 200 |
+
query: str,
|
| 201 |
+
limit: int = 50,
|
| 202 |
+
) -> dict:
|
| 203 |
+
"""Run a read-only SPARQL SELECT / ASK against the GraphDB catalog.
|
| 204 |
+
|
| 205 |
+
Use this for questions the semantic search can't answer cleanly —
|
| 206 |
+
aggregates ("how many tools support DICOM?"), structural filters
|
| 207 |
+
("tools that require a GPU AND are free"), distinct property values
|
| 208 |
+
("which licences appear in the catalog?"), etc.
|
| 209 |
+
|
| 210 |
+
UPDATE-style operations are rejected. Results capped at `limit` rows
|
| 211 |
+
(max 200). Returns columns + rows, plus a boolean for ASK queries.
|
| 212 |
+
"""
|
| 213 |
+
started = time.perf_counter()
|
| 214 |
+
inp = SparqlQueryInput(query=query, limit=max(1, min(200, int(limit))))
|
| 215 |
+
out = tool_sparql_query(inp)
|
| 216 |
+
ctx.deps.tool_calls.append(
|
| 217 |
+
{
|
| 218 |
+
"tool": "sparql_query",
|
| 219 |
+
"query": query[:300],
|
| 220 |
+
"row_count": out.row_count,
|
| 221 |
+
"truncated": out.truncated,
|
| 222 |
+
"boolean": out.boolean,
|
| 223 |
+
"error": out.error,
|
| 224 |
+
"duration_ms": round((time.perf_counter() - started) * 1000, 1),
|
| 225 |
+
"timestamp": datetime.now().isoformat(),
|
| 226 |
+
}
|
| 227 |
+
)
|
| 228 |
+
return out.model_dump(mode="python")
|
| 229 |
+
|
| 230 |
+
|
| 231 |
+
@agent.tool(retries=2, prepare=cap_prepare)
|
| 232 |
+
@limit_tool_calls("repo_info_batch", cap=4)
|
| 233 |
+
async def repo_info_batch(
|
| 234 |
+
ctx: RunContext[AgentState],
|
| 235 |
+
urls: List[str],
|
| 236 |
+
) -> List[dict]:
|
| 237 |
+
"""Fetch repository summaries for multiple repositories in parallel."""
|
| 238 |
+
started = time.perf_counter()
|
| 239 |
+
|
| 240 |
+
if not urls:
|
| 241 |
+
return []
|
| 242 |
+
|
| 243 |
+
normalized: List[str] = []
|
| 244 |
+
skipped: List[dict] = []
|
| 245 |
+
seen: set[str] = set()
|
| 246 |
+
for raw in urls:
|
| 247 |
+
norm = coerce_github_url_or_none(raw)
|
| 248 |
+
if not norm:
|
| 249 |
+
skipped.append(
|
| 250 |
+
{
|
| 251 |
+
"url": raw,
|
| 252 |
+
"skipped": True,
|
| 253 |
+
"reason": "NON_GITHUB_URL",
|
| 254 |
+
}
|
| 255 |
+
)
|
| 256 |
+
continue
|
| 257 |
+
if norm in seen:
|
| 258 |
+
continue
|
| 259 |
+
seen.add(norm)
|
| 260 |
+
normalized.append(norm)
|
| 261 |
+
|
| 262 |
+
tasks = [tool_repo_summary(RepoSummaryInput(url=u)) for u in normalized]
|
| 263 |
+
outcomes = await asyncio.gather(*tasks, return_exceptions=True)
|
| 264 |
+
|
| 265 |
+
results: List[dict] = []
|
| 266 |
+
for url, outcome in zip(normalized, outcomes):
|
| 267 |
+
if isinstance(outcome, Exception):
|
| 268 |
+
results.append(
|
| 269 |
+
{
|
| 270 |
+
"url": url,
|
| 271 |
+
"source": "error",
|
| 272 |
+
"error": str(outcome),
|
| 273 |
+
}
|
| 274 |
+
)
|
| 275 |
+
continue
|
| 276 |
+
payload = outcome.model_dump(mode="python")
|
| 277 |
+
payload["url"] = url
|
| 278 |
+
results.append(payload)
|
| 279 |
+
|
| 280 |
+
if skipped:
|
| 281 |
+
results.extend(skipped)
|
| 282 |
+
|
| 283 |
+
ctx.deps.tool_calls.append(
|
| 284 |
+
{
|
| 285 |
+
"tool": "repo_info_batch",
|
| 286 |
+
"requested": len(urls),
|
| 287 |
+
"normalized": len(normalized),
|
| 288 |
+
"returned": len(results),
|
| 289 |
+
"duration_ms": round((time.perf_counter() - started) * 1000, 1),
|
| 290 |
+
"timestamp": datetime.now().isoformat(),
|
| 291 |
+
}
|
| 292 |
+
)
|
| 293 |
+
|
| 294 |
+
return results
|
| 295 |
+
|
| 296 |
+
|
| 297 |
+
# ---------------------------------------------------------------------------
|
| 298 |
+
# High level entry point: run the agent on (text query + image)
|
| 299 |
+
# ---------------------------------------------------------------------------
|
| 300 |
+
def run_agent(
|
| 301 |
+
task: str,
|
| 302 |
+
image_paths: List[str],
|
| 303 |
+
excluded: List[str] | None = None,
|
| 304 |
+
conversation_history: List[str] | None = None,
|
| 305 |
+
*,
|
| 306 |
+
image_bytes: bytes | None = None,
|
| 307 |
+
model: str | None = None,
|
| 308 |
+
base_url: str | None = None,
|
| 309 |
+
api_key_env: str | None = None,
|
| 310 |
+
top_k: int | None = None,
|
| 311 |
+
num_choices: int | None = None,
|
| 312 |
+
image_metadata: str | None = None,
|
| 313 |
+
) -> AgentToolSelection:
|
| 314 |
+
"""
|
| 315 |
+
Execute the agent for a user task and at least one image path.
|
| 316 |
+
|
| 317 |
+
- derive canonical original_formats (tiff / dicom / nifti / ...)
|
| 318 |
+
- build a compact image metadata summary (or use pre-computed one)
|
| 319 |
+
- pass both to the LLM as hidden context
|
| 320 |
+
- store image_paths/original_formats in deps so retrieval tools can use them
|
| 321 |
+
- optionally allow runtime model/base_url/top_k/num_choices overrides
|
| 322 |
+
|
| 323 |
+
IMPORTANT:
|
| 324 |
+
The model only sees an actual image if `image_bytes` is provided.
|
| 325 |
+
`image_paths` are used for metadata + tool context only.
|
| 326 |
+
"""
|
| 327 |
+
run_started = time.perf_counter()
|
| 328 |
+
# image_paths may now be empty — the agent runs in text-only mode and
|
| 329 |
+
# skips the VLM. Retrieval still works on the text query alone.
|
| 330 |
+
image_paths = list(image_paths or [])
|
| 331 |
+
|
| 332 |
+
tool_logs: List[ToolRunLog] = []
|
| 333 |
+
|
| 334 |
+
# ---- 1) Derive image-based metadata and format hints --------------------
|
| 335 |
+
metadata_started = time.perf_counter()
|
| 336 |
+
meta_str = (
|
| 337 |
+
image_metadata
|
| 338 |
+
if image_metadata is not None
|
| 339 |
+
else (summarize_image_metadata(image_paths) or "")
|
| 340 |
+
)
|
| 341 |
+
fmt_str = detect_ext_token(image_paths) or ""
|
| 342 |
+
original_formats = [t.lower() for t in fmt_str.split()] if fmt_str else []
|
| 343 |
+
metadata_duration_ms = round((time.perf_counter() - metadata_started) * 1000, 1)
|
| 344 |
+
|
| 345 |
+
effective_top_k = top_k if top_k is not None else 12
|
| 346 |
+
effective_num_choices = num_choices if num_choices is not None else 3
|
| 347 |
+
|
| 348 |
+
# ---- 2) Prepare dependency state passed to all tools --------------------
|
| 349 |
+
deps = AgentState(
|
| 350 |
+
excluded_tools=excluded or [],
|
| 351 |
+
override_model=model,
|
| 352 |
+
override_base_url=base_url,
|
| 353 |
+
override_top_k=effective_top_k,
|
| 354 |
+
override_num_choices=effective_num_choices,
|
| 355 |
+
)
|
| 356 |
+
|
| 357 |
+
setattr(deps, "image_paths", list(image_paths))
|
| 358 |
+
setattr(deps, "original_formats", original_formats)
|
| 359 |
+
|
| 360 |
+
# ---- 3) Hidden metadata lines for the model ----------------------------
|
| 361 |
+
hidden_meta = ""
|
| 362 |
+
if original_formats:
|
| 363 |
+
hidden_meta += "\n(Formats Hint: " + ",".join(original_formats) + ")"
|
| 364 |
+
if meta_str:
|
| 365 |
+
short_meta = " ".join(x.strip() for x in meta_str.splitlines() if x.strip())
|
| 366 |
+
hidden_meta += (
|
| 367 |
+
"\n(Image Metadata: "
|
| 368 |
+
+ short_meta[:500]
|
| 369 |
+
+ ("…" if len(short_meta) > 500 else "")
|
| 370 |
+
+ ")"
|
| 371 |
+
)
|
| 372 |
+
hidden_meta += f"\n(Search top_k: {effective_top_k})"
|
| 373 |
+
|
| 374 |
+
extra_context = "\n\n**CRITICAL: Analyze the attached preview image showing the user's data.**\nUse visual observations (anatomy visible, image quality, dimensionality, contrast) combined with the metadata below to recommend tools. Reference what you see in your explanations."
|
| 375 |
+
|
| 376 |
+
# ---- 4) Build the prompt (optionally including history) ----------------
|
| 377 |
+
if conversation_history and len(conversation_history) > 0:
|
| 378 |
+
history_text = "\n".join(conversation_history)
|
| 379 |
+
prompt = (
|
| 380 |
+
f"Previous conversation:\n{history_text}\n\n"
|
| 381 |
+
f"Current request: {task}{extra_context}{hidden_meta}"
|
| 382 |
+
)
|
| 383 |
+
else:
|
| 384 |
+
prompt = task + extra_context + hidden_meta
|
| 385 |
+
|
| 386 |
+
# -----------------------------------------------------------------------
|
| 387 |
+
# Determine which agent instance to use
|
| 388 |
+
# -----------------------------------------------------------------------
|
| 389 |
+
agent_instance = agent
|
| 390 |
+
effective_num_choices = num_choices if num_choices is not None else 3
|
| 391 |
+
effective_model = model if model else agent_model_config.name
|
| 392 |
+
effective_top_k = top_k if top_k is not None else 12
|
| 393 |
+
|
| 394 |
+
# When model is provided from UI, base_url comes with it (can be None for OpenAI)
|
| 395 |
+
if model:
|
| 396 |
+
# Use api_key_env from config if provided, otherwise default to OPENAI_API_KEY
|
| 397 |
+
key_env_name = api_key_env if api_key_env else "OPENAI_API_KEY"
|
| 398 |
+
runtime_api_key = os.getenv(key_env_name)
|
| 399 |
+
if not runtime_api_key:
|
| 400 |
+
raise ValueError(
|
| 401 |
+
f"{key_env_name} not found in environment. Cannot use this model."
|
| 402 |
+
)
|
| 403 |
+
effective_base_url = base_url # Can be None for OpenAI
|
| 404 |
+
log.info(f"✓ Using {key_env_name} for model {effective_model}")
|
| 405 |
+
log.debug(f"{key_env_name} is set: {bool(runtime_api_key)}")
|
| 406 |
+
else:
|
| 407 |
+
# No model override - use config defaults
|
| 408 |
+
effective_base_url = agent_model_config.base_url
|
| 409 |
+
runtime_api_key = api_key # Already loaded from config at startup
|
| 410 |
+
log.info(f"✓ Using API key from config for model {effective_model}")
|
| 411 |
+
|
| 412 |
+
# Log runtime configuration
|
| 413 |
+
endpoint_display = effective_base_url if effective_base_url else "api.openai.com"
|
| 414 |
+
log.info(
|
| 415 |
+
f"🤖 Agent execution - Model: {effective_model}, endpoint: {endpoint_display}, "
|
| 416 |
+
f"top_k: {effective_top_k}, num_choices: {effective_num_choices}, excluded: {len(excluded or [])}"
|
| 417 |
+
)
|
| 418 |
+
|
| 419 |
+
needs_dynamic_agent = model is not None
|
| 420 |
+
|
| 421 |
+
if needs_dynamic_agent:
|
| 422 |
+
cache_key = (effective_model, effective_base_url or "", api_key_env or "OPENAI_API_KEY", effective_num_choices)
|
| 423 |
+
with _AGENT_CACHE_LOCK:
|
| 424 |
+
agent_instance = _AGENT_CACHE.get(cache_key)
|
| 425 |
+
if agent_instance is not None:
|
| 426 |
+
_AGENT_CACHE.move_to_end(cache_key)
|
| 427 |
+
if agent_instance is None:
|
| 428 |
+
log.info(
|
| 429 |
+
f"📦 Creating runtime agent with model={effective_model}, endpoint={effective_base_url or 'api.openai.com'}"
|
| 430 |
+
)
|
| 431 |
+
|
| 432 |
+
runtime_provider = OpenAIProvider(
|
| 433 |
+
base_url=effective_base_url,
|
| 434 |
+
api_key=runtime_api_key,
|
| 435 |
+
)
|
| 436 |
+
|
| 437 |
+
# Use OpenAIChatModel (chat/completions) for custom endpoints, OpenAIResponsesModel for default OpenAI
|
| 438 |
+
if effective_base_url:
|
| 439 |
+
log.info("Using OpenAIChatModel (chat/completions API) for custom endpoint")
|
| 440 |
+
runtime_model = OpenAIChatModel(
|
| 441 |
+
model_name=effective_model, provider=runtime_provider
|
| 442 |
+
)
|
| 443 |
+
else:
|
| 444 |
+
runtime_model = OpenAIResponsesModel(
|
| 445 |
+
model_name=effective_model, provider=runtime_provider
|
| 446 |
+
)
|
| 447 |
+
|
| 448 |
+
agent_instance = Agent(
|
| 449 |
+
model=runtime_model,
|
| 450 |
+
system_prompt=get_agent_system_prompt(effective_num_choices),
|
| 451 |
+
deps_type=AgentState,
|
| 452 |
+
output_retries=int(os.getenv("AGENT_OUTPUT_RETRIES", "3")),
|
| 453 |
+
)
|
| 454 |
+
|
| 455 |
+
# Register tools on the dynamic agent
|
| 456 |
+
agent_instance.tool(search_tools, retries=2, prepare=cap_prepare)
|
| 457 |
+
agent_instance.tool(search_alternative, retries=2, prepare=cap_prepare)
|
| 458 |
+
agent_instance.tool(repo_info_batch, retries=2, prepare=cap_prepare)
|
| 459 |
+
agent_instance.tool(sparql_query, retries=1, prepare=cap_prepare)
|
| 460 |
+
|
| 461 |
+
with _AGENT_CACHE_LOCK:
|
| 462 |
+
_AGENT_CACHE[cache_key] = agent_instance
|
| 463 |
+
_AGENT_CACHE.move_to_end(cache_key)
|
| 464 |
+
while len(_AGENT_CACHE) > _AGENT_CACHE_MAX:
|
| 465 |
+
_AGENT_CACHE.popitem(last=False)
|
| 466 |
+
else:
|
| 467 |
+
log.info(
|
| 468 |
+
f"♻️ Reusing cached dynamic agent (model: {effective_model}, num_choices: {effective_num_choices})"
|
| 469 |
+
)
|
| 470 |
+
|
| 471 |
+
elif (
|
| 472 |
+
num_choices is not None and num_choices != DEFAULT_NUM_CHOICES
|
| 473 |
+
):
|
| 474 |
+
cache_key = (effective_model, effective_base_url or "", api_key_env or "OPENAI_API_KEY", effective_num_choices)
|
| 475 |
+
with _AGENT_CACHE_LOCK:
|
| 476 |
+
agent_instance = _AGENT_CACHE.get(cache_key)
|
| 477 |
+
if agent_instance is not None:
|
| 478 |
+
_AGENT_CACHE.move_to_end(cache_key)
|
| 479 |
+
if agent_instance is None:
|
| 480 |
+
log.info(
|
| 481 |
+
f"📦 Creating runtime agent with num_choices={effective_num_choices} (model: {effective_model})"
|
| 482 |
+
)
|
| 483 |
+
agent_instance = Agent(
|
| 484 |
+
model=openai_model,
|
| 485 |
+
system_prompt=get_agent_system_prompt(effective_num_choices),
|
| 486 |
+
deps_type=AgentState,
|
| 487 |
+
output_retries=int(os.getenv("AGENT_OUTPUT_RETRIES", "3")),
|
| 488 |
+
)
|
| 489 |
+
|
| 490 |
+
# Register tools on the dynamic agent
|
| 491 |
+
agent_instance.tool(search_tools, retries=2, prepare=cap_prepare)
|
| 492 |
+
agent_instance.tool(search_alternative, retries=2, prepare=cap_prepare)
|
| 493 |
+
agent_instance.tool(repo_info_batch, retries=2, prepare=cap_prepare)
|
| 494 |
+
agent_instance.tool(sparql_query, retries=1, prepare=cap_prepare)
|
| 495 |
+
|
| 496 |
+
with _AGENT_CACHE_LOCK:
|
| 497 |
+
_AGENT_CACHE[cache_key] = agent_instance
|
| 498 |
+
_AGENT_CACHE.move_to_end(cache_key)
|
| 499 |
+
while len(_AGENT_CACHE) > _AGENT_CACHE_MAX:
|
| 500 |
+
_AGENT_CACHE.popitem(last=False)
|
| 501 |
+
else:
|
| 502 |
+
log.info(
|
| 503 |
+
f"♻️ Reusing cached dynamic agent with num_choices={effective_num_choices} (model: {effective_model})"
|
| 504 |
+
)
|
| 505 |
+
|
| 506 |
+
else:
|
| 507 |
+
log.info(
|
| 508 |
+
f"♻️ Using global agent (model: {effective_model}, num_choices: {effective_num_choices})"
|
| 509 |
+
)
|
| 510 |
+
|
| 511 |
+
log.debug(
|
| 512 |
+
f"Prompt length: {len(prompt)} chars, has_image_paths: {bool(image_paths)}, has_image_bytes: {bool(image_bytes)}"
|
| 513 |
+
)
|
| 514 |
+
|
| 515 |
+
# ---- 5) Build multimodal prompt if image bytes provided ----------------
|
| 516 |
+
if image_bytes:
|
| 517 |
+
log.info(
|
| 518 |
+
f"🖼️ Sending image preview to model ({len(image_bytes)} bytes = {len(image_bytes)/1024:.1f}KB)"
|
| 519 |
+
)
|
| 520 |
+
user_prompt = [
|
| 521 |
+
prompt,
|
| 522 |
+
BinaryContent(
|
| 523 |
+
data=image_bytes,
|
| 524 |
+
media_type="image/png",
|
| 525 |
+
),
|
| 526 |
+
]
|
| 527 |
+
else:
|
| 528 |
+
log.warning(
|
| 529 |
+
"⚠️ No image bytes provided - the model will not see the image preview"
|
| 530 |
+
)
|
| 531 |
+
user_prompt = prompt
|
| 532 |
+
|
| 533 |
+
# ---- 6) Run the agent --------------------------------------------------
|
| 534 |
+
try:
|
| 535 |
+
llm_started = time.perf_counter()
|
| 536 |
+
run_result = agent_instance.run_sync(
|
| 537 |
+
user_prompt,
|
| 538 |
+
deps=deps,
|
| 539 |
+
output_type=ToolSelection,
|
| 540 |
+
usage_limits=UsageLimits(tool_calls_limit=20),
|
| 541 |
+
)
|
| 542 |
+
llm_duration_ms = round((time.perf_counter() - llm_started) * 1000, 1)
|
| 543 |
+
result = run_result.output
|
| 544 |
+
|
| 545 |
+
log.info(
|
| 546 |
+
f"✅ Agent execution complete - choices returned: {len(result.choices)}"
|
| 547 |
+
)
|
| 548 |
+
|
| 549 |
+
# Log usage (helpful, but may not explicitly expose image-specific counters)
|
| 550 |
+
if run_result.usage:
|
| 551 |
+
usage = run_result.usage()
|
| 552 |
+
log.info(
|
| 553 |
+
f"📊 Usage: total_tokens={usage.total_tokens}, "
|
| 554 |
+
f"input_tokens={usage.input_tokens}, output_tokens={usage.output_tokens}"
|
| 555 |
+
)
|
| 556 |
+
|
| 557 |
+
# Warn if using non-OpenAI endpoint with images
|
| 558 |
+
if image_bytes and effective_base_url:
|
| 559 |
+
log.warning(
|
| 560 |
+
"⚠️ Using custom endpoint - confirm the selected model supports vision."
|
| 561 |
+
)
|
| 562 |
+
|
| 563 |
+
except Exception as e:
|
| 564 |
+
# Handle global tool quota limit (UsageLimitExceeded) and other errors gracefully
|
| 565 |
+
error_msg = str(e)
|
| 566 |
+
llm_duration_ms = round((time.perf_counter() - llm_started) * 1000, 1)
|
| 567 |
+
log.warning(f"⚠️ Agent execution encountered an error: {error_msg}")
|
| 568 |
+
run_result = None # Ensure run_result is defined for usage stats extraction
|
| 569 |
+
|
| 570 |
+
# Check if this is a usage limit error (global tool quota)
|
| 571 |
+
if (
|
| 572 |
+
"UsageLimitExceeded" in str(type(e).__name__)
|
| 573 |
+
or "tool_calls_limit" in error_msg.lower()
|
| 574 |
+
):
|
| 575 |
+
log.warning(
|
| 576 |
+
"Global tool call quota reached - continuing with partial results"
|
| 577 |
+
)
|
| 578 |
+
|
| 579 |
+
result = ToolSelection(
|
| 580 |
+
conversation=Conversation(
|
| 581 |
+
status=ConversationStatus.COMPLETE,
|
| 582 |
+
context="The agent reached the maximum number of tool calls allowed. Please try a more specific query or break down your request into smaller parts.",
|
| 583 |
+
question=None,
|
| 584 |
+
options=None,
|
| 585 |
+
),
|
| 586 |
+
choices=[],
|
| 587 |
+
explanation="Tool call limit reached during execution. Try refining your query.",
|
| 588 |
+
reason=None,
|
| 589 |
+
)
|
| 590 |
+
else:
|
| 591 |
+
raise
|
| 592 |
+
|
| 593 |
+
# ---- 7) Convert raw tool call records into ToolRunLog objects ----------
|
| 594 |
+
for tc in getattr(deps, "tool_calls", []):
|
| 595 |
+
tool_name = tc.get("tool")
|
| 596 |
+
timestamp = tc.get("timestamp")
|
| 597 |
+
error = tc.get("error")
|
| 598 |
+
inputs = {
|
| 599 |
+
k: v for k, v in tc.items() if k not in ("tool", "timestamp", "error")
|
| 600 |
+
}
|
| 601 |
+
tool_logs.append(
|
| 602 |
+
ToolRunLog(
|
| 603 |
+
tool=tool_name,
|
| 604 |
+
inputs=inputs,
|
| 605 |
+
timestamp=timestamp,
|
| 606 |
+
error=error,
|
| 607 |
+
)
|
| 608 |
+
)
|
| 609 |
+
|
| 610 |
+
stage_counts: dict[str, int] = {}
|
| 611 |
+
stage_durations: dict[str, float] = {}
|
| 612 |
+
for tc in getattr(deps, "tool_calls", []):
|
| 613 |
+
name = tc.get("tool", "unknown")
|
| 614 |
+
stage_counts[name] = stage_counts.get(name, 0) + 1
|
| 615 |
+
duration_ms = tc.get("duration_ms")
|
| 616 |
+
if isinstance(duration_ms, (int, float)):
|
| 617 |
+
stage_durations[name] = stage_durations.get(name, 0.0) + float(duration_ms)
|
| 618 |
+
|
| 619 |
+
total_duration_ms = round((time.perf_counter() - run_started) * 1000, 1)
|
| 620 |
+
log.info(
|
| 621 |
+
"⏱️ Latency summary: total_ms=%s metadata_ms=%s llm_ms=%s tools=%s tool_ms=%s",
|
| 622 |
+
total_duration_ms,
|
| 623 |
+
metadata_duration_ms,
|
| 624 |
+
llm_duration_ms,
|
| 625 |
+
stage_counts,
|
| 626 |
+
{k: round(v, 1) for k, v in stage_durations.items()},
|
| 627 |
+
)
|
| 628 |
+
|
| 629 |
+
# ---- 8) Extract usage statistics if available -------------------------
|
| 630 |
+
usage_stats = None
|
| 631 |
+
if run_result and hasattr(run_result, "usage") and run_result.usage:
|
| 632 |
+
usage = run_result.usage()
|
| 633 |
+
usage_stats = UsageStats(
|
| 634 |
+
total_tokens=usage.total_tokens,
|
| 635 |
+
input_tokens=usage.input_tokens,
|
| 636 |
+
output_tokens=usage.output_tokens,
|
| 637 |
+
)
|
| 638 |
+
|
| 639 |
+
# ---- 9) Wrap into high-level AgentToolSelection ------------------------
|
| 640 |
+
return AgentToolSelection(
|
| 641 |
+
conversation=result.conversation,
|
| 642 |
+
choices=result.choices,
|
| 643 |
+
explanation=result.explanation,
|
| 644 |
+
reason=result.reason,
|
| 645 |
+
tool_calls=tool_logs,
|
| 646 |
+
usage=usage_stats,
|
| 647 |
+
)
|
| 648 |
+
|
| 649 |
+
|
| 650 |
+
__all__ = ["run_agent", "agent"]
|
src/ai_agent/agent/tools/__init__.py
CHANGED
|
@@ -1,38 +1,38 @@
|
|
| 1 |
-
"""Agent tools package."""
|
| 2 |
-
|
| 3 |
-
# Only export registry - tools will self-register when imported explicitly
|
| 4 |
-
from .mcp import (
|
| 5 |
-
TOOL_REGISTRY,
|
| 6 |
-
get_tool,
|
| 7 |
-
register_tool,
|
| 8 |
-
list_tools,
|
| 9 |
-
ensure_mcp_tools_registered,
|
| 10 |
-
)
|
| 11 |
-
|
| 12 |
-
# Import tools lazily to avoid loading heavy dependencies at package import
|
| 13 |
-
# Tools should be imported explicitly where needed, e.g.:
|
| 14 |
-
# from ai_agent.agent.tools.mcp.lungs_segmentation_tool import tool_lungs_segmentation
|
| 15 |
-
|
| 16 |
-
__all__ = [
|
| 17 |
-
"TOOL_REGISTRY",
|
| 18 |
-
"get_tool",
|
| 19 |
-
"register_tool",
|
| 20 |
-
"list_tools",
|
| 21 |
-
"ensure_tools_registered",
|
| 22 |
-
]
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
def ensure_tools_registered():
|
| 26 |
-
"""
|
| 27 |
-
Import all tools to trigger their registration.
|
| 28 |
-
Call this once at app startup.
|
| 29 |
-
"""
|
| 30 |
-
from importlib import import_module
|
| 31 |
-
|
| 32 |
-
import_module("ai_agent.agent.tools.search_tool")
|
| 33 |
-
import_module("ai_agent.agent.tools.search_alternative_tool")
|
| 34 |
-
import_module("ai_agent.agent.tools.repo_info_tool")
|
| 35 |
-
import_module("ai_agent.agent.tools.gradio_space_tool")
|
| 36 |
-
|
| 37 |
-
# Import MCP tools
|
| 38 |
-
ensure_mcp_tools_registered()
|
|
|
|
| 1 |
+
"""Agent tools package."""
|
| 2 |
+
|
| 3 |
+
# Only export registry - tools will self-register when imported explicitly
|
| 4 |
+
from .mcp import (
|
| 5 |
+
TOOL_REGISTRY,
|
| 6 |
+
get_tool,
|
| 7 |
+
register_tool,
|
| 8 |
+
list_tools,
|
| 9 |
+
ensure_mcp_tools_registered,
|
| 10 |
+
)
|
| 11 |
+
|
| 12 |
+
# Import tools lazily to avoid loading heavy dependencies at package import
|
| 13 |
+
# Tools should be imported explicitly where needed, e.g.:
|
| 14 |
+
# from ai_agent.agent.tools.mcp.lungs_segmentation_tool import tool_lungs_segmentation
|
| 15 |
+
|
| 16 |
+
__all__ = [
|
| 17 |
+
"TOOL_REGISTRY",
|
| 18 |
+
"get_tool",
|
| 19 |
+
"register_tool",
|
| 20 |
+
"list_tools",
|
| 21 |
+
"ensure_tools_registered",
|
| 22 |
+
]
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def ensure_tools_registered():
|
| 26 |
+
"""
|
| 27 |
+
Import all tools to trigger their registration.
|
| 28 |
+
Call this once at app startup.
|
| 29 |
+
"""
|
| 30 |
+
from importlib import import_module
|
| 31 |
+
|
| 32 |
+
import_module("ai_agent.agent.tools.search_tool")
|
| 33 |
+
import_module("ai_agent.agent.tools.search_alternative_tool")
|
| 34 |
+
import_module("ai_agent.agent.tools.repo_info_tool")
|
| 35 |
+
import_module("ai_agent.agent.tools.gradio_space_tool")
|
| 36 |
+
|
| 37 |
+
# Import MCP tools
|
| 38 |
+
ensure_mcp_tools_registered()
|
src/ai_agent/agent/tools/deepwiki_tool.py
CHANGED
|
@@ -1,103 +1,103 @@
|
|
| 1 |
-
from __future__ import annotations
|
| 2 |
-
|
| 3 |
-
import asyncio
|
| 4 |
-
import logging
|
| 5 |
-
import os
|
| 6 |
-
from typing import Optional
|
| 7 |
-
|
| 8 |
-
from pydantic import BaseModel
|
| 9 |
-
from pydantic_ai.mcp import MCPServerStreamableHTTP
|
| 10 |
-
|
| 11 |
-
from ai_agent.agent.tools.utils import _clip
|
| 12 |
-
from ai_agent.agent.utils import _coerce_owner_repo_ref
|
| 13 |
-
|
| 14 |
-
log = logging.getLogger("agent.deepwiki")
|
| 15 |
-
|
| 16 |
-
# DeepWiki MCP server endpoint (Streamable HTTP transport)
|
| 17 |
-
DEEPWIKI_HTTP_URL = "https://mcp.deepwiki.com/mcp"
|
| 18 |
-
|
| 19 |
-
# Timeout for DeepWiki operations (seconds)
|
| 20 |
-
DEEPWIKI_TIMEOUT = int(os.getenv("DEEPWIKI_TIMEOUT", "20"))
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
class DeepWikiInput(BaseModel):
|
| 24 |
-
"""Input for DeepWiki operations."""
|
| 25 |
-
|
| 26 |
-
url: str # GitHub repository URL or owner/repo format
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
class DeepWikiContentsOutput(BaseModel):
|
| 30 |
-
"""Output from read_wiki_contents."""
|
| 31 |
-
|
| 32 |
-
success: bool
|
| 33 |
-
contents: Optional[str] = None
|
| 34 |
-
error: Optional[str] = None
|
| 35 |
-
truncated: bool = False
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
async def get_wiki_contents(input: DeepWikiInput) -> DeepWikiContentsOutput:
|
| 39 |
-
"""
|
| 40 |
-
Fetch repo docs from DeepWiki MCP (Streamable HTTP) and return a clipped string
|
| 41 |
-
to keep LLM token usage under control.
|
| 42 |
-
"""
|
| 43 |
-
owner, repo, _ = _coerce_owner_repo_ref(input.url)
|
| 44 |
-
repo = f"{owner}/{repo}"
|
| 45 |
-
|
| 46 |
-
try:
|
| 47 |
-
server = MCPServerStreamableHTTP(DEEPWIKI_HTTP_URL)
|
| 48 |
-
|
| 49 |
-
async with server:
|
| 50 |
-
result = await asyncio.wait_for(
|
| 51 |
-
server.direct_call_tool("read_wiki_contents", {"repoName": repo}),
|
| 52 |
-
timeout=DEEPWIKI_TIMEOUT,
|
| 53 |
-
)
|
| 54 |
-
|
| 55 |
-
# Handle different result types from MCP
|
| 56 |
-
text = None
|
| 57 |
-
if isinstance(result, str):
|
| 58 |
-
# Direct string result
|
| 59 |
-
text = result
|
| 60 |
-
elif hasattr(result, "content"):
|
| 61 |
-
# MCP ToolResult with content field
|
| 62 |
-
text_parts = []
|
| 63 |
-
for item in result.content:
|
| 64 |
-
if hasattr(item, "text"):
|
| 65 |
-
text_parts.append(item.text)
|
| 66 |
-
elif isinstance(item, str):
|
| 67 |
-
text_parts.append(item)
|
| 68 |
-
text = "\n".join(text_parts) if text_parts else None
|
| 69 |
-
elif isinstance(result, list):
|
| 70 |
-
# List of strings or content items
|
| 71 |
-
text = "\n".join([str(p) for p in result if p]) or None
|
| 72 |
-
|
| 73 |
-
if text and text.strip():
|
| 74 |
-
clipped_text, truncated = _clip(text.strip())
|
| 75 |
-
return DeepWikiContentsOutput(
|
| 76 |
-
success=True,
|
| 77 |
-
contents=clipped_text,
|
| 78 |
-
truncated=truncated,
|
| 79 |
-
)
|
| 80 |
-
|
| 81 |
-
return DeepWikiContentsOutput(
|
| 82 |
-
success=False, error="No content returned from DeepWiki"
|
| 83 |
-
)
|
| 84 |
-
|
| 85 |
-
except asyncio.TimeoutError:
|
| 86 |
-
log.warning(f"DeepWiki timed out after {DEEPWIKI_TIMEOUT}s for {repo}")
|
| 87 |
-
return DeepWikiContentsOutput(
|
| 88 |
-
success=False,
|
| 89 |
-
error=f"DeepWiki request timed out after {DEEPWIKI_TIMEOUT}s",
|
| 90 |
-
)
|
| 91 |
-
except Exception as e:
|
| 92 |
-
log.error(f"Failed to get wiki contents for {repo}: {e}")
|
| 93 |
-
return DeepWikiContentsOutput(
|
| 94 |
-
success=False,
|
| 95 |
-
error=f"Failed to connect to DeepWiki: {str(e)}",
|
| 96 |
-
)
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
__all__ = [
|
| 100 |
-
"get_wiki_contents",
|
| 101 |
-
"DeepWikiInput",
|
| 102 |
-
"DeepWikiContentsOutput",
|
| 103 |
-
]
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import asyncio
|
| 4 |
+
import logging
|
| 5 |
+
import os
|
| 6 |
+
from typing import Optional
|
| 7 |
+
|
| 8 |
+
from pydantic import BaseModel
|
| 9 |
+
from pydantic_ai.mcp import MCPServerStreamableHTTP
|
| 10 |
+
|
| 11 |
+
from ai_agent.agent.tools.utils import _clip
|
| 12 |
+
from ai_agent.agent.utils import _coerce_owner_repo_ref
|
| 13 |
+
|
| 14 |
+
log = logging.getLogger("agent.deepwiki")
|
| 15 |
+
|
| 16 |
+
# DeepWiki MCP server endpoint (Streamable HTTP transport)
|
| 17 |
+
DEEPWIKI_HTTP_URL = "https://mcp.deepwiki.com/mcp"
|
| 18 |
+
|
| 19 |
+
# Timeout for DeepWiki operations (seconds)
|
| 20 |
+
DEEPWIKI_TIMEOUT = int(os.getenv("DEEPWIKI_TIMEOUT", "20"))
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
class DeepWikiInput(BaseModel):
|
| 24 |
+
"""Input for DeepWiki operations."""
|
| 25 |
+
|
| 26 |
+
url: str # GitHub repository URL or owner/repo format
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
class DeepWikiContentsOutput(BaseModel):
|
| 30 |
+
"""Output from read_wiki_contents."""
|
| 31 |
+
|
| 32 |
+
success: bool
|
| 33 |
+
contents: Optional[str] = None
|
| 34 |
+
error: Optional[str] = None
|
| 35 |
+
truncated: bool = False
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
async def get_wiki_contents(input: DeepWikiInput) -> DeepWikiContentsOutput:
|
| 39 |
+
"""
|
| 40 |
+
Fetch repo docs from DeepWiki MCP (Streamable HTTP) and return a clipped string
|
| 41 |
+
to keep LLM token usage under control.
|
| 42 |
+
"""
|
| 43 |
+
owner, repo, _ = _coerce_owner_repo_ref(input.url)
|
| 44 |
+
repo = f"{owner}/{repo}"
|
| 45 |
+
|
| 46 |
+
try:
|
| 47 |
+
server = MCPServerStreamableHTTP(DEEPWIKI_HTTP_URL)
|
| 48 |
+
|
| 49 |
+
async with server:
|
| 50 |
+
result = await asyncio.wait_for(
|
| 51 |
+
server.direct_call_tool("read_wiki_contents", {"repoName": repo}),
|
| 52 |
+
timeout=DEEPWIKI_TIMEOUT,
|
| 53 |
+
)
|
| 54 |
+
|
| 55 |
+
# Handle different result types from MCP
|
| 56 |
+
text = None
|
| 57 |
+
if isinstance(result, str):
|
| 58 |
+
# Direct string result
|
| 59 |
+
text = result
|
| 60 |
+
elif hasattr(result, "content"):
|
| 61 |
+
# MCP ToolResult with content field
|
| 62 |
+
text_parts = []
|
| 63 |
+
for item in result.content:
|
| 64 |
+
if hasattr(item, "text"):
|
| 65 |
+
text_parts.append(item.text)
|
| 66 |
+
elif isinstance(item, str):
|
| 67 |
+
text_parts.append(item)
|
| 68 |
+
text = "\n".join(text_parts) if text_parts else None
|
| 69 |
+
elif isinstance(result, list):
|
| 70 |
+
# List of strings or content items
|
| 71 |
+
text = "\n".join([str(p) for p in result if p]) or None
|
| 72 |
+
|
| 73 |
+
if text and text.strip():
|
| 74 |
+
clipped_text, truncated = _clip(text.strip())
|
| 75 |
+
return DeepWikiContentsOutput(
|
| 76 |
+
success=True,
|
| 77 |
+
contents=clipped_text,
|
| 78 |
+
truncated=truncated,
|
| 79 |
+
)
|
| 80 |
+
|
| 81 |
+
return DeepWikiContentsOutput(
|
| 82 |
+
success=False, error="No content returned from DeepWiki"
|
| 83 |
+
)
|
| 84 |
+
|
| 85 |
+
except asyncio.TimeoutError:
|
| 86 |
+
log.warning(f"DeepWiki timed out after {DEEPWIKI_TIMEOUT}s for {repo}")
|
| 87 |
+
return DeepWikiContentsOutput(
|
| 88 |
+
success=False,
|
| 89 |
+
error=f"DeepWiki request timed out after {DEEPWIKI_TIMEOUT}s",
|
| 90 |
+
)
|
| 91 |
+
except Exception as e:
|
| 92 |
+
log.error(f"Failed to get wiki contents for {repo}: {e}")
|
| 93 |
+
return DeepWikiContentsOutput(
|
| 94 |
+
success=False,
|
| 95 |
+
error=f"Failed to connect to DeepWiki: {str(e)}",
|
| 96 |
+
)
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
__all__ = [
|
| 100 |
+
"get_wiki_contents",
|
| 101 |
+
"DeepWikiInput",
|
| 102 |
+
"DeepWikiContentsOutput",
|
| 103 |
+
]
|
src/ai_agent/agent/tools/mcp/__init__.py
CHANGED
|
@@ -1,53 +1,53 @@
|
|
| 1 |
-
"""
|
| 2 |
-
MCP (Model Context Protocol) tools package.
|
| 3 |
-
|
| 4 |
-
This package contains registered imaging tools that require approval
|
| 5 |
-
and follow the tool registry pattern.
|
| 6 |
-
"""
|
| 7 |
-
|
| 8 |
-
from .registry import (
|
| 9 |
-
TOOL_REGISTRY,
|
| 10 |
-
CATALOG_NAME_TO_TOOL,
|
| 11 |
-
get_tool,
|
| 12 |
-
register_tool,
|
| 13 |
-
list_tools,
|
| 14 |
-
get_tool_display_name,
|
| 15 |
-
get_tool_icon,
|
| 16 |
-
extract_preview,
|
| 17 |
-
extract_downloads,
|
| 18 |
-
extract_metadata,
|
| 19 |
-
extract_output_field,
|
| 20 |
-
ToolConfig,
|
| 21 |
-
)
|
| 22 |
-
|
| 23 |
-
from .base import BaseToolInput, BaseToolOutput, ImageToolInput
|
| 24 |
-
|
| 25 |
-
__all__ = [
|
| 26 |
-
# Registry
|
| 27 |
-
"TOOL_REGISTRY",
|
| 28 |
-
"CATALOG_NAME_TO_TOOL",
|
| 29 |
-
"get_tool",
|
| 30 |
-
"register_tool",
|
| 31 |
-
"list_tools",
|
| 32 |
-
"get_tool_display_name",
|
| 33 |
-
"get_tool_icon",
|
| 34 |
-
"extract_preview",
|
| 35 |
-
"extract_downloads",
|
| 36 |
-
"extract_metadata",
|
| 37 |
-
"extract_output_field",
|
| 38 |
-
"ToolConfig",
|
| 39 |
-
# Base models
|
| 40 |
-
"BaseToolInput",
|
| 41 |
-
"BaseToolOutput",
|
| 42 |
-
"ImageToolInput",
|
| 43 |
-
]
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
def ensure_mcp_tools_registered():
|
| 47 |
-
"""
|
| 48 |
-
Import all MCP tools to trigger their registration.
|
| 49 |
-
Call this once at app startup.
|
| 50 |
-
"""
|
| 51 |
-
from importlib import import_module
|
| 52 |
-
|
| 53 |
-
import_module("ai_agent.agent.tools.mcp.lungs_segmentation_tool")
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
MCP (Model Context Protocol) tools package.
|
| 3 |
+
|
| 4 |
+
This package contains registered imaging tools that require approval
|
| 5 |
+
and follow the tool registry pattern.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
from .registry import (
|
| 9 |
+
TOOL_REGISTRY,
|
| 10 |
+
CATALOG_NAME_TO_TOOL,
|
| 11 |
+
get_tool,
|
| 12 |
+
register_tool,
|
| 13 |
+
list_tools,
|
| 14 |
+
get_tool_display_name,
|
| 15 |
+
get_tool_icon,
|
| 16 |
+
extract_preview,
|
| 17 |
+
extract_downloads,
|
| 18 |
+
extract_metadata,
|
| 19 |
+
extract_output_field,
|
| 20 |
+
ToolConfig,
|
| 21 |
+
)
|
| 22 |
+
|
| 23 |
+
from .base import BaseToolInput, BaseToolOutput, ImageToolInput
|
| 24 |
+
|
| 25 |
+
__all__ = [
|
| 26 |
+
# Registry
|
| 27 |
+
"TOOL_REGISTRY",
|
| 28 |
+
"CATALOG_NAME_TO_TOOL",
|
| 29 |
+
"get_tool",
|
| 30 |
+
"register_tool",
|
| 31 |
+
"list_tools",
|
| 32 |
+
"get_tool_display_name",
|
| 33 |
+
"get_tool_icon",
|
| 34 |
+
"extract_preview",
|
| 35 |
+
"extract_downloads",
|
| 36 |
+
"extract_metadata",
|
| 37 |
+
"extract_output_field",
|
| 38 |
+
"ToolConfig",
|
| 39 |
+
# Base models
|
| 40 |
+
"BaseToolInput",
|
| 41 |
+
"BaseToolOutput",
|
| 42 |
+
"ImageToolInput",
|
| 43 |
+
]
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def ensure_mcp_tools_registered():
|
| 47 |
+
"""
|
| 48 |
+
Import all MCP tools to trigger their registration.
|
| 49 |
+
Call this once at app startup.
|
| 50 |
+
"""
|
| 51 |
+
from importlib import import_module
|
| 52 |
+
|
| 53 |
+
import_module("ai_agent.agent.tools.mcp.lungs_segmentation_tool")
|
src/ai_agent/agent/tools/mcp/base.py
CHANGED
|
@@ -1,88 +1,88 @@
|
|
| 1 |
-
from __future__ import annotations
|
| 2 |
-
|
| 3 |
-
from typing import Optional, Dict, Any
|
| 4 |
-
from pydantic import BaseModel, Field
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
class BaseToolInput(BaseModel):
|
| 8 |
-
"""
|
| 9 |
-
Base input model that tools can extend.
|
| 10 |
-
|
| 11 |
-
Common patterns:
|
| 12 |
-
- image_path: Path to uploaded image/volume
|
| 13 |
-
- description: Optional context from agent
|
| 14 |
-
"""
|
| 15 |
-
|
| 16 |
-
pass # Intentionally minimal - tools define their own inputs
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
class BaseToolOutput(BaseModel):
|
| 20 |
-
"""
|
| 21 |
-
Base output model that all tools should follow.
|
| 22 |
-
|
| 23 |
-
This ensures consistent handling in the UI layer without
|
| 24 |
-
needing tool-specific code.
|
| 25 |
-
|
| 26 |
-
Standard fields:
|
| 27 |
-
- success: bool - Whether execution succeeded
|
| 28 |
-
- error: Optional[str] - Error message if failed
|
| 29 |
-
- compute_time_seconds: float - Time taken by tool
|
| 30 |
-
- notes: Optional[str] - Additional info for user
|
| 31 |
-
|
| 32 |
-
File outputs (at least one should be provided on success):
|
| 33 |
-
- result_preview: Optional[str] - PNG/GIF preview for inline display
|
| 34 |
-
- result_origin: Optional[str] - Original format file for download
|
| 35 |
-
- result_path: Optional[str] - Backward compat field
|
| 36 |
-
|
| 37 |
-
Metadata:
|
| 38 |
-
- metadata_text: Optional[str] - Structured info about result
|
| 39 |
-
- metadata: Dict[str, Any] - Machine-readable metadata
|
| 40 |
-
|
| 41 |
-
Tracking:
|
| 42 |
-
- endpoint_url: str - API endpoint used
|
| 43 |
-
- api_name: str - API method called
|
| 44 |
-
"""
|
| 45 |
-
|
| 46 |
-
# Core status
|
| 47 |
-
success: bool = False
|
| 48 |
-
error: Optional[str] = None
|
| 49 |
-
compute_time_seconds: float = 0.0
|
| 50 |
-
|
| 51 |
-
# File outputs (tools should provide these for UI to display/download)
|
| 52 |
-
result_preview: Optional[str] = Field(
|
| 53 |
-
default=None,
|
| 54 |
-
description="Path to preview image (PNG/GIF) for inline display in chat",
|
| 55 |
-
)
|
| 56 |
-
result_origin: Optional[str] = Field(
|
| 57 |
-
default=None,
|
| 58 |
-
description="Path to original format file (TIFF/NIfTI/DICOM) for download",
|
| 59 |
-
)
|
| 60 |
-
result_path: Optional[str] = Field(
|
| 61 |
-
default=None, description="Backward compatibility: primary result path"
|
| 62 |
-
)
|
| 63 |
-
|
| 64 |
-
# Metadata
|
| 65 |
-
metadata_text: Optional[str] = Field(
|
| 66 |
-
default=None, description="Human-readable metadata about the result"
|
| 67 |
-
)
|
| 68 |
-
metadata: Dict[str, Any] = Field(
|
| 69 |
-
default_factory=dict, description="Machine-readable metadata"
|
| 70 |
-
)
|
| 71 |
-
notes: Optional[str] = Field(
|
| 72 |
-
default=None, description="Additional notes or context for user"
|
| 73 |
-
)
|
| 74 |
-
|
| 75 |
-
# Tracking/debugging
|
| 76 |
-
endpoint_url: str = ""
|
| 77 |
-
api_name: str = ""
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
class ImageToolInput(BaseToolInput):
|
| 81 |
-
"""
|
| 82 |
-
Common input pattern for image/volume processing tools.
|
| 83 |
-
"""
|
| 84 |
-
|
| 85 |
-
image_path: str = Field(description="Path to the image/volume file")
|
| 86 |
-
description: Optional[str] = Field(
|
| 87 |
-
default=None, description="Optional context or notes from agent about the task"
|
| 88 |
-
)
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from typing import Optional, Dict, Any
|
| 4 |
+
from pydantic import BaseModel, Field
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
class BaseToolInput(BaseModel):
|
| 8 |
+
"""
|
| 9 |
+
Base input model that tools can extend.
|
| 10 |
+
|
| 11 |
+
Common patterns:
|
| 12 |
+
- image_path: Path to uploaded image/volume
|
| 13 |
+
- description: Optional context from agent
|
| 14 |
+
"""
|
| 15 |
+
|
| 16 |
+
pass # Intentionally minimal - tools define their own inputs
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
class BaseToolOutput(BaseModel):
|
| 20 |
+
"""
|
| 21 |
+
Base output model that all tools should follow.
|
| 22 |
+
|
| 23 |
+
This ensures consistent handling in the UI layer without
|
| 24 |
+
needing tool-specific code.
|
| 25 |
+
|
| 26 |
+
Standard fields:
|
| 27 |
+
- success: bool - Whether execution succeeded
|
| 28 |
+
- error: Optional[str] - Error message if failed
|
| 29 |
+
- compute_time_seconds: float - Time taken by tool
|
| 30 |
+
- notes: Optional[str] - Additional info for user
|
| 31 |
+
|
| 32 |
+
File outputs (at least one should be provided on success):
|
| 33 |
+
- result_preview: Optional[str] - PNG/GIF preview for inline display
|
| 34 |
+
- result_origin: Optional[str] - Original format file for download
|
| 35 |
+
- result_path: Optional[str] - Backward compat field
|
| 36 |
+
|
| 37 |
+
Metadata:
|
| 38 |
+
- metadata_text: Optional[str] - Structured info about result
|
| 39 |
+
- metadata: Dict[str, Any] - Machine-readable metadata
|
| 40 |
+
|
| 41 |
+
Tracking:
|
| 42 |
+
- endpoint_url: str - API endpoint used
|
| 43 |
+
- api_name: str - API method called
|
| 44 |
+
"""
|
| 45 |
+
|
| 46 |
+
# Core status
|
| 47 |
+
success: bool = False
|
| 48 |
+
error: Optional[str] = None
|
| 49 |
+
compute_time_seconds: float = 0.0
|
| 50 |
+
|
| 51 |
+
# File outputs (tools should provide these for UI to display/download)
|
| 52 |
+
result_preview: Optional[str] = Field(
|
| 53 |
+
default=None,
|
| 54 |
+
description="Path to preview image (PNG/GIF) for inline display in chat",
|
| 55 |
+
)
|
| 56 |
+
result_origin: Optional[str] = Field(
|
| 57 |
+
default=None,
|
| 58 |
+
description="Path to original format file (TIFF/NIfTI/DICOM) for download",
|
| 59 |
+
)
|
| 60 |
+
result_path: Optional[str] = Field(
|
| 61 |
+
default=None, description="Backward compatibility: primary result path"
|
| 62 |
+
)
|
| 63 |
+
|
| 64 |
+
# Metadata
|
| 65 |
+
metadata_text: Optional[str] = Field(
|
| 66 |
+
default=None, description="Human-readable metadata about the result"
|
| 67 |
+
)
|
| 68 |
+
metadata: Dict[str, Any] = Field(
|
| 69 |
+
default_factory=dict, description="Machine-readable metadata"
|
| 70 |
+
)
|
| 71 |
+
notes: Optional[str] = Field(
|
| 72 |
+
default=None, description="Additional notes or context for user"
|
| 73 |
+
)
|
| 74 |
+
|
| 75 |
+
# Tracking/debugging
|
| 76 |
+
endpoint_url: str = ""
|
| 77 |
+
api_name: str = ""
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
class ImageToolInput(BaseToolInput):
|
| 81 |
+
"""
|
| 82 |
+
Common input pattern for image/volume processing tools.
|
| 83 |
+
"""
|
| 84 |
+
|
| 85 |
+
image_path: str = Field(description="Path to the image/volume file")
|
| 86 |
+
description: Optional[str] = Field(
|
| 87 |
+
default=None, description="Optional context or notes from agent about the task"
|
| 88 |
+
)
|
src/ai_agent/agent/tools/mcp/lungs_segmentation_tool.py
CHANGED
|
@@ -1,442 +1,442 @@
|
|
| 1 |
-
from __future__ import annotations
|
| 2 |
-
|
| 3 |
-
from typing import Optional, Any, Dict, Tuple
|
| 4 |
-
import os
|
| 5 |
-
import logging
|
| 6 |
-
import tempfile
|
| 7 |
-
from pathlib import Path
|
| 8 |
-
import time
|
| 9 |
-
|
| 10 |
-
import requests
|
| 11 |
-
from gradio_client import Client, handle_file
|
| 12 |
-
|
| 13 |
-
from ai_agent.utils.previews import _build_preview_for_vlm
|
| 14 |
-
from ai_agent.utils.temp_file_manager import register_temp_file
|
| 15 |
-
from ai_agent.agent.tools.mcp.registry import register_tool, ToolConfig
|
| 16 |
-
from ai_agent.agent.tools.mcp.base import BaseToolOutput, ImageToolInput
|
| 17 |
-
|
| 18 |
-
log = logging.getLogger("agent.lungs_segmentation")
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
# ---------------------------------------------------------------------
|
| 22 |
-
# Models
|
| 23 |
-
# ---------------------------------------------------------------------
|
| 24 |
-
class LungsSegmentationInput(ImageToolInput):
|
| 25 |
-
"""Input for 3D lungs segmentation tool."""
|
| 26 |
-
|
| 27 |
-
pass # Inherits image_path and description from ImageToolInput
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
class LungsSegmentationOutput(BaseToolOutput):
|
| 31 |
-
"""Output from 3D lungs segmentation tool."""
|
| 32 |
-
|
| 33 |
-
# All standard fields inherited from BaseToolOutput:
|
| 34 |
-
# - success, error, compute_time_seconds, notes
|
| 35 |
-
# - result_preview, result_origin, result_path
|
| 36 |
-
# - metadata_text, endpoint_url, api_name
|
| 37 |
-
pass
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
# ---------------------------------------------------------------------
|
| 41 |
-
# Config
|
| 42 |
-
# ---------------------------------------------------------------------
|
| 43 |
-
LUNGS_SEGMENTATION_ENDPOINT = "https://qchapp-3d-lungs-segmentation.hf.space/"
|
| 44 |
-
LUNGS_SEGMENTATION_API_NAME = "/segment"
|
| 45 |
-
|
| 46 |
-
# Maximum file size for downloads (1GB for medical imaging)
|
| 47 |
-
MAX_DOWNLOAD_SIZE = 1024 * 1024 * 1024 # 1GB in bytes
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
# ---------------------------------------------------------------------
|
| 51 |
-
# Public tool
|
| 52 |
-
# ---------------------------------------------------------------------
|
| 53 |
-
def tool_lungs_segmentation(inp: LungsSegmentationInput) -> LungsSegmentationOutput:
|
| 54 |
-
"""
|
| 55 |
-
Run 3D lungs segmentation on a CT scan image via a Gradio Space.
|
| 56 |
-
|
| 57 |
-
Materialization strategy (robust):
|
| 58 |
-
1) If Space returns dict FileData (url/path/etc) -> download via URL.
|
| 59 |
-
2) If Space returns URL string -> download.
|
| 60 |
-
3) If Space returns local file -> use it.
|
| 61 |
-
4) If Space returns server path (/tmp/...) -> try /gradio_api/file=... (may 403).
|
| 62 |
-
"""
|
| 63 |
-
start_time = time.time()
|
| 64 |
-
|
| 65 |
-
if not os.path.exists(inp.image_path):
|
| 66 |
-
return LungsSegmentationOutput(
|
| 67 |
-
success=False,
|
| 68 |
-
error=f"Image file not found: {inp.image_path}",
|
| 69 |
-
endpoint_url=LUNGS_SEGMENTATION_ENDPOINT,
|
| 70 |
-
api_name=LUNGS_SEGMENTATION_API_NAME,
|
| 71 |
-
)
|
| 72 |
-
|
| 73 |
-
hf_token = os.getenv("HF_TOKEN") or os.getenv("HUGGINGFACE_TOKEN")
|
| 74 |
-
|
| 75 |
-
try:
|
| 76 |
-
log.info(
|
| 77 |
-
"Running lungs segmentation on %s (endpoint: %s)",
|
| 78 |
-
inp.image_path,
|
| 79 |
-
LUNGS_SEGMENTATION_ENDPOINT,
|
| 80 |
-
)
|
| 81 |
-
|
| 82 |
-
client = _make_gradio_client(LUNGS_SEGMENTATION_ENDPOINT, hf_token)
|
| 83 |
-
|
| 84 |
-
# Call API
|
| 85 |
-
try:
|
| 86 |
-
result = client.predict(
|
| 87 |
-
file_obj=handle_file(inp.image_path),
|
| 88 |
-
api_name=LUNGS_SEGMENTATION_API_NAME,
|
| 89 |
-
)
|
| 90 |
-
log.info("API returned type=%s value=%r", type(result), result)
|
| 91 |
-
except Exception as e:
|
| 92 |
-
return LungsSegmentationOutput(
|
| 93 |
-
success=False,
|
| 94 |
-
error=f"API call failed: {e}",
|
| 95 |
-
compute_time_seconds=time.time() - start_time,
|
| 96 |
-
endpoint_url=LUNGS_SEGMENTATION_ENDPOINT,
|
| 97 |
-
api_name=LUNGS_SEGMENTATION_API_NAME,
|
| 98 |
-
)
|
| 99 |
-
|
| 100 |
-
# Materialize to local file
|
| 101 |
-
origin_path = _materialize_any(result, client=client, hf_token=hf_token)
|
| 102 |
-
|
| 103 |
-
compute_time = time.time() - start_time
|
| 104 |
-
|
| 105 |
-
if not origin_path or not os.path.exists(origin_path):
|
| 106 |
-
# This is the common case if the Space returns '/tmp/...' and Gradio blocks it (403).
|
| 107 |
-
return LungsSegmentationOutput(
|
| 108 |
-
success=False,
|
| 109 |
-
error="Could not materialize/download the result file.",
|
| 110 |
-
compute_time_seconds=compute_time,
|
| 111 |
-
endpoint_url=LUNGS_SEGMENTATION_ENDPOINT,
|
| 112 |
-
api_name=LUNGS_SEGMENTATION_API_NAME,
|
| 113 |
-
notes=(
|
| 114 |
-
f"API returned: {result!r}. If this is a '/tmp/...' path and you see HTTP 403, "
|
| 115 |
-
"the Space must return a FileData/url (recommended) or whitelist the output directory "
|
| 116 |
-
"via allowed_paths / GRADIO_TEMP_DIR."
|
| 117 |
-
),
|
| 118 |
-
)
|
| 119 |
-
|
| 120 |
-
# Build preview + metadata using your shared function
|
| 121 |
-
preview_path, meta_text = _safe_build_preview(origin_path)
|
| 122 |
-
|
| 123 |
-
# Back-compat: prefer preview in result_path
|
| 124 |
-
result_path = preview_path or origin_path
|
| 125 |
-
|
| 126 |
-
return LungsSegmentationOutput(
|
| 127 |
-
success=True,
|
| 128 |
-
result_path=result_path,
|
| 129 |
-
result_origin=origin_path,
|
| 130 |
-
result_preview=preview_path,
|
| 131 |
-
metadata_text=meta_text,
|
| 132 |
-
compute_time_seconds=compute_time,
|
| 133 |
-
endpoint_url=LUNGS_SEGMENTATION_ENDPOINT,
|
| 134 |
-
api_name=LUNGS_SEGMENTATION_API_NAME,
|
| 135 |
-
notes=f"Successfully segmented lungs from {os.path.basename(inp.image_path)}",
|
| 136 |
-
)
|
| 137 |
-
|
| 138 |
-
except Exception as e:
|
| 139 |
-
log.exception("Lungs segmentation failed")
|
| 140 |
-
return LungsSegmentationOutput(
|
| 141 |
-
success=False,
|
| 142 |
-
error=str(e),
|
| 143 |
-
compute_time_seconds=time.time() - start_time,
|
| 144 |
-
endpoint_url=LUNGS_SEGMENTATION_ENDPOINT,
|
| 145 |
-
api_name=LUNGS_SEGMENTATION_API_NAME,
|
| 146 |
-
)
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
# ---------------------------------------------------------------------
|
| 150 |
-
# Helpers
|
| 151 |
-
# ---------------------------------------------------------------------
|
| 152 |
-
def _make_gradio_client(endpoint: str, hf_token: Optional[str]) -> Client:
|
| 153 |
-
"""
|
| 154 |
-
Create a gradio_client.Client with best compatibility across versions.
|
| 155 |
-
"""
|
| 156 |
-
# Set extended timeout for both connection and operations (5 minutes for large files)
|
| 157 |
-
httpx_kwargs = {"timeout": 300.0}
|
| 158 |
-
|
| 159 |
-
# Newer versions use token=, older versions used hf_token=
|
| 160 |
-
if hf_token:
|
| 161 |
-
try:
|
| 162 |
-
return Client(endpoint, hf_token=hf_token, httpx_kwargs=httpx_kwargs)
|
| 163 |
-
except TypeError:
|
| 164 |
-
# Fallback for very old versions without httpx_kwargs support
|
| 165 |
-
try:
|
| 166 |
-
return Client(endpoint, hf_token=hf_token)
|
| 167 |
-
except TypeError:
|
| 168 |
-
return Client(endpoint)
|
| 169 |
-
|
| 170 |
-
try:
|
| 171 |
-
return Client(endpoint, httpx_kwargs=httpx_kwargs)
|
| 172 |
-
except TypeError:
|
| 173 |
-
# Fallback for very old versions
|
| 174 |
-
return Client(endpoint)
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
def _safe_build_preview(origin_path: str) -> Tuple[Optional[str], Optional[str]]:
|
| 178 |
-
"""
|
| 179 |
-
Wrapper around _build_preview_for_vlm so preview failures never break the tool.
|
| 180 |
-
"""
|
| 181 |
-
try:
|
| 182 |
-
preview_path, meta_text = _build_preview_for_vlm([origin_path])
|
| 183 |
-
return preview_path, meta_text
|
| 184 |
-
except Exception as e:
|
| 185 |
-
log.debug("Preview build failed for %s: %r", origin_path, e)
|
| 186 |
-
return None, None
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
def _materialize_any(
|
| 190 |
-
obj: Any, client: Client, hf_token: Optional[str] = None, _depth: int = 0
|
| 191 |
-
) -> Optional[str]:
|
| 192 |
-
"""
|
| 193 |
-
Convert common Gradio outputs into a local file path.
|
| 194 |
-
|
| 195 |
-
Supported:
|
| 196 |
-
- local path string
|
| 197 |
-
- URL string
|
| 198 |
-
- dict (FileData-like) containing url/path/name/filepath
|
| 199 |
-
- list/tuple containing any of the above
|
| 200 |
-
- server path '/tmp/...' -> attempt Gradio file endpoint (may 403)
|
| 201 |
-
|
| 202 |
-
Args:
|
| 203 |
-
obj: Object to materialize
|
| 204 |
-
client: Gradio client
|
| 205 |
-
hf_token: Optional HuggingFace token
|
| 206 |
-
_depth: Internal recursion depth counter (max 10)
|
| 207 |
-
"""
|
| 208 |
-
if obj is None or _depth > 10:
|
| 209 |
-
if _depth > 10:
|
| 210 |
-
log.warning("Recursion depth limit reached in _materialize_any, halting.")
|
| 211 |
-
return None
|
| 212 |
-
|
| 213 |
-
# list/tuple: most Gradio outputs are single-element lists
|
| 214 |
-
if isinstance(obj, (list, tuple)) and obj:
|
| 215 |
-
return _materialize_any(
|
| 216 |
-
obj[0], client=client, hf_token=hf_token, _depth=_depth + 1
|
| 217 |
-
)
|
| 218 |
-
|
| 219 |
-
# dict: FileData-like is best case (url provided)
|
| 220 |
-
if isinstance(obj, dict):
|
| 221 |
-
# Prefer URL if present
|
| 222 |
-
url = obj.get("url")
|
| 223 |
-
if isinstance(url, str) and url.startswith(("http://", "https://")):
|
| 224 |
-
log.info("Materialize: dict url=%s", url)
|
| 225 |
-
return _download_to_temp(url, hf_token=hf_token)
|
| 226 |
-
|
| 227 |
-
# Fall back through common keys
|
| 228 |
-
for k in ("path", "filepath", "file", "name"):
|
| 229 |
-
v = obj.get(k)
|
| 230 |
-
if isinstance(v, str) and v:
|
| 231 |
-
return _materialize_any(
|
| 232 |
-
v, client=client, hf_token=hf_token, _depth=_depth + 1
|
| 233 |
-
)
|
| 234 |
-
|
| 235 |
-
return None
|
| 236 |
-
|
| 237 |
-
# string: local file, URL, or server path
|
| 238 |
-
if isinstance(obj, str):
|
| 239 |
-
s = obj.strip()
|
| 240 |
-
if not s:
|
| 241 |
-
return None
|
| 242 |
-
|
| 243 |
-
# local file?
|
| 244 |
-
p = Path(s)
|
| 245 |
-
if p.exists() and p.is_file():
|
| 246 |
-
log.info("Materialize: local file=%s", s)
|
| 247 |
-
return str(p)
|
| 248 |
-
|
| 249 |
-
# URL?
|
| 250 |
-
if s.startswith(("http://", "https://")):
|
| 251 |
-
log.info("Materialize: url=%s", s)
|
| 252 |
-
return _download_to_temp(s, hf_token=hf_token)
|
| 253 |
-
|
| 254 |
-
# server path? (e.g. /tmp/xxx_mask.tif)
|
| 255 |
-
if s.startswith("/"):
|
| 256 |
-
log.info("Materialize: server path=%s", s)
|
| 257 |
-
return _download_from_gradio_file_endpoint(client, s, hf_token=hf_token)
|
| 258 |
-
|
| 259 |
-
return None
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
def _download_to_temp(url: str, hf_token: Optional[str] = None) -> Optional[str]:
|
| 263 |
-
"""
|
| 264 |
-
Download a URL to a temporary file (streaming) with size limit checks.
|
| 265 |
-
"""
|
| 266 |
-
headers: Dict[str, str] = {}
|
| 267 |
-
if hf_token:
|
| 268 |
-
headers["Authorization"] = f"Bearer {hf_token}"
|
| 269 |
-
|
| 270 |
-
try:
|
| 271 |
-
with requests.get(
|
| 272 |
-
url, headers=headers, timeout=120, stream=True, allow_redirects=True
|
| 273 |
-
) as r:
|
| 274 |
-
if r.status_code != 200:
|
| 275 |
-
log.error("Download failed: url=%s status=%s", url, r.status_code)
|
| 276 |
-
return None
|
| 277 |
-
|
| 278 |
-
# Check Content-Length if available
|
| 279 |
-
content_length = r.headers.get("content-length")
|
| 280 |
-
if content_length and int(content_length) > MAX_DOWNLOAD_SIZE:
|
| 281 |
-
log.error(
|
| 282 |
-
"File too large: %s bytes (max %s)",
|
| 283 |
-
content_length,
|
| 284 |
-
MAX_DOWNLOAD_SIZE,
|
| 285 |
-
)
|
| 286 |
-
return None
|
| 287 |
-
|
| 288 |
-
ext = _guess_ext(url, r.headers.get("content-type", ""))
|
| 289 |
-
|
| 290 |
-
with tempfile.NamedTemporaryFile(
|
| 291 |
-
delete=False, prefix="lungs_seg_", suffix=ext
|
| 292 |
-
) as f:
|
| 293 |
-
downloaded_size = 0
|
| 294 |
-
for chunk in r.iter_content(chunk_size=1024 * 1024):
|
| 295 |
-
if chunk:
|
| 296 |
-
downloaded_size += len(chunk)
|
| 297 |
-
if downloaded_size > MAX_DOWNLOAD_SIZE:
|
| 298 |
-
log.error(
|
| 299 |
-
"Download exceeded size limit: %s bytes",
|
| 300 |
-
downloaded_size,
|
| 301 |
-
)
|
| 302 |
-
f.close()
|
| 303 |
-
os.remove(f.name)
|
| 304 |
-
return None
|
| 305 |
-
f.write(chunk)
|
| 306 |
-
log.info("Downloaded %s bytes: %s -> %s", downloaded_size, url, f.name)
|
| 307 |
-
return register_temp_file(f.name)
|
| 308 |
-
except Exception as e:
|
| 309 |
-
log.error("Failed to download %s: %r", url, e)
|
| 310 |
-
return None
|
| 311 |
-
|
| 312 |
-
|
| 313 |
-
def _download_from_gradio_file_endpoint(
|
| 314 |
-
client: Client, server_path: str, hf_token: Optional[str] = None
|
| 315 |
-
) -> Optional[str]:
|
| 316 |
-
"""
|
| 317 |
-
Last-resort fallback when API returns '/tmp/...' but no URL.
|
| 318 |
-
Often blocked with 403 unless Space allows that directory or writes into Gradio temp/cache.
|
| 319 |
-
Includes size limit checks.
|
| 320 |
-
"""
|
| 321 |
-
base = (getattr(client, "src", None) or LUNGS_SEGMENTATION_ENDPOINT).rstrip("/")
|
| 322 |
-
file_url = f"{base}/gradio_api/file={server_path}"
|
| 323 |
-
|
| 324 |
-
headers: Dict[str, str] = {}
|
| 325 |
-
if hf_token:
|
| 326 |
-
headers["Authorization"] = f"Bearer {hf_token}"
|
| 327 |
-
|
| 328 |
-
params: Dict[str, str] = {}
|
| 329 |
-
session_hash = getattr(client, "session_hash", None)
|
| 330 |
-
if session_hash:
|
| 331 |
-
params["session_hash"] = session_hash
|
| 332 |
-
|
| 333 |
-
try:
|
| 334 |
-
r = requests.get(
|
| 335 |
-
file_url, headers=headers, params=params, timeout=60, stream=True
|
| 336 |
-
)
|
| 337 |
-
if r.status_code == 403:
|
| 338 |
-
# Common: file exists but not allowed to be served
|
| 339 |
-
detail: Any
|
| 340 |
-
try:
|
| 341 |
-
detail = r.json()
|
| 342 |
-
except Exception:
|
| 343 |
-
detail = r.text[:200]
|
| 344 |
-
log.error("HTTP 403 from %s detail=%r", file_url, detail)
|
| 345 |
-
return None
|
| 346 |
-
|
| 347 |
-
if r.status_code != 200:
|
| 348 |
-
log.error("HTTP %s from %s", r.status_code, file_url)
|
| 349 |
-
return None
|
| 350 |
-
|
| 351 |
-
# Check Content-Length before downloading
|
| 352 |
-
content_length = r.headers.get("content-length")
|
| 353 |
-
if content_length and int(content_length) > MAX_DOWNLOAD_SIZE:
|
| 354 |
-
log.error(
|
| 355 |
-
"File too large: %s bytes (max %s)", content_length, MAX_DOWNLOAD_SIZE
|
| 356 |
-
)
|
| 357 |
-
return None
|
| 358 |
-
|
| 359 |
-
# Read content with size check
|
| 360 |
-
content = b""
|
| 361 |
-
for chunk in r.iter_content(chunk_size=1024 * 1024):
|
| 362 |
-
if chunk:
|
| 363 |
-
content += chunk
|
| 364 |
-
if len(content) > MAX_DOWNLOAD_SIZE:
|
| 365 |
-
log.error("Download exceeded size limit: %s bytes", len(content))
|
| 366 |
-
return None
|
| 367 |
-
|
| 368 |
-
ct = r.headers.get("content-type", "")
|
| 369 |
-
if "html" in ct.lower() or content.startswith(b"<!"):
|
| 370 |
-
log.error("Got HTML instead of file from %s", file_url)
|
| 371 |
-
return None
|
| 372 |
-
|
| 373 |
-
ext = os.path.splitext(server_path)[1] or ".tif"
|
| 374 |
-
with tempfile.NamedTemporaryFile(
|
| 375 |
-
delete=False, prefix="lungs_seg_", suffix=ext
|
| 376 |
-
) as f:
|
| 377 |
-
f.write(content)
|
| 378 |
-
log.info(
|
| 379 |
-
"Downloaded %s bytes from gradio file endpoint -> %s",
|
| 380 |
-
len(content),
|
| 381 |
-
f.name,
|
| 382 |
-
)
|
| 383 |
-
return register_temp_file(f.name)
|
| 384 |
-
|
| 385 |
-
except Exception as e:
|
| 386 |
-
log.error("Failed gradio file endpoint download: %r", e)
|
| 387 |
-
return None
|
| 388 |
-
|
| 389 |
-
|
| 390 |
-
def _guess_ext(url: str, content_type: str) -> str:
|
| 391 |
-
"""
|
| 392 |
-
Guess file extension from URL path or Content-Type.
|
| 393 |
-
"""
|
| 394 |
-
from urllib.parse import urlparse
|
| 395 |
-
|
| 396 |
-
path = urlparse(url).path.lower()
|
| 397 |
-
|
| 398 |
-
if path.endswith(".nii.gz"):
|
| 399 |
-
return ".nii.gz"
|
| 400 |
-
|
| 401 |
-
ext = os.path.splitext(path)[1]
|
| 402 |
-
if ext:
|
| 403 |
-
return ext
|
| 404 |
-
|
| 405 |
-
ct = (content_type or "").lower()
|
| 406 |
-
if "tiff" in ct or "tif" in ct:
|
| 407 |
-
return ".tif"
|
| 408 |
-
if "png" in ct:
|
| 409 |
-
return ".png"
|
| 410 |
-
if "jpeg" in ct or "jpg" in ct:
|
| 411 |
-
return ".jpg"
|
| 412 |
-
if "gif" in ct:
|
| 413 |
-
return ".gif"
|
| 414 |
-
if "nifti" in ct or "nii" in ct:
|
| 415 |
-
return ".nii.gz"
|
| 416 |
-
return ".bin"
|
| 417 |
-
|
| 418 |
-
|
| 419 |
-
# ---------------------------------------------------------------------
|
| 420 |
-
# Tool Registration
|
| 421 |
-
# ---------------------------------------------------------------------
|
| 422 |
-
register_tool(
|
| 423 |
-
ToolConfig(
|
| 424 |
-
name="lungs_segmentation",
|
| 425 |
-
display_name="3D Lungs Segmentation",
|
| 426 |
-
icon="🫁",
|
| 427 |
-
catalog_names=["lungs-segmentation"], # Catalog name from dataset/catalog.jsonl
|
| 428 |
-
input_model=LungsSegmentationInput,
|
| 429 |
-
output_model=LungsSegmentationOutput,
|
| 430 |
-
executor=tool_lungs_segmentation,
|
| 431 |
-
supports_images=True,
|
| 432 |
-
supports_files=True,
|
| 433 |
-
requires_approval=True,
|
| 434 |
-
preview_field="result_preview",
|
| 435 |
-
download_fields="result_origin", # Could also be ["result_origin", "other_file"]
|
| 436 |
-
metadata_field="metadata_text",
|
| 437 |
-
notes_field="notes",
|
| 438 |
-
success_field="success",
|
| 439 |
-
error_field="error",
|
| 440 |
-
compute_time_field="compute_time_seconds",
|
| 441 |
-
)
|
| 442 |
-
)
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from typing import Optional, Any, Dict, Tuple
|
| 4 |
+
import os
|
| 5 |
+
import logging
|
| 6 |
+
import tempfile
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
import time
|
| 9 |
+
|
| 10 |
+
import requests
|
| 11 |
+
from gradio_client import Client, handle_file
|
| 12 |
+
|
| 13 |
+
from ai_agent.utils.previews import _build_preview_for_vlm
|
| 14 |
+
from ai_agent.utils.temp_file_manager import register_temp_file
|
| 15 |
+
from ai_agent.agent.tools.mcp.registry import register_tool, ToolConfig
|
| 16 |
+
from ai_agent.agent.tools.mcp.base import BaseToolOutput, ImageToolInput
|
| 17 |
+
|
| 18 |
+
log = logging.getLogger("agent.lungs_segmentation")
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
# ---------------------------------------------------------------------
|
| 22 |
+
# Models
|
| 23 |
+
# ---------------------------------------------------------------------
|
| 24 |
+
class LungsSegmentationInput(ImageToolInput):
|
| 25 |
+
"""Input for 3D lungs segmentation tool."""
|
| 26 |
+
|
| 27 |
+
pass # Inherits image_path and description from ImageToolInput
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
class LungsSegmentationOutput(BaseToolOutput):
|
| 31 |
+
"""Output from 3D lungs segmentation tool."""
|
| 32 |
+
|
| 33 |
+
# All standard fields inherited from BaseToolOutput:
|
| 34 |
+
# - success, error, compute_time_seconds, notes
|
| 35 |
+
# - result_preview, result_origin, result_path
|
| 36 |
+
# - metadata_text, endpoint_url, api_name
|
| 37 |
+
pass
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
# ---------------------------------------------------------------------
|
| 41 |
+
# Config
|
| 42 |
+
# ---------------------------------------------------------------------
|
| 43 |
+
LUNGS_SEGMENTATION_ENDPOINT = "https://qchapp-3d-lungs-segmentation.hf.space/"
|
| 44 |
+
LUNGS_SEGMENTATION_API_NAME = "/segment"
|
| 45 |
+
|
| 46 |
+
# Maximum file size for downloads (1GB for medical imaging)
|
| 47 |
+
MAX_DOWNLOAD_SIZE = 1024 * 1024 * 1024 # 1GB in bytes
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
# ---------------------------------------------------------------------
|
| 51 |
+
# Public tool
|
| 52 |
+
# ---------------------------------------------------------------------
|
| 53 |
+
def tool_lungs_segmentation(inp: LungsSegmentationInput) -> LungsSegmentationOutput:
|
| 54 |
+
"""
|
| 55 |
+
Run 3D lungs segmentation on a CT scan image via a Gradio Space.
|
| 56 |
+
|
| 57 |
+
Materialization strategy (robust):
|
| 58 |
+
1) If Space returns dict FileData (url/path/etc) -> download via URL.
|
| 59 |
+
2) If Space returns URL string -> download.
|
| 60 |
+
3) If Space returns local file -> use it.
|
| 61 |
+
4) If Space returns server path (/tmp/...) -> try /gradio_api/file=... (may 403).
|
| 62 |
+
"""
|
| 63 |
+
start_time = time.time()
|
| 64 |
+
|
| 65 |
+
if not os.path.exists(inp.image_path):
|
| 66 |
+
return LungsSegmentationOutput(
|
| 67 |
+
success=False,
|
| 68 |
+
error=f"Image file not found: {inp.image_path}",
|
| 69 |
+
endpoint_url=LUNGS_SEGMENTATION_ENDPOINT,
|
| 70 |
+
api_name=LUNGS_SEGMENTATION_API_NAME,
|
| 71 |
+
)
|
| 72 |
+
|
| 73 |
+
hf_token = os.getenv("HF_TOKEN") or os.getenv("HUGGINGFACE_TOKEN")
|
| 74 |
+
|
| 75 |
+
try:
|
| 76 |
+
log.info(
|
| 77 |
+
"Running lungs segmentation on %s (endpoint: %s)",
|
| 78 |
+
inp.image_path,
|
| 79 |
+
LUNGS_SEGMENTATION_ENDPOINT,
|
| 80 |
+
)
|
| 81 |
+
|
| 82 |
+
client = _make_gradio_client(LUNGS_SEGMENTATION_ENDPOINT, hf_token)
|
| 83 |
+
|
| 84 |
+
# Call API
|
| 85 |
+
try:
|
| 86 |
+
result = client.predict(
|
| 87 |
+
file_obj=handle_file(inp.image_path),
|
| 88 |
+
api_name=LUNGS_SEGMENTATION_API_NAME,
|
| 89 |
+
)
|
| 90 |
+
log.info("API returned type=%s value=%r", type(result), result)
|
| 91 |
+
except Exception as e:
|
| 92 |
+
return LungsSegmentationOutput(
|
| 93 |
+
success=False,
|
| 94 |
+
error=f"API call failed: {e}",
|
| 95 |
+
compute_time_seconds=time.time() - start_time,
|
| 96 |
+
endpoint_url=LUNGS_SEGMENTATION_ENDPOINT,
|
| 97 |
+
api_name=LUNGS_SEGMENTATION_API_NAME,
|
| 98 |
+
)
|
| 99 |
+
|
| 100 |
+
# Materialize to local file
|
| 101 |
+
origin_path = _materialize_any(result, client=client, hf_token=hf_token)
|
| 102 |
+
|
| 103 |
+
compute_time = time.time() - start_time
|
| 104 |
+
|
| 105 |
+
if not origin_path or not os.path.exists(origin_path):
|
| 106 |
+
# This is the common case if the Space returns '/tmp/...' and Gradio blocks it (403).
|
| 107 |
+
return LungsSegmentationOutput(
|
| 108 |
+
success=False,
|
| 109 |
+
error="Could not materialize/download the result file.",
|
| 110 |
+
compute_time_seconds=compute_time,
|
| 111 |
+
endpoint_url=LUNGS_SEGMENTATION_ENDPOINT,
|
| 112 |
+
api_name=LUNGS_SEGMENTATION_API_NAME,
|
| 113 |
+
notes=(
|
| 114 |
+
f"API returned: {result!r}. If this is a '/tmp/...' path and you see HTTP 403, "
|
| 115 |
+
"the Space must return a FileData/url (recommended) or whitelist the output directory "
|
| 116 |
+
"via allowed_paths / GRADIO_TEMP_DIR."
|
| 117 |
+
),
|
| 118 |
+
)
|
| 119 |
+
|
| 120 |
+
# Build preview + metadata using your shared function
|
| 121 |
+
preview_path, meta_text = _safe_build_preview(origin_path)
|
| 122 |
+
|
| 123 |
+
# Back-compat: prefer preview in result_path
|
| 124 |
+
result_path = preview_path or origin_path
|
| 125 |
+
|
| 126 |
+
return LungsSegmentationOutput(
|
| 127 |
+
success=True,
|
| 128 |
+
result_path=result_path,
|
| 129 |
+
result_origin=origin_path,
|
| 130 |
+
result_preview=preview_path,
|
| 131 |
+
metadata_text=meta_text,
|
| 132 |
+
compute_time_seconds=compute_time,
|
| 133 |
+
endpoint_url=LUNGS_SEGMENTATION_ENDPOINT,
|
| 134 |
+
api_name=LUNGS_SEGMENTATION_API_NAME,
|
| 135 |
+
notes=f"Successfully segmented lungs from {os.path.basename(inp.image_path)}",
|
| 136 |
+
)
|
| 137 |
+
|
| 138 |
+
except Exception as e:
|
| 139 |
+
log.exception("Lungs segmentation failed")
|
| 140 |
+
return LungsSegmentationOutput(
|
| 141 |
+
success=False,
|
| 142 |
+
error=str(e),
|
| 143 |
+
compute_time_seconds=time.time() - start_time,
|
| 144 |
+
endpoint_url=LUNGS_SEGMENTATION_ENDPOINT,
|
| 145 |
+
api_name=LUNGS_SEGMENTATION_API_NAME,
|
| 146 |
+
)
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
# ---------------------------------------------------------------------
|
| 150 |
+
# Helpers
|
| 151 |
+
# ---------------------------------------------------------------------
|
| 152 |
+
def _make_gradio_client(endpoint: str, hf_token: Optional[str]) -> Client:
|
| 153 |
+
"""
|
| 154 |
+
Create a gradio_client.Client with best compatibility across versions.
|
| 155 |
+
"""
|
| 156 |
+
# Set extended timeout for both connection and operations (5 minutes for large files)
|
| 157 |
+
httpx_kwargs = {"timeout": 300.0}
|
| 158 |
+
|
| 159 |
+
# Newer versions use token=, older versions used hf_token=
|
| 160 |
+
if hf_token:
|
| 161 |
+
try:
|
| 162 |
+
return Client(endpoint, hf_token=hf_token, httpx_kwargs=httpx_kwargs)
|
| 163 |
+
except TypeError:
|
| 164 |
+
# Fallback for very old versions without httpx_kwargs support
|
| 165 |
+
try:
|
| 166 |
+
return Client(endpoint, hf_token=hf_token)
|
| 167 |
+
except TypeError:
|
| 168 |
+
return Client(endpoint)
|
| 169 |
+
|
| 170 |
+
try:
|
| 171 |
+
return Client(endpoint, httpx_kwargs=httpx_kwargs)
|
| 172 |
+
except TypeError:
|
| 173 |
+
# Fallback for very old versions
|
| 174 |
+
return Client(endpoint)
|
| 175 |
+
|
| 176 |
+
|
| 177 |
+
def _safe_build_preview(origin_path: str) -> Tuple[Optional[str], Optional[str]]:
|
| 178 |
+
"""
|
| 179 |
+
Wrapper around _build_preview_for_vlm so preview failures never break the tool.
|
| 180 |
+
"""
|
| 181 |
+
try:
|
| 182 |
+
preview_path, meta_text = _build_preview_for_vlm([origin_path])
|
| 183 |
+
return preview_path, meta_text
|
| 184 |
+
except Exception as e:
|
| 185 |
+
log.debug("Preview build failed for %s: %r", origin_path, e)
|
| 186 |
+
return None, None
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
def _materialize_any(
|
| 190 |
+
obj: Any, client: Client, hf_token: Optional[str] = None, _depth: int = 0
|
| 191 |
+
) -> Optional[str]:
|
| 192 |
+
"""
|
| 193 |
+
Convert common Gradio outputs into a local file path.
|
| 194 |
+
|
| 195 |
+
Supported:
|
| 196 |
+
- local path string
|
| 197 |
+
- URL string
|
| 198 |
+
- dict (FileData-like) containing url/path/name/filepath
|
| 199 |
+
- list/tuple containing any of the above
|
| 200 |
+
- server path '/tmp/...' -> attempt Gradio file endpoint (may 403)
|
| 201 |
+
|
| 202 |
+
Args:
|
| 203 |
+
obj: Object to materialize
|
| 204 |
+
client: Gradio client
|
| 205 |
+
hf_token: Optional HuggingFace token
|
| 206 |
+
_depth: Internal recursion depth counter (max 10)
|
| 207 |
+
"""
|
| 208 |
+
if obj is None or _depth > 10:
|
| 209 |
+
if _depth > 10:
|
| 210 |
+
log.warning("Recursion depth limit reached in _materialize_any, halting.")
|
| 211 |
+
return None
|
| 212 |
+
|
| 213 |
+
# list/tuple: most Gradio outputs are single-element lists
|
| 214 |
+
if isinstance(obj, (list, tuple)) and obj:
|
| 215 |
+
return _materialize_any(
|
| 216 |
+
obj[0], client=client, hf_token=hf_token, _depth=_depth + 1
|
| 217 |
+
)
|
| 218 |
+
|
| 219 |
+
# dict: FileData-like is best case (url provided)
|
| 220 |
+
if isinstance(obj, dict):
|
| 221 |
+
# Prefer URL if present
|
| 222 |
+
url = obj.get("url")
|
| 223 |
+
if isinstance(url, str) and url.startswith(("http://", "https://")):
|
| 224 |
+
log.info("Materialize: dict url=%s", url)
|
| 225 |
+
return _download_to_temp(url, hf_token=hf_token)
|
| 226 |
+
|
| 227 |
+
# Fall back through common keys
|
| 228 |
+
for k in ("path", "filepath", "file", "name"):
|
| 229 |
+
v = obj.get(k)
|
| 230 |
+
if isinstance(v, str) and v:
|
| 231 |
+
return _materialize_any(
|
| 232 |
+
v, client=client, hf_token=hf_token, _depth=_depth + 1
|
| 233 |
+
)
|
| 234 |
+
|
| 235 |
+
return None
|
| 236 |
+
|
| 237 |
+
# string: local file, URL, or server path
|
| 238 |
+
if isinstance(obj, str):
|
| 239 |
+
s = obj.strip()
|
| 240 |
+
if not s:
|
| 241 |
+
return None
|
| 242 |
+
|
| 243 |
+
# local file?
|
| 244 |
+
p = Path(s)
|
| 245 |
+
if p.exists() and p.is_file():
|
| 246 |
+
log.info("Materialize: local file=%s", s)
|
| 247 |
+
return str(p)
|
| 248 |
+
|
| 249 |
+
# URL?
|
| 250 |
+
if s.startswith(("http://", "https://")):
|
| 251 |
+
log.info("Materialize: url=%s", s)
|
| 252 |
+
return _download_to_temp(s, hf_token=hf_token)
|
| 253 |
+
|
| 254 |
+
# server path? (e.g. /tmp/xxx_mask.tif)
|
| 255 |
+
if s.startswith("/"):
|
| 256 |
+
log.info("Materialize: server path=%s", s)
|
| 257 |
+
return _download_from_gradio_file_endpoint(client, s, hf_token=hf_token)
|
| 258 |
+
|
| 259 |
+
return None
|
| 260 |
+
|
| 261 |
+
|
| 262 |
+
def _download_to_temp(url: str, hf_token: Optional[str] = None) -> Optional[str]:
|
| 263 |
+
"""
|
| 264 |
+
Download a URL to a temporary file (streaming) with size limit checks.
|
| 265 |
+
"""
|
| 266 |
+
headers: Dict[str, str] = {}
|
| 267 |
+
if hf_token:
|
| 268 |
+
headers["Authorization"] = f"Bearer {hf_token}"
|
| 269 |
+
|
| 270 |
+
try:
|
| 271 |
+
with requests.get(
|
| 272 |
+
url, headers=headers, timeout=120, stream=True, allow_redirects=True
|
| 273 |
+
) as r:
|
| 274 |
+
if r.status_code != 200:
|
| 275 |
+
log.error("Download failed: url=%s status=%s", url, r.status_code)
|
| 276 |
+
return None
|
| 277 |
+
|
| 278 |
+
# Check Content-Length if available
|
| 279 |
+
content_length = r.headers.get("content-length")
|
| 280 |
+
if content_length and int(content_length) > MAX_DOWNLOAD_SIZE:
|
| 281 |
+
log.error(
|
| 282 |
+
"File too large: %s bytes (max %s)",
|
| 283 |
+
content_length,
|
| 284 |
+
MAX_DOWNLOAD_SIZE,
|
| 285 |
+
)
|
| 286 |
+
return None
|
| 287 |
+
|
| 288 |
+
ext = _guess_ext(url, r.headers.get("content-type", ""))
|
| 289 |
+
|
| 290 |
+
with tempfile.NamedTemporaryFile(
|
| 291 |
+
delete=False, prefix="lungs_seg_", suffix=ext
|
| 292 |
+
) as f:
|
| 293 |
+
downloaded_size = 0
|
| 294 |
+
for chunk in r.iter_content(chunk_size=1024 * 1024):
|
| 295 |
+
if chunk:
|
| 296 |
+
downloaded_size += len(chunk)
|
| 297 |
+
if downloaded_size > MAX_DOWNLOAD_SIZE:
|
| 298 |
+
log.error(
|
| 299 |
+
"Download exceeded size limit: %s bytes",
|
| 300 |
+
downloaded_size,
|
| 301 |
+
)
|
| 302 |
+
f.close()
|
| 303 |
+
os.remove(f.name)
|
| 304 |
+
return None
|
| 305 |
+
f.write(chunk)
|
| 306 |
+
log.info("Downloaded %s bytes: %s -> %s", downloaded_size, url, f.name)
|
| 307 |
+
return register_temp_file(f.name)
|
| 308 |
+
except Exception as e:
|
| 309 |
+
log.error("Failed to download %s: %r", url, e)
|
| 310 |
+
return None
|
| 311 |
+
|
| 312 |
+
|
| 313 |
+
def _download_from_gradio_file_endpoint(
|
| 314 |
+
client: Client, server_path: str, hf_token: Optional[str] = None
|
| 315 |
+
) -> Optional[str]:
|
| 316 |
+
"""
|
| 317 |
+
Last-resort fallback when API returns '/tmp/...' but no URL.
|
| 318 |
+
Often blocked with 403 unless Space allows that directory or writes into Gradio temp/cache.
|
| 319 |
+
Includes size limit checks.
|
| 320 |
+
"""
|
| 321 |
+
base = (getattr(client, "src", None) or LUNGS_SEGMENTATION_ENDPOINT).rstrip("/")
|
| 322 |
+
file_url = f"{base}/gradio_api/file={server_path}"
|
| 323 |
+
|
| 324 |
+
headers: Dict[str, str] = {}
|
| 325 |
+
if hf_token:
|
| 326 |
+
headers["Authorization"] = f"Bearer {hf_token}"
|
| 327 |
+
|
| 328 |
+
params: Dict[str, str] = {}
|
| 329 |
+
session_hash = getattr(client, "session_hash", None)
|
| 330 |
+
if session_hash:
|
| 331 |
+
params["session_hash"] = session_hash
|
| 332 |
+
|
| 333 |
+
try:
|
| 334 |
+
r = requests.get(
|
| 335 |
+
file_url, headers=headers, params=params, timeout=60, stream=True
|
| 336 |
+
)
|
| 337 |
+
if r.status_code == 403:
|
| 338 |
+
# Common: file exists but not allowed to be served
|
| 339 |
+
detail: Any
|
| 340 |
+
try:
|
| 341 |
+
detail = r.json()
|
| 342 |
+
except Exception:
|
| 343 |
+
detail = r.text[:200]
|
| 344 |
+
log.error("HTTP 403 from %s detail=%r", file_url, detail)
|
| 345 |
+
return None
|
| 346 |
+
|
| 347 |
+
if r.status_code != 200:
|
| 348 |
+
log.error("HTTP %s from %s", r.status_code, file_url)
|
| 349 |
+
return None
|
| 350 |
+
|
| 351 |
+
# Check Content-Length before downloading
|
| 352 |
+
content_length = r.headers.get("content-length")
|
| 353 |
+
if content_length and int(content_length) > MAX_DOWNLOAD_SIZE:
|
| 354 |
+
log.error(
|
| 355 |
+
"File too large: %s bytes (max %s)", content_length, MAX_DOWNLOAD_SIZE
|
| 356 |
+
)
|
| 357 |
+
return None
|
| 358 |
+
|
| 359 |
+
# Read content with size check
|
| 360 |
+
content = b""
|
| 361 |
+
for chunk in r.iter_content(chunk_size=1024 * 1024):
|
| 362 |
+
if chunk:
|
| 363 |
+
content += chunk
|
| 364 |
+
if len(content) > MAX_DOWNLOAD_SIZE:
|
| 365 |
+
log.error("Download exceeded size limit: %s bytes", len(content))
|
| 366 |
+
return None
|
| 367 |
+
|
| 368 |
+
ct = r.headers.get("content-type", "")
|
| 369 |
+
if "html" in ct.lower() or content.startswith(b"<!"):
|
| 370 |
+
log.error("Got HTML instead of file from %s", file_url)
|
| 371 |
+
return None
|
| 372 |
+
|
| 373 |
+
ext = os.path.splitext(server_path)[1] or ".tif"
|
| 374 |
+
with tempfile.NamedTemporaryFile(
|
| 375 |
+
delete=False, prefix="lungs_seg_", suffix=ext
|
| 376 |
+
) as f:
|
| 377 |
+
f.write(content)
|
| 378 |
+
log.info(
|
| 379 |
+
"Downloaded %s bytes from gradio file endpoint -> %s",
|
| 380 |
+
len(content),
|
| 381 |
+
f.name,
|
| 382 |
+
)
|
| 383 |
+
return register_temp_file(f.name)
|
| 384 |
+
|
| 385 |
+
except Exception as e:
|
| 386 |
+
log.error("Failed gradio file endpoint download: %r", e)
|
| 387 |
+
return None
|
| 388 |
+
|
| 389 |
+
|
| 390 |
+
def _guess_ext(url: str, content_type: str) -> str:
|
| 391 |
+
"""
|
| 392 |
+
Guess file extension from URL path or Content-Type.
|
| 393 |
+
"""
|
| 394 |
+
from urllib.parse import urlparse
|
| 395 |
+
|
| 396 |
+
path = urlparse(url).path.lower()
|
| 397 |
+
|
| 398 |
+
if path.endswith(".nii.gz"):
|
| 399 |
+
return ".nii.gz"
|
| 400 |
+
|
| 401 |
+
ext = os.path.splitext(path)[1]
|
| 402 |
+
if ext:
|
| 403 |
+
return ext
|
| 404 |
+
|
| 405 |
+
ct = (content_type or "").lower()
|
| 406 |
+
if "tiff" in ct or "tif" in ct:
|
| 407 |
+
return ".tif"
|
| 408 |
+
if "png" in ct:
|
| 409 |
+
return ".png"
|
| 410 |
+
if "jpeg" in ct or "jpg" in ct:
|
| 411 |
+
return ".jpg"
|
| 412 |
+
if "gif" in ct:
|
| 413 |
+
return ".gif"
|
| 414 |
+
if "nifti" in ct or "nii" in ct:
|
| 415 |
+
return ".nii.gz"
|
| 416 |
+
return ".bin"
|
| 417 |
+
|
| 418 |
+
|
| 419 |
+
# ---------------------------------------------------------------------
|
| 420 |
+
# Tool Registration
|
| 421 |
+
# ---------------------------------------------------------------------
|
| 422 |
+
register_tool(
|
| 423 |
+
ToolConfig(
|
| 424 |
+
name="lungs_segmentation",
|
| 425 |
+
display_name="3D Lungs Segmentation",
|
| 426 |
+
icon="🫁",
|
| 427 |
+
catalog_names=["lungs-segmentation"], # Catalog name from dataset/catalog.jsonl
|
| 428 |
+
input_model=LungsSegmentationInput,
|
| 429 |
+
output_model=LungsSegmentationOutput,
|
| 430 |
+
executor=tool_lungs_segmentation,
|
| 431 |
+
supports_images=True,
|
| 432 |
+
supports_files=True,
|
| 433 |
+
requires_approval=True,
|
| 434 |
+
preview_field="result_preview",
|
| 435 |
+
download_fields="result_origin", # Could also be ["result_origin", "other_file"]
|
| 436 |
+
metadata_field="metadata_text",
|
| 437 |
+
notes_field="notes",
|
| 438 |
+
success_field="success",
|
| 439 |
+
error_field="error",
|
| 440 |
+
compute_time_field="compute_time_seconds",
|
| 441 |
+
)
|
| 442 |
+
)
|
src/ai_agent/agent/tools/mcp/registry.py
CHANGED
|
@@ -1,203 +1,203 @@
|
|
| 1 |
-
from __future__ import annotations
|
| 2 |
-
|
| 3 |
-
from typing import Dict, Type, Callable, Optional, List, Any
|
| 4 |
-
from pydantic import BaseModel
|
| 5 |
-
from dataclasses import dataclass
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
@dataclass
|
| 9 |
-
class ToolConfig:
|
| 10 |
-
"""
|
| 11 |
-
Declarative configuration for a tool.
|
| 12 |
-
|
| 13 |
-
Tools register themselves with this config, and the UI uses it
|
| 14 |
-
to generically handle execution, display, and file management.
|
| 15 |
-
"""
|
| 16 |
-
|
| 17 |
-
# Core identification
|
| 18 |
-
name: str # Internal name (e.g., "lungs_segmentation")
|
| 19 |
-
display_name: str # User-facing name (e.g., "3D Lungs Segmentation")
|
| 20 |
-
icon: str # Emoji for UI display
|
| 21 |
-
|
| 22 |
-
# Type information
|
| 23 |
-
input_model: Type[BaseModel] # Pydantic model for inputs
|
| 24 |
-
output_model: Type[BaseModel] # Pydantic model for outputs
|
| 25 |
-
executor: Callable # Function that takes input_model and returns output_model
|
| 26 |
-
|
| 27 |
-
# Capability flags
|
| 28 |
-
catalog_names: Optional[List[str]] = (
|
| 29 |
-
None # Catalog names for this tool (e.g., ["lungs-segmentation"])
|
| 30 |
-
)
|
| 31 |
-
supports_images: bool = True
|
| 32 |
-
supports_files: bool = True
|
| 33 |
-
requires_approval: bool = True # Whether to show approval button
|
| 34 |
-
|
| 35 |
-
# Output field mappings (how to extract results from output_model)
|
| 36 |
-
# These map generic concepts to tool-specific field names
|
| 37 |
-
preview_field: str = "result_preview" # Field containing preview image path
|
| 38 |
-
download_fields: List[str] | str = (
|
| 39 |
-
"result_origin" # Field(s) for downloadable files
|
| 40 |
-
)
|
| 41 |
-
metadata_field: Optional[str] = "metadata_text" # Optional metadata text
|
| 42 |
-
notes_field: str = "notes" # Field containing execution notes
|
| 43 |
-
|
| 44 |
-
# Success detection
|
| 45 |
-
success_field: str = "success" # Field indicating success/failure
|
| 46 |
-
error_field: str = "error" # Field containing error message
|
| 47 |
-
compute_time_field: str = "compute_time_seconds" # Field with timing info
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
# Global tool registry
|
| 51 |
-
TOOL_REGISTRY: Dict[str, ToolConfig] = {}
|
| 52 |
-
|
| 53 |
-
# Reverse mapping from catalog names to tool names
|
| 54 |
-
CATALOG_NAME_TO_TOOL: Dict[str, str] = {}
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
def register_tool(config: ToolConfig) -> None:
|
| 58 |
-
"""
|
| 59 |
-
Register a tool with the global registry.
|
| 60 |
-
|
| 61 |
-
Args:
|
| 62 |
-
config: Tool configuration
|
| 63 |
-
|
| 64 |
-
Raises:
|
| 65 |
-
ValueError: If tool name already registered or catalog name collision
|
| 66 |
-
"""
|
| 67 |
-
if config.name in TOOL_REGISTRY:
|
| 68 |
-
raise ValueError(f"Tool '{config.name}' is already registered")
|
| 69 |
-
|
| 70 |
-
# Check for catalog name collisions before registering
|
| 71 |
-
if config.catalog_names:
|
| 72 |
-
for catalog_name in config.catalog_names:
|
| 73 |
-
if (
|
| 74 |
-
catalog_name in CATALOG_NAME_TO_TOOL
|
| 75 |
-
and CATALOG_NAME_TO_TOOL[catalog_name] != config.name
|
| 76 |
-
):
|
| 77 |
-
raise ValueError(
|
| 78 |
-
f"Catalog name '{catalog_name}' already registered to "
|
| 79 |
-
f"'{CATALOG_NAME_TO_TOOL[catalog_name]}'"
|
| 80 |
-
)
|
| 81 |
-
|
| 82 |
-
TOOL_REGISTRY[config.name] = config
|
| 83 |
-
|
| 84 |
-
# Register catalog name mappings
|
| 85 |
-
if config.catalog_names:
|
| 86 |
-
for catalog_name in config.catalog_names:
|
| 87 |
-
CATALOG_NAME_TO_TOOL[catalog_name] = config.name
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
def get_tool(name: str) -> Optional[ToolConfig]:
|
| 91 |
-
"""
|
| 92 |
-
Get tool configuration by name.
|
| 93 |
-
|
| 94 |
-
Args:
|
| 95 |
-
name: Tool name (registry name or catalog name)
|
| 96 |
-
|
| 97 |
-
Returns:
|
| 98 |
-
ToolConfig if found, None otherwise
|
| 99 |
-
|
| 100 |
-
Note:
|
| 101 |
-
This function checks both the tool registry name and catalog names.
|
| 102 |
-
"""
|
| 103 |
-
# First try direct registry lookup
|
| 104 |
-
config = TOOL_REGISTRY.get(name)
|
| 105 |
-
if config:
|
| 106 |
-
return config
|
| 107 |
-
|
| 108 |
-
# Try catalog name mapping
|
| 109 |
-
tool_name = CATALOG_NAME_TO_TOOL.get(name)
|
| 110 |
-
if tool_name:
|
| 111 |
-
return TOOL_REGISTRY.get(tool_name)
|
| 112 |
-
|
| 113 |
-
return None
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
def list_tools() -> List[str]:
|
| 117 |
-
"""Get list of all registered tool names."""
|
| 118 |
-
return list(TOOL_REGISTRY.keys())
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
def get_tool_display_name(name: str) -> str:
|
| 122 |
-
"""
|
| 123 |
-
Get display name for a tool, with fallback to name.
|
| 124 |
-
|
| 125 |
-
Args:
|
| 126 |
-
name: Tool name
|
| 127 |
-
|
| 128 |
-
Returns:
|
| 129 |
-
Display name or formatted version of name
|
| 130 |
-
"""
|
| 131 |
-
tool = get_tool(name)
|
| 132 |
-
if tool:
|
| 133 |
-
return tool.display_name
|
| 134 |
-
# Fallback: format name nicely
|
| 135 |
-
return name.replace("_", " ").title()
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
def get_tool_icon(name: str) -> str:
|
| 139 |
-
"""
|
| 140 |
-
Get icon for a tool, with fallback.
|
| 141 |
-
|
| 142 |
-
Args:
|
| 143 |
-
name: Tool name
|
| 144 |
-
|
| 145 |
-
Returns:
|
| 146 |
-
Icon emoji or default
|
| 147 |
-
"""
|
| 148 |
-
tool = get_tool(name)
|
| 149 |
-
if tool:
|
| 150 |
-
return tool.icon
|
| 151 |
-
return "🔧" # Default tool icon
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
def extract_output_field(output: BaseModel, field_name: str) -> Any:
|
| 155 |
-
"""
|
| 156 |
-
Safely extract a field from tool output.
|
| 157 |
-
|
| 158 |
-
Args:
|
| 159 |
-
output: Tool output object
|
| 160 |
-
field_name: Field name to extract
|
| 161 |
-
|
| 162 |
-
Returns:
|
| 163 |
-
Field value or None if not found
|
| 164 |
-
"""
|
| 165 |
-
return getattr(output, field_name, None)
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
def extract_preview(output: BaseModel, tool_name: str) -> Optional[str]:
|
| 169 |
-
"""Extract preview image path from tool output."""
|
| 170 |
-
tool = get_tool(tool_name)
|
| 171 |
-
if not tool:
|
| 172 |
-
return None
|
| 173 |
-
return extract_output_field(output, tool.preview_field)
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
def extract_downloads(output: BaseModel, tool_name: str) -> List[str]:
|
| 177 |
-
"""Extract downloadable file paths from tool output."""
|
| 178 |
-
tool = get_tool(tool_name)
|
| 179 |
-
if not tool:
|
| 180 |
-
return []
|
| 181 |
-
|
| 182 |
-
download_fields = tool.download_fields
|
| 183 |
-
if isinstance(download_fields, str):
|
| 184 |
-
download_fields = [download_fields]
|
| 185 |
-
|
| 186 |
-
downloads = []
|
| 187 |
-
for field in download_fields:
|
| 188 |
-
value = extract_output_field(output, field)
|
| 189 |
-
if value:
|
| 190 |
-
if isinstance(value, list):
|
| 191 |
-
downloads.extend([v for v in value if v])
|
| 192 |
-
elif isinstance(value, str):
|
| 193 |
-
downloads.append(value)
|
| 194 |
-
|
| 195 |
-
return [d for d in downloads if d] # Filter None/empty
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
def extract_metadata(output: BaseModel, tool_name: str) -> Optional[str]:
|
| 199 |
-
"""Extract metadata text from tool output."""
|
| 200 |
-
tool = get_tool(tool_name)
|
| 201 |
-
if not tool or not tool.metadata_field:
|
| 202 |
-
return None
|
| 203 |
-
return extract_output_field(output, tool.metadata_field)
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from typing import Dict, Type, Callable, Optional, List, Any
|
| 4 |
+
from pydantic import BaseModel
|
| 5 |
+
from dataclasses import dataclass
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
@dataclass
|
| 9 |
+
class ToolConfig:
|
| 10 |
+
"""
|
| 11 |
+
Declarative configuration for a tool.
|
| 12 |
+
|
| 13 |
+
Tools register themselves with this config, and the UI uses it
|
| 14 |
+
to generically handle execution, display, and file management.
|
| 15 |
+
"""
|
| 16 |
+
|
| 17 |
+
# Core identification
|
| 18 |
+
name: str # Internal name (e.g., "lungs_segmentation")
|
| 19 |
+
display_name: str # User-facing name (e.g., "3D Lungs Segmentation")
|
| 20 |
+
icon: str # Emoji for UI display
|
| 21 |
+
|
| 22 |
+
# Type information
|
| 23 |
+
input_model: Type[BaseModel] # Pydantic model for inputs
|
| 24 |
+
output_model: Type[BaseModel] # Pydantic model for outputs
|
| 25 |
+
executor: Callable # Function that takes input_model and returns output_model
|
| 26 |
+
|
| 27 |
+
# Capability flags
|
| 28 |
+
catalog_names: Optional[List[str]] = (
|
| 29 |
+
None # Catalog names for this tool (e.g., ["lungs-segmentation"])
|
| 30 |
+
)
|
| 31 |
+
supports_images: bool = True
|
| 32 |
+
supports_files: bool = True
|
| 33 |
+
requires_approval: bool = True # Whether to show approval button
|
| 34 |
+
|
| 35 |
+
# Output field mappings (how to extract results from output_model)
|
| 36 |
+
# These map generic concepts to tool-specific field names
|
| 37 |
+
preview_field: str = "result_preview" # Field containing preview image path
|
| 38 |
+
download_fields: List[str] | str = (
|
| 39 |
+
"result_origin" # Field(s) for downloadable files
|
| 40 |
+
)
|
| 41 |
+
metadata_field: Optional[str] = "metadata_text" # Optional metadata text
|
| 42 |
+
notes_field: str = "notes" # Field containing execution notes
|
| 43 |
+
|
| 44 |
+
# Success detection
|
| 45 |
+
success_field: str = "success" # Field indicating success/failure
|
| 46 |
+
error_field: str = "error" # Field containing error message
|
| 47 |
+
compute_time_field: str = "compute_time_seconds" # Field with timing info
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
# Global tool registry
|
| 51 |
+
TOOL_REGISTRY: Dict[str, ToolConfig] = {}
|
| 52 |
+
|
| 53 |
+
# Reverse mapping from catalog names to tool names
|
| 54 |
+
CATALOG_NAME_TO_TOOL: Dict[str, str] = {}
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def register_tool(config: ToolConfig) -> None:
|
| 58 |
+
"""
|
| 59 |
+
Register a tool with the global registry.
|
| 60 |
+
|
| 61 |
+
Args:
|
| 62 |
+
config: Tool configuration
|
| 63 |
+
|
| 64 |
+
Raises:
|
| 65 |
+
ValueError: If tool name already registered or catalog name collision
|
| 66 |
+
"""
|
| 67 |
+
if config.name in TOOL_REGISTRY:
|
| 68 |
+
raise ValueError(f"Tool '{config.name}' is already registered")
|
| 69 |
+
|
| 70 |
+
# Check for catalog name collisions before registering
|
| 71 |
+
if config.catalog_names:
|
| 72 |
+
for catalog_name in config.catalog_names:
|
| 73 |
+
if (
|
| 74 |
+
catalog_name in CATALOG_NAME_TO_TOOL
|
| 75 |
+
and CATALOG_NAME_TO_TOOL[catalog_name] != config.name
|
| 76 |
+
):
|
| 77 |
+
raise ValueError(
|
| 78 |
+
f"Catalog name '{catalog_name}' already registered to "
|
| 79 |
+
f"'{CATALOG_NAME_TO_TOOL[catalog_name]}'"
|
| 80 |
+
)
|
| 81 |
+
|
| 82 |
+
TOOL_REGISTRY[config.name] = config
|
| 83 |
+
|
| 84 |
+
# Register catalog name mappings
|
| 85 |
+
if config.catalog_names:
|
| 86 |
+
for catalog_name in config.catalog_names:
|
| 87 |
+
CATALOG_NAME_TO_TOOL[catalog_name] = config.name
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
def get_tool(name: str) -> Optional[ToolConfig]:
|
| 91 |
+
"""
|
| 92 |
+
Get tool configuration by name.
|
| 93 |
+
|
| 94 |
+
Args:
|
| 95 |
+
name: Tool name (registry name or catalog name)
|
| 96 |
+
|
| 97 |
+
Returns:
|
| 98 |
+
ToolConfig if found, None otherwise
|
| 99 |
+
|
| 100 |
+
Note:
|
| 101 |
+
This function checks both the tool registry name and catalog names.
|
| 102 |
+
"""
|
| 103 |
+
# First try direct registry lookup
|
| 104 |
+
config = TOOL_REGISTRY.get(name)
|
| 105 |
+
if config:
|
| 106 |
+
return config
|
| 107 |
+
|
| 108 |
+
# Try catalog name mapping
|
| 109 |
+
tool_name = CATALOG_NAME_TO_TOOL.get(name)
|
| 110 |
+
if tool_name:
|
| 111 |
+
return TOOL_REGISTRY.get(tool_name)
|
| 112 |
+
|
| 113 |
+
return None
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
def list_tools() -> List[str]:
|
| 117 |
+
"""Get list of all registered tool names."""
|
| 118 |
+
return list(TOOL_REGISTRY.keys())
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
def get_tool_display_name(name: str) -> str:
|
| 122 |
+
"""
|
| 123 |
+
Get display name for a tool, with fallback to name.
|
| 124 |
+
|
| 125 |
+
Args:
|
| 126 |
+
name: Tool name
|
| 127 |
+
|
| 128 |
+
Returns:
|
| 129 |
+
Display name or formatted version of name
|
| 130 |
+
"""
|
| 131 |
+
tool = get_tool(name)
|
| 132 |
+
if tool:
|
| 133 |
+
return tool.display_name
|
| 134 |
+
# Fallback: format name nicely
|
| 135 |
+
return name.replace("_", " ").title()
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
def get_tool_icon(name: str) -> str:
|
| 139 |
+
"""
|
| 140 |
+
Get icon for a tool, with fallback.
|
| 141 |
+
|
| 142 |
+
Args:
|
| 143 |
+
name: Tool name
|
| 144 |
+
|
| 145 |
+
Returns:
|
| 146 |
+
Icon emoji or default
|
| 147 |
+
"""
|
| 148 |
+
tool = get_tool(name)
|
| 149 |
+
if tool:
|
| 150 |
+
return tool.icon
|
| 151 |
+
return "🔧" # Default tool icon
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
def extract_output_field(output: BaseModel, field_name: str) -> Any:
|
| 155 |
+
"""
|
| 156 |
+
Safely extract a field from tool output.
|
| 157 |
+
|
| 158 |
+
Args:
|
| 159 |
+
output: Tool output object
|
| 160 |
+
field_name: Field name to extract
|
| 161 |
+
|
| 162 |
+
Returns:
|
| 163 |
+
Field value or None if not found
|
| 164 |
+
"""
|
| 165 |
+
return getattr(output, field_name, None)
|
| 166 |
+
|
| 167 |
+
|
| 168 |
+
def extract_preview(output: BaseModel, tool_name: str) -> Optional[str]:
|
| 169 |
+
"""Extract preview image path from tool output."""
|
| 170 |
+
tool = get_tool(tool_name)
|
| 171 |
+
if not tool:
|
| 172 |
+
return None
|
| 173 |
+
return extract_output_field(output, tool.preview_field)
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
def extract_downloads(output: BaseModel, tool_name: str) -> List[str]:
|
| 177 |
+
"""Extract downloadable file paths from tool output."""
|
| 178 |
+
tool = get_tool(tool_name)
|
| 179 |
+
if not tool:
|
| 180 |
+
return []
|
| 181 |
+
|
| 182 |
+
download_fields = tool.download_fields
|
| 183 |
+
if isinstance(download_fields, str):
|
| 184 |
+
download_fields = [download_fields]
|
| 185 |
+
|
| 186 |
+
downloads = []
|
| 187 |
+
for field in download_fields:
|
| 188 |
+
value = extract_output_field(output, field)
|
| 189 |
+
if value:
|
| 190 |
+
if isinstance(value, list):
|
| 191 |
+
downloads.extend([v for v in value if v])
|
| 192 |
+
elif isinstance(value, str):
|
| 193 |
+
downloads.append(value)
|
| 194 |
+
|
| 195 |
+
return [d for d in downloads if d] # Filter None/empty
|
| 196 |
+
|
| 197 |
+
|
| 198 |
+
def extract_metadata(output: BaseModel, tool_name: str) -> Optional[str]:
|
| 199 |
+
"""Extract metadata text from tool output."""
|
| 200 |
+
tool = get_tool(tool_name)
|
| 201 |
+
if not tool or not tool.metadata_field:
|
| 202 |
+
return None
|
| 203 |
+
return extract_output_field(output, tool.metadata_field)
|
src/ai_agent/agent/tools/query_utils.py
CHANGED
|
@@ -1,128 +1,128 @@
|
|
| 1 |
-
from __future__ import annotations
|
| 2 |
-
|
| 3 |
-
import re
|
| 4 |
-
from typing import List
|
| 5 |
-
|
| 6 |
-
FORMAT_TOKEN_MAP = {
|
| 7 |
-
"tif": "TIFF",
|
| 8 |
-
"tiff": "TIFF",
|
| 9 |
-
"nii": "NIfTI",
|
| 10 |
-
"nii.gz": "NIfTI",
|
| 11 |
-
"dcm": "DICOM",
|
| 12 |
-
"dicom": "DICOM",
|
| 13 |
-
"nrrd": "NRRD",
|
| 14 |
-
"png": "PNG",
|
| 15 |
-
"jpg": "JPEG",
|
| 16 |
-
"jpeg": "JPEG",
|
| 17 |
-
}
|
| 18 |
-
|
| 19 |
-
_REPO_DRIFT_TERMS = {
|
| 20 |
-
"github",
|
| 21 |
-
"repository",
|
| 22 |
-
"repo",
|
| 23 |
-
"official",
|
| 24 |
-
"readme",
|
| 25 |
-
"docs",
|
| 26 |
-
"documentation",
|
| 27 |
-
"source",
|
| 28 |
-
"sourcecode",
|
| 29 |
-
}
|
| 30 |
-
|
| 31 |
-
_LOW_SIGNAL_TERMS = _REPO_DRIFT_TERMS | {
|
| 32 |
-
"tool",
|
| 33 |
-
"tools",
|
| 34 |
-
"project",
|
| 35 |
-
"framework",
|
| 36 |
-
}
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
def _tokenize_query(query: str) -> List[str]:
|
| 40 |
-
return [t for t in re.findall(r"[a-z0-9_+-]+", (query or "").lower()) if t]
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
def normalize_formats(formats: List[str]) -> List[str]:
|
| 44 |
-
seen = set()
|
| 45 |
-
out: List[str] = []
|
| 46 |
-
for ext in formats:
|
| 47 |
-
norm = (ext or "").strip().lower()
|
| 48 |
-
if not norm or norm in seen:
|
| 49 |
-
continue
|
| 50 |
-
seen.add(norm)
|
| 51 |
-
out.append(norm)
|
| 52 |
-
return out
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
def append_format_tokens(query: str, formats: List[str]) -> str:
|
| 56 |
-
fmt_tokens: List[str] = []
|
| 57 |
-
for ext in normalize_formats(formats):
|
| 58 |
-
canon = FORMAT_TOKEN_MAP.get(ext, ext.upper())
|
| 59 |
-
if canon not in fmt_tokens:
|
| 60 |
-
fmt_tokens.append(canon)
|
| 61 |
-
|
| 62 |
-
if not fmt_tokens:
|
| 63 |
-
return query.strip()
|
| 64 |
-
return (query.strip() + " " + " ".join(f"format:{t}" for t in fmt_tokens)).strip()
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
def strip_legacy_original_formats_line(query: str) -> tuple[str, List[str]]:
|
| 68 |
-
"""Parse and remove legacy OriginalFormats: line from query text."""
|
| 69 |
-
original_formats: List[str] = []
|
| 70 |
-
clean_lines = []
|
| 71 |
-
|
| 72 |
-
for line in (query or "").splitlines():
|
| 73 |
-
if line.lower().startswith("originalformats:"):
|
| 74 |
-
parts = line.split(":", 1)[1].strip().split()
|
| 75 |
-
original_formats.extend(parts)
|
| 76 |
-
continue
|
| 77 |
-
clean_lines.append(line)
|
| 78 |
-
|
| 79 |
-
base_query = " ".join(ln.strip() for ln in clean_lines if ln.strip())
|
| 80 |
-
return base_query, normalize_formats(original_formats)
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
def sanitize_retrieval_query(
|
| 84 |
-
query: str,
|
| 85 |
-
known_tool_names: List[str] | None = None,
|
| 86 |
-
fallback_query: str | None = None,
|
| 87 |
-
) -> str:
|
| 88 |
-
"""
|
| 89 |
-
Sanitize LLM-generated retrieval queries by removing repository drift terms.
|
| 90 |
-
|
| 91 |
-
If the query collapses into a tool-name-only or low-signal query, fallback
|
| 92 |
-
to the previous task-centric query when provided.
|
| 93 |
-
"""
|
| 94 |
-
raw = (query or "").strip()
|
| 95 |
-
if not raw:
|
| 96 |
-
return (fallback_query or "").strip()
|
| 97 |
-
|
| 98 |
-
# Remove URLs and punctuation-heavy fragments.
|
| 99 |
-
s = re.sub(r"https?://\S+", " ", raw, flags=re.IGNORECASE)
|
| 100 |
-
s = re.sub(r"www\.\S+", " ", s, flags=re.IGNORECASE)
|
| 101 |
-
|
| 102 |
-
tokens = _tokenize_query(s)
|
| 103 |
-
if not tokens:
|
| 104 |
-
return (fallback_query or raw).strip()
|
| 105 |
-
|
| 106 |
-
filtered = [t for t in tokens if t not in _REPO_DRIFT_TERMS]
|
| 107 |
-
if not filtered:
|
| 108 |
-
return (fallback_query or raw).strip()
|
| 109 |
-
|
| 110 |
-
# Detect tool-name-only drift (e.g., "dhsegment official github repository").
|
| 111 |
-
if known_tool_names:
|
| 112 |
-
token_set = set(filtered)
|
| 113 |
-
for nm in known_tool_names:
|
| 114 |
-
nm_tokens = set(_tokenize_query(nm))
|
| 115 |
-
if not nm_tokens:
|
| 116 |
-
continue
|
| 117 |
-
if token_set.issubset(nm_tokens) and len(token_set) <= 3:
|
| 118 |
-
return (fallback_query or " ".join(filtered)).strip()
|
| 119 |
-
|
| 120 |
-
low_signal = all(t in _LOW_SIGNAL_TERMS for t in filtered)
|
| 121 |
-
if low_signal:
|
| 122 |
-
return (fallback_query or " ".join(filtered)).strip()
|
| 123 |
-
|
| 124 |
-
# If it became too short and a previous task query exists, prefer that.
|
| 125 |
-
if len(filtered) <= 2 and fallback_query:
|
| 126 |
-
return fallback_query.strip()
|
| 127 |
-
|
| 128 |
-
return " ".join(filtered).strip()
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import re
|
| 4 |
+
from typing import List
|
| 5 |
+
|
| 6 |
+
FORMAT_TOKEN_MAP = {
|
| 7 |
+
"tif": "TIFF",
|
| 8 |
+
"tiff": "TIFF",
|
| 9 |
+
"nii": "NIfTI",
|
| 10 |
+
"nii.gz": "NIfTI",
|
| 11 |
+
"dcm": "DICOM",
|
| 12 |
+
"dicom": "DICOM",
|
| 13 |
+
"nrrd": "NRRD",
|
| 14 |
+
"png": "PNG",
|
| 15 |
+
"jpg": "JPEG",
|
| 16 |
+
"jpeg": "JPEG",
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
_REPO_DRIFT_TERMS = {
|
| 20 |
+
"github",
|
| 21 |
+
"repository",
|
| 22 |
+
"repo",
|
| 23 |
+
"official",
|
| 24 |
+
"readme",
|
| 25 |
+
"docs",
|
| 26 |
+
"documentation",
|
| 27 |
+
"source",
|
| 28 |
+
"sourcecode",
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
_LOW_SIGNAL_TERMS = _REPO_DRIFT_TERMS | {
|
| 32 |
+
"tool",
|
| 33 |
+
"tools",
|
| 34 |
+
"project",
|
| 35 |
+
"framework",
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def _tokenize_query(query: str) -> List[str]:
|
| 40 |
+
return [t for t in re.findall(r"[a-z0-9_+-]+", (query or "").lower()) if t]
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def normalize_formats(formats: List[str]) -> List[str]:
|
| 44 |
+
seen = set()
|
| 45 |
+
out: List[str] = []
|
| 46 |
+
for ext in formats:
|
| 47 |
+
norm = (ext or "").strip().lower()
|
| 48 |
+
if not norm or norm in seen:
|
| 49 |
+
continue
|
| 50 |
+
seen.add(norm)
|
| 51 |
+
out.append(norm)
|
| 52 |
+
return out
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def append_format_tokens(query: str, formats: List[str]) -> str:
|
| 56 |
+
fmt_tokens: List[str] = []
|
| 57 |
+
for ext in normalize_formats(formats):
|
| 58 |
+
canon = FORMAT_TOKEN_MAP.get(ext, ext.upper())
|
| 59 |
+
if canon not in fmt_tokens:
|
| 60 |
+
fmt_tokens.append(canon)
|
| 61 |
+
|
| 62 |
+
if not fmt_tokens:
|
| 63 |
+
return query.strip()
|
| 64 |
+
return (query.strip() + " " + " ".join(f"format:{t}" for t in fmt_tokens)).strip()
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
def strip_legacy_original_formats_line(query: str) -> tuple[str, List[str]]:
|
| 68 |
+
"""Parse and remove legacy OriginalFormats: line from query text."""
|
| 69 |
+
original_formats: List[str] = []
|
| 70 |
+
clean_lines = []
|
| 71 |
+
|
| 72 |
+
for line in (query or "").splitlines():
|
| 73 |
+
if line.lower().startswith("originalformats:"):
|
| 74 |
+
parts = line.split(":", 1)[1].strip().split()
|
| 75 |
+
original_formats.extend(parts)
|
| 76 |
+
continue
|
| 77 |
+
clean_lines.append(line)
|
| 78 |
+
|
| 79 |
+
base_query = " ".join(ln.strip() for ln in clean_lines if ln.strip())
|
| 80 |
+
return base_query, normalize_formats(original_formats)
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
def sanitize_retrieval_query(
|
| 84 |
+
query: str,
|
| 85 |
+
known_tool_names: List[str] | None = None,
|
| 86 |
+
fallback_query: str | None = None,
|
| 87 |
+
) -> str:
|
| 88 |
+
"""
|
| 89 |
+
Sanitize LLM-generated retrieval queries by removing repository drift terms.
|
| 90 |
+
|
| 91 |
+
If the query collapses into a tool-name-only or low-signal query, fallback
|
| 92 |
+
to the previous task-centric query when provided.
|
| 93 |
+
"""
|
| 94 |
+
raw = (query or "").strip()
|
| 95 |
+
if not raw:
|
| 96 |
+
return (fallback_query or "").strip()
|
| 97 |
+
|
| 98 |
+
# Remove URLs and punctuation-heavy fragments.
|
| 99 |
+
s = re.sub(r"https?://\S+", " ", raw, flags=re.IGNORECASE)
|
| 100 |
+
s = re.sub(r"www\.\S+", " ", s, flags=re.IGNORECASE)
|
| 101 |
+
|
| 102 |
+
tokens = _tokenize_query(s)
|
| 103 |
+
if not tokens:
|
| 104 |
+
return (fallback_query or raw).strip()
|
| 105 |
+
|
| 106 |
+
filtered = [t for t in tokens if t not in _REPO_DRIFT_TERMS]
|
| 107 |
+
if not filtered:
|
| 108 |
+
return (fallback_query or raw).strip()
|
| 109 |
+
|
| 110 |
+
# Detect tool-name-only drift (e.g., "dhsegment official github repository").
|
| 111 |
+
if known_tool_names:
|
| 112 |
+
token_set = set(filtered)
|
| 113 |
+
for nm in known_tool_names:
|
| 114 |
+
nm_tokens = set(_tokenize_query(nm))
|
| 115 |
+
if not nm_tokens:
|
| 116 |
+
continue
|
| 117 |
+
if token_set.issubset(nm_tokens) and len(token_set) <= 3:
|
| 118 |
+
return (fallback_query or " ".join(filtered)).strip()
|
| 119 |
+
|
| 120 |
+
low_signal = all(t in _LOW_SIGNAL_TERMS for t in filtered)
|
| 121 |
+
if low_signal:
|
| 122 |
+
return (fallback_query or " ".join(filtered)).strip()
|
| 123 |
+
|
| 124 |
+
# If it became too short and a previous task query exists, prefer that.
|
| 125 |
+
if len(filtered) <= 2 and fallback_query:
|
| 126 |
+
return fallback_query.strip()
|
| 127 |
+
|
| 128 |
+
return " ".join(filtered).strip()
|
src/ai_agent/agent/tools/repo_info_tool.py
CHANGED
|
@@ -2,7 +2,6 @@ from __future__ import annotations
|
|
| 2 |
|
| 3 |
import asyncio
|
| 4 |
import os
|
| 5 |
-
import time
|
| 6 |
from typing import Optional
|
| 7 |
import repocards
|
| 8 |
|
|
@@ -10,6 +9,7 @@ from pydantic import BaseModel
|
|
| 10 |
|
| 11 |
from .deepwiki_tool import get_wiki_contents, DeepWikiInput
|
| 12 |
from .utils import _clip, get_catalog_docs, _is_github_url
|
|
|
|
| 13 |
|
| 14 |
import logging
|
| 15 |
|
|
@@ -18,7 +18,9 @@ log = logging.getLogger("agent.repo_info")
|
|
| 18 |
REPO_INFO_CACHE_TTL_SECONDS = int(os.getenv("REPO_INFO_CACHE_TTL_SECONDS", "3600"))
|
| 19 |
REPO_INFO_CACHE_MAX_ENTRIES = int(os.getenv("REPO_INFO_CACHE_MAX_ENTRIES", "256"))
|
| 20 |
|
| 21 |
-
|
|
|
|
|
|
|
| 22 |
_REPO_INFO_INFLIGHT: dict[str, asyncio.Future["RepoSummaryOutput"]] = {}
|
| 23 |
_REPO_INFO_LOCK = asyncio.Lock()
|
| 24 |
|
|
@@ -41,25 +43,9 @@ def _normalize_cache_key(url: str) -> str:
|
|
| 41 |
return url.strip().lower()
|
| 42 |
|
| 43 |
|
| 44 |
-
def _evict_expired_entries(now: float) -> None:
|
| 45 |
-
expired = [k for k, (exp, _) in _REPO_INFO_CACHE.items() if exp <= now]
|
| 46 |
-
for key in expired:
|
| 47 |
-
_REPO_INFO_CACHE.pop(key, None)
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
def _enforce_cache_capacity() -> None:
|
| 51 |
-
if len(_REPO_INFO_CACHE) <= REPO_INFO_CACHE_MAX_ENTRIES:
|
| 52 |
-
return
|
| 53 |
-
# Evict oldest expiration first. Good enough for bounded in-memory cache.
|
| 54 |
-
keys_by_expiry = sorted(_REPO_INFO_CACHE.items(), key=lambda item: item[1][0])
|
| 55 |
-
over = len(_REPO_INFO_CACHE) - REPO_INFO_CACHE_MAX_ENTRIES
|
| 56 |
-
for key, _ in keys_by_expiry[:over]:
|
| 57 |
-
_REPO_INFO_CACHE.pop(key, None)
|
| 58 |
-
|
| 59 |
-
|
| 60 |
def _clear_repo_summary_cache_for_tests() -> None:
|
| 61 |
"""Test helper to avoid state leakage across test cases."""
|
| 62 |
-
|
| 63 |
_REPO_INFO_INFLIGHT.clear()
|
| 64 |
|
| 65 |
|
|
@@ -94,17 +80,31 @@ async def tool_repo_summary(input: RepoSummaryInput) -> RepoSummaryOutput:
|
|
| 94 |
log.warning(f"Failed to lookup repo URL from catalog: {e}")
|
| 95 |
|
| 96 |
cache_key = _normalize_cache_key(effective_url)
|
| 97 |
-
|
| 98 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
await _REPO_INFO_LOCK.acquire()
|
| 100 |
try:
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
log.
|
| 107 |
-
|
|
|
|
|
|
|
|
|
|
| 108 |
|
| 109 |
inflight = _REPO_INFO_INFLIGHT.get(cache_key)
|
| 110 |
if inflight is None:
|
|
@@ -133,19 +133,30 @@ async def tool_repo_summary(input: RepoSummaryInput) -> RepoSummaryOutput:
|
|
| 133 |
_REPO_INFO_LOCK.release()
|
| 134 |
raise
|
| 135 |
|
|
|
|
|
|
|
| 136 |
await _REPO_INFO_LOCK.acquire()
|
| 137 |
try:
|
| 138 |
-
if result.source != "error" and REPO_INFO_CACHE_TTL_SECONDS > 0:
|
| 139 |
-
expires_at = time.monotonic() + REPO_INFO_CACHE_TTL_SECONDS
|
| 140 |
-
_REPO_INFO_CACHE[cache_key] = (expires_at, result)
|
| 141 |
-
_enforce_cache_capacity()
|
| 142 |
-
|
| 143 |
if not inflight.done():
|
| 144 |
inflight.set_result(result)
|
| 145 |
_REPO_INFO_INFLIGHT.pop(cache_key, None)
|
| 146 |
finally:
|
| 147 |
_REPO_INFO_LOCK.release()
|
| 148 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 149 |
return result.model_copy(deep=True)
|
| 150 |
|
| 151 |
|
|
|
|
| 2 |
|
| 3 |
import asyncio
|
| 4 |
import os
|
|
|
|
| 5 |
from typing import Optional
|
| 6 |
import repocards
|
| 7 |
|
|
|
|
| 9 |
|
| 10 |
from .deepwiki_tool import get_wiki_contents, DeepWikiInput
|
| 11 |
from .utils import _clip, get_catalog_docs, _is_github_url
|
| 12 |
+
from ai_agent.utils.cache_db import get_cache_db
|
| 13 |
|
| 14 |
import logging
|
| 15 |
|
|
|
|
| 18 |
REPO_INFO_CACHE_TTL_SECONDS = int(os.getenv("REPO_INFO_CACHE_TTL_SECONDS", "3600"))
|
| 19 |
REPO_INFO_CACHE_MAX_ENTRIES = int(os.getenv("REPO_INFO_CACHE_MAX_ENTRIES", "256"))
|
| 20 |
|
| 21 |
+
_REPO_INFO_NS = "repo_info"
|
| 22 |
+
|
| 23 |
+
# In-flight request deduplication must remain in-memory (holds asyncio.Future objects)
|
| 24 |
_REPO_INFO_INFLIGHT: dict[str, asyncio.Future["RepoSummaryOutput"]] = {}
|
| 25 |
_REPO_INFO_LOCK = asyncio.Lock()
|
| 26 |
|
|
|
|
| 43 |
return url.strip().lower()
|
| 44 |
|
| 45 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
def _clear_repo_summary_cache_for_tests() -> None:
|
| 47 |
"""Test helper to avoid state leakage across test cases."""
|
| 48 |
+
get_cache_db().clear(_REPO_INFO_NS)
|
| 49 |
_REPO_INFO_INFLIGHT.clear()
|
| 50 |
|
| 51 |
|
|
|
|
| 80 |
log.warning(f"Failed to lookup repo URL from catalog: {e}")
|
| 81 |
|
| 82 |
cache_key = _normalize_cache_key(effective_url)
|
| 83 |
+
db = get_cache_db()
|
| 84 |
|
| 85 |
+
# --- Cache read outside the lock so we don't block other coroutines ---
|
| 86 |
+
try:
|
| 87 |
+
raw = await asyncio.to_thread(db.get, _REPO_INFO_NS, cache_key)
|
| 88 |
+
except Exception:
|
| 89 |
+
log.warning("Repo info cache read failed; treating as cache miss.", exc_info=True)
|
| 90 |
+
raw = None
|
| 91 |
+
if raw is not None:
|
| 92 |
+
log.info(f"Repo info cache hit for {effective_url}")
|
| 93 |
+
return RepoSummaryOutput.model_validate_json(raw)
|
| 94 |
+
|
| 95 |
+
# --- Lock only for inflight bookkeeping (pure in-memory, no I/O) ---
|
| 96 |
await _REPO_INFO_LOCK.acquire()
|
| 97 |
try:
|
| 98 |
+
# Re-check the cache inside the lock in case another coroutine wrote it
|
| 99 |
+
# between our lockless read above and acquiring the lock now.
|
| 100 |
+
try:
|
| 101 |
+
raw = await asyncio.to_thread(db.get, _REPO_INFO_NS, cache_key)
|
| 102 |
+
except Exception:
|
| 103 |
+
log.warning("Repo info cache read (after lock) failed; treating as cache miss.", exc_info=True)
|
| 104 |
+
raw = None
|
| 105 |
+
if raw is not None:
|
| 106 |
+
log.info(f"Repo info cache hit (after lock) for {effective_url}")
|
| 107 |
+
return RepoSummaryOutput.model_validate_json(raw)
|
| 108 |
|
| 109 |
inflight = _REPO_INFO_INFLIGHT.get(cache_key)
|
| 110 |
if inflight is None:
|
|
|
|
| 133 |
_REPO_INFO_LOCK.release()
|
| 134 |
raise
|
| 135 |
|
| 136 |
+
# --- Resolve the in-flight future immediately so waiters are unblocked
|
| 137 |
+
# before the (potentially slow) DB write happens. ---
|
| 138 |
await _REPO_INFO_LOCK.acquire()
|
| 139 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 140 |
if not inflight.done():
|
| 141 |
inflight.set_result(result)
|
| 142 |
_REPO_INFO_INFLIGHT.pop(cache_key, None)
|
| 143 |
finally:
|
| 144 |
_REPO_INFO_LOCK.release()
|
| 145 |
|
| 146 |
+
# --- DB write after waiters are already released ---
|
| 147 |
+
if result.source != "error" and REPO_INFO_CACHE_TTL_SECONDS > 0:
|
| 148 |
+
try:
|
| 149 |
+
await asyncio.to_thread(
|
| 150 |
+
db.set,
|
| 151 |
+
_REPO_INFO_NS,
|
| 152 |
+
cache_key,
|
| 153 |
+
result.model_dump_json(),
|
| 154 |
+
ttl_seconds=REPO_INFO_CACHE_TTL_SECONDS,
|
| 155 |
+
max_entries=REPO_INFO_CACHE_MAX_ENTRIES,
|
| 156 |
+
)
|
| 157 |
+
except Exception:
|
| 158 |
+
log.warning("Repo info cache write failed; result will not be cached.", exc_info=True)
|
| 159 |
+
|
| 160 |
return result.model_copy(deep=True)
|
| 161 |
|
| 162 |
|
src/ai_agent/agent/tools/search_alternative_tool.py
CHANGED
|
@@ -1,73 +1,73 @@
|
|
| 1 |
-
from __future__ import annotations
|
| 2 |
-
|
| 3 |
-
from typing import List
|
| 4 |
-
from pydantic import BaseModel, Field
|
| 5 |
-
|
| 6 |
-
from ai_agent.generator.schema import CandidateDoc
|
| 7 |
-
from .utils import get_known_names, get_pipeline
|
| 8 |
-
from .query_utils import append_format_tokens, normalize_formats, sanitize_retrieval_query
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
class SearchAlternativeInput(BaseModel):
|
| 12 |
-
"""
|
| 13 |
-
Input for searching with an alternative query formulation.
|
| 14 |
-
|
| 15 |
-
Use this when initial search results are insufficient and you want to
|
| 16 |
-
try a different phrasing or broader/narrower terms.
|
| 17 |
-
"""
|
| 18 |
-
|
| 19 |
-
alternative_query: str = Field(
|
| 20 |
-
description="Alternative query phrasing to try (can be similar terms, broader/narrower, etc.)"
|
| 21 |
-
)
|
| 22 |
-
excluded: List[str] = Field(default_factory=list)
|
| 23 |
-
top_k: int = 12
|
| 24 |
-
original_formats: List[str] = Field(default_factory=list)
|
| 25 |
-
image_paths: List[str] = Field(default_factory=list)
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
class SearchAlternativeOutput(BaseModel):
|
| 29 |
-
candidates: List[CandidateDoc]
|
| 30 |
-
query_used: str
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
def tool_search_alternative(inp: SearchAlternativeInput) -> SearchAlternativeOutput:
|
| 34 |
-
"""
|
| 35 |
-
Search with an alternative query formulation, with automatic reranking.
|
| 36 |
-
|
| 37 |
-
This tool allows the agent to explicitly try a different search approach
|
| 38 |
-
when initial results are not satisfactory.
|
| 39 |
-
"""
|
| 40 |
-
pipe = get_pipeline()
|
| 41 |
-
|
| 42 |
-
# Use the alternative query directly
|
| 43 |
-
query = sanitize_retrieval_query(
|
| 44 |
-
inp.alternative_query.strip(), known_tool_names=get_known_names()
|
| 45 |
-
)
|
| 46 |
-
|
| 47 |
-
# Normalize formats
|
| 48 |
-
original_formats: List[str] = normalize_formats(inp.original_formats)
|
| 49 |
-
query = append_format_tokens(query, original_formats)
|
| 50 |
-
|
| 51 |
-
# Call retrieve() which includes automatic reranking
|
| 52 |
-
hits = pipe.retrieve(
|
| 53 |
-
query,
|
| 54 |
-
image_paths=inp.image_paths or None,
|
| 55 |
-
exclusions=inp.excluded,
|
| 56 |
-
top_k=inp.top_k,
|
| 57 |
-
)
|
| 58 |
-
|
| 59 |
-
# Convert hits to CandidateDoc objects
|
| 60 |
-
candidates: List[CandidateDoc] = []
|
| 61 |
-
for h in hits:
|
| 62 |
-
d = h.get("doc")
|
| 63 |
-
if not d:
|
| 64 |
-
continue
|
| 65 |
-
try:
|
| 66 |
-
candidates.append(CandidateDoc.model_validate(d.model_dump(mode="python")))
|
| 67 |
-
except Exception:
|
| 68 |
-
continue
|
| 69 |
-
|
| 70 |
-
return SearchAlternativeOutput(
|
| 71 |
-
candidates=candidates,
|
| 72 |
-
query_used=query,
|
| 73 |
-
)
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from typing import List
|
| 4 |
+
from pydantic import BaseModel, Field
|
| 5 |
+
|
| 6 |
+
from ai_agent.generator.schema import CandidateDoc
|
| 7 |
+
from .utils import get_known_names, get_pipeline
|
| 8 |
+
from .query_utils import append_format_tokens, normalize_formats, sanitize_retrieval_query
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class SearchAlternativeInput(BaseModel):
|
| 12 |
+
"""
|
| 13 |
+
Input for searching with an alternative query formulation.
|
| 14 |
+
|
| 15 |
+
Use this when initial search results are insufficient and you want to
|
| 16 |
+
try a different phrasing or broader/narrower terms.
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
alternative_query: str = Field(
|
| 20 |
+
description="Alternative query phrasing to try (can be similar terms, broader/narrower, etc.)"
|
| 21 |
+
)
|
| 22 |
+
excluded: List[str] = Field(default_factory=list)
|
| 23 |
+
top_k: int = 12
|
| 24 |
+
original_formats: List[str] = Field(default_factory=list)
|
| 25 |
+
image_paths: List[str] = Field(default_factory=list)
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
class SearchAlternativeOutput(BaseModel):
|
| 29 |
+
candidates: List[CandidateDoc]
|
| 30 |
+
query_used: str
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def tool_search_alternative(inp: SearchAlternativeInput) -> SearchAlternativeOutput:
|
| 34 |
+
"""
|
| 35 |
+
Search with an alternative query formulation, with automatic reranking.
|
| 36 |
+
|
| 37 |
+
This tool allows the agent to explicitly try a different search approach
|
| 38 |
+
when initial results are not satisfactory.
|
| 39 |
+
"""
|
| 40 |
+
pipe = get_pipeline()
|
| 41 |
+
|
| 42 |
+
# Use the alternative query directly
|
| 43 |
+
query = sanitize_retrieval_query(
|
| 44 |
+
inp.alternative_query.strip(), known_tool_names=get_known_names()
|
| 45 |
+
)
|
| 46 |
+
|
| 47 |
+
# Normalize formats
|
| 48 |
+
original_formats: List[str] = normalize_formats(inp.original_formats)
|
| 49 |
+
query = append_format_tokens(query, original_formats)
|
| 50 |
+
|
| 51 |
+
# Call retrieve() which includes automatic reranking
|
| 52 |
+
hits = pipe.retrieve(
|
| 53 |
+
query,
|
| 54 |
+
image_paths=inp.image_paths or None,
|
| 55 |
+
exclusions=inp.excluded,
|
| 56 |
+
top_k=inp.top_k,
|
| 57 |
+
)
|
| 58 |
+
|
| 59 |
+
# Convert hits to CandidateDoc objects
|
| 60 |
+
candidates: List[CandidateDoc] = []
|
| 61 |
+
for h in hits:
|
| 62 |
+
d = h.get("doc")
|
| 63 |
+
if not d:
|
| 64 |
+
continue
|
| 65 |
+
try:
|
| 66 |
+
candidates.append(CandidateDoc.model_validate(d.model_dump(mode="python")))
|
| 67 |
+
except Exception:
|
| 68 |
+
continue
|
| 69 |
+
|
| 70 |
+
return SearchAlternativeOutput(
|
| 71 |
+
candidates=candidates,
|
| 72 |
+
query_used=query,
|
| 73 |
+
)
|
src/ai_agent/agent/tools/sparql_tool.py
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""SPARQL query tool — lets the agent ask the catalog's GraphDB anything the
|
| 2 |
+
RAG can't answer cleanly (counts, distincts, structural filters, etc.).
|
| 3 |
+
|
| 4 |
+
Safety:
|
| 5 |
+
· read-only by syntax check — UPDATE / INSERT / DELETE / DROP / CLEAR /
|
| 6 |
+
CREATE / LOAD / COPY / MOVE / ADD are rejected before the query goes
|
| 7 |
+
anywhere near the endpoint
|
| 8 |
+
· result row count is capped (we inject / override LIMIT)
|
| 9 |
+
· network and query time are bounded (SPARQLWrapper timeout)
|
| 10 |
+
· auth comes from the same GRAPHDB_USER / GRAPHDB_PASSWORD env the
|
| 11 |
+
catalog sync uses
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
from __future__ import annotations
|
| 15 |
+
|
| 16 |
+
import logging
|
| 17 |
+
import os
|
| 18 |
+
import re
|
| 19 |
+
from typing import Any, Dict, List, Optional
|
| 20 |
+
|
| 21 |
+
from pydantic import BaseModel, Field
|
| 22 |
+
from SPARQLWrapper import JSON, SPARQLWrapper
|
| 23 |
+
|
| 24 |
+
log = logging.getLogger("agent.tools.sparql")
|
| 25 |
+
|
| 26 |
+
# Default ceiling for any SELECT — overrideable via input.
|
| 27 |
+
DEFAULT_LIMIT = 50
|
| 28 |
+
HARD_MAX_LIMIT = 200
|
| 29 |
+
|
| 30 |
+
# Update keywords that must NOT appear at the top level of the query. We
|
| 31 |
+
# match on word boundaries (case-insensitive) outside of `<...>` IRIs and
|
| 32 |
+
# quoted strings — a real parser would be safer but for an agent-authored
|
| 33 |
+
# query this catches the common cases.
|
| 34 |
+
_UPDATE_KEYWORDS = re.compile(
|
| 35 |
+
r"\b("
|
| 36 |
+
r"INSERT|DELETE|UPDATE|DROP|CLEAR|CREATE|LOAD|COPY|MOVE|ADD|MODIFY"
|
| 37 |
+
r")\b",
|
| 38 |
+
re.IGNORECASE,
|
| 39 |
+
)
|
| 40 |
+
|
| 41 |
+
_LIMIT_RE = re.compile(r"\blimit\s+(\d+)\b", re.IGNORECASE)
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
class SparqlQueryInput(BaseModel):
|
| 45 |
+
query: str = Field(..., description="A SPARQL SELECT / ASK query.")
|
| 46 |
+
limit: int = Field(
|
| 47 |
+
default=DEFAULT_LIMIT,
|
| 48 |
+
ge=1,
|
| 49 |
+
le=HARD_MAX_LIMIT,
|
| 50 |
+
description="Maximum row count (we'll add / cap LIMIT in the query).",
|
| 51 |
+
)
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
class SparqlQueryOutput(BaseModel):
|
| 55 |
+
columns: List[str] = Field(default_factory=list)
|
| 56 |
+
rows: List[Dict[str, Any]] = Field(default_factory=list)
|
| 57 |
+
row_count: int = 0
|
| 58 |
+
truncated: bool = False
|
| 59 |
+
boolean: Optional[bool] = None # only for ASK queries
|
| 60 |
+
query_executed: str = ""
|
| 61 |
+
error: Optional[str] = None
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def _strip_strings_and_iris(q: str) -> str:
|
| 65 |
+
"""Remove `"..."`, `'...'`, and `<...>` so keyword scanning isn't fooled
|
| 66 |
+
by literal text inside the query."""
|
| 67 |
+
q = re.sub(r'"(?:[^"\\]|\\.)*"', '""', q)
|
| 68 |
+
q = re.sub(r"'(?:[^'\\]|\\.)*'", "''", q)
|
| 69 |
+
q = re.sub(r"<[^>]*>", "<>", q)
|
| 70 |
+
return q
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def _looks_readonly(query: str) -> bool:
|
| 74 |
+
scrubbed = _strip_strings_and_iris(query)
|
| 75 |
+
return _UPDATE_KEYWORDS.search(scrubbed) is None
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
def _enforce_limit(query: str, limit: int) -> str:
|
| 79 |
+
"""Inject LIMIT, or cap an existing one to `limit`. SELECT only — ASK,
|
| 80 |
+
CONSTRUCT, DESCRIBE pass through unchanged (LIMIT is moot for ASK; the
|
| 81 |
+
other two are blocked upstream)."""
|
| 82 |
+
if not re.search(r"\bselect\b", query, re.IGNORECASE):
|
| 83 |
+
return query
|
| 84 |
+
m = _LIMIT_RE.search(query)
|
| 85 |
+
if m:
|
| 86 |
+
requested = int(m.group(1))
|
| 87 |
+
if requested <= limit:
|
| 88 |
+
return query
|
| 89 |
+
return _LIMIT_RE.sub(f"LIMIT {limit}", query)
|
| 90 |
+
trimmed = query.rstrip(" \t\n;")
|
| 91 |
+
return trimmed + "\nLIMIT " + str(limit)
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
def tool_sparql_query(inp: SparqlQueryInput) -> SparqlQueryOutput:
|
| 95 |
+
"""Execute a SPARQL SELECT or ASK against GRAPHDB_URL and return a flat
|
| 96 |
+
table of bindings."""
|
| 97 |
+
endpoint = (os.getenv("GRAPHDB_URL") or "").strip()
|
| 98 |
+
if not endpoint:
|
| 99 |
+
return SparqlQueryOutput(
|
| 100 |
+
error="GRAPHDB_URL is not configured on the server.",
|
| 101 |
+
query_executed=inp.query,
|
| 102 |
+
)
|
| 103 |
+
|
| 104 |
+
if not _looks_readonly(inp.query):
|
| 105 |
+
return SparqlQueryOutput(
|
| 106 |
+
error="rejected_update_query — only read-only SPARQL is allowed",
|
| 107 |
+
query_executed=inp.query,
|
| 108 |
+
)
|
| 109 |
+
|
| 110 |
+
# We currently only normalise SELECT/ASK. CONSTRUCT/DESCRIBE return RDF
|
| 111 |
+
# graphs which would need a different output schema — bail with a clear
|
| 112 |
+
# message rather than silently misformatting.
|
| 113 |
+
qkind = re.search(
|
| 114 |
+
r"\b(select|ask|construct|describe)\b", inp.query, re.IGNORECASE
|
| 115 |
+
)
|
| 116 |
+
if not qkind:
|
| 117 |
+
return SparqlQueryOutput(
|
| 118 |
+
error="missing_query_form — start the query with SELECT or ASK",
|
| 119 |
+
query_executed=inp.query,
|
| 120 |
+
)
|
| 121 |
+
form = qkind.group(1).lower()
|
| 122 |
+
if form in ("construct", "describe"):
|
| 123 |
+
return SparqlQueryOutput(
|
| 124 |
+
error=f"unsupported_form — {form.upper()} not supported by this tool yet; rephrase as SELECT",
|
| 125 |
+
query_executed=inp.query,
|
| 126 |
+
)
|
| 127 |
+
|
| 128 |
+
final_query = _enforce_limit(inp.query, inp.limit) if form == "select" else inp.query
|
| 129 |
+
|
| 130 |
+
sparql = SPARQLWrapper(endpoint)
|
| 131 |
+
sparql.setQuery(final_query)
|
| 132 |
+
sparql.setReturnFormat(JSON)
|
| 133 |
+
user = os.getenv("GRAPHDB_USER")
|
| 134 |
+
pwd = os.getenv("GRAPHDB_PASSWORD")
|
| 135 |
+
if user and pwd:
|
| 136 |
+
sparql.setCredentials(user=user, passwd=pwd)
|
| 137 |
+
sparql.setTimeout(20)
|
| 138 |
+
|
| 139 |
+
try:
|
| 140 |
+
result = sparql.query().convert()
|
| 141 |
+
except Exception as exc:
|
| 142 |
+
log.warning("sparql_query failed: %s", exc)
|
| 143 |
+
return SparqlQueryOutput(
|
| 144 |
+
error=f"sparql_error: {exc}", query_executed=final_query
|
| 145 |
+
)
|
| 146 |
+
|
| 147 |
+
# ASK → boolean
|
| 148 |
+
if "boolean" in result:
|
| 149 |
+
return SparqlQueryOutput(
|
| 150 |
+
boolean=bool(result["boolean"]),
|
| 151 |
+
row_count=0,
|
| 152 |
+
query_executed=final_query,
|
| 153 |
+
)
|
| 154 |
+
|
| 155 |
+
cols: List[str] = list(result.get("head", {}).get("vars", []))
|
| 156 |
+
bindings = result.get("results", {}).get("bindings", []) or []
|
| 157 |
+
rows: List[Dict[str, Any]] = []
|
| 158 |
+
for b in bindings:
|
| 159 |
+
row: Dict[str, Any] = {}
|
| 160 |
+
for c in cols:
|
| 161 |
+
cell = b.get(c)
|
| 162 |
+
if cell is None:
|
| 163 |
+
row[c] = None
|
| 164 |
+
else:
|
| 165 |
+
row[c] = cell.get("value")
|
| 166 |
+
rows.append(row)
|
| 167 |
+
|
| 168 |
+
truncated = len(rows) >= inp.limit
|
| 169 |
+
return SparqlQueryOutput(
|
| 170 |
+
columns=cols,
|
| 171 |
+
rows=rows,
|
| 172 |
+
row_count=len(rows),
|
| 173 |
+
truncated=truncated,
|
| 174 |
+
query_executed=final_query,
|
| 175 |
+
)
|
| 176 |
+
|
| 177 |
+
|
| 178 |
+
__all__ = ["SparqlQueryInput", "SparqlQueryOutput", "tool_sparql_query"]
|
src/ai_agent/api/deps.py
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Shared FastAPI dependencies.
|
| 2 |
+
|
| 3 |
+
- ``require_auth``: simple cookie-based gate using ``APP_PASSWORD`` env var.
|
| 4 |
+
If ``APP_PASSWORD`` is empty/unset, auth is disabled entirely (dev mode).
|
| 5 |
+
- ``get_pipeline``: returns the singleton ``RAGImagingPipeline`` (lazy-init,
|
| 6 |
+
shared with the Gradio path via ``ai_agent.core.pipeline_registry``).
|
| 7 |
+
- ``get_doc_index``: name -> SoftwareDoc, derived from the pipeline.
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
from __future__ import annotations
|
| 11 |
+
|
| 12 |
+
import hashlib
|
| 13 |
+
import hmac
|
| 14 |
+
import logging
|
| 15 |
+
import os
|
| 16 |
+
from typing import Dict, Optional
|
| 17 |
+
|
| 18 |
+
from fastapi import Cookie, Depends, HTTPException, status
|
| 19 |
+
|
| 20 |
+
from ai_agent.api.pipeline import RAGImagingPipeline
|
| 21 |
+
from ai_agent.core.pipeline_registry import get_pipeline as _shared_pipeline
|
| 22 |
+
from ai_agent.retriever.software_doc import SoftwareDoc
|
| 23 |
+
|
| 24 |
+
log = logging.getLogger("api.deps")
|
| 25 |
+
|
| 26 |
+
AUTH_COOKIE_NAME = "ai_agent_auth"
|
| 27 |
+
_AUTH_VERSION = "v1"
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def _expected_cookie() -> Optional[str]:
|
| 31 |
+
"""The cookie value clients must present, or None if auth is disabled."""
|
| 32 |
+
pw = os.getenv("APP_PASSWORD") or ""
|
| 33 |
+
if not pw:
|
| 34 |
+
return None
|
| 35 |
+
return hmac.new(
|
| 36 |
+
pw.encode("utf-8"),
|
| 37 |
+
_AUTH_VERSION.encode("utf-8"),
|
| 38 |
+
hashlib.sha256,
|
| 39 |
+
).hexdigest()
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def make_cookie_value(password: str) -> str:
|
| 43 |
+
"""Compute the cookie value for a successful login."""
|
| 44 |
+
return hmac.new(
|
| 45 |
+
password.encode("utf-8"),
|
| 46 |
+
_AUTH_VERSION.encode("utf-8"),
|
| 47 |
+
hashlib.sha256,
|
| 48 |
+
).hexdigest()
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def verify_password(password: str) -> bool:
|
| 52 |
+
expected = os.getenv("APP_PASSWORD") or ""
|
| 53 |
+
if not expected:
|
| 54 |
+
# Auth disabled — accept any password (login is then effectively a noop).
|
| 55 |
+
return True
|
| 56 |
+
return hmac.compare_digest(password, expected)
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
def auth_disabled() -> bool:
|
| 60 |
+
return not (os.getenv("APP_PASSWORD") or "")
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
async def require_auth(
|
| 64 |
+
ai_agent_auth: Optional[str] = Cookie(default=None, alias=AUTH_COOKIE_NAME),
|
| 65 |
+
) -> None:
|
| 66 |
+
"""FastAPI dependency: 401 if cookie missing or wrong (when auth on)."""
|
| 67 |
+
expected = _expected_cookie()
|
| 68 |
+
if expected is None:
|
| 69 |
+
return # auth disabled
|
| 70 |
+
if not ai_agent_auth or not hmac.compare_digest(ai_agent_auth, expected):
|
| 71 |
+
raise HTTPException(
|
| 72 |
+
status_code=status.HTTP_401_UNAUTHORIZED, detail="not_authenticated"
|
| 73 |
+
)
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
# ---------------------------------------------------------------------------
|
| 77 |
+
# Pipeline / doc index providers (re-export so routers don't import internals).
|
| 78 |
+
# ---------------------------------------------------------------------------
|
| 79 |
+
def get_pipeline() -> RAGImagingPipeline:
|
| 80 |
+
index_dir = os.getenv("RAG_INDEX_DIR", "artifacts/rag_index")
|
| 81 |
+
return _shared_pipeline(index_dir=index_dir)
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
def get_doc_index(
|
| 85 |
+
pipe: RAGImagingPipeline = Depends(get_pipeline),
|
| 86 |
+
) -> Dict[str, SoftwareDoc]:
|
| 87 |
+
try:
|
| 88 |
+
docs = list(pipe.index.docs.values())
|
| 89 |
+
return {d.name: d for d in docs if getattr(d, "name", None)}
|
| 90 |
+
except Exception:
|
| 91 |
+
log.exception("Failed to build doc_index from pipeline")
|
| 92 |
+
return {}
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
__all__ = [
|
| 96 |
+
"AUTH_COOKIE_NAME",
|
| 97 |
+
"auth_disabled",
|
| 98 |
+
"get_doc_index",
|
| 99 |
+
"get_pipeline",
|
| 100 |
+
"make_cookie_value",
|
| 101 |
+
"require_auth",
|
| 102 |
+
"verify_password",
|
| 103 |
+
]
|