mirror of
https://github.com/temporal-community/temporal-ai-agent.git
synced 2026-03-16 22:48:09 +01:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3180084e7c | ||
|
|
1bd69db1d0 | ||
|
|
98a1b75dff | ||
|
|
e248a6778d | ||
|
|
d0dca40b93 | ||
|
|
457fa1fce8 | ||
|
|
3c71a062fd | ||
|
|
380009f292 | ||
|
|
68ac9c40eb | ||
|
|
4ed4efbe83 | ||
|
|
40714071d6 |
20
.devcontainer/devcontainer.json
Normal file
20
.devcontainer/devcontainer.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"image": "mcr.microsoft.com/devcontainers/base:ubuntu",
|
||||||
|
"name": "Temporal AI Agentic Demo",
|
||||||
|
"features": {
|
||||||
|
"ghcr.io/devcontainers/features/node:1": {},
|
||||||
|
"ghcr.io/va-h/devcontainers-features/uv:1": {},
|
||||||
|
"ghcr.io/devcontainers/features/python:1": {},
|
||||||
|
"ghcr.io/devcontainers-extra/features/temporal-cli:1": {},
|
||||||
|
"ghcr.io/mrsimonemms/devcontainers/tcld:1": {}
|
||||||
|
},
|
||||||
|
"forwardPorts": [
|
||||||
|
5173,
|
||||||
|
7233,
|
||||||
|
8000,
|
||||||
|
8233
|
||||||
|
],
|
||||||
|
"containerEnv": {
|
||||||
|
"VITE_HOST": "0.0.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
37
.github/workflows/ci.yml
vendored
Normal file
37
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
push:
|
||||||
|
branches: [ main ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint-test:
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
python-version: ['3.13']
|
||||||
|
os: [ubuntu-latest]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install uv
|
||||||
|
uses: astral-sh/setup-uv@v6
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python-version }}
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
uv sync
|
||||||
|
|
||||||
|
- name: Format, lint, type check
|
||||||
|
run: |
|
||||||
|
uv run poe format
|
||||||
|
uv run poe lint
|
||||||
|
uv run poe lint-types
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: |
|
||||||
|
uv run pytest
|
||||||
41
AGENTS.md
41
AGENTS.md
@@ -34,11 +34,10 @@ Default URLs:
|
|||||||
|
|
||||||
1. **Prerequisites:**
|
1. **Prerequisites:**
|
||||||
```bash
|
```bash
|
||||||
# Install Poetry for Python dependency management
|
# Install uv and Temporal server (MacOS)
|
||||||
curl -sSL https://install.python-poetry.org | python3 -
|
brew install uv
|
||||||
|
|
||||||
# Start Temporal server (Mac)
|
|
||||||
brew install temporal
|
brew install temporal
|
||||||
|
|
||||||
temporal server start-dev
|
temporal server start-dev
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -50,9 +49,9 @@ Default URLs:
|
|||||||
make run-api # Starts the API server
|
make run-api # Starts the API server
|
||||||
|
|
||||||
# Or manually:
|
# Or manually:
|
||||||
poetry install
|
uv sync
|
||||||
poetry run python scripts/run_worker.py # In one terminal
|
uv run scripts/run_worker.py # In one terminal
|
||||||
poetry run uvicorn api.main:app --reload # In another terminal
|
uv run uvicorn api.main:app --reload # In another terminal
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **Frontend (React):**
|
3. **Frontend (React):**
|
||||||
@@ -102,20 +101,20 @@ The project includes comprehensive tests using Temporal's testing framework:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Install test dependencies
|
# Install test dependencies
|
||||||
poetry install --with dev
|
uv sync
|
||||||
|
|
||||||
# Run all tests
|
# Run all tests
|
||||||
poetry run pytest
|
uv run pytest
|
||||||
|
|
||||||
# Run with time-skipping for faster execution
|
# Run with time-skipping for faster execution
|
||||||
poetry run pytest --workflow-environment=time-skipping
|
uv run pytest --workflow-environment=time-skipping
|
||||||
|
|
||||||
# Run specific test categories
|
# Run specific test categories
|
||||||
poetry run pytest tests/test_tool_activities.py -v # Activity tests
|
uv run pytest tests/test_tool_activities.py -v # Activity tests
|
||||||
poetry run pytest tests/test_agent_goal_workflow.py -v # Workflow tests
|
uv run pytest tests/test_agent_goal_workflow.py -v # Workflow tests
|
||||||
|
|
||||||
# Run with coverage
|
# Run with coverage
|
||||||
poetry run pytest --cov=workflows --cov=activities
|
uv run pytest --cov=workflows --cov=activities
|
||||||
```
|
```
|
||||||
|
|
||||||
**Test Coverage:**
|
**Test Coverage:**
|
||||||
@@ -130,15 +129,15 @@ poetry run pytest --cov=workflows --cov=activities
|
|||||||
## Linting and Code Quality
|
## Linting and Code Quality
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Using Poetry tasks
|
# Using poe tasks
|
||||||
poetry run poe format # Format code with black and isort
|
uv run poe format # Format code with black and isort
|
||||||
poetry run poe lint # Check code style and types
|
uv run poe lint # Check code style and types
|
||||||
poetry run poe test # Run test suite
|
uv run poe test # Run test suite
|
||||||
|
|
||||||
# Manual commands
|
# Manual commands
|
||||||
poetry run black .
|
uv run black .
|
||||||
poetry run isort .
|
uv run isort .
|
||||||
poetry run mypy --check-untyped-defs --namespace-packages .
|
uv run mypy --check-untyped-defs --namespace-packages .
|
||||||
```
|
```
|
||||||
|
|
||||||
## Agent Customization
|
## Agent Customization
|
||||||
@@ -192,7 +191,7 @@ For detailed architecture information, see [architecture.md](docs/architecture.m
|
|||||||
- Use clear commit messages describing the change purpose
|
- Use clear commit messages describing the change purpose
|
||||||
- Reference specific files and line numbers when relevant (e.g., `workflows/agent_goal_workflow.py:125`)
|
- Reference specific files and line numbers when relevant (e.g., `workflows/agent_goal_workflow.py:125`)
|
||||||
- Open PRs describing **what changed** and **why**
|
- Open PRs describing **what changed** and **why**
|
||||||
- Ensure tests pass before submitting: `poetry run pytest --workflow-environment=time-skipping`
|
- Ensure tests pass before submitting: `uv run pytest --workflow-environment=time-skipping`
|
||||||
|
|
||||||
## Additional Resources
|
## Additional Resources
|
||||||
- **Setup Guide**: [setup.md](docs/setup.md) - Detailed configuration instructions
|
- **Setup Guide**: [setup.md](docs/setup.md) - Detailed configuration instructions
|
||||||
|
|||||||
18
Dockerfile
18
Dockerfile
@@ -4,17 +4,19 @@ WORKDIR /app
|
|||||||
|
|
||||||
# Install system dependencies
|
# Install system dependencies
|
||||||
RUN apt-get update && \
|
RUN apt-get update && \
|
||||||
apt-get install -y --no-install-recommends gcc build-essential && \
|
apt-get install -y --no-install-recommends gcc build-essential curl && \
|
||||||
apt-get clean && \
|
apt-get clean && \
|
||||||
rm -rf /var/lib/apt/lists/*
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# Copy requirements first for better caching
|
# Install uv
|
||||||
RUN pip install --no-cache-dir poetry
|
RUN curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||||
|
ENV PATH="$PATH:/root/.local/bin"
|
||||||
|
|
||||||
# Install Python dependencies without creating a virtualenv
|
# Copy dependency files and README (needed for package build)
|
||||||
COPY pyproject.toml poetry.lock ./
|
COPY pyproject.toml uv.lock README.md ./
|
||||||
RUN poetry config virtualenvs.create false \
|
|
||||||
&& poetry install --without dev --no-interaction --no-ansi --no-root
|
# Install dependencies and create virtual environment
|
||||||
|
RUN uv sync --frozen
|
||||||
|
|
||||||
# Copy application code
|
# Copy application code
|
||||||
COPY . .
|
COPY . .
|
||||||
@@ -27,4 +29,4 @@ ENV PYTHONPATH=/app
|
|||||||
EXPOSE 8000
|
EXPOSE 8000
|
||||||
|
|
||||||
# Default to running only the API server; worker and train-api are separate Compose services
|
# Default to running only the API server; worker and train-api are separate Compose services
|
||||||
CMD ["uvicorn", "api.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
CMD ["uv", "run", "uvicorn", "api.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||||
|
|||||||
27
Makefile
27
Makefile
@@ -1,35 +1,24 @@
|
|||||||
.PHONY: setup install run-worker run-api run-frontend run-train-api run-legacy-worker run-enterprise setup-venv check-python run-dev
|
.PHONY: setup install run-worker run-api run-frontend run-train-api run-legacy-worker run-enterprise setup-venv check-python run-dev
|
||||||
|
|
||||||
# Setup commands
|
setup:
|
||||||
setup: check-python setup-venv install
|
uv sync
|
||||||
|
|
||||||
check-python:
|
|
||||||
@which python3 >/dev/null 2>&1 || (echo "Python 3 is required. Please install it first." && exit 1)
|
|
||||||
@which poetry >/dev/null 2>&1 || (echo "Poetry is required. Please install it first." && exit 1)
|
|
||||||
|
|
||||||
setup-venv:
|
|
||||||
python3 -m venv venv
|
|
||||||
@echo "Virtual environment created. Don't forget to activate it with 'source venv/bin/activate'"
|
|
||||||
|
|
||||||
install:
|
|
||||||
poetry install
|
|
||||||
cd frontend && npm install
|
cd frontend && npm install
|
||||||
|
|
||||||
# Run commands
|
# Run commands
|
||||||
run-worker:
|
run-worker:
|
||||||
poetry run python scripts/run_worker.py
|
uv run scripts/run_worker.py
|
||||||
|
|
||||||
run-api:
|
run-api:
|
||||||
poetry run uvicorn api.main:app --reload
|
uv run uvicorn api.main:app --reload
|
||||||
|
|
||||||
run-frontend:
|
run-frontend:
|
||||||
cd frontend && npx vite
|
cd frontend && npx vite
|
||||||
|
|
||||||
run-train-api:
|
run-train-api:
|
||||||
poetry run python thirdparty/train_api.py
|
uv run thirdparty/train_api.py
|
||||||
|
|
||||||
run-legacy-worker:
|
run-legacy-worker:
|
||||||
poetry run python scripts/run_legacy_worker.py
|
uv run scripts/run_legacy_worker.py
|
||||||
|
|
||||||
run-enterprise:
|
run-enterprise:
|
||||||
cd enterprise && dotnet build && dotnet run
|
cd enterprise && dotnet build && dotnet run
|
||||||
@@ -50,9 +39,7 @@ run-dev:
|
|||||||
# Help command
|
# Help command
|
||||||
help:
|
help:
|
||||||
@echo "Available commands:"
|
@echo "Available commands:"
|
||||||
@echo " make setup - Create virtual environment and install dependencies"
|
@echo " make setup - Install all dependencies"
|
||||||
@echo " make setup-venv - Create virtual environment only"
|
|
||||||
@echo " make install - Install all dependencies"
|
|
||||||
@echo " make run-worker - Start the Temporal worker"
|
@echo " make run-worker - Start the Temporal worker"
|
||||||
@echo " make run-api - Start the API server"
|
@echo " make run-api - Start the API server"
|
||||||
@echo " make run-frontend - Start the frontend development server"
|
@echo " make run-frontend - Start the frontend development server"
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ It's really helpful to [watch the demo (5 minute YouTube video)](https://www.you
|
|||||||
See multi-agent execution in action [here](https://www.youtube.com/watch?v=8Dc_0dC14yY).
|
See multi-agent execution in action [here](https://www.youtube.com/watch?v=8Dc_0dC14yY).
|
||||||
|
|
||||||
## Why Temporal?
|
## Why Temporal?
|
||||||
There are a lot of AI and Agentic AI tools out there, and more on the way. But why Temporal? Temporal gives this system reliablity, state management, a code-first approach that we really like, built-in observability and easy error handling.
|
There are a lot of AI and Agentic AI tools out there, and more on the way. But why Temporal? Temporal gives this system reliability, state management, a code-first approach that we really like, built-in observability and easy error handling.
|
||||||
For more, check out [architecture-decisions](docs/architecture-decisions.md).
|
For more, check out [architecture-decisions](docs/architecture-decisions.md).
|
||||||
|
|
||||||
## What is "Agentic AI"?
|
## What is "Agentic AI"?
|
||||||
@@ -65,13 +65,13 @@ The project includes comprehensive tests for workflows and activities using Temp
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Install dependencies including test dependencies
|
# Install dependencies including test dependencies
|
||||||
poetry install --with dev
|
uv sync
|
||||||
|
|
||||||
# Run all tests
|
# Run all tests
|
||||||
poetry run pytest
|
uv run pytest
|
||||||
|
|
||||||
# Run with time-skipping for faster execution
|
# Run with time-skipping for faster execution
|
||||||
poetry run pytest --workflow-environment=time-skipping
|
uv run pytest --workflow-environment=time-skipping
|
||||||
```
|
```
|
||||||
|
|
||||||
**Test Coverage:**
|
**Test Coverage:**
|
||||||
|
|||||||
@@ -2,17 +2,17 @@ services:
|
|||||||
api:
|
api:
|
||||||
volumes:
|
volumes:
|
||||||
- ./:/app:cached
|
- ./:/app:cached
|
||||||
command: uvicorn api.main:app --host 0.0.0.0 --port 8000 --reload
|
command: uv run uvicorn api.main:app --host 0.0.0.0 --port 8000 --reload
|
||||||
|
|
||||||
worker:
|
worker:
|
||||||
volumes:
|
volumes:
|
||||||
- ./:/app:cached
|
- ./:/app:cached
|
||||||
command: python scripts/run_worker.py
|
command: uv run scripts/run_worker.py
|
||||||
|
|
||||||
train-api:
|
train-api:
|
||||||
volumes:
|
volumes:
|
||||||
- ./:/app:cached
|
- ./:/app:cached
|
||||||
command: python thirdparty/train_api.py
|
command: uv run thirdparty/train_api.py
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ services:
|
|||||||
- .env
|
- .env
|
||||||
environment:
|
environment:
|
||||||
- TEMPORAL_ADDRESS=temporal:7233
|
- TEMPORAL_ADDRESS=temporal:7233
|
||||||
command: python scripts/run_worker.py
|
command: uv run scripts/run_worker.py
|
||||||
networks:
|
networks:
|
||||||
- temporal-network
|
- temporal-network
|
||||||
|
|
||||||
@@ -94,7 +94,7 @@ services:
|
|||||||
- .env
|
- .env
|
||||||
environment:
|
environment:
|
||||||
- TEMPORAL_ADDRESS=temporal:7233
|
- TEMPORAL_ADDRESS=temporal:7233
|
||||||
command: python thirdparty/train_api.py
|
command: uv run thirdparty/train_api.py
|
||||||
networks:
|
networks:
|
||||||
- temporal-network
|
- temporal-network
|
||||||
|
|
||||||
|
|||||||
@@ -8,40 +8,40 @@ This document provides guidelines for contributing to `temporal-ai-agent`. All s
|
|||||||
We use `black` for code formatting and `isort` for import sorting to maintain a consistent codebase.
|
We use `black` for code formatting and `isort` for import sorting to maintain a consistent codebase.
|
||||||
- **Format code:**
|
- **Format code:**
|
||||||
```bash
|
```bash
|
||||||
poetry run poe format
|
uv run poe format
|
||||||
```
|
```
|
||||||
Or manually:
|
Or manually
|
||||||
```bash
|
```
|
||||||
poetry run black .
|
uv run black .
|
||||||
poetry run isort .
|
uv run isort .
|
||||||
```
|
```
|
||||||
Please format your code before committing.
|
Please format your code before committing.
|
||||||
|
|
||||||
### Linting & Type Checking
|
### Linting & Type Checking
|
||||||
We use `mypy` for static type checking and other linters configured via `poe the poet`.
|
We use `mypy` for static type checking and other linters configured via `poe`.
|
||||||
- **Run linters and type checks:**
|
- **Run linters and type checks:**
|
||||||
```bash
|
```bash
|
||||||
poetry run poe lint
|
uv run poe lint
|
||||||
```
|
```
|
||||||
Or manually for type checking:
|
Or manually for type checking:
|
||||||
```bash
|
```bash
|
||||||
poetry run mypy --check-untyped-defs --namespace-packages .
|
uv run mypy --check-untyped-defs --namespace-packages .
|
||||||
```
|
```
|
||||||
Ensure all linting and type checks pass before submitting a pull request.
|
Ensure all linting and type checks pass before submitting a pull request.
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
Comprehensive testing is crucial for this project. We use `pytest` and Temporal's testing framework.
|
Comprehensive testing is crucial for this project. We use `pytest` and Temporal's testing framework.
|
||||||
- **Install test dependencies** (if not already done with `poetry install --with dev`):
|
- **Install test dependencies:**
|
||||||
```bash
|
```bash
|
||||||
poetry install --with dev
|
uv sync
|
||||||
```
|
```
|
||||||
- **Run all tests:**
|
- **Run all tests:**
|
||||||
```bash
|
```bash
|
||||||
poetry run pytest
|
uv run pytest
|
||||||
```
|
```
|
||||||
- **Run tests with time-skipping (recommended for faster execution, especially in CI):**
|
- **Run tests with time-skipping (recommended for faster execution, especially in CI):**
|
||||||
```bash
|
```bash
|
||||||
poetry run pytest --workflow-environment=time-skipping
|
uv run pytest --workflow-environment=time-skipping
|
||||||
```
|
```
|
||||||
|
|
||||||
For detailed information on test categories, running specific tests, test environments, coverage, and troubleshooting, please refer to:
|
For detailed information on test categories, running specific tests, test environments, coverage, and troubleshooting, please refer to:
|
||||||
@@ -73,7 +73,7 @@ When you're ready to submit your changes:
|
|||||||
1. Push your branch to the remote repository.
|
1. Push your branch to the remote repository.
|
||||||
2. Open a Pull Request (PR) against the `main` branch.
|
2. Open a Pull Request (PR) against the `main` branch.
|
||||||
3. **Describe your changes:** Clearly explain what you changed and why. Reference any related issues.
|
3. **Describe your changes:** Clearly explain what you changed and why. Reference any related issues.
|
||||||
4. **Ensure tests pass:** All CI checks, including tests and linters, must pass. The command `poetry run pytest --workflow-environment=time-skipping` is a good one to run locally.
|
4. **Ensure tests pass:** All CI checks, including tests and linters, must pass. The command `uv run pytest --workflow-environment=time-skipping` is a good one to run locally.
|
||||||
5. **Request review:** Request a review from one or more maintainers.
|
5. **Request review:** Request a review from one or more maintainers.
|
||||||
|
|
||||||
## Reporting Bugs
|
## Reporting Bugs
|
||||||
|
|||||||
@@ -22,8 +22,6 @@ We've provided a Makefile to simplify the setup and running of the application.
|
|||||||
```bash
|
```bash
|
||||||
# Initial setup
|
# Initial setup
|
||||||
make setup # Creates virtual environment and installs dependencies
|
make setup # Creates virtual environment and installs dependencies
|
||||||
make setup-venv # Creates virtual environment only
|
|
||||||
make install # Installs all dependencies
|
|
||||||
|
|
||||||
# Running the application
|
# Running the application
|
||||||
make run-worker # Starts the Temporal worker
|
make run-worker # Starts the Temporal worker
|
||||||
@@ -159,24 +157,22 @@ Default urls:
|
|||||||
|
|
||||||
**Python Backend**
|
**Python Backend**
|
||||||
|
|
||||||
Requires [Poetry](https://python-poetry.org/) to manage dependencies.
|
Requires [`uv`](https://docs.astral.sh/uv/) to manage dependencies.
|
||||||
|
|
||||||
1. `python -m venv venv`
|
1. Install uv: `curl -LsSf https://astral.sh/uv/install.sh | sh`
|
||||||
|
|
||||||
2. `source venv/bin/activate`
|
2. `uv sync`
|
||||||
|
|
||||||
3. `poetry install`
|
|
||||||
|
|
||||||
Run the following commands in separate terminal windows:
|
Run the following commands in separate terminal windows:
|
||||||
|
|
||||||
1. Start the Temporal worker:
|
1. Start the Temporal worker:
|
||||||
```bash
|
```bash
|
||||||
poetry run python scripts/run_worker.py
|
uv run scripts/run_worker.py
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Start the API server:
|
2. Start the API server:
|
||||||
```bash
|
```bash
|
||||||
poetry run uvicorn api.main:app --reload
|
uv run uvicorn api.main:app --reload
|
||||||
```
|
```
|
||||||
Access the API at `/docs` to see the available endpoints.
|
Access the API at `/docs` to see the available endpoints.
|
||||||
|
|
||||||
@@ -261,7 +257,7 @@ NOTE: This goal was developed for an on-stage demo and has failure (and its reso
|
|||||||
|
|
||||||
Required to search and book trains!
|
Required to search and book trains!
|
||||||
```bash
|
```bash
|
||||||
poetry run python thirdparty/train_api.py
|
uv run thirdparty/train_api.py
|
||||||
|
|
||||||
# example url
|
# example url
|
||||||
# http://localhost:8080/api/search?from=london&to=liverpool&outbound_time=2025-04-18T09:00:00&inbound_time=2025-04-20T09:00:00
|
# http://localhost:8080/api/search?from=london&to=liverpool&outbound_time=2025-04-18T09:00:00&inbound_time=2025-04-20T09:00:00
|
||||||
@@ -273,7 +269,7 @@ poetry run python thirdparty/train_api.py
|
|||||||
These are Python activities that fail (raise NotImplemented) to show how Temporal handles a failure. You can run these activities with.
|
These are Python activities that fail (raise NotImplemented) to show how Temporal handles a failure. You can run these activities with.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
poetry run python scripts/run_legacy_worker.py
|
uv run scripts/run_legacy_worker.py
|
||||||
```
|
```
|
||||||
|
|
||||||
The activity will fail and be retried infinitely. To rescue the activity (and its corresponding workflows), kill the worker and run the .NET one in the section below.
|
The activity will fail and be retried infinitely. To rescue the activity (and its corresponding workflows), kill the worker and run the .NET one in the section below.
|
||||||
@@ -328,8 +324,8 @@ For more details, check out [adding goals and tools guide](./adding-goals-and-to
|
|||||||
[ ] Select an LLM and add your API key to `.env` <br />
|
[ ] Select an LLM and add your API key to `.env` <br />
|
||||||
[ ] (Optional) set your starting goal and goal category in `.env` <br />
|
[ ] (Optional) set your starting goal and goal category in `.env` <br />
|
||||||
[ ] (Optional) configure your Temporal Cloud settings in `.env` <br />
|
[ ] (Optional) configure your Temporal Cloud settings in `.env` <br />
|
||||||
[ ] `poetry run python scripts/run_worker.py` <br />
|
[ ] `uv run scripts/run_worker.py` <br />
|
||||||
[ ] `poetry run uvicorn api.main:app --reload` <br />
|
[ ] `uv run uvicorn api.main:app --reload` <br />
|
||||||
[ ] `cd frontend`, `npm install`, `npx vite` <br />
|
[ ] `cd frontend`, `npm install`, `npx vite` <br />
|
||||||
[ ] Access the UI at `http://localhost:5173` <br />
|
[ ] Access the UI at `http://localhost:5173` <br />
|
||||||
|
|
||||||
|
|||||||
@@ -6,17 +6,17 @@ This guide provides instructions for running the comprehensive test suite for th
|
|||||||
|
|
||||||
1. **Install dependencies**:
|
1. **Install dependencies**:
|
||||||
```bash
|
```bash
|
||||||
poetry install --with dev
|
uv sync
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Run all tests**:
|
2. **Run all tests**:
|
||||||
```bash
|
```bash
|
||||||
poetry run pytest
|
uv run pytest
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **Run with time-skipping for faster execution**:
|
3. **Run with time-skipping for faster execution**:
|
||||||
```bash
|
```bash
|
||||||
poetry run pytest --workflow-environment=time-skipping
|
uv run pytest --workflow-environment=time-skipping
|
||||||
```
|
```
|
||||||
|
|
||||||
## Test Categories
|
## Test Categories
|
||||||
@@ -39,33 +39,33 @@ This guide provides instructions for running the comprehensive test suite for th
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Run only activity tests
|
# Run only activity tests
|
||||||
poetry run pytest tests/test_tool_activities.py -v
|
uv run pytest tests/test_tool_activities.py -v
|
||||||
|
|
||||||
# Run only workflow tests
|
# Run only workflow tests
|
||||||
poetry run pytest tests/test_agent_goal_workflow.py -v
|
uv run pytest tests/test_agent_goal_workflow.py -v
|
||||||
|
|
||||||
# Run a specific test
|
# Run a specific test
|
||||||
poetry run pytest tests/test_tool_activities.py::TestToolActivities::test_sanitize_json_response -v
|
uv run pytest tests/test_tool_activities.py::TestToolActivities::test_sanitize_json_response -v
|
||||||
|
|
||||||
# Run tests matching a pattern
|
# Run tests matching a pattern
|
||||||
poetry run pytest -k "validation" -v
|
uv run pytest -k "validation" -v
|
||||||
```
|
```
|
||||||
|
|
||||||
## Test Environment Options
|
## Test Environment Options
|
||||||
|
|
||||||
### Local Environment (Default)
|
### Local Environment (Default)
|
||||||
```bash
|
```bash
|
||||||
poetry run pytest --workflow-environment=local
|
uv run pytest --workflow-environment=local
|
||||||
```
|
```
|
||||||
|
|
||||||
### Time-Skipping Environment (Recommended for CI)
|
### Time-Skipping Environment (Recommended for CI)
|
||||||
```bash
|
```bash
|
||||||
poetry run pytest --workflow-environment=time-skipping
|
uv run pytest --workflow-environment=time-skipping
|
||||||
```
|
```
|
||||||
|
|
||||||
### External Temporal Server
|
### External Temporal Server
|
||||||
```bash
|
```bash
|
||||||
poetry run pytest --workflow-environment=localhost:7233
|
uv run pytest --workflow-environment=localhost:7233
|
||||||
```
|
```
|
||||||
|
|
||||||
## Environment Variables
|
## Environment Variables
|
||||||
@@ -122,7 +122,7 @@ tests/test_tool_activities.py::TestToolActivities::test_get_wf_env_vars_default_
|
|||||||
|
|
||||||
### Common Issues
|
### Common Issues
|
||||||
|
|
||||||
1. **Module not found errors**: Run `poetry install --with dev`
|
1. **Module not found errors**: Run `uv sync`
|
||||||
2. **Async warnings**: These are expected with pytest-asyncio and can be ignored
|
2. **Async warnings**: These are expected with pytest-asyncio and can be ignored
|
||||||
3. **Test timeouts**: Use `--workflow-environment=time-skipping` for faster execution
|
3. **Test timeouts**: Use `--workflow-environment=time-skipping` for faster execution
|
||||||
4. **Import errors**: Check that you're running tests from the project root directory
|
4. **Import errors**: Check that you're running tests from the project root directory
|
||||||
@@ -131,19 +131,19 @@ tests/test_tool_activities.py::TestToolActivities::test_get_wf_env_vars_default_
|
|||||||
|
|
||||||
Enable verbose logging:
|
Enable verbose logging:
|
||||||
```bash
|
```bash
|
||||||
poetry run pytest --log-cli-level=DEBUG -s
|
uv run pytest --log-cli-level=DEBUG -s
|
||||||
```
|
```
|
||||||
|
|
||||||
Run with coverage:
|
Run with coverage:
|
||||||
```bash
|
```bash
|
||||||
poetry run pytest --cov=workflows --cov=activities
|
uv run pytest --cov=workflows --cov=activities
|
||||||
```
|
```
|
||||||
|
|
||||||
## Continuous Integration
|
## Continuous Integration
|
||||||
|
|
||||||
For CI environments, use:
|
For CI environments, use:
|
||||||
```bash
|
```bash
|
||||||
poetry run pytest --workflow-environment=time-skipping --tb=short
|
uv run pytest --workflow-environment=time-skipping --tb=short
|
||||||
```
|
```
|
||||||
|
|
||||||
## Additional Resources
|
## Additional Resources
|
||||||
|
|||||||
@@ -23,6 +23,8 @@
|
|||||||
|
|
||||||
[ ] enable user to list agents at any time - like end conversation - probably with a next step<br />
|
[ ] enable user to list agents at any time - like end conversation - probably with a next step<br />
|
||||||
|
|
||||||
|
[ ] get this on the Model Context Protocol site's list of MCP clients https://modelcontextprotocol.io/clients
|
||||||
|
|
||||||
## Ideas for more goals and tools
|
## Ideas for more goals and tools
|
||||||
|
|
||||||
[ ] Add fintech goals <br />
|
[ ] Add fintech goals <br />
|
||||||
|
|||||||
@@ -6,6 +6,10 @@ import { apiService } from "../services/api";
|
|||||||
const POLL_INTERVAL = 600; // 0.6 seconds
|
const POLL_INTERVAL = 600; // 0.6 seconds
|
||||||
const INITIAL_ERROR_STATE = { visible: false, message: '' };
|
const INITIAL_ERROR_STATE = { visible: false, message: '' };
|
||||||
const DEBOUNCE_DELAY = 300; // 300ms debounce for user input
|
const DEBOUNCE_DELAY = 300; // 300ms debounce for user input
|
||||||
|
const CONVERSATION_FETCH_ERROR_DELAY_MS = 10000; // wait 10s before showing fetch errors
|
||||||
|
const CONVERSATION_FETCH_ERROR_THRESHOLD = Math.ceil(
|
||||||
|
CONVERSATION_FETCH_ERROR_DELAY_MS / POLL_INTERVAL
|
||||||
|
);
|
||||||
|
|
||||||
function useDebounce(value, delay) {
|
function useDebounce(value, delay) {
|
||||||
const [debouncedValue, setDebouncedValue] = useState(value);
|
const [debouncedValue, setDebouncedValue] = useState(value);
|
||||||
@@ -39,13 +43,34 @@ export default function App() {
|
|||||||
const debouncedUserInput = useDebounce(userInput, DEBOUNCE_DELAY);
|
const debouncedUserInput = useDebounce(userInput, DEBOUNCE_DELAY);
|
||||||
|
|
||||||
const errorTimerRef = useRef(null);
|
const errorTimerRef = useRef(null);
|
||||||
|
const conversationFetchErrorCountRef = useRef(0);
|
||||||
|
|
||||||
const handleError = useCallback((error, context) => {
|
const handleError = useCallback((error, context) => {
|
||||||
console.error(`${context}:`, error);
|
console.error(`${context}:`, error);
|
||||||
|
|
||||||
const isConversationFetchError = error.status === 404;
|
const isConversationFetchError =
|
||||||
|
context === "fetching conversation" && (error.status === 404 || error.status === 408);
|
||||||
|
|
||||||
|
if (isConversationFetchError) {
|
||||||
|
if (error.status === 404) {
|
||||||
|
conversationFetchErrorCountRef.current += 1;
|
||||||
|
|
||||||
|
const hasExceededThreshold =
|
||||||
|
conversationFetchErrorCountRef.current >= CONVERSATION_FETCH_ERROR_THRESHOLD;
|
||||||
|
|
||||||
|
if (!hasExceededThreshold) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// For timeouts or other connectivity errors surface immediately
|
||||||
|
conversationFetchErrorCountRef.current = CONVERSATION_FETCH_ERROR_THRESHOLD;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
conversationFetchErrorCountRef.current = 0;
|
||||||
|
}
|
||||||
|
|
||||||
const errorMessage = isConversationFetchError
|
const errorMessage = isConversationFetchError
|
||||||
? "Error fetching conversation. Retrying..." // Updated message
|
? "Error fetching conversation. Retrying..."
|
||||||
: `Error ${context.toLowerCase()}. Please try again.`;
|
: `Error ${context.toLowerCase()}. Please try again.`;
|
||||||
|
|
||||||
setError(prevError => {
|
setError(prevError => {
|
||||||
@@ -72,6 +97,7 @@ export default function App() {
|
|||||||
if (errorTimerRef.current) {
|
if (errorTimerRef.current) {
|
||||||
clearTimeout(errorTimerRef.current);
|
clearTimeout(errorTimerRef.current);
|
||||||
}
|
}
|
||||||
|
conversationFetchErrorCountRef.current = 0;
|
||||||
setError(INITIAL_ERROR_STATE);
|
setError(INITIAL_ERROR_STATE);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,17 @@
|
|||||||
const API_BASE_URL = 'http://127.0.0.1:8000';
|
const API_BASE_URL = 'http://127.0.0.1:8000';
|
||||||
|
|
||||||
|
const resolveRequestTimeout = () => {
|
||||||
|
const env = typeof import.meta !== 'undefined' ? import.meta.env : undefined;
|
||||||
|
const configured = env?.VITE_API_TIMEOUT_MS;
|
||||||
|
const parsed = Number.parseInt(configured, 10);
|
||||||
|
if (Number.isFinite(parsed) && parsed > 0) {
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
return 15000;
|
||||||
|
};
|
||||||
|
|
||||||
|
const REQUEST_TIMEOUT_MS = resolveRequestTimeout(); // default to 15s, overridable via Vite env
|
||||||
|
|
||||||
class ApiError extends Error {
|
class ApiError extends Error {
|
||||||
constructor(message, status) {
|
constructor(message, status) {
|
||||||
super(message);
|
super(message);
|
||||||
@@ -19,12 +31,31 @@ async function handleResponse(response) {
|
|||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function fetchWithTimeout(url, options = {}, timeout = REQUEST_TIMEOUT_MS) {
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await fetch(url, { ...options, signal: controller.signal });
|
||||||
|
} catch (error) {
|
||||||
|
if (error.name === 'AbortError') {
|
||||||
|
throw new ApiError('Request timed out', 408);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const apiService = {
|
export const apiService = {
|
||||||
async getConversationHistory() {
|
async getConversationHistory() {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${API_BASE_URL}/get-conversation-history`);
|
const res = await fetchWithTimeout(`${API_BASE_URL}/get-conversation-history`);
|
||||||
return handleResponse(res);
|
return handleResponse(res);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (error instanceof ApiError) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
throw new ApiError(
|
throw new ApiError(
|
||||||
'Failed to fetch conversation history',
|
'Failed to fetch conversation history',
|
||||||
error.status || 500
|
error.status || 500
|
||||||
@@ -38,7 +69,7 @@ export const apiService = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(
|
const res = await fetchWithTimeout(
|
||||||
`${API_BASE_URL}/send-prompt?prompt=${encodeURIComponent(message)}`,
|
`${API_BASE_URL}/send-prompt?prompt=${encodeURIComponent(message)}`,
|
||||||
{
|
{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -49,6 +80,9 @@ export const apiService = {
|
|||||||
);
|
);
|
||||||
return handleResponse(res);
|
return handleResponse(res);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (error instanceof ApiError) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
throw new ApiError(
|
throw new ApiError(
|
||||||
'Failed to send message',
|
'Failed to send message',
|
||||||
error.status || 500
|
error.status || 500
|
||||||
@@ -58,7 +92,7 @@ export const apiService = {
|
|||||||
|
|
||||||
async startWorkflow() {
|
async startWorkflow() {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(
|
const res = await fetchWithTimeout(
|
||||||
`${API_BASE_URL}/start-workflow`,
|
`${API_BASE_URL}/start-workflow`,
|
||||||
{
|
{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -69,6 +103,9 @@ export const apiService = {
|
|||||||
);
|
);
|
||||||
return handleResponse(res);
|
return handleResponse(res);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (error instanceof ApiError) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
throw new ApiError(
|
throw new ApiError(
|
||||||
'Failed to start workflow',
|
'Failed to start workflow',
|
||||||
error.status || 500
|
error.status || 500
|
||||||
@@ -78,7 +115,7 @@ export const apiService = {
|
|||||||
|
|
||||||
async confirm() {
|
async confirm() {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${API_BASE_URL}/confirm`, {
|
const res = await fetchWithTimeout(`${API_BASE_URL}/confirm`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
@@ -86,6 +123,9 @@ export const apiService = {
|
|||||||
});
|
});
|
||||||
return handleResponse(res);
|
return handleResponse(res);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (error instanceof ApiError) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
throw new ApiError(
|
throw new ApiError(
|
||||||
'Failed to confirm action',
|
'Failed to confirm action',
|
||||||
error.status || 500
|
error.status || 500
|
||||||
|
|||||||
@@ -5,5 +5,6 @@ export default defineConfig({
|
|||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
server: {
|
server: {
|
||||||
open: true,
|
open: true,
|
||||||
|
host: process.env.VITE_HOST ?? 'localhost',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -27,7 +27,7 @@ goal_food_ordering = AgentGoal(
|
|||||||
"When they express interest in items, get pricing using list_prices. "
|
"When they express interest in items, get pricing using list_prices. "
|
||||||
"Add items to their cart using AddToCart as they decide - the order doesn't matter, multiple items can be added. "
|
"Add items to their cart using AddToCart as they decide - the order doesn't matter, multiple items can be added. "
|
||||||
"After they're done selecting items, get their customer details and create a Stripe customer. "
|
"After they're done selecting items, get their customer details and create a Stripe customer. "
|
||||||
"For checkout: 1) create_invoice, 2) create_invoice_item for each individual item (IMPORTANT: create_invoice_item does NOT accept quantity parameter - call it once per item, so if user wants 2 pizzas, call create_invoice_item twice with the same price), "
|
"For checkout: 1) create_invoice (always include days_until_due so the invoice has a due date, e.g., days_until_due=7), 2) create_invoice_item for each individual item (IMPORTANT: create_invoice_item does NOT accept quantity parameter - call it once per item, so if user wants 2 pizzas, call create_invoice_item twice with the same price), "
|
||||||
"3) finalize_invoice. The finalized invoice will contain a hosted_invoice_url for payment.",
|
"3) finalize_invoice. The finalized invoice will contain a hosted_invoice_url for payment.",
|
||||||
starter_prompt=starter_prompt_generic,
|
starter_prompt=starter_prompt_generic,
|
||||||
example_conversation_history="\n ".join(
|
example_conversation_history="\n ".join(
|
||||||
@@ -59,8 +59,8 @@ goal_food_ordering = AgentGoal(
|
|||||||
"agent: Thank you Jane! Creating your Stripe customer profile with name and email.",
|
"agent: Thank you Jane! Creating your Stripe customer profile with name and email.",
|
||||||
"user_confirmed_tool_run: <user clicks confirm on create_customer tool with name='Jane Smith' and email='jane.smith@example.com'>",
|
"user_confirmed_tool_run: <user clicks confirm on create_customer tool with name='Jane Smith' and email='jane.smith@example.com'>",
|
||||||
'tool_result: {"customer": {"id": "cus_ExAmPlE12345", "name": "Jane Smith", "email": "jane.smith@example.com"}}',
|
'tool_result: {"customer": {"id": "cus_ExAmPlE12345", "name": "Jane Smith", "email": "jane.smith@example.com"}}',
|
||||||
"agent: Customer profile created! Now I'll create a draft invoice for your order.",
|
"agent: Customer profile created! Now I'll create a draft invoice for your order with payment due in 7 days.",
|
||||||
"user_confirmed_tool_run: <user clicks confirm on create_invoice tool with customer='cus_ExAmPlE12345'>",
|
"user_confirmed_tool_run: <user clicks confirm on create_invoice tool with customer='cus_ExAmPlE12345', days_until_due=7>",
|
||||||
'tool_result: {"invoice": {"id": "in_InvOicE54321", "status": "draft", "customer": "cus_ExAmPlE12345"}}',
|
'tool_result: {"invoice": {"id": "in_InvOicE54321", "status": "draft", "customer": "cus_ExAmPlE12345"}}',
|
||||||
"agent: Invoice created! Now adding your first Pepperoni Pizza to the invoice.",
|
"agent: Invoice created! Now adding your first Pepperoni Pizza to the invoice.",
|
||||||
"user_confirmed_tool_run: <user clicks confirm on create_invoice_item tool with customer='cus_ExAmPlE12345', invoice='in_InvOicE54321', price='price_pepperoni_large'>",
|
"user_confirmed_tool_run: <user clicks confirm on create_invoice_item tool with customer='cus_ExAmPlE12345', invoice='in_InvOicE54321', price='price_pepperoni_large'>",
|
||||||
|
|||||||
3147
poetry.lock
generated
3147
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,57 +1,57 @@
|
|||||||
[tool.poetry]
|
[project]
|
||||||
name = "temporal_AI_agent"
|
name = "temporal_AI_agent"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
description = "Temporal AI Agent"
|
description = "Temporal AI Agent"
|
||||||
license = "MIT"
|
|
||||||
authors = [
|
authors = [
|
||||||
"Steve Androulakis <steve.androulakis@temporal.io>",
|
{ name = "Steve Androulakis", email = "steve.androulakis@temporal.io" },
|
||||||
"Laine Smith <lainecaseysmith@gmail.com>",
|
{ name = "Laine Smith", email = "lainecaseysmith@gmail.com" },
|
||||||
"Joshua Smith <josh.smith@temporal.io>"
|
{ name = "Joshua Smith", email = "josh.smith@temporal.io" },
|
||||||
]
|
]
|
||||||
|
requires-python = ">=3.10,<4.0"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
license = "MIT"
|
||||||
# By default, Poetry will find packages automatically,
|
dependencies = [
|
||||||
# but explicitly including them is fine:
|
"temporalio>=1.8.0,<2",
|
||||||
packages = [
|
"litellm>=1.70.0,<2",
|
||||||
{ include = "**/*.py", from = "." }
|
"pyyaml>=6.0.2,<7",
|
||||||
|
"fastapi>=0.115.6,<0.116",
|
||||||
|
"uvicorn>=0.34.0,<0.35",
|
||||||
|
"python-dotenv>=1.0.1,<2",
|
||||||
|
"requests>=2.32.3,<3",
|
||||||
|
"pandas>=2.2.3,<3",
|
||||||
|
"stripe>=11.4.1,<12",
|
||||||
|
"gtfs-kit>=10.1.1,<11",
|
||||||
|
"fastmcp>=2.7.0,<3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.poetry.urls]
|
[project.urls]
|
||||||
"Bug Tracker" = "https://github.com/temporal-community/temporal-ai-agent/issues"
|
"Bug Tracker" = "https://github.com/temporal-community/temporal-ai-agent/issues"
|
||||||
|
|
||||||
|
[dependency-groups]
|
||||||
|
dev = [
|
||||||
|
"pytest>=8.2",
|
||||||
|
"pytest-asyncio>=0.26.0,<0.27",
|
||||||
|
"black~=23.7",
|
||||||
|
"isort~=5.12",
|
||||||
|
"mypy>=1.16.0,<2",
|
||||||
|
"poethepoet>=0.37.0",
|
||||||
|
]
|
||||||
|
|
||||||
[tool.poe.tasks]
|
[tool.poe.tasks]
|
||||||
format = [{cmd = "black ."}, {cmd = "isort ."}]
|
format = [{cmd = "black ."}, {cmd = "isort ."}]
|
||||||
lint = [{cmd = "black --check ."}, {cmd = "isort --check-only ."}, {ref = "lint-types" }]
|
lint = [{cmd = "black --check ."}, {cmd = "isort --check-only ."}, {ref = "lint-types" }]
|
||||||
lint-types = "mypy --check-untyped-defs --namespace-packages ."
|
lint-types = "mypy --check-untyped-defs --namespace-packages ."
|
||||||
test = "pytest"
|
test = "pytest"
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.hatch.metadata]
|
||||||
python = ">=3.10,<4.0"
|
allow-direct-references = true
|
||||||
temporalio = "^1.8.0"
|
|
||||||
|
|
||||||
# Standard library modules (e.g. asyncio, collections) don't need to be added
|
[tool.hatch.build]
|
||||||
# since they're built-in for Python 3.8+.
|
packages = ["activities", "api", "goals", "models", "prompts", "shared", "tools", "workflows"]
|
||||||
litellm = "^1.70.0"
|
|
||||||
pyyaml = "^6.0.2"
|
|
||||||
fastapi = "^0.115.6"
|
|
||||||
uvicorn = "^0.34.0"
|
|
||||||
python-dotenv = "^1.0.1"
|
|
||||||
requests = "^2.32.3"
|
|
||||||
pandas = "^2.2.3"
|
|
||||||
stripe = "^11.4.1"
|
|
||||||
gtfs-kit = "^10.1.1"
|
|
||||||
fastmcp = "^2.7.0"
|
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
|
||||||
pytest = ">=8.2"
|
|
||||||
pytest-asyncio = "^0.26.0"
|
|
||||||
black = "^23.7"
|
|
||||||
isort = "^5.12"
|
|
||||||
mypy = "^1.16.0"
|
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry-core>=1.4.0"]
|
requires = ["hatchling"]
|
||||||
build-backend = "poetry.core.masonry.api"
|
build-backend = "hatchling.build"
|
||||||
|
|
||||||
[tool.pytest.ini_options]
|
[tool.pytest.ini_options]
|
||||||
asyncio_mode = "auto"
|
asyncio_mode = "auto"
|
||||||
|
|||||||
@@ -53,31 +53,31 @@ Provides shared test fixtures and configuration:
|
|||||||
Ensure you have the required dependencies installed:
|
Ensure you have the required dependencies installed:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
poetry install --with dev
|
uv sync
|
||||||
```
|
```
|
||||||
|
|
||||||
### Basic Test Execution
|
### Basic Test Execution
|
||||||
|
|
||||||
Run all tests:
|
Run all tests:
|
||||||
```bash
|
```bash
|
||||||
poetry run pytest
|
uv run pytest
|
||||||
```
|
```
|
||||||
|
|
||||||
Run specific test files:
|
Run specific test files:
|
||||||
```bash
|
```bash
|
||||||
# Workflow tests only
|
# Workflow tests only
|
||||||
poetry run pytest tests/test_agent_goal_workflow.py
|
uv run pytest tests/test_agent_goal_workflow.py
|
||||||
|
|
||||||
# Activity tests only
|
# Activity tests only
|
||||||
poetry run pytest tests/test_tool_activities.py
|
uv run pytest tests/test_tool_activities.py
|
||||||
|
|
||||||
# Legacy tests
|
# Legacy tests
|
||||||
poetry run pytest tests/workflowtests/
|
uv run pytest tests/workflowtests/
|
||||||
```
|
```
|
||||||
|
|
||||||
Run with verbose output:
|
Run with verbose output:
|
||||||
```bash
|
```bash
|
||||||
poetry run pytest -v
|
uv run pytest -v
|
||||||
```
|
```
|
||||||
|
|
||||||
### Test Environment Options
|
### Test Environment Options
|
||||||
@@ -87,34 +87,34 @@ The tests support different Temporal environments via the `--workflow-environmen
|
|||||||
#### Local Environment (Default)
|
#### Local Environment (Default)
|
||||||
Uses a local Temporal test server:
|
Uses a local Temporal test server:
|
||||||
```bash
|
```bash
|
||||||
poetry run pytest --workflow-environment=local
|
uv run pytest --workflow-environment=local
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Time-Skipping Environment
|
#### Time-Skipping Environment
|
||||||
Uses Temporal's time-skipping test environment for faster execution:
|
Uses Temporal's time-skipping test environment for faster execution:
|
||||||
```bash
|
```bash
|
||||||
poetry run pytest --workflow-environment=time-skipping
|
uv run pytest --workflow-environment=time-skipping
|
||||||
```
|
```
|
||||||
|
|
||||||
#### External Server
|
#### External Server
|
||||||
Connect to an existing Temporal server:
|
Connect to an existing Temporal server:
|
||||||
```bash
|
```bash
|
||||||
poetry run pytest --workflow-environment=localhost:7233
|
uv run pytest --workflow-environment=localhost:7233
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Setup Script for AI Agent environments such as OpenAI Codex
|
#### Setup Script for AI Agent environments such as OpenAI Codex
|
||||||
```bash
|
```bash
|
||||||
export SHELL=/bin/bash
|
export SHELL=/bin/bash
|
||||||
curl -sSL https://install.python-poetry.org | python3 -
|
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||||
export PATH="$HOME/.local/bin:$PATH"
|
export PATH="$HOME/.local/bin:$PATH"
|
||||||
ls
|
ls
|
||||||
poetry install --with dev
|
uv sync
|
||||||
cd frontend
|
cd frontend
|
||||||
npm install
|
npm install
|
||||||
cd ..
|
cd ..
|
||||||
|
|
||||||
# Pre-download the temporal test server binary
|
# Pre-download the temporal test server binary
|
||||||
poetry run python3 -c "
|
uv run python -c "
|
||||||
import asyncio
|
import asyncio
|
||||||
import sys
|
import sys
|
||||||
from temporalio.testing import WorkflowEnvironment
|
from temporalio.testing import WorkflowEnvironment
|
||||||
@@ -139,22 +139,22 @@ asyncio.run(predownload())
|
|||||||
Run tests by pattern:
|
Run tests by pattern:
|
||||||
```bash
|
```bash
|
||||||
# Run only validation tests
|
# Run only validation tests
|
||||||
poetry run pytest -k "validation"
|
uv run pytest -k "validation"
|
||||||
|
|
||||||
# Run only workflow tests
|
# Run only workflow tests
|
||||||
poetry run pytest -k "workflow"
|
uv run pytest -k "workflow"
|
||||||
|
|
||||||
# Run only activity tests
|
# Run only activity tests
|
||||||
poetry run pytest -k "activity"
|
uv run pytest -k "activity"
|
||||||
```
|
```
|
||||||
|
|
||||||
Run tests by marker (if you add custom markers):
|
Run tests by marker (if you add custom markers):
|
||||||
```bash
|
```bash
|
||||||
# Run only integration tests
|
# Run only integration tests
|
||||||
poetry run pytest -m integration
|
uv run pytest -m integration
|
||||||
|
|
||||||
# Skip slow tests
|
# Skip slow tests
|
||||||
poetry run pytest -m "not slow"
|
uv run pytest -m "not slow"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Test Configuration
|
## Test Configuration
|
||||||
@@ -276,7 +276,7 @@ The `sample_combined_input` fixture provides:
|
|||||||
|
|
||||||
Enable detailed logging:
|
Enable detailed logging:
|
||||||
```bash
|
```bash
|
||||||
poetry run pytest --log-cli-level=DEBUG -s
|
uv run pytest --log-cli-level=DEBUG -s
|
||||||
```
|
```
|
||||||
|
|
||||||
### Temporal Web UI
|
### Temporal Web UI
|
||||||
@@ -301,21 +301,18 @@ jobs:
|
|||||||
test:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-python@v4
|
- uses: astral-sh/setup-uv@v5
|
||||||
with:
|
- run: uv sync
|
||||||
python-version: '3.10'
|
- run: uv run pytest --workflow-environment=time-skipping
|
||||||
- run: pip install poetry
|
|
||||||
- run: poetry install --with dev
|
|
||||||
- run: poetry run pytest --workflow-environment=time-skipping
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Test Coverage
|
### Test Coverage
|
||||||
|
|
||||||
Generate coverage reports:
|
Generate coverage reports:
|
||||||
```bash
|
```bash
|
||||||
poetry add --group dev pytest-cov
|
uv add --group dev pytest-cov
|
||||||
poetry run pytest --cov=workflows --cov=activities --cov-report=html
|
uv run pytest --cov=workflows --cov=activities --cov-report=html
|
||||||
```
|
```
|
||||||
|
|
||||||
## Best Practices
|
## Best Practices
|
||||||
@@ -342,7 +339,7 @@ poetry run pytest --cov=workflows --cov=activities --cov-report=html
|
|||||||
|
|
||||||
- Check Temporal Python SDK documentation
|
- Check Temporal Python SDK documentation
|
||||||
- Review existing test patterns in the codebase
|
- Review existing test patterns in the codebase
|
||||||
- Use `poetry run pytest --collect-only` to verify test discovery
|
- Use `uv run pytest --collect-only` to verify test discovery
|
||||||
- Run with `-v` flag for detailed output
|
- Run with `-v` flag for detailed output
|
||||||
|
|
||||||
## Legacy Tests
|
## Legacy Tests
|
||||||
|
|||||||
@@ -312,6 +312,109 @@ async def test_mcp_tool_execution_flow(client: Client):
|
|||||||
assert captured["dynamic_args"]["server_definition"]["name"] == server_def.name
|
assert captured["dynamic_args"]["server_definition"]["name"] == server_def.name
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_create_invoice_defaults_days_until_due(client: Client):
|
||||||
|
"""create_invoice should include a default days_until_due when missing."""
|
||||||
|
task_queue_name = str(uuid.uuid4())
|
||||||
|
server_def = MCPServerDefinition(name="test", command="python", args=["srv.py"])
|
||||||
|
goal = AgentGoal(
|
||||||
|
id="g_invoice_default",
|
||||||
|
category_tag="food",
|
||||||
|
agent_name="agent",
|
||||||
|
agent_friendly_description="",
|
||||||
|
description="",
|
||||||
|
tools=[],
|
||||||
|
starter_prompt="",
|
||||||
|
example_conversation_history="",
|
||||||
|
mcp_server_definition=server_def,
|
||||||
|
)
|
||||||
|
combined_input = CombinedInput(
|
||||||
|
agent_goal=goal,
|
||||||
|
tool_params=AgentGoalWorkflowParams(
|
||||||
|
conversation_summary=None, prompt_queue=deque()
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
captured: dict = {}
|
||||||
|
|
||||||
|
@activity.defn(name="get_wf_env_vars")
|
||||||
|
async def mock_get_wf_env_vars(input: EnvLookupInput) -> EnvLookupOutput:
|
||||||
|
return EnvLookupOutput(show_confirm=True, multi_goal_mode=True)
|
||||||
|
|
||||||
|
@activity.defn(name="agent_validatePrompt")
|
||||||
|
async def mock_validate(prompt: ValidationInput) -> ValidationResult:
|
||||||
|
return ValidationResult(validationResult=True, validationFailedReason={})
|
||||||
|
|
||||||
|
@activity.defn(name="agent_toolPlanner")
|
||||||
|
async def mock_planner(input: ToolPromptInput) -> dict:
|
||||||
|
if "planner_called" not in captured:
|
||||||
|
captured["planner_called"] = True
|
||||||
|
return {
|
||||||
|
"next": "confirm",
|
||||||
|
"tool": "create_invoice",
|
||||||
|
"args": {"customer": "cus_123"},
|
||||||
|
"response": "Creating invoice",
|
||||||
|
}
|
||||||
|
return {"next": "done", "response": "done"}
|
||||||
|
|
||||||
|
@activity.defn(name="mcp_list_tools")
|
||||||
|
async def mock_mcp_list_tools(
|
||||||
|
server_definition: MCPServerDefinition, include_tools=None
|
||||||
|
):
|
||||||
|
return {
|
||||||
|
"server_name": server_definition.name,
|
||||||
|
"success": True,
|
||||||
|
"tools": {
|
||||||
|
"create_invoice": {
|
||||||
|
"name": "create_invoice",
|
||||||
|
"description": "",
|
||||||
|
"inputSchema": {
|
||||||
|
"properties": {
|
||||||
|
"customer": {"type": "string"},
|
||||||
|
"days_until_due": {"type": "number"},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"total_available": 1,
|
||||||
|
"filtered_count": 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
@activity.defn(name="dynamic_tool_activity", dynamic=True)
|
||||||
|
async def mock_dynamic_tool_activity(args: Sequence[RawValue]) -> dict:
|
||||||
|
payload = activity.payload_converter().from_payload(args[0].payload, dict)
|
||||||
|
captured["dynamic_args"] = payload
|
||||||
|
return {"tool": "create_invoice", "success": True, "content": {"ok": True}}
|
||||||
|
|
||||||
|
async with Worker(
|
||||||
|
client,
|
||||||
|
task_queue=task_queue_name,
|
||||||
|
workflows=[AgentGoalWorkflow],
|
||||||
|
activities=[
|
||||||
|
mock_get_wf_env_vars,
|
||||||
|
mock_validate,
|
||||||
|
mock_planner,
|
||||||
|
mock_mcp_list_tools,
|
||||||
|
mock_dynamic_tool_activity,
|
||||||
|
],
|
||||||
|
):
|
||||||
|
handle = await client.start_workflow(
|
||||||
|
AgentGoalWorkflow.run,
|
||||||
|
combined_input,
|
||||||
|
id=str(uuid.uuid4()),
|
||||||
|
task_queue=task_queue_name,
|
||||||
|
)
|
||||||
|
|
||||||
|
await handle.signal(AgentGoalWorkflow.user_prompt, "make invoice")
|
||||||
|
await asyncio.sleep(0.5)
|
||||||
|
await handle.signal(AgentGoalWorkflow.confirm)
|
||||||
|
await asyncio.sleep(0.5)
|
||||||
|
await handle.result()
|
||||||
|
|
||||||
|
assert "dynamic_args" in captured
|
||||||
|
assert captured["dynamic_args"]["days_until_due"] == 7
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_mcp_tool_failure_recorded(client: Client):
|
async def test_mcp_tool_failure_recorded(client: Client):
|
||||||
"""Failure of an MCP tool should be recorded in conversation history."""
|
"""Failure of an MCP tool should be recorded in conversation history."""
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ def create_invoice(args: dict) -> dict:
|
|||||||
stripe.InvoiceItem.create(
|
stripe.InvoiceItem.create(
|
||||||
customer=customer_id,
|
customer=customer_id,
|
||||||
amount=amount_cents,
|
amount=amount_cents,
|
||||||
currency="gbp",
|
currency="usd",
|
||||||
description=args.get("tripDetails", "Service Invoice"),
|
description=args.get("tripDetails", "Service Invoice"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,21 @@
|
|||||||
|
import calendar
|
||||||
import json
|
import json
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
def find_events(args: dict) -> dict:
|
def find_events(args: dict[str, Any]) -> dict[str, Any]:
|
||||||
|
"""Find events that overlap with a given month in a specified city.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
args: Dictionary containing:
|
||||||
|
- city: City name to search for events (e.g., 'Melbourne')
|
||||||
|
- month: Month name to search (e.g., 'April')
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dictionary with 'events' list and 'note' with search context.
|
||||||
|
"""
|
||||||
search_city = args.get("city", "").lower()
|
search_city = args.get("city", "").lower()
|
||||||
search_month = args.get("month", "").capitalize()
|
search_month = args.get("month", "").capitalize()
|
||||||
|
|
||||||
@@ -16,36 +28,33 @@ def find_events(args: dict) -> dict:
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
return {"error": "Invalid month provided."}
|
return {"error": "Invalid month provided."}
|
||||||
|
|
||||||
# Helper to wrap months into [1..12]
|
# Determine the target year: use next upcoming occurrence of the month
|
||||||
def get_adjacent_months(m):
|
today = datetime.now()
|
||||||
prev_m = 12 if m == 1 else (m - 1)
|
if month_number >= today.month:
|
||||||
next_m = 1 if m == 12 else (m + 1)
|
target_year = today.year
|
||||||
return [prev_m, m, next_m]
|
else:
|
||||||
|
target_year = today.year + 1
|
||||||
|
|
||||||
valid_months = get_adjacent_months(month_number)
|
# Build the search month date range
|
||||||
|
month_start = datetime(target_year, month_number, 1)
|
||||||
|
last_day = calendar.monthrange(target_year, month_number)[1]
|
||||||
|
month_end = datetime(target_year, month_number, last_day)
|
||||||
|
|
||||||
matching_events = []
|
matching_events = []
|
||||||
for city_name, events in json.load(open(file_path)).items():
|
with open(file_path) as f:
|
||||||
|
data = json.load(f)
|
||||||
|
|
||||||
|
for city_name, events in data.items():
|
||||||
if search_city and search_city not in city_name.lower():
|
if search_city and search_city not in city_name.lower():
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for event in events:
|
for event in events:
|
||||||
date_from = datetime.strptime(event["dateFrom"], "%Y-%m-%d")
|
event_start = datetime.strptime(event["dateFrom"], "%Y-%m-%d")
|
||||||
date_to = datetime.strptime(event["dateTo"], "%Y-%m-%d")
|
event_end = datetime.strptime(event["dateTo"], "%Y-%m-%d")
|
||||||
|
|
||||||
# If the event's start or end month is in our valid months
|
|
||||||
if date_from.month in valid_months or date_to.month in valid_months:
|
|
||||||
# Add metadata explaining how it matches
|
|
||||||
if date_from.month == month_number or date_to.month == month_number:
|
|
||||||
month_context = "requested month"
|
|
||||||
elif (
|
|
||||||
date_from.month == valid_months[0]
|
|
||||||
or date_to.month == valid_months[0]
|
|
||||||
):
|
|
||||||
month_context = "previous month"
|
|
||||||
else:
|
|
||||||
month_context = "next month"
|
|
||||||
|
|
||||||
|
# Check if the event overlaps with the search month
|
||||||
|
# Two date ranges overlap if: start1 <= end2 AND start2 <= end1
|
||||||
|
if month_start <= event_end and event_start <= month_end:
|
||||||
matching_events.append(
|
matching_events.append(
|
||||||
{
|
{
|
||||||
"city": city_name,
|
"city": city_name,
|
||||||
@@ -53,12 +62,10 @@ def find_events(args: dict) -> dict:
|
|||||||
"dateFrom": event["dateFrom"],
|
"dateFrom": event["dateFrom"],
|
||||||
"dateTo": event["dateTo"],
|
"dateTo": event["dateTo"],
|
||||||
"description": event["description"],
|
"description": event["description"],
|
||||||
"month": month_context,
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
# Add top-level metadata if you wish
|
|
||||||
return {
|
return {
|
||||||
"note": f"Returning events from {search_month} plus one month either side (i.e., {', '.join(datetime(2025, m, 1).strftime('%B') for m in valid_months)}).",
|
"note": f"Returning events that overlap with {search_month} {target_year}.",
|
||||||
"events": matching_events,
|
"events": matching_events,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -180,10 +180,9 @@ search_fixtures_tool = ToolDefinition(
|
|||||||
|
|
||||||
find_events_tool = ToolDefinition(
|
find_events_tool = ToolDefinition(
|
||||||
name="FindEvents",
|
name="FindEvents",
|
||||||
description="Find upcoming events to travel to a given city (e.g., 'Melbourne') and a date or month. "
|
description="Find upcoming events to travel to a given city (e.g., 'Melbourne') and a month. "
|
||||||
"It knows about events in Oceania only (e.g. major Australian and New Zealand cities). "
|
"It knows about events in Oceania only (e.g. major Australian and New Zealand cities). "
|
||||||
"It will search 1 month either side of the month provided. "
|
"Returns events that overlap with the specified month. ",
|
||||||
"Returns a list of events. ",
|
|
||||||
arguments=[
|
arguments=[
|
||||||
ToolArgument(
|
ToolArgument(
|
||||||
name="city",
|
name="city",
|
||||||
@@ -193,7 +192,7 @@ find_events_tool = ToolDefinition(
|
|||||||
ToolArgument(
|
ToolArgument(
|
||||||
name="month",
|
name="month",
|
||||||
type="string",
|
type="string",
|
||||||
description="The month to search for events (will search 1 month either side of the month provided)",
|
description="The month to search for events (e.g., 'April')",
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from temporalio.common import RetryPolicy
|
|||||||
from temporalio.exceptions import ActivityError
|
from temporalio.exceptions import ActivityError
|
||||||
|
|
||||||
from models.data_types import ConversationHistory, ToolPromptInput
|
from models.data_types import ConversationHistory, ToolPromptInput
|
||||||
from models.tool_definitions import AgentGoal
|
from models.tool_definitions import AgentGoal, ToolDefinition
|
||||||
from prompts.agent_prompt_generators import (
|
from prompts.agent_prompt_generators import (
|
||||||
generate_missing_args_prompt,
|
generate_missing_args_prompt,
|
||||||
generate_tool_completion_prompt,
|
generate_tool_completion_prompt,
|
||||||
@@ -21,63 +21,19 @@ LLM_ACTIVITY_SCHEDULE_TO_CLOSE_TIMEOUT = timedelta(minutes=30)
|
|||||||
|
|
||||||
|
|
||||||
def is_mcp_tool(tool_name: str, goal: AgentGoal) -> bool:
|
def is_mcp_tool(tool_name: str, goal: AgentGoal) -> bool:
|
||||||
"""Check if a tool is an MCP tool based on the goal's MCP server definition"""
|
"""Check if a tool should be dispatched via MCP."""
|
||||||
if not goal.mcp_server_definition:
|
if not goal.mcp_server_definition:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Check if the tool name matches any MCP tools that were loaded
|
# Native tools are registered with tools.get_handler. If lookup succeeds,
|
||||||
# We can identify MCP tools by checking if they're not in the original static tools
|
# the tool should execute locally; otherwise treat it as MCP-provided.
|
||||||
from tools.tool_registry import (
|
from tools import get_handler
|
||||||
book_pto_tool,
|
|
||||||
book_trains_tool,
|
|
||||||
change_goal_tool,
|
|
||||||
create_invoice_tool,
|
|
||||||
current_pto_tool,
|
|
||||||
ecomm_get_order,
|
|
||||||
ecomm_list_orders,
|
|
||||||
ecomm_track_package,
|
|
||||||
financial_check_account_is_valid,
|
|
||||||
financial_get_account_balances,
|
|
||||||
financial_move_money,
|
|
||||||
financial_submit_loan_approval,
|
|
||||||
find_events_tool,
|
|
||||||
food_add_to_cart_tool,
|
|
||||||
future_pto_calc_tool,
|
|
||||||
give_hint_tool,
|
|
||||||
guess_location_tool,
|
|
||||||
list_agents_tool,
|
|
||||||
paycheck_bank_integration_status_check,
|
|
||||||
search_fixtures_tool,
|
|
||||||
search_flights_tool,
|
|
||||||
search_trains_tool,
|
|
||||||
)
|
|
||||||
|
|
||||||
static_tool_names = {
|
try:
|
||||||
list_agents_tool.name,
|
get_handler(tool_name)
|
||||||
change_goal_tool.name,
|
return False
|
||||||
give_hint_tool.name,
|
except ValueError:
|
||||||
guess_location_tool.name,
|
return True
|
||||||
search_flights_tool.name,
|
|
||||||
search_trains_tool.name,
|
|
||||||
book_trains_tool.name,
|
|
||||||
create_invoice_tool.name,
|
|
||||||
search_fixtures_tool.name,
|
|
||||||
find_events_tool.name,
|
|
||||||
current_pto_tool.name,
|
|
||||||
future_pto_calc_tool.name,
|
|
||||||
book_pto_tool.name,
|
|
||||||
paycheck_bank_integration_status_check.name,
|
|
||||||
financial_check_account_is_valid.name,
|
|
||||||
financial_get_account_balances.name,
|
|
||||||
financial_move_money.name,
|
|
||||||
financial_submit_loan_approval.name,
|
|
||||||
ecomm_list_orders.name,
|
|
||||||
ecomm_get_order.name,
|
|
||||||
ecomm_track_package.name,
|
|
||||||
food_add_to_cart_tool.name,
|
|
||||||
}
|
|
||||||
|
|
||||||
return tool_name not in static_tool_names
|
|
||||||
|
|
||||||
|
|
||||||
async def handle_tool_execution(
|
async def handle_tool_execution(
|
||||||
@@ -98,6 +54,13 @@ async def handle_tool_execution(
|
|||||||
|
|
||||||
# Add server definition to args for MCP tools
|
# Add server definition to args for MCP tools
|
||||||
mcp_args = tool_data["args"].copy()
|
mcp_args = tool_data["args"].copy()
|
||||||
|
|
||||||
|
# Stripe's MCP server enforces days_until_due when the collection
|
||||||
|
# method defaults to send_invoice. Provide a reasonable default when
|
||||||
|
# the planner omits it so invoice creation doesn't fail upstream.
|
||||||
|
if current_tool == "create_invoice" and "days_until_due" not in mcp_args:
|
||||||
|
mcp_args["days_until_due"] = 7
|
||||||
|
|
||||||
mcp_args["server_definition"] = goal.mcp_server_definition
|
mcp_args["server_definition"] = goal.mcp_server_definition
|
||||||
|
|
||||||
dynamic_result = await workflow.execute_activity(
|
dynamic_result = await workflow.execute_activity(
|
||||||
|
|||||||
Reference in New Issue
Block a user