diff --git a/demos/hr_agent/Dockerfile b/demos/hr_agent/Dockerfile new file mode 100644 index 00000000..503ffeff --- /dev/null +++ b/demos/hr_agent/Dockerfile @@ -0,0 +1,19 @@ +FROM python:3.10 AS base + +FROM base AS builder + +WORKDIR /src + +COPY requirements.txt /src/ +RUN pip install --prefix=/runtime --force-reinstall -r requirements.txt + +COPY . /src + +FROM python:3.10-slim AS output + +COPY --from=builder /runtime /usr/local + +COPY . /app +WORKDIR /app + +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80", "--log-level", "info"] diff --git a/demos/hr_agent/arch_config.yaml b/demos/hr_agent/arch_config.yaml new file mode 100644 index 00000000..4294278a --- /dev/null +++ b/demos/hr_agent/arch_config.yaml @@ -0,0 +1,58 @@ +version: v0.1 +listener: + address: 127.0.0.1 + port: 8080 #If you configure port 443, you'll need to update the listener with tls_certificates + message_format: huggingface + +# Centralized way to manage LLMs, manage keys, retry logic, failover and limits in a central way +llm_providers: + - name: OpenAI + provider: openai + access_key: OPENAI_API_KEY + model: gpt-4o + default: true + +# default system prompt used by all prompt targets +system_prompt: | + You are a HR agent assistant that helps HR decision makers with reporting and workfoce planning. Nothing else. Please stay on topic of HR. + +prompt_targets: + - name: headcount + description: Get headcount data for a region by staffing type + endpoint: + name: app_server + path: /agent/headcount + parameters: + - name: staffing_type + type: str + description: The staffing type like contract, fte or agency + required: true + - name: region + type: str + required: true + description: the geographical region for which you want head count data. + - name: hr_qa + endpoint: + name: app_server + path: /agent/hr_qa + description: Handle general Q/A related to HR. + default: true + +# Arch creates a round-robin load balancing between different endpoints, managed via the cluster subsystem. +endpoints: + app_server: + # value could be ip address or a hostname with port + # this could also be a list of endpoints for load balancing + # for example endpoint: [ ip1:port, ip2:port ] + endpoint: host.docker.internal:18083 + # max time to wait for a connection to be established + connect_timeout: 0.005s + +ratelimits: + - model: gpt-4 + selector: + key: selector-key + value: selector-value + limit: + tokens: 1 + unit: minute diff --git a/demos/hr_agent/docker-compose.yaml b/demos/hr_agent/docker-compose.yaml new file mode 100644 index 00000000..7ea41173 --- /dev/null +++ b/demos/hr_agent/docker-compose.yaml @@ -0,0 +1,23 @@ +services: + api_server: + build: + context: . + dockerfile: Dockerfile + ports: + - "18083:80" + healthcheck: + test: ["CMD", "curl" ,"http://localhost:80/healthz"] + interval: 5s + retries: 20 + + chatbot_ui: + build: + context: ../../chatbot_ui + dockerfile: Dockerfile + ports: + - "18080:8080" + environment: + - OPENAI_API_KEY=${OPENAI_API_KEY:?error} + - CHAT_COMPLETION_ENDPOINT=http://host.docker.internal:10000/v1 + extra_hosts: + - "host.docker.internal:host-gateway" diff --git a/demos/hr_agent/main.py b/demos/hr_agent/main.py new file mode 100644 index 00000000..5bd65b62 --- /dev/null +++ b/demos/hr_agent/main.py @@ -0,0 +1,73 @@ +from fastapi import FastAPI, HTTPException +from pydantic import BaseModel, Field +from typing import List, Optional +from enum import Enum +import re + +app = FastAPI() + +class StaffingType(Enum): + CONTRACT = "contract" + FTE = "fte" + AGENCY = "agency" + +# Define the request model +class HeadcountRequest(BaseModel): + region: str + staffing_type: str + +class HeadcountResponseSummary(BaseModel): + region: str + headcount: int + staffing_type: str + +# Post method for device summary +@app.post("/agent/headcount") +def get_headcount(request: HeadcountRequest): + """ + Endpoint to headcount data by region, staffing type over time range + """ + staffing_type_value = request.staffing_type + + if re.match(r"(?i)contract", staffing_type_value): # Case-insensitive regex match + headcount = 500 + elif re.match(r"(?i)fte", staffing_type_value): + headcount = 1000 + elif re.match(r"(?i)agency", staffing_type_value): + headcount = 4000 + else: + raise HTTPException( + status_code=400, detail="staffing_type parameter is invalid." + ) + + response = { + "region": request.region, + "staffing_type": f"Staffing agency: {staffing_type_value}", + "headcount" : f"Headcount: {headcount}" + } + + return response + +@app.post("/agent/hr_qa") +async def general_hr_qa(): + """ + This method handles Q/A related to general issues in HR. + It forwards the conversation to the OpenAI client via a local proxy and returns the response. + """ + return { + "choices": [ + { + "message": { + "role": "assistant", + "content": "I am a helpful HR agent, and I can help you plan for workforce related questions", + }, + "finish_reason": "completed", + "index": 0, + } + ], + "model": "hr_agent", + "usage": {"completion_tokens": 0}, + } + +if __name__ == "__main__": + app.run(debug=True) diff --git a/demos/hr_agent/requirements.txt b/demos/hr_agent/requirements.txt new file mode 100644 index 00000000..77e7584c --- /dev/null +++ b/demos/hr_agent/requirements.txt @@ -0,0 +1,4 @@ +fastapi +uvicorn +pydantic +typing diff --git a/demos/hr_agent/run_demo.sh b/demos/hr_agent/run_demo.sh new file mode 100644 index 00000000..f6097b09 --- /dev/null +++ b/demos/hr_agent/run_demo.sh @@ -0,0 +1,46 @@ +#!/bin/bash + +# Function to start the demo +start_demo() { + # Step 1: Check if .env file exists + if [ -f ".env" ]; then + echo ".env file already exists. Skipping creation." + else + # Step 2: Create `.env` file and set OpenAI key + if [ -z "$OPENAI_API_KEY" ]; then + echo "Error: OPENAI_API_KEY environment variable is not set for the demo." + exit 1 + fi + + echo "Creating .env file..." + echo "OPENAI_API_KEY=$OPENAI_API_KEY" > .env + echo ".env file created with OPENAI_API_KEY." + fi + + # Step 3: Start Arch + echo "Starting Arch with arch_config.yaml..." + archgw up arch_config.yaml + + # Step 4: Start Network Agent + echo "Starting Network Agent using Docker Compose..." + docker compose up -d # Run in detached mode +} + +# Function to stop the demo +stop_demo() { + # Step 1: Stop Docker Compose services + echo "Stopping Network Agent using Docker Compose..." + docker compose down + + # Step 2: Stop Arch + echo "Stopping Arch..." + archgw down +} + +# Main script logic +if [ "$1" == "down" ]; then + stop_demo +else + # Default action is to bring the demo up + start_demo +fi