← Back to DevBytes

Building a CI/CD Automation Agent with vLLM: Complete Guide

Building a CI/CD Automation Agent with vLLM: Complete Guide

Modern software delivery pipelines generate enormous amounts of telemetry, logs, and failure signals. Manually triaging build failures, writing rollback scripts, and updating configurations is slow and error-prone. By combining an open-source, high-throughput inference engine like vLLM with a structured agent loop, you can build a CI/CD automation agent that reads pipeline output, reasons about failures, and executes remediation actions safely. This guide walks through the architecture, implementation, and production best practices for shipping such an agent.

What Is a CI/CD Automation Agent?

A CI/CD automation agent is an LLM-powered system that observes your continuous integration and continuous delivery pipeline and takes action on its behalf. Unlike a chatbot that only answers questions, an agent operates in a closed loop: it receives events (a failed build, a flaky test, a deployment anomaly), reasons about them using a language model, and invokes tools to resolve or escalate the issue.

vLLM is the inference engine that powers the reasoning. It is an open-source library that runs large language models locally or on your own GPU infrastructure with PagedAttention, continuous batching, and tensor parallelism. Running the model yourself means you keep proprietary source code, logs, and secrets inside your network — a critical requirement for CI/CD workloads where logs often contain sensitive information.

Why It Matters

Architecture Overview

The agent is composed of four layers: an event ingest that receives webhook payloads from your CI system, a context builder that gathers logs and metadata, the vLLM reasoning core that decides what to do, and a tool executor that carries out actions such as restarting a job, opening a pull request, or rolling back a deployment. A safety guard wraps every tool call so destructive actions require human approval.

Prerequisites

Step 1: Serving a Model with vLLM

The fastest way to start is to run vLLM as an OpenAI-compatible API server. This lets you swap models without changing client code. The example below launches a Qwen2.5-Coder-7B model, which is well-suited for code and log reasoning tasks.

# Pull and run vLLM in a container
docker run --gpus all \
  -p 8000:8000 \
  --ipc=host \
  vllm/vllm-openai:latest \
  --model Qwen/Qwen2.5-Coder-7B-Instruct \
  --max-model-len 16384 \
  --gpu-memory-utilization 0.9 \
  --enable-auto-tool-choice \
  --tool-call-parser hermes

The --enable-auto-tool-choice flag is essential. It allows the model to emit structured tool calls that your agent can parse and execute. Once the container is healthy, you can verify the endpoint with a simple curl request.

curl http://localhost:8000/v1/models

Step 2: Defining the Agent's Tools

Tools are the hands of your agent. Each tool is a Python function with a typed signature and a docstring that the model reads to understand when and how to use it. The following module defines four core tools: fetching logs, restarting a failed job, creating a pull request with a fix, and rolling back a deployment.

# tools.py
import subprocess
import json
import requests
from typing import Optional

CI_API_BASE = "https://api.your-ci.example.com"
CI_TOKEN = "REPLACE_WITH_SECRET"

def fetch_failed_logs(job_id: str, tail_lines: int = 200) -> str:
    """Retrieve the last N lines of logs from a failed CI job.

    Args:
        job_id: The unique identifier of the CI job.
        tail_lines: Number of log lines to return from the end.
    """
    resp = requests.get(
        f"{CI_API_BASE}/jobs/{job_id}/logs",
        headers={"Authorization": f"Bearer {CI_TOKEN}"},
        params={"tail": tail_lines},
        timeout=30,
    )
    resp.raise_for_status()
    return resp.text

def restart_job(job_id: str) -> str:
    """Restart a CI job that previously failed.

    Args:
        job_id: The unique identifier of the CI job to restart.
    """
    resp = requests.post(
        f"{CI_API_BASE}/jobs/{job_id}/restart",
        headers={"Authorization": f"Bearer {CI_TOKEN}"},
        timeout=30,
    )
    resp.raise_for_status()
    return json.dumps(resp.json())

def create_fix_pull_request(
    repo: str,
    branch: str,
    file_path: str,
    new_content: str,
    title: str,
) -> str:
    """Open a pull request with a proposed fix to a file.

    Args:
        repo: Full repository name, e.g. "org/repo".
        branch: Name of the new branch to create.
        file_path: Path of the file to modify.
        new_content: Full new content of the file.
        title: Title of the pull request.
    """
    # In production, use the GitHub/GitLab SDK with proper auth.
    return json.dumps({
        "repo": repo,
        "branch": branch,
        "file_path": file_path,
        "title": title,
        "status": "pr_created_simulated",
    })

def rollback_deployment(service: str, environment: str) -> str:
    """Roll back a service deployment to the previous stable version.

    Args:
        service: Name of the service to roll back.
        environment: Target environment, e.g. "staging" or "production".
    """
    return json.dumps({
        "service": service,
        "environment": environment,
        "status": "rolled_back_simulated",
    })

TOOL_REGISTRY = {
    "fetch_failed_logs": fetch_failed_logs,
    "restart_job": restart_job,
    "create_fix_pull_request": create_fix_pull_request,
    "rollback_deployment": rollback_deployment,
}

Step 3: Building the Agent Loop

The agent loop is the heart of the system. It sends the conversation (including the system prompt, the failure event, and any tool results) to vLLM, parses any tool calls, executes them, and feeds the results back. The loop terminates when the model produces a final answer with no further tool calls, or when a maximum iteration count is reached.

# agent.py
import json
import requests
from tools import TOOL_REGISTRY

VLLM_URL = "http://localhost:8000/v1/chat/completions"
MODEL_NAME = "Qwen/Qwen2.5-Coder-7B-Instruct"
MAX_ITERATIONS = 8

SYSTEM_PROMPT = """You are a CI/CD automation agent.
You receive failure events from a continuous integration pipeline.
Your job is to:
1. Fetch and analyze the relevant logs.
2. Diagnose the root cause.
3. Take the least disruptive corrective action.
4. Summarize what happened and what you did.

Rules:
- Always fetch logs before acting.
- Prefer restarting a job over rolling back a deployment.
- Only roll back if a deployment is actively broken.
- Never invent tool names. Use only the tools provided.
- If unsure, return a recommendation instead of acting.
"""

def call_vllm(messages, tools):
    payload = {
        "model": MODEL_NAME,
        "messages": messages,
        "tools": tools,
        "tool_choice": "auto",
        "temperature": 0.2,
        "max_tokens": 2048,
    }
    resp = requests.post(VLLM_URL, json=payload, timeout=120)
    resp.raise_for_status()
    return resp.json()["choices"][0]["message"]

def build_tool_schemas():
    schemas = []
    for name, fn in TOOL_REGISTRY.items():
        schemas.append({
            "type": "function",
            "function": {
                "name": name,
                "description": fn.__doc__.strip().split("\n\n")[0],
                "parameters": {"type": "object", "properties": {}},
            },
        })
    return schemas

def execute_tool_call(tool_call):
    name = tool_call["function"]["name"]
    args = json.loads(tool_call["function"]["arguments"] or "{}")
    fn = TOOL_REGISTRY.get(name)
    if not fn:
        return json.dumps({"error": f"Unknown tool: {name}"})
    try:
        return fn(**args)
    except Exception as exc:
        return json.dumps({"error": str(exc)})

def run_agent(failure_event: str) -> str:
    tools = build_tool_schemas()
    messages = [
        {"role": "system", "content": SYSTEM_PROMPT},
        {"role": "user", "content": f"Failure event:\n{failure_event}"},
    ]

    for _ in range(MAX_ITERATIONS):
        assistant_msg = call_vllm(messages, tools)
        messages.append(assistant_msg)

        tool_calls = assistant_msg.get("tool_calls")
        if not tool_calls:
            return assistant_msg.get("content", "No response from model.")

        for tc in tool_calls:
            result = execute_tool_call(tc)
            messages.append({
                "role": "tool",
                "tool_call_id": tc["id"],
                "content": result,
            })

    return "Agent reached maximum iterations without a final answer."

Step 4: Wiring Up the Webhook Receiver

The agent needs an entry point that your CI system can call when a job fails. A lightweight FastAPI server works well. It validates the incoming payload, invokes the agent asynchronously, and returns an immediate acknowledgment so the webhook does not time out.

# server.py
from fastapi import FastAPI, Request, BackgroundTasks
import asyncio
import logging
from agent import run_agent

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("cicd-agent")

app = FastAPI(title="CI/CD Automation Agent")

async def handle_failure(payload: dict):
    try:
        summary = await asyncio.to_thread(run_agent, str(payload))
        logger.info("Agent summary:\n%s", summary)
        # In production: post summary to Slack, PagerDuty, or a ticketing system.
    except Exception as exc:
        logger.exception("Agent failed: %s", exc)

@app.post("/webhook/ci-failure")
async def ci_failure(request: Request, background_tasks: BackgroundTasks):
    payload = await request.json()
    background_tasks.add_task(handle_failure, payload)
    return {"status": "accepted"}

Run the server with uvicorn and expose it behind your existing ingress or a tunnel that your CI platform can reach.

pip install fastapi uvicorn requests
uvicorn server:app --host 0.0.0.0 --port 9000

Step 5: Testing the Agent End-to-End

Before connecting real pipelines, simulate a failure event to confirm the full loop works. The example below mimics a webhook payload from a failed build.

import requests

payload = {
    "event": "job_failed",
    "job_id": "build-4821",
    "repo": "acme/payments-service",
    "branch": "main",
    "stage": "test",
    "exit_code": 1,
    "timestamp": "2025-01-15T10:42:00Z",
}

resp = requests.post("http://localhost:9000/webhook/ci-failure", json=payload)
print(resp.status_code, resp.json())

Watch the server logs. You should see the agent fetch logs, reason about the failure, and either restart the job or propose a fix. Adjust the system prompt and tool descriptions until the behavior matches your team's runbooks.

Best Practices

Conclusion

Building a CI/CD automation agent with vLLM gives you a self-hosted, high-throughput reasoning engine that can triage failures, propose fixes, and execute remediation without sending proprietary data to third-party APIs. By defining a small, well-documented set of tools, wrapping them in a disciplined agent loop, and gating destructive actions behind human approval, you can ship a reliable assistant that meaningfully reduces MTTR and frees engineers to focus on harder problems. Start with a single pipeline and a narrow set of tools, observe the agent's decisions in shadow mode, and gradually expand its autonomy as trust and observability grow.

— Ad —

Google AdSense will appear here after approval

← Back to all articles