Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions ai/agent/.env.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#INFERENCE_BASE_URL=http://0.0.0.0:11434/v1
#INFERENCE_API_KEY="dummy"
#INFERENCE_MODEL_NAME="openai/qwen3:4b"

#INFERENCE_BASE_URL=https://api.perplexity.ai/v1
#INFERENCE_API_KEY="REDACTED"
#INFERENCE_MODEL_NAME="perplexity/sonar-pro"

INFERENCE_BASE_URL=https://eu-de.ml.cloud.ibm.com
INFERENCE_API_KEY="REDACTED"
INFERENCE_MODEL_NAME="watsonx/meta-llama/llama-3-3-70b-instruct"
INFERENCE_PROJECT_ID="REDACTED"

1 change: 1 addition & 0 deletions ai/agent/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.env
95 changes: 95 additions & 0 deletions ai/agent/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
Agent Overview
--------------

An agent is a small, purpose-built service that performs autonomous tasks by combining conversational logic, tool wrappers, and connectors to external services. Agents typically:

- **Purpose**: Automate workflows and respond to user inputs or events.
- **Components**: Orchestration logic, task definitions, tool adapters (APIs, search, databases), and an optional lightweight UI.

![](./images/architecture.png)

Why IBM Cloud Code Engine
--------------

[IBM Cloud Code Engine](https://www.ibm.com/products/code-engine) is a great fit for containerized agents because it provides:

- **Serverless containers**: Deploy container images without managing infrastructure.
- **Automatic scaling**: Scale to zero when idle and scale up on demand.
- **Pay-per-use pricing**: Cost-efficient for intermittent workloads common to agents.
- **Simple deployment**: Integrates with container registries and CI/CD pipelines.
- **Managed endpoint**: Provides a secure http endpoint with a managed certificate.

A very simple Agent
--------------


The agent implementation in this folder demonstrates a compact, production-minded agent pattern. It contains orchestration, task definitions, and tool wrappers that together implement a conversational assistant with tooling capabilities (search, task execution, and basic web UI).

- **Agent entrypoint**: [./src/main.py](./src/main.py) — starts the agent process and HTTP endpoints.
- **Agent orchestration**: [./src/agents.py](./src/agents.py) — coordinates agent behavior and message routing.
- **Tasks**: [./src/tasks.py](./src/tasks.py) — concrete task implementations the agent can run.
- **Tools / adapters**: [./src/tools.py](./src/tools.py) — wrappers for external APIs or search capabilities.
- **Utilities**: [./src/utils.py](./src/utils.py) — helpers and common utilities.
- **Frontend**: [./src/frontend/landing_page.py](./src/frontend/landing_page.py) — lightweight landing page for human interaction (optional).
- **Example payload**: [./payload/payload.json](./agent/payload/payload.json) — sample input the agent can accept.

Conceptually this agent acts like a Crew AI / ACP-style assistant: it receives a payload, enriches context (via tools/search), executes defined tasks, and returns results. The implementation is intentionally modular so you can swap tool implementations or extend task behaviors.




Deploy the Agent to IBM Cloud Code Engine
--------------

**Prerequisites**

The Agent requires an nferencing backend (an LLM or similar service) to operate. Configure the backend endpoint and credentials via environment variables. Configure any OpenAI compatible API endpoint such as [OpenAI](https://platform.openai.com/) or [IBM watsonx.ai](https://www.ibm.com/products/watsonx-ai)

Copy the template
```bash
# If you have a .env.template
cp .env.template .env
```
Edit the parameter
```bash
INFERENCE_BASE_URL=https://eu-de.ml.cloud.ibm.com
INFERENCE_API_KEY="<YOUR API KEY>"
INFERENCE_MODEL_NAME="watsonx/meta-llama/llama-3-3-70b-instruct"
INFERENCE_PROJECT_ID="<YOUR PROJECT>"
```

**Deploy**

1. Log in to IBM Cloud (choose the method that fits your account):

```bash
# Single-sign-on login (interactive)
ibmcloud login --sso

# Or with API key (non-interactive):
ibmcloud login --apikey "$IBMCLOUD_APIKEY" -r REGION -g RESOURCE_GROUP
```

2. Change into the agent folder and run the provided deploy script:

```bash
NAME_PREFIX=ce-agent REGION=eu-de ./deploy.sh
```

3. The [deploy script](./agent/deploy.sh) builds and/or pushes the container and deploys it to IBM Cloud Code Engine.


4. Follow the script output for the agents URL and an example http request.


What's next
---------

- Modify the tasks in [./src/tasks.py](./src/tasks.py) and register them with the orchestration logic in [./src/agents.py](./src/agents.py).
- Add your own tools by editing [./src/tools.py](./src/tools.py).
- Deploy another agent and add it to the Crew [./src/main.py](./src/main.py)
- [Deploy a MCP Server](../mcp_server_fastmcp/README.md) and make it accessible to your agent(s)

Follow the [IBM Cloud Code Engine documentation](https://cloud.ibm.com/docs/codeengine?topic=codeengine-getting-started) to learn about more features and functions.

![](./images/architecture2.png)
138 changes: 138 additions & 0 deletions ai/agent/deploy.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
#!/bin/bash

# ==============================
# ENVIRONMENT SETUP
# ==============================

source .env

REGION="${REGION:=eu-de}"
NAME_PREFIX="${NAME_PREFIX:=ce-agent}"

ce_project_name=${PROJECT_NAME:-${NAME_PREFIX}-project}
resource_group_name=${NAME_PREFIX}-rg

# ==============================
# COMMON FUNCTIONS
# ==============================

SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
source "${SCRIPT_DIR}/../common.sh"

# Clean up previous run
function clean() {
(
ibmcloud ce project select --name ${ce_project_name} --quiet 2>/dev/null
if [ $? == 0 ]; then
ibmcloud ce project delete --name ${ce_project_name} --force --hard --no-wait 2>/dev/null
fi

ibmcloud resource group $resource_group_name --quiet 2>/dev/null
if [[ $? == 0 ]]; then
COUNTER=0
# some resources (e.g. boot volumes) are deleted with some delay. Hence, the script waits before exiting with an error
while (( "$(ibmcloud resource service-instances --type all -g $resource_group_name --location $REGION --output json | jq -r '. | length')" > 0 )); do
sleep 5
COUNTER=$((COUNTER + 1))
if ((COUNTER > 3)); then
print_error "Cleanup failed! Please make sure to delete remaining resources manually to avoid unwanted charges."
ibmcloud resource service-instances --type all -g $resource_group_name --location $REGION
exit 1
fi
done
fi

ibmcloud resource group-delete $resource_group_name --force 2>/dev/null
)
}

if [[ "$1" == "clean" ]]; then
print_msg "\nCleaning up the created IBM Cloud resources ..."
clean
print_success "\n==========================================\n DONE\n==========================================\n"
exit 0
fi

# ==============================
# MAIN SCRIPT FLOW
# ==============================

# Ensure that latest versions of used IBM Cloud ClI is installed
print_msg "\nPulling latest IBM Cloud CLI release ..."
ibmcloud update --force

# Ensure that latest versions of used IBM Cloud CLI plugins are installed
print_msg "\nInstalling required IBM Cloud CLI plugins ..."
ensure_plugin_is_up_to_date code-engine

print_msg "\n======================================================"
print_msg " Setting up \"Code Engine AI Agent\" sample"
print_msg "======================================================\n"

target_region $REGION

#
# Create the resource group, if it does not exist
ibmcloud resource group $resource_group_name --quiet
if [ $? != 0 ]; then
print_msg "\nCreating resource group '$resource_group_name' ..."
ibmcloud resource group-create $resource_group_name
fi
target_resource_group $resource_group_name

if ! does_instance_exist codeengine "$ce_project_name"; then
print_msg "\nCreating the Code Engine project '$ce_project_name' ..."
ibmcloud ce project create --name $ce_project_name
if [ $? -ne 0 ]; then
print_error "Code Engine project creation failed!"
abortScript
fi
else
print_msg "\nSelecting the Code Engine project '$ce_project_name' ..."
ibmcloud ce project select --name $ce_project_name
fi
project_guid=$(ibmcloud ce project get --name $ce_project_name --output json | jq -r '.guid')

print_msg "\nCreating code engine secret for the agents with parameters from .env file"
ibmcloud ce secret create --name agent-secret --from-literal INFERENCE_BASE_URL="$INFERENCE_BASE_URL" \
--from-literal INFERENCE_API_KEY="$INFERENCE_API_KEY" \
--from-literal INFERENCE_MODEL_NAME="$INFERENCE_MODEL_NAME" \
--from-literal INFERENCE_PROJECT_ID="$INFERENCE_PROJECT_ID"

print_msg "\nCreating the agent as a code engine application"
ibmcloud ce app create --name agent --env-from-secret agent-secret --build-source ./src --build-strategy dockerfile --cpu 1 --memory 4G -p 8080 --wait-timeout 600 --min-scale 0 --visibility public

while true; do
ibmcloud ce app list
not_ready_apps=$(ibmcloud ce app list | grep -e "agent" | grep "Not Ready")
if [ "$not_ready_apps" == "" ]; then
break # all apps are ready
fi
print_msg "\nWaiting for all applications to be ready (sleep 15s)..."
sleep 15
done

print_msg "\n"
print_msg "\nAgent application is ready."
print_msg "\n"

url=$(ibmcloud ce app get --name agent -o json | jq -r '.status.url')
print_msg "\nAgent is reachable under the following url:"
echo $url

print_msg "\n"
print_msg "\nList the agents:"
curl -s -X GET $url/agents | jq


print_msg "\n"
print_msg "\nCalling the agent with the following payload"
cat payload/payload.json | jq

curl -s -X POST $url/runs -H "Content-Type: application/json" -d @payload/payload.json | jq

print_success "\n=========================================="
print_success " SUCCESS"
print_success "==========================================\n"
print_msg "\n"
print_msg "\nrun ./deploy.sh clean to remove all created resources"
Binary file added ai/agent/images/architecture.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ai/agent/images/architecture2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions ai/agent/payload/payload.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"agent_name": "City_Expert", "input": [{"role": "User", "parts": [{"content": "{\"city\": \"Stuttgart\", \"country\": \"Germany\", \"currency\": \"EUR\"}"}]}]}
33 changes: 33 additions & 0 deletions ai/agent/src/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
__pycache__/
.my_cache_dir
.pytest_cache
.ruff_cache
*.pyc
*.pyo
*.pyd
*.db
*.sqlite3

.env
.venv
*.env

**/.env
**/.venv
**/*.env

.git
.gitignore
Dockerfile
.dockerignore

*.log
*.bak
*.swp

.vscode/
.idea/

# OS-specific
.DS_Store
Thumbs.db
13 changes: 13 additions & 0 deletions ai/agent/src/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Python-generated files
__pycache__/
*.py[oc]
build/
dist/
wheels/
*.egg-info
.mypy_cache
.coverage
.python-version

# Virtual environments
.venv
53 changes: 53 additions & 0 deletions ai/agent/src/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#################################################
# Build stage
#################################################

FROM registry.access.redhat.com/ubi9/python-312:latest AS build-stage

USER 1001
WORKDIR $HOME

ENV PATH="$HOME/.venv/bin:$PATH"

# Install uv.
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/

# Copy the application into the container.
COPY . .

# Install the application dependencies.
RUN uv sync --frozen --no-cache

USER root

# https://docs.trychroma.com/updates/troubleshooting#sqlite
RUN uv add pysqlite3-binary
RUN sed -i '1i # ruff: noqa: E402,I001\n__import__("pysqlite3")\nimport sys\nsys.modules["sqlite3"] = sys.modules.pop("pysqlite3")' main.py

# Linting
RUN ruff check . --no-cache

# Reinstall without dev dependencies
RUN uv sync --frozen --no-dev

USER 1001

#################################################
# Production stage
#################################################

FROM registry.access.redhat.com/ubi9/python-312:latest

USER 1001
WORKDIR $HOME

ENV PATH="$HOME/.venv/bin:$PATH"
ENV MODE=${MODE:-production}
ENV LOG_LEVEL=${LOG_LEVEL:-DEBUG}
ENV PORT=${PORT:-5630}
ENV WORKERS=${WORKERS:-1}

COPY --from=build-stage $HOME/ .

# Run the application.
CMD uvicorn main:app --host 0.0.0.0 --port ${PORT} --workers ${WORKERS} --log-level ${LOG_LEVEL,,} --timeout-keep-alive 50 --header server:acp
9 changes: 9 additions & 0 deletions ai/agent/src/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# City Expert Agent

```sh
source .venv/bin/activate
```

```sh
uv run main.py
```
30 changes: 30 additions & 0 deletions ai/agent/src/agents.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import os

from crewai import LLM, Agent
from dotenv import load_dotenv

from tools import duckduckgosearch_tool, scrape_tool

load_dotenv()

llm = LLM(
model=os.getenv("INFERENCE_MODEL_NAME"),
base_url=os.getenv("INFERENCE_BASE_URL"),
api_key=os.getenv("INFERENCE_API_KEY", ""),
project_id=os.getenv("INFERENCE_PROJECT_ID"),
max_tokens=8192,
temperature=0,
)

city_local_expert_agent = Agent(
role="Local City Expert",
goal="Provide the BEST insights about the selected city",
backstory="A knowledgeable local guide with extensive information about the city, it's attractions and customs",
tools=[
scrape_tool,
duckduckgosearch_tool,
],
reasoning=True,
verbose=True,
llm=llm,
)
Loading