diff --git a/.gitignore b/.gitignore
index 11bca9c2..83a639be 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,3 +15,9 @@ demos/function_calling/ollama/models/
demos/function_calling/ollama/id_ed*
docs/build/
demos/function_calling/open-webui/
+demos/employee_details_copilot/open-webui/
+demos/employee_details_copilot_arch/open-webui/
+demos/network_copilot/open-webui/
+demos/employee_details_copilot/ollama/models/
+demos/employee_details_copilot_arch/ollama/models/
+demos/network_copilot/ollama/models/
diff --git a/demos/employee_details_copilot/README.md b/demos/employee_details_copilot/README.md
index 931f6ae3..c8fcf1a0 100644
--- a/demos/employee_details_copilot/README.md
+++ b/demos/employee_details_copilot/README.md
@@ -2,10 +2,6 @@
This demo shows how you can use intelligent prompt gateway to act a copilot for calling the correct proc by capturing the required and optional parametrs from the prompt. This demo assumes you are using ollama running natively. If you want to run ollama running inside docker then please update ollama endpoint in docker-compose file.
# Starting the demo
-1. Ensure that submodule is up to date
- ```sh
- git submodule sync --recursive
- ```
1. Create `.env` file and set OpenAI key using env var `OPENAI_API_KEY`
1. Start services
```sh
diff --git a/demos/employee_details_copilot/api_server/app/functions.py b/demos/employee_details_copilot/api_server/app/functions.py
new file mode 100644
index 00000000..c883ac48
--- /dev/null
+++ b/demos/employee_details_copilot/api_server/app/functions.py
@@ -0,0 +1,29 @@
+from typing import List, Optional
+
+# Function for top_employees
+def top_employees(grouping: str, ranking_criteria: str, top_n: int):
+ pass
+
+# Function for aggregate_stats
+def aggregate_stats(grouping: str, aggregate_criteria: str, aggregate_type: str):
+ pass
+
+# Function for employees_projects
+def employees_projects(min_performance_score: float, min_years_experience: int, department: str, min_project_count: int = None, months_range: int = None):
+ pass
+
+# Function for salary_growth
+def salary_growth(min_salary_increase_percentage: float, department: str = None):
+ pass
+
+# Function for promotions_increases
+def promotions_increases(year: int, min_salary_increase_percentage: float = None, department: str = None):
+ pass
+
+# Function for avg_project_performance
+def avg_project_performance(min_project_count: int, min_performance_score: float, department: str = None):
+ pass
+
+# Function for certifications_experience
+def certifications_experience(certifications: list, min_years_experience: int, department: str = None):
+ pass
diff --git a/demos/employee_details_copilot/api_server/app/generate_config.py b/demos/employee_details_copilot/api_server/app/generate_config.py
new file mode 100644
index 00000000..95ee3b56
--- /dev/null
+++ b/demos/employee_details_copilot/api_server/app/generate_config.py
@@ -0,0 +1,78 @@
+import inspect
+import yaml
+import functions # This is your module containing the function definitions
+import os
+
+
+def generate_config_from_function(func):
+ func_name = func.__name__
+ func_doc = func.__doc__
+
+ # Get function signature
+ sig = inspect.signature(func)
+ params = []
+
+ # Extract parameter info
+ for name, param in sig.parameters.items():
+ param_info = {
+ 'name': name,
+ 'description': f"Provide the {name.replace('_', ' ')}", # Customize as needed
+ 'required': param.default == inspect.Parameter.empty, # True if no default value
+ 'type': param.annotation.__name__ if param.annotation != inspect.Parameter.empty else 'str' # Get type if available
+ }
+ params.append(param_info)
+
+ # Define the config for this function
+ config = {
+ 'name': func_name,
+ 'description': func_doc or "",
+ 'parameters': params,
+ 'endpoint': {
+ 'cluster': 'api_server',
+ 'path': f"/{func_name}"
+ },
+ 'system_prompt': f"You are responsible for handling {func_name} requests."
+ }
+
+ return config
+
+
+def generate_full_config(module):
+ config = {'prompt_targets': []}
+
+ # Automatically get all functions from the module
+ functions_list = inspect.getmembers(module, inspect.isfunction)
+
+ for func_name, func_obj in functions_list:
+ func_config = generate_config_from_function(func_obj)
+ config['prompt_targets'].append(func_config)
+
+ return config
+
+
+def replace_prompt_targets_in_config(file_path, new_prompt_targets):
+ # Load the existing bolt_config.yaml
+ with open(file_path, 'r') as file:
+ config_data = yaml.safe_load(file)
+
+ # Replace the prompt_targets section with the new one
+ config_data['prompt_targets'] = new_prompt_targets
+
+ # Write the updated config back to the YAML file
+ with open("bolt_config.yaml", 'w+') as file:
+ yaml.dump(config_data, file, sort_keys=False)
+
+ print(f"Updated prompt_targets in bolt_config.yaml")
+
+
+# Main execution
+if __name__ == "__main__":
+ # Path to the existing bolt_config.yaml two directories up
+ bolt_config_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../bolt_config.yaml'))
+
+ # Generate new prompt_targets from the functions module
+ new_config = generate_full_config(functions)
+ new_prompt_targets = new_config['prompt_targets']
+
+ # Replace the prompt_targets in the existing bolt_config.yaml
+ replace_prompt_targets_in_config(bolt_config_path, new_prompt_targets)
diff --git a/demos/employee_details_copilot/api_server/app/main.py b/demos/employee_details_copilot/api_server/app/main.py
index 11d36758..0e13c0fe 100644
--- a/demos/employee_details_copilot/api_server/app/main.py
+++ b/demos/employee_details_copilot/api_server/app/main.py
@@ -1,7 +1,6 @@
import random
from typing import List
from fastapi import FastAPI, HTTPException, Response
-from datetime import datetime, date, timedelta, timezone
import logging
from pydantic import BaseModel
from utils import load_sql
@@ -118,7 +117,7 @@ class TopEmployeesProjects(BaseModel):
months_range: int = None # Optional (for filtering recent projects)
-@app.post("/top_employees_projects")
+@app.post("/employees_projects")
async def employees_projects(req: TopEmployeesProjects, res: Response):
params, filters = {}, []
@@ -225,8 +224,8 @@ class AvgProjPerformanceRequest(BaseModel):
department: str = None # Optional
-@app.post("/avg_project_performance")
-async def avg_project_performance(req: AvgProjPerformanceRequest, res: Response):
+@app.post("/project_performance")
+async def project_performance(req: AvgProjPerformanceRequest, res: Response):
params, filters = {}, []
if req.department:
@@ -257,7 +256,7 @@ class CertificationsExperienceRequest(BaseModel):
min_years_experience: int
department: str = None # Optional
-@app.post("/employees_certifications_experience")
+@app.post("/certifications_experience")
async def certifications_experience(req: CertificationsExperienceRequest, res: Response):
# Convert the list of certifications into a format for SQL query
certs_filter = ', '.join([f"'{cert}'" for cert in req.certifications])
diff --git a/demos/employee_details_copilot/bolt_config.yaml b/demos/employee_details_copilot/bolt_config.yaml
index 3b4b2a01..d47f7973 100644
--- a/demos/employee_details_copilot/bolt_config.yaml
+++ b/demos/employee_details_copilot/bolt_config.yaml
@@ -149,7 +149,7 @@ prompt_targets:
# 5. Employees with Highest Average Project Performance
- type: function_resolver
- name: avg_project_performance
+ name: project_performance
description: |
Fetch employees with the highest average performance across all projects they have worked on over time. You can filter by minimum project count, department, and minimum performance score.
parameters:
@@ -161,13 +161,14 @@ prompt_targets:
description: Minimum performance score to filter employees.
required: true
type: float
+ default: 4.0
- name: department
description: Department to filter by (optional).
required: false
type: string
endpoint:
cluster: api_server
- path: /avg_project_performance
+ path: /project_performance
system_prompt: |
You are responsible for fetching employees with the highest average performance across all projects they’ve worked on. Apply filters for minimum project count, performance score, and department.
@@ -190,6 +191,7 @@ prompt_targets:
description: Department to filter employees by (optional).
required: false
type: string
+ default: "Engineering"
endpoint:
cluster: api_server
path: /certifications_experience
diff --git a/demos/employee_details_copilot/Bolt-FC-1B-Q3_K_L.model_file b/demos/employee_details_copilot_arch/Bolt-FC-1B-Q4_K_M.model_file
similarity index 89%
rename from demos/employee_details_copilot/Bolt-FC-1B-Q3_K_L.model_file
rename to demos/employee_details_copilot_arch/Bolt-FC-1B-Q4_K_M.model_file
index d58a6a17..1def85b1 100644
--- a/demos/employee_details_copilot/Bolt-FC-1B-Q3_K_L.model_file
+++ b/demos/employee_details_copilot_arch/Bolt-FC-1B-Q4_K_M.model_file
@@ -1,7 +1,6 @@
-FROM Bolt-Function-Calling-1B-Q3_K_L.gguf
+FROM Bolt-Function-Calling-1B-Q4_K_M.gguf
# Set the size of the context window used to generate the next token
-# PARAMETER num_ctx 16384
PARAMETER num_ctx 4096
# Set parameters for response generation
diff --git a/demos/employee_details_copilot_arch/README.md b/demos/employee_details_copilot_arch/README.md
new file mode 100644
index 00000000..44d7d60b
--- /dev/null
+++ b/demos/employee_details_copilot_arch/README.md
@@ -0,0 +1,24 @@
+# Function calling
+This demo shows how you can use intelligent prompt gateway as copilot to explore employee data by calling the correct api functions. It calls appropriate function and also engages with user to extract required parameters. This demo assumes you are using ollama natively.
+
+# Starting the demo
+1. Create `.env` file and set OpenAI key using env var `OPENAI_API_KEY`
+1. Start services
+ ```sh
+ docker compose up
+ ```
+1. Download Bolt-FC model. This demo assumes we have downloaded [Bolt-Function-Calling-1B:Q4_K_M](https://huggingface.co/katanemolabs/Bolt-Function-Calling-1B.gguf/blob/main/Bolt-Function-Calling-1B-Q4_K_M.gguf) to local folder.
+1. If running ollama natively run
+ ```sh
+ ollama serve
+ ```
+2. Create model file in ollama repository
+ ```sh
+ ollama create Bolt-Function-Calling-1B:Q4_K_M -f Bolt-FC-1B-Q4_K_M.model_file
+ ```
+3. Navigate to http://localhost:18080/
+4. You can type in queries like "show me the top 5 employees in each department with highest salary"
+ - You can also ask follow up questions like "just show the top 2"
+5. To see metrics navigate to "http://localhost:3000/" (use admin/grafana for login)
+ - Open up dahsboard named "Intelligent Gateway Overview"
+ - On this dashboard you can see reuqest latency and number of requests
diff --git a/demos/employee_details_copilot_arch/api_server/.vscode/launch.json b/demos/employee_details_copilot_arch/api_server/.vscode/launch.json
new file mode 100644
index 00000000..4d9c76d4
--- /dev/null
+++ b/demos/employee_details_copilot_arch/api_server/.vscode/launch.json
@@ -0,0 +1,16 @@
+{
+ // Use IntelliSense to learn about possible attributes.
+ // Hover to view descriptions of existing attributes.
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "function-calling api server",
+ "cwd": "${workspaceFolder}/app",
+ "type": "debugpy",
+ "request": "launch",
+ "module": "uvicorn",
+ "args": ["main:app","--reload", "--port", "8001"],
+ }
+ ]
+}
diff --git a/demos/employee_details_copilot_arch/api_server/Dockerfile b/demos/employee_details_copilot_arch/api_server/Dockerfile
new file mode 100644
index 00000000..abd21357
--- /dev/null
+++ b/demos/employee_details_copilot_arch/api_server/Dockerfile
@@ -0,0 +1,19 @@
+FROM python:3 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-slim AS output
+
+COPY --from=builder /runtime /usr/local
+
+COPY /app /app
+WORKDIR /app
+
+CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"]
diff --git a/demos/employee_details_copilot_arch/api_server/app/main.py b/demos/employee_details_copilot_arch/api_server/app/main.py
new file mode 100644
index 00000000..11d36758
--- /dev/null
+++ b/demos/employee_details_copilot_arch/api_server/app/main.py
@@ -0,0 +1,289 @@
+import random
+from typing import List
+from fastapi import FastAPI, HTTPException, Response
+from datetime import datetime, date, timedelta, timezone
+import logging
+from pydantic import BaseModel
+from utils import load_sql
+import pandas as pd
+
+
+logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
+logger = logging.getLogger(__name__)
+
+app = FastAPI()
+
+@app.get("/healthz")
+async def healthz():
+ return {
+ "status": "ok"
+ }
+
+conn = load_sql()
+name_col = "name"
+
+
+class TopEmployees(BaseModel):
+ grouping: str
+ ranking_criteria: str
+ top_n: int
+
+
+@app.post("/top_employees")
+async def top_employees(req: TopEmployees, res: Response):
+ name_col = "name"
+ # Check if `req.ranking_criteria` is a Text object and extract its value accordingly
+ logger.info(
+ f"{'* ' * 50}\n\nCaptured Ranking Criteria: {req.ranking_criteria}\n\n{'* ' * 50}"
+ )
+
+ if req.ranking_criteria == "yoe":
+ req.ranking_criteria = "years_of_experience"
+ elif req.ranking_criteria == "rating":
+ req.ranking_criteria = "performance_score"
+
+ logger.info(
+ f"{'* ' * 50}\n\nFinal Ranking Criteria: {req.ranking_criteria}\n\n{'* ' * 50}"
+ )
+
+ query = f"""
+ SELECT {req.grouping}, {name_col}, {req.ranking_criteria}
+ FROM (
+ SELECT {req.grouping}, {name_col}, {req.ranking_criteria},
+ DENSE_RANK() OVER (PARTITION BY {req.grouping} ORDER BY {req.ranking_criteria} DESC) as emp_rank
+ FROM employees
+ ) ranked_employees
+ WHERE emp_rank <= {req.top_n};
+ """
+ result_df = pd.read_sql_query(query, conn)
+ result = result_df.to_dict(orient="records")
+ return result
+
+
+class AggregateStats(BaseModel):
+ grouping: str
+ aggregate_criteria: str
+ aggregate_type: str
+
+
+@app.post("/aggregate_stats")
+async def aggregate_stats(req: AggregateStats, res: Response):
+ logger.info(
+ f"{'* ' * 50}\n\nCaptured Aggregate Criteria: {req.aggregate_criteria}\n\n{'* ' * 50}"
+ )
+
+ if req.aggregate_criteria == "yoe":
+ req.aggregate_criteria = "years_of_experience"
+
+ logger.info(
+ f"{'* ' * 50}\n\nFinal Aggregate Criteria: {req.aggregate_criteria}\n\n{'* ' * 50}"
+ )
+
+ logger.info(
+ f"{'* ' * 50}\n\nCaptured Aggregate Type: {req.aggregate_type}\n\n{'* ' * 50}"
+ )
+ if req.aggregate_type.lower() not in ["sum", "avg", "min", "max"]:
+ if req.aggregate_type.lower() == "count":
+ req.aggregate_type = "COUNT"
+ elif req.aggregate_type.lower() == "total":
+ req.aggregate_type = "SUM"
+ elif req.aggregate_type.lower() == "average":
+ req.aggregate_type = "AVG"
+ elif req.aggregate_type.lower() == "minimum":
+ req.aggregate_type = "MIN"
+ elif req.aggregate_type.lower() == "maximum":
+ req.aggregate_type = "MAX"
+ else:
+ raise HTTPException(status_code=400, detail="Invalid aggregate type")
+
+ logger.info(
+ f"{'* ' * 50}\n\nFinal Aggregate Type: {req.aggregate_type}\n\n{'* ' * 50}"
+ )
+
+ query = f"""
+ SELECT {req.grouping}, {req.aggregate_type}({req.aggregate_criteria}) as {req.aggregate_type}_{req.aggregate_criteria}
+ FROM employees
+ GROUP BY {req.grouping};
+ """
+ result_df = pd.read_sql_query(query, conn)
+ result = result_df.to_dict(orient="records")
+ return result
+
+# 1. Top Employees by Performance, Projects, and Timeframe
+class TopEmployeesProjects(BaseModel):
+ min_performance_score: float
+ min_years_experience: int
+ department: str
+ min_project_count: int = None # Optional
+ months_range: int = None # Optional (for filtering recent projects)
+
+
+@app.post("/top_employees_projects")
+async def employees_projects(req: TopEmployeesProjects, res: Response):
+ params, filters = {}, []
+
+ # Add optional months_range filter
+ if req.months_range:
+ params['months_range'] = req.months_range
+ filters.append(f"p.start_date >= DATE('now', '-{req.months_range} months')")
+
+ # Add project count filter if provided
+ if req.min_project_count:
+ filters.append(f"COUNT(p.project_id) >= {req.min_project_count}")
+
+ where_clause = " AND ".join(filters)
+ if where_clause:
+ where_clause = "AND " + where_clause
+
+ query = f"""
+ SELECT e.name, e.department, e.years_of_experience, e.performance_score, COUNT(p.project_id) as project_count
+ FROM employees e
+ LEFT JOIN projects p ON e.eid = p.eid
+ WHERE e.performance_score >= {req.min_performance_score}
+ AND e.years_of_experience >= {req.min_years_experience}
+ AND e.department = '{req.department}'
+ {where_clause}
+ GROUP BY e.eid, e.name, e.department, e.years_of_experience, e.performance_score
+ ORDER BY e.performance_score DESC;
+ """
+
+ result_df = pd.read_sql_query(query, conn, params=params)
+ return result_df.to_dict(orient='records')
+
+
+# 2. Employees with Salary Growth Since Last Promotion
+class SalaryGrowthRequest(BaseModel):
+ min_salary_increase_percentage: float
+ department: str = None # Optional
+
+
+@app.post("/salary_growth")
+async def salary_growth(req: SalaryGrowthRequest, res: Response):
+ params, filters = {}, []
+
+ if req.department:
+ filters.append("e.department = :department")
+ params['department'] = req.department
+
+ where_clause = " AND ".join(filters)
+ if where_clause:
+ where_clause = "AND " + where_clause
+
+ query = f"""
+ SELECT e.name, e.department, s.salary_increase_percentage
+ FROM employees e
+ JOIN salary_history s ON e.eid = s.eid
+ WHERE s.salary_increase_percentage >= {req.min_salary_increase_percentage}
+ AND s.promotion_date IS NOT NULL
+ {where_clause}
+ ORDER BY s.salary_increase_percentage DESC;
+ """
+
+ result_df = pd.read_sql_query(query, conn, params=params)
+ return result_df.to_dict(orient='records')
+
+
+# 4. Employees with Promotions and Salary Increases
+class PromotionsIncreasesRequest(BaseModel):
+ year: int
+ min_salary_increase_percentage: float = None # Optional
+ department: str = None # Optional
+
+
+@app.post("/promotions_increases")
+async def promotions_increases(req: PromotionsIncreasesRequest, res: Response):
+ params, filters = {}, []
+
+ if req.min_salary_increase_percentage:
+ filters.append(f"s.salary_increase_percentage >= {req.min_salary_increase_percentage}")
+
+ if req.department:
+ filters.append("e.department = :department")
+ params['department'] = req.department
+
+ where_clause = " AND ".join(filters)
+ if where_clause:
+ where_clause = "AND " + where_clause
+
+ query = f"""
+ SELECT e.name, e.department, s.salary_increase_percentage, s.promotion_date
+ FROM employees e
+ JOIN salary_history s ON e.eid = s.eid
+ WHERE strftime('%Y', s.promotion_date) = '{req.year}'
+ {where_clause}
+ ORDER BY s.salary_increase_percentage DESC;
+ """
+
+ result_df = pd.read_sql_query(query, conn, params=params)
+ return result_df.to_dict(orient='records')
+
+
+# 5. Employees with Highest Average Project Performance
+class AvgProjPerformanceRequest(BaseModel):
+ min_project_count: int
+ min_performance_score: float
+ department: str = None # Optional
+
+
+@app.post("/avg_project_performance")
+async def avg_project_performance(req: AvgProjPerformanceRequest, res: Response):
+ params, filters = {}, []
+
+ if req.department:
+ filters.append("e.department = :department")
+ params['department'] = req.department
+
+ filters.append(f"p.performance_score >= {req.min_performance_score}")
+
+ where_clause = " AND ".join(filters)
+
+ query = f"""
+ SELECT e.name, e.department, AVG(p.performance_score) as avg_performance_score, COUNT(p.project_id) as project_count
+ FROM employees e
+ JOIN projects p ON e.eid = p.eid
+ WHERE {where_clause}
+ GROUP BY e.eid, e.name, e.department
+ HAVING COUNT(p.project_id) >= {req.min_project_count}
+ ORDER BY avg_performance_score DESC;
+ """
+
+ result_df = pd.read_sql_query(query, conn, params=params)
+ return result_df.to_dict(orient='records')
+
+
+# 6. Employees by Certification and Years of Experience
+class CertificationsExperienceRequest(BaseModel):
+ certifications: List[str]
+ min_years_experience: int
+ department: str = None # Optional
+
+@app.post("/employees_certifications_experience")
+async def certifications_experience(req: CertificationsExperienceRequest, res: Response):
+ # Convert the list of certifications into a format for SQL query
+ certs_filter = ', '.join([f"'{cert}'" for cert in req.certifications])
+
+ params, filters = {}, []
+
+ # Add department filter if provided
+ if req.department:
+ filters.append("e.department = :department")
+ params['department'] = req.department
+
+ filters.append("e.years_of_experience >= :min_years_experience")
+ params['min_years_experience'] = req.min_years_experience
+
+ where_clause = " AND ".join(filters)
+
+ query = f"""
+ SELECT e.name, e.department, e.years_of_experience, COUNT(c.certification_name) as cert_count
+ FROM employees e
+ JOIN certifications c ON e.eid = c.eid
+ WHERE c.certification_name IN ({certs_filter})
+ AND {where_clause}
+ GROUP BY e.eid, e.name, e.department, e.years_of_experience
+ HAVING COUNT(c.certification_name) = {len(req.certifications)}
+ ORDER BY e.years_of_experience DESC;
+ """
+
+ result_df = pd.read_sql_query(query, conn, params=params)
+ return result_df.to_dict(orient='records')
diff --git a/demos/employee_details_copilot_arch/api_server/app/utils.py b/demos/employee_details_copilot_arch/api_server/app/utils.py
new file mode 100644
index 00000000..3db7b8f9
--- /dev/null
+++ b/demos/employee_details_copilot_arch/api_server/app/utils.py
@@ -0,0 +1,157 @@
+import pandas as pd
+import random
+import datetime
+import sqlite3
+
+def load_sql():
+ # Example Usage
+ conn = sqlite3.connect(":memory:")
+
+ # create and load the employees table
+ generate_employee_data(conn)
+
+ # create and load the projects table
+ generate_project_data(conn)
+
+ # create and load the salary_history table
+ generate_salary_history(conn)
+
+ # create and load the certifications table
+ generate_certifications(conn)
+
+ return conn
+
+# Function to generate random employee data with `eid` as the primary key
+def generate_employee_data(conn):
+ # List of possible names, positions, departments, and locations
+ names = [
+ "Alice",
+ "Bob",
+ "Charlie",
+ "David",
+ "Eve",
+ "Frank",
+ "Grace",
+ "Hank",
+ "Ivy",
+ "Jack",
+ ]
+ positions = [
+ "Manager",
+ "Engineer",
+ "Salesperson",
+ "HR Specialist",
+ "Marketing Analyst",
+ ]
+ # List of possible names, positions, departments, locations, and certifications
+ names = ["Alice", "Bob", "Charlie", "David", "Eve", "Frank", "Grace", "Hank", "Ivy", "Jack"]
+ positions = ["Manager", "Engineer", "Salesperson", "HR Specialist", "Marketing Analyst"]
+ departments = ["Engineering", "Marketing", "HR", "Sales", "Finance"]
+ locations = ["New York", "San Francisco", "Austin", "Boston", "Chicago"]
+ certifications = ["AWS Certified", "Google Cloud Certified", "PMP", "Scrum Master", "Cisco Certified"]
+
+ # Generate random hire dates
+ def random_hire_date():
+ start_date = datetime.date(2000, 1, 1)
+ end_date = datetime.date(2023, 12, 31)
+ time_between_dates = end_date - start_date
+ days_between_dates = time_between_dates.days
+ random_number_of_days = random.randrange(days_between_dates)
+ return start_date + datetime.timedelta(days=random_number_of_days)
+
+ # Generate random employee records with an employee ID (eid)
+ employees = []
+ for eid in range(1, 101): # 100 employees with `eid` starting from 1
+ name = random.choice(names)
+ position = random.choice(positions)
+ salary = round(random.uniform(50000, 150000), 2) # Salary between 50,000 and 150,000
+ department = random.choice(departments)
+ location = random.choice(locations)
+ hire_date = random_hire_date()
+ performance_score = round(random.uniform(1, 5), 2) # Performance score between 1.0 and 5.0
+ years_of_experience = random.randint(1, 30) # Years of experience between 1 and 30
+
+ employee = {
+ "eid": eid, # Employee ID
+ "name": name,
+ "position": position,
+ "salary": salary,
+ "department": department,
+ "location": location,
+ "hire_date": hire_date,
+ "performance_score": performance_score,
+ "years_of_experience": years_of_experience
+ }
+
+ employees.append(employee)
+
+ # Convert the list of dictionaries to a DataFrame and save to DB
+ df_employees = pd.DataFrame(employees)
+ df_employees.to_sql('employees', conn, index=False, if_exists='replace')
+
+# Function to generate random project data with `eid`
+def generate_project_data(conn):
+ employees = pd.read_sql_query("SELECT eid FROM employees", conn)
+ projects = []
+
+ for _ in range(500): # 500 projects
+ eid = random.choice(employees['eid'])
+ project_name = f"Project_{random.randint(1, 100)}"
+ start_date = datetime.date(2020, 1, 1) + datetime.timedelta(days=random.randint(0, 365 * 3)) # Within the last 3 years
+ performance_score = round(random.uniform(1, 5), 2) # Performance score for the project between 1.0 and 5.0
+
+ project = {
+ "eid": eid, # Foreign key from employees table
+ "project_name": project_name,
+ "start_date": start_date,
+ "performance_score": performance_score
+ }
+
+ projects.append(project)
+
+ # Convert the list of dictionaries to a DataFrame and save to DB
+ df_projects = pd.DataFrame(projects)
+ df_projects.to_sql('projects', conn, index=False, if_exists='replace')
+
+# Function to generate random salary history data with `eid`
+def generate_salary_history(conn):
+ employees = pd.read_sql_query("SELECT eid FROM employees", conn)
+ salary_history = []
+
+ for _ in range(300): # 300 salary records
+ eid = random.choice(employees['eid'])
+ salary_increase_percentage = round(random.uniform(5, 30), 2) # Salary increase between 5% and 30%
+ promotion_date = datetime.date(2018, 1, 1) + datetime.timedelta(days=random.randint(0, 365 * 5)) # Promotions in the last 5 years
+
+ salary_record = {
+ "eid": eid, # Foreign key from employees table
+ "salary_increase_percentage": salary_increase_percentage,
+ "promotion_date": promotion_date
+ }
+
+ salary_history.append(salary_record)
+
+ # Convert the list of dictionaries to a DataFrame and save to DB
+ df_salary_history = pd.DataFrame(salary_history)
+ df_salary_history.to_sql('salary_history', conn, index=False, if_exists='replace')
+
+# Function to generate random certifications data with `eid`
+def generate_certifications(conn):
+ employees = pd.read_sql_query("SELECT eid FROM employees", conn)
+ certifications_list = ["AWS Certified", "Google Cloud Certified", "PMP", "Scrum Master", "Cisco Certified"]
+ employee_certifications = []
+
+ for _ in range(300): # 300 certification records
+ eid = random.choice(employees['eid'])
+ certification = random.choice(certifications_list)
+
+ cert_record = {
+ "eid": eid, # Foreign key from employees table
+ "certification_name": certification
+ }
+
+ employee_certifications.append(cert_record)
+
+ # Convert the list of dictionaries to a DataFrame and save to DB
+ df_certifications = pd.DataFrame(employee_certifications)
+ df_certifications.to_sql('certifications', conn, index=False, if_exists='replace')
diff --git a/demos/employee_details_copilot_arch/api_server/requirements.txt b/demos/employee_details_copilot_arch/api_server/requirements.txt
new file mode 100644
index 00000000..bfc7be35
--- /dev/null
+++ b/demos/employee_details_copilot_arch/api_server/requirements.txt
@@ -0,0 +1,4 @@
+fastapi
+uvicorn
+pandas
+dateparser
diff --git a/demos/employee_details_copilot_arch/bolt_config.yaml b/demos/employee_details_copilot_arch/bolt_config.yaml
new file mode 100644
index 00000000..3b4b2a01
--- /dev/null
+++ b/demos/employee_details_copilot_arch/bolt_config.yaml
@@ -0,0 +1,197 @@
+default_prompt_endpoint: "127.0.0.1"
+load_balancing: "round_robin"
+timeout_ms: 5000
+
+overrides:
+ # confidence threshold for prompt target intent matching
+ prompt_target_intent_matching_threshold: 0.7
+
+llm_providers:
+
+ - name: open-ai-gpt-4
+ api_key: $OPEN_AI_API_KEY
+ model: gpt-4
+ default: true
+
+prompt_targets:
+
+ - type: function_resolver
+ name: top_employees
+ description: |
+ Allows you to find the top employees in different groups, such as departments, locations, or position. You can rank the employees by different criteria, like salary, yoe, or rating. Returns the best-ranked employees for each group, helping you identify top n in the list.
+ parameters:
+ - name: grouping
+ description: |
+ Select how you'd like to group the employees. For example, you can group them by department, location, or their position. The tool will provide the top-ranked employees within each group you choose.
+ required: true
+ type: string
+ enum: [department, location, position]
+ - name: ranking_criteria
+ required: true
+ type: string
+ description: |
+ Choose how you'd like to rank the employees. You can rank them by their salary, their years of experience, or their rating. The tool will sort the employees based on this ranking and return the best ones from each group.
+ enum: [salary, years_of_experience, performance_score]
+ - name: top_n
+ required: true
+ type: integer
+ description: |
+ Enter how many of the top employees you want to see in each group. For example, if you enter 3, the tool will show you the top 3 employees for each group you selected.
+ endpoint:
+ cluster: api_server
+ path: /top_employees
+ system_prompt: |
+ You are responsible for retrieving the top N employees per group ranked by a constraint.
+
+ - type: function_resolver
+ name: aggregate_stats
+ description: |
+ Calculate summary statistics for groups of employees. You can group employees by categories like department or location and then compute totals, averages, or other statistics for specific attributes such as salary or years of experience.
+ parameters:
+ - name: grouping
+ description: |
+ Choose how you'd like to organize the employees. For example, you can group them by department, location, or position. The tool will calculate the summary statistics for each group.
+ required: true
+ enum: [department, location, position]
+ - name: aggregate_criteria
+ description: |
+ Select the specific attribute you'd like to analyze. This could be something like salary, years of experience, or rating. The tool will calculate the statistic you request for this attribute.
+ required: true
+ enum: [salary, years_of_experience, performance_score]
+ - name: aggregate_type
+ description: |
+ Choose the type of statistic you'd like to calculate for the selected attribute. For example, you can calculate the sum, average, minimum, or maximum value for each group.
+ required: true
+ enum: [SUM, AVG, MIN, MAX]
+ endpoint:
+ cluster: api_server
+ path: /aggregate_stats
+ system_prompt: |
+ You help calculate summary statistics for groups of employees. First, organize the employees by the specified grouping (e.g., department, location, or position). Then, compute the requested statistic (e.g., total, average, minimum, or maximum) for a specific attribute like salary, experience, or rating.
+
+ # 1. Top Employees by Performance, Projects, and Timeframe
+ - type: function_resolver
+ name: employees_projects
+ description: |
+ Fetch employees with the highest performance scores, considering their project participation and years of experience. You can filter by minimum performance score, years of experience, and department. Optionally, you can also filter by recent project participation within the last Y months.
+ parameters:
+ - name: min_performance_score
+ description: Minimum performance score to filter employees.
+ required: true
+ type: float
+ - name: min_years_experience
+ description: Minimum years of experience to filter employees.
+ required: true
+ type: integer
+ - name: department
+ description: Department to filter employees by.
+ required: true
+ type: string
+ - name: min_project_count
+ description: Minimum number of projects employees participated in (optional).
+ required: false
+ type: integer
+ - name: months_range
+ description: Timeframe (in months) for filtering recent projects (optional).
+ required: false
+ type: integer
+ endpoint:
+ cluster: api_server
+ path: /employees_projects
+ system_prompt: |
+ You are responsible for retrieving the top N employees ranked by performance and project participation. Use filters for experience and optional project criteria.
+
+
+ # 2. Employees with Salary Growth Since Last Promotion
+ - type: function_resolver
+ name: salary_growth
+ description: |
+ Fetch employees with the highest salary growth since their last promotion, grouped by department. You can filter by a minimum salary increase percentage and department.
+ parameters:
+ - name: min_salary_increase_percentage
+ description: Minimum percentage increase in salary since the last promotion.
+ required: true
+ type: float
+ - name: department
+ description: Department to filter employees by (optional).
+ required: false
+ type: string
+ endpoint:
+ cluster: api_server
+ path: /salary_growth
+ system_prompt: |
+ You are responsible for retrieving employees with the highest salary growth since their last promotion. Filter by minimum salary increase percentage and department.
+
+ # 4. Employees with Promotions and Salary Increases by Year
+ - type: function_resolver
+ name: promotions_increases
+ description: |
+ Fetch employees who were promoted and received a salary increase in a specific year, grouped by department. You can optionally filter by minimum percentage salary increase and department.
+ parameters:
+ - name: year
+ description: The year in which the promotion and salary increase occurred.
+ required: true
+ type: integer
+ - name: min_salary_increase_percentage
+ description: Minimum percentage salary increase to filter employees.
+ required: false
+ type: float
+ - name: department
+ description: Department to filter by (optional).
+ required: false
+ type: string
+ endpoint:
+ cluster: api_server
+ path: /promotions_increases
+ system_prompt: |
+ You are responsible for fetching employees who were promoted and received a salary increase in a specific year. Apply filters for salary increase percentage and department.
+
+
+ # 5. Employees with Highest Average Project Performance
+ - type: function_resolver
+ name: avg_project_performance
+ description: |
+ Fetch employees with the highest average performance across all projects they have worked on over time. You can filter by minimum project count, department, and minimum performance score.
+ parameters:
+ - name: min_project_count
+ description: Minimum number of projects an employee must have participated in.
+ required: true
+ type: integer
+ - name: min_performance_score
+ description: Minimum performance score to filter employees.
+ required: true
+ type: float
+ - name: department
+ description: Department to filter by (optional).
+ required: false
+ type: string
+ endpoint:
+ cluster: api_server
+ path: /avg_project_performance
+ system_prompt: |
+ You are responsible for fetching employees with the highest average performance across all projects they’ve worked on. Apply filters for minimum project count, performance score, and department.
+
+
+ # 6. Employees by Certification and Years of Experience
+ - type: function_resolver
+ name: certifications_experience
+ description: |
+ Fetch employees who have all the required certifications and meet the minimum years of experience. You can filter by department and provide a list of certifications to match.
+ parameters:
+ - name: certifications
+ description: List of required certifications.
+ required: true
+ type: list
+ - name: min_years_experience
+ description: Minimum years of experience.
+ required: true
+ type: integer
+ - name: department
+ description: Department to filter employees by (optional).
+ required: false
+ type: string
+ endpoint:
+ cluster: api_server
+ path: /certifications_experience
+ system_prompt: |
+ You are responsible for fetching employees who have the required certifications and meet the minimum years of experience. Optionally, filter by department.
diff --git a/demos/employee_details_copilot_arch/docker-compose.yaml b/demos/employee_details_copilot_arch/docker-compose.yaml
new file mode 100644
index 00000000..c6a83c41
--- /dev/null
+++ b/demos/employee_details_copilot_arch/docker-compose.yaml
@@ -0,0 +1,142 @@
+services:
+
+ config_generator:
+ build:
+ context: ../../
+ dockerfile: config_generator/Dockerfile
+ volumes:
+ - ../../envoyfilter/envoy.template.yaml:/usr/src/app/envoy.template.yaml
+ - ./bolt_config.yaml:/usr/src/app/bolt_config.yaml
+ - ./generated:/usr/src/app/out
+
+ bolt:
+ build:
+ context: ../../
+ dockerfile: envoyfilter/Dockerfile
+ hostname: bolt
+ ports:
+ - "10010:10000"
+ - "19911:9901"
+ volumes:
+ - ./generated/envoy.yaml:/etc/envoy/envoy.yaml
+ - /etc/ssl/cert.pem:/etc/ssl/cert.pem
+ depends_on:
+ config_generator:
+ condition: service_completed_successfully
+ model_server:
+ condition: service_healthy
+ environment:
+ - LOG_LEVEL=debug
+
+ model_server:
+ build:
+ context: ../../model_server
+ dockerfile: Dockerfile
+ ports:
+ - "18091:80"
+ healthcheck:
+ test: ["CMD", "curl" ,"http://localhost:80/healthz"]
+ interval: 5s
+ retries: 20
+ volumes:
+ - ~/.cache/huggingface:/root/.cache/huggingface
+ - ./bolt_config.yaml:/root/bolt_config.yaml
+
+ api_server:
+ build:
+ context: api_server
+ dockerfile: Dockerfile
+ ports:
+ - "18093:80"
+ healthcheck:
+ test: ["CMD", "curl" ,"http://localhost:80/healthz"]
+ interval: 5s
+ retries: 20
+
+ function_resolver:
+ build:
+ context: ../../function_resolver
+ dockerfile: Dockerfile
+ ports:
+ - "18092:80"
+ healthcheck:
+ test: ["CMD", "curl" ,"http://localhost:80/healthz"]
+ interval: 5s
+ retries: 20
+ volumes:
+ - ~/.cache/huggingface:/root/.cache/huggingface
+ environment:
+ # use ollama endpoint that is hosted by host machine (no virtualization)
+ - OLLAMA_ENDPOINT=${OLLAMA_ENDPOINT:-host.docker.internal}
+ # uncomment following line to use ollama endpoint that is hosted by docker
+ # - OLLAMA_ENDPOINT=ollama
+ - OLLAMA_MODEL=Arch-Function-Calling-1.5B:Q4_K_M
+
+ ollama:
+ image: ollama/ollama
+ container_name: ollama
+ volumes:
+ - ./ollama:/root/.ollama
+ restart: unless-stopped
+ ports:
+ - '11444:11434'
+ profiles:
+ - manual
+
+ open-webui:
+ image: ghcr.io/open-webui/open-webui:${WEBUI_DOCKER_TAG-main}
+ container_name: open-webui
+ volumes:
+ - ./open-webui:/app/backend/data
+ # depends_on:
+ # - ollama
+ ports:
+ - 18100:8080
+ environment:
+ - OLLAMA_BASE_URL=http://${OLLAMA_ENDPOINT:-host.docker.internal}:11434
+ - WEBUI_AUTH=false
+ extra_hosts:
+ - host.docker.internal:host-gateway
+ restart: unless-stopped
+ profiles:
+ - monitoring
+
+ chatbot_ui:
+ build:
+ context: ../../chatbot_ui
+ dockerfile: Dockerfile
+ ports:
+ - "18090:8080"
+ environment:
+ - OPENAI_API_KEY=${OPENAI_API_KEY:?error}
+ - CHAT_COMPLETION_ENDPOINT=http://bolt:10000/v1
+
+ prometheus:
+ image: prom/prometheus
+ container_name: prometheus
+ command:
+ - '--config.file=/etc/prometheus/prometheus.yaml'
+ ports:
+ - 9100:9090
+ restart: unless-stopped
+ volumes:
+ - ./prometheus:/etc/prometheus
+ - ./prom_data:/prometheus
+ profiles:
+ - monitoring
+
+ grafana:
+ image: grafana/grafana
+ container_name: grafana
+ ports:
+ - 3010:3000
+ restart: unless-stopped
+ environment:
+ - GF_SECURITY_ADMIN_USER=admin
+ - GF_SECURITY_ADMIN_PASSWORD=grafana
+ volumes:
+ - ./grafana:/etc/grafana/provisioning/datasources
+ - ./grafana/dashboard.yaml:/etc/grafana/provisioning/dashboards/main.yaml
+ - ./grafana/dashboards:/var/lib/grafana/dashboards
+ profiles:
+ - monitoring
diff --git a/demos/employee_details_copilot_arch/grafana/dashboard.yaml b/demos/employee_details_copilot_arch/grafana/dashboard.yaml
new file mode 100644
index 00000000..fd66a479
--- /dev/null
+++ b/demos/employee_details_copilot_arch/grafana/dashboard.yaml
@@ -0,0 +1,12 @@
+apiVersion: 1
+
+providers:
+ - name: "Dashboard provider"
+ orgId: 1
+ type: file
+ disableDeletion: false
+ updateIntervalSeconds: 10
+ allowUiUpdates: false
+ options:
+ path: /var/lib/grafana/dashboards
+ foldersFromFilesStructure: true
diff --git a/demos/employee_details_copilot_arch/grafana/dashboards/envoy_overview.json b/demos/employee_details_copilot_arch/grafana/dashboards/envoy_overview.json
new file mode 100644
index 00000000..51bff777
--- /dev/null
+++ b/demos/employee_details_copilot_arch/grafana/dashboards/envoy_overview.json
@@ -0,0 +1,355 @@
+{
+ "annotations": {
+ "list": [
+ {
+ "builtIn": 1,
+ "datasource": {
+ "type": "grafana",
+ "uid": "-- Grafana --"
+ },
+ "enable": true,
+ "hide": true,
+ "iconColor": "rgba(0, 211, 255, 1)",
+ "name": "Annotations & Alerts",
+ "type": "dashboard"
+ }
+ ]
+ },
+ "editable": true,
+ "fiscalYearStartMonth": 0,
+ "graphTooltip": 1,
+ "links": [],
+ "panels": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ }
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 0,
+ "y": 0
+ },
+ "id": 2,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "disableTextWrap": false,
+ "editorMode": "code",
+ "expr": "avg(rate(envoy_cluster_internal_upstream_rq_time_sum[1m]) / rate(envoy_cluster_internal_upstream_rq_time_count[1m])) by (envoy_cluster_name)",
+ "fullMetaSearch": false,
+ "hide": false,
+ "includeNullMetadata": true,
+ "instant": false,
+ "legendFormat": "__auto",
+ "range": true,
+ "refId": "A",
+ "useBackend": false
+ }
+ ],
+ "title": "request latency - internal (ms)",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ }
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 12,
+ "y": 0
+ },
+ "id": 1,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "disableTextWrap": false,
+ "editorMode": "code",
+ "expr": "avg(rate(envoy_cluster_external_upstream_rq_time_sum[1m]) / rate(envoy_cluster_external_upstream_rq_time_count[1m])) by (envoy_cluster_name)",
+ "fullMetaSearch": false,
+ "hide": false,
+ "includeNullMetadata": true,
+ "instant": false,
+ "legendFormat": "__auto",
+ "range": true,
+ "refId": "A",
+ "useBackend": false
+ }
+ ],
+ "title": "request latency - external (ms)",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ }
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 0,
+ "y": 8
+ },
+ "id": 3,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "disableTextWrap": false,
+ "editorMode": "code",
+ "expr": "avg(rate(envoy_cluster_internal_upstream_rq_completed[1m])) by (envoy_cluster_name)",
+ "fullMetaSearch": false,
+ "includeNullMetadata": true,
+ "instant": false,
+ "legendFormat": "__auto",
+ "range": true,
+ "refId": "A",
+ "useBackend": false
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "PBFA97CFB590B2093"
+ },
+ "disableTextWrap": false,
+ "editorMode": "code",
+ "expr": "avg(rate(envoy_cluster_external_upstream_rq_completed[1m])) by (envoy_cluster_name)",
+ "fullMetaSearch": false,
+ "hide": false,
+ "includeNullMetadata": true,
+ "instant": false,
+ "legendFormat": "__auto",
+ "range": true,
+ "refId": "B",
+ "useBackend": false
+ }
+ ],
+ "title": "Upstream request count",
+ "type": "timeseries"
+ }
+ ],
+ "schemaVersion": 39,
+ "tags": [],
+ "templating": {
+ "list": []
+ },
+ "time": {
+ "from": "now-15m",
+ "to": "now"
+ },
+ "timepicker": {},
+ "timezone": "browser",
+ "title": "Intelligent Gateway Overview",
+ "uid": "adt6uhx5lk8aob",
+ "version": 3,
+ "weekStart": ""
+}
diff --git a/demos/employee_details_copilot_arch/grafana/datasource.yaml b/demos/employee_details_copilot_arch/grafana/datasource.yaml
new file mode 100644
index 00000000..4870174e
--- /dev/null
+++ b/demos/employee_details_copilot_arch/grafana/datasource.yaml
@@ -0,0 +1,9 @@
+apiVersion: 1
+
+datasources:
+- name: Prometheus
+ type: prometheus
+ url: http://prometheus:9090
+ isDefault: true
+ access: proxy
+ editable: true
diff --git a/demos/employee_details_copilot_arch/prometheus/prometheus.yaml b/demos/employee_details_copilot_arch/prometheus/prometheus.yaml
new file mode 100644
index 00000000..cf10e36d
--- /dev/null
+++ b/demos/employee_details_copilot_arch/prometheus/prometheus.yaml
@@ -0,0 +1,23 @@
+global:
+ scrape_interval: 15s
+ scrape_timeout: 10s
+ evaluation_interval: 15s
+alerting:
+ alertmanagers:
+ - static_configs:
+ - targets: []
+ scheme: http
+ timeout: 10s
+ api_version: v1
+scrape_configs:
+- job_name: envoy
+ honor_timestamps: true
+ scrape_interval: 15s
+ scrape_timeout: 10s
+ metrics_path: /stats
+ scheme: http
+ static_configs:
+ - targets:
+ - bolt:9901
+ params:
+ format: ['prometheus']
diff --git a/demos/network_copilot/Bolt-FC-1B-Q3_K_L.model_file b/demos/network_copilot/Bolt-FC-1B-Q3_K_L.model_file
deleted file mode 100644
index d58a6a17..00000000
--- a/demos/network_copilot/Bolt-FC-1B-Q3_K_L.model_file
+++ /dev/null
@@ -1,25 +0,0 @@
-FROM Bolt-Function-Calling-1B-Q3_K_L.gguf
-
-# Set the size of the context window used to generate the next token
-# PARAMETER num_ctx 16384
-PARAMETER num_ctx 4096
-
-# Set parameters for response generation
-PARAMETER num_predict 1024
-PARAMETER temperature 0.1
-PARAMETER top_p 0.5
-PARAMETER top_k 32022
-PARAMETER repeat_penalty 1.0
-PARAMETER stop "<|EOT|>"
-
-# Set the random number seed to use for generation
-PARAMETER seed 42
-
-# Set the prompt template to be passed into the model
-TEMPLATE """{{ if .System }}<|begin▁of▁sentence|>
-{{ .System }}
-{{ end }}{{ if .Prompt }}### Instruction:
-{{ .Prompt }}
-{{ end }}### Response:
-{{ .Response }}
-<|EOT|>"""
diff --git a/demos/network_copilot/README.md b/demos/network_copilot/README.md
index 36655cf7..c19898fd 100644
--- a/demos/network_copilot/README.md
+++ b/demos/network_copilot/README.md
@@ -2,10 +2,6 @@
This demo shows how you can use intelligent prompt gateway as a network copilot that could give information about correlation between packet loss with device reboots, downs, or maintainence. This demo assumes you are using ollama running natively. If you want to run ollama running inside docker then please update ollama endpoint in docker-compose file.
# Starting the demo
-1. Ensure that submodule is up to date
- ```sh
- git submodule sync --recursive
- ```
1. Create `.env` file and set OpenAI key using env var `OPENAI_API_KEY`
1. Start services
```sh
diff --git a/function_resolver/app/arch_handler.py b/function_resolver/app/arch_handler.py
new file mode 100644
index 00000000..77b0a65d
--- /dev/null
+++ b/function_resolver/app/arch_handler.py
@@ -0,0 +1,125 @@
+import json
+from typing import Any, Dict, List
+
+
+ARCH_FUNCTION_CALLING_TASK_PROMPT = """
+You are a helpful assistant.
+""".strip()
+
+
+ARCH_FUNCTION_CALLING_TOOL_PROMPT = """
+# Tools
+
+You may call one or more functions to assist with the user query.
+
+You are provided with function signatures within XML tags:
+
+{tool_text}
+
+""".strip()
+
+
+ARCH_FUNCTION_CALLING_FORMAT_PROMPT = """
+For each function call, return a json object with function name and arguments within XML tags:
+
+{"name": , "arguments": }
+
+""".strip()
+
+
+class ArchHandler:
+ def __init__(self) -> None:
+ super().__init__()
+
+ def _format_system(self, tools: List[Dict[str, Any]]):
+ def convert_tools(tools):
+ return "\n".join([json.dumps(tool) for tool in tools])
+
+ tool_text = convert_tools(tools)
+
+ system_prompt = (
+ ARCH_FUNCTION_CALLING_TASK_PROMPT
+ + "\n\n"
+ + ARCH_FUNCTION_CALLING_TOOL_PROMPT.format(tool_text=tool_text)
+ + "\n\n"
+ + ARCH_FUNCTION_CALLING_FORMAT_PROMPT
+ + "\n"
+ )
+
+ return system_prompt
+
+ def _add_execution_results_prompting(
+ self,
+ messages: list[dict],
+ execution_results: list,
+ ) -> dict:
+
+ content = []
+ for result in execution_results:
+ content.append(f"\n{json.dumps(result)}\n")
+
+ content = "\n".join(content)
+ messages.append({"role": "user", "content": content})
+
+ return messages
+
+ def extract_tools(self, result: str):
+ lines = result.split("\n")
+ flag = False
+ func_call = []
+ for line in lines:
+ if "" == line:
+ flag = True
+ elif "" == line:
+ flag = False
+ else:
+ if flag:
+ try:
+ tool_result = json.loads(line)
+ except Exception:
+ fixed_content = self.fix_json_string(line)
+ try:
+ tool_result = json.loads(fixed_content)
+ except json.JSONDecodeError:
+ return result
+ func_call.append({tool_result["name"]: tool_result["arguments"]})
+ flag = False
+ return func_call
+
+ def fix_json_string(self, json_str: str):
+ # Remove any leading or trailing whitespace or newline characters
+ json_str = json_str.strip()
+
+ # Stack to keep track of brackets
+ stack = []
+
+ # Clean string to collect valid characters
+ fixed_str = ""
+
+ # Dictionary for matching brackets
+ matching_bracket = {")": "(", "}": "{", "]": "["}
+
+ # Dictionary for the opposite of matching_bracket
+ opening_bracket = {v: k for k, v in matching_bracket.items()}
+
+ for char in json_str:
+ if char in "{[(":
+ stack.append(char)
+ fixed_str += char
+ elif char in "}])":
+ if stack and stack[-1] == matching_bracket[char]:
+ stack.pop()
+ fixed_str += char
+ else:
+ # Ignore the unmatched closing brackets
+ continue
+ else:
+ fixed_str += char
+
+ # If there are unmatched opening brackets left in the stack, add corresponding closing brackets
+ while stack:
+ unmatched_opening = stack.pop()
+ fixed_str += opening_bracket[unmatched_opening]
+
+ # Attempt to parse the corrected string to ensure it’s valid JSON
+ return fixed_str
diff --git a/function_resolver/app/main.py b/function_resolver/app/main.py
index 3c3fde9b..4c1d028a 100644
--- a/function_resolver/app/main.py
+++ b/function_resolver/app/main.py
@@ -1,6 +1,7 @@
import json
from fastapi import FastAPI, Response
from bolt_handler import BoltHandler
+from arch_handler import ArchHandler
from common import ChatMessage
import logging
from openai import OpenAI
@@ -14,7 +15,8 @@ logger.info(f"using model: {ollama_model}")
logger.info(f"using ollama endpoint: {ollama_endpoint}")
app = FastAPI()
-handler = BoltHandler()
+bolt_handler = BoltHandler()
+arch_handler = ArchHandler()
client = OpenAI(
base_url='http://{}:11434/v1/'.format(ollama_endpoint),
@@ -33,6 +35,10 @@ async def healthz():
@app.post("/v1/chat/completions")
async def chat_completion(req: ChatMessage, res: Response):
logger.info("starting request")
+ if ollama_model.startswith("Bolt"):
+ handler = bolt_handler
+ else:
+ handler = arch_handler
tools_encoded = handler._format_system(req.tools)
# append system prompt with tools to messages
messages = [{"role": "system", "content": tools_encoded}]
diff --git a/open-message-format b/open-message-format
new file mode 160000
index 00000000..1e838f3f
--- /dev/null
+++ b/open-message-format
@@ -0,0 +1 @@
+Subproject commit 1e838f3f4019e802099309687ca404509cb114a2
diff --git a/public_types/src/common_types.rs b/public_types/src/common_types.rs
index 07bfd46b..7ee8e974 100644
--- a/public_types/src/common_types.rs
+++ b/public_types/src/common_types.rs
@@ -68,6 +68,7 @@ pub struct ToolsDefinition {
pub enum IntOrString {
Integer(i32),
Text(String),
+ Float(f64),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -187,4 +188,4 @@ pub struct PromptGuardResponse {
pub jailbreak_prob: Option,
pub toxic_verdict: Option,
pub jailbreak_verdict: Option,
-}
\ No newline at end of file
+}
diff --git a/public_types/src/configuration.rs b/public_types/src/configuration.rs
index 91bdcd8d..3cef27e7 100644
--- a/public_types/src/configuration.rs
+++ b/public_types/src/configuration.rs
@@ -193,4 +193,4 @@ ratelimits:
let c: super::Configuration = serde_yaml::from_str(CONFIGURATION).unwrap();
assert_eq!(c.prompt_guards.unwrap().input_guard.len(), 2);
}
-}
\ No newline at end of file
+}