chore: import upstream snapshot with attribution
CI / Shell Format Check (push) Has been cancelled
CI / Check Ruby (3.4) (push) Has been cancelled
CI / CI Config (push) Has been cancelled
CI / Test on Node ${{ matrix.node }} and ${{ matrix.os }}${{ matrix.shard && format(' (shard {0}/3)', matrix.shard) || '' }} (push) Has been cancelled
CI / Build on Node ${{ matrix.node }} (push) Has been cancelled
CI / Style Check (push) Has been cancelled
CI / Generate Assets (push) Has been cancelled
CI / Check Python (3.14) (push) Has been cancelled
CI / Check Python (3.9) (push) Has been cancelled
CI / Build Docs (push) Has been cancelled
CI / Code Scan Action (push) Has been cancelled
CI / Site tests (push) Has been cancelled
CI / webui tests (push) Has been cancelled
CI / Run Integration Tests (push) Has been cancelled
CI / Run Smoke Tests (push) Has been cancelled
CI / Go Tests (push) Has been cancelled
CI / Share Test (push) Has been cancelled
CI / Redteam (Production API) (push) Has been cancelled
CI / Redteam (Staging API) (push) Has been cancelled
CI / GitHub Actions Lint (push) Has been cancelled
CI / Check Ruby (3.0) (push) Has been cancelled
release-please / release-please (push) Has been cancelled
release-please / build (push) Has been cancelled
release-please / publish-npm (push) Has been cancelled
release-please / publish-npm-backfill (push) Has been cancelled
release-please / docker (push) Has been cancelled
release-please / publish-code-scan-action (push) Has been cancelled
release-please / attest-code-scan-action (push) Has been cancelled
Deploy local.promptfoo.app / Deploy to Cloudflare Pages (push) Has been cancelled
Test and Publish Multi-arch Docker Image / test (push) Has been cancelled
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-amd64 platform:linux/amd64 runner:ubuntu-latest]) (push) Has been cancelled
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-arm64 platform:linux/arm64 runner:ubuntu-24.04-arm]) (push) Has been cancelled
Test and Publish Multi-arch Docker Image / merge-docker-digests (push) Has been cancelled
Test and Publish Multi-arch Docker Image / Attest Multi-arch Image (push) Has been cancelled
Validate Renovate Config / Validate Renovate Configuration (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:24:08 +08:00
commit 0d3cb498a3
5438 changed files with 1316560 additions and 0 deletions
+91
View File
@@ -0,0 +1,91 @@
# integration-crewai (CrewAI Integration)
This example shows how to use **CrewAI agents** with promptfoo to evaluate AI agent performance.
## What is CrewAI?
CrewAI is a framework for orchestrating role-playing, autonomous AI agents. By fostering collaborative intelligence, CrewAI empowers agents to work together seamlessly, tackling complex tasks.
## Quick Start
You can run this example with:
```bash
npx promptfoo@latest init --example integration-crewai
cd integration-crewai
```
## Prerequisites
This example requires the following:
1. **Python 3.10+**
2. **Node.js ^20.20.0 or >=22.22.0 (Node.js 20 support ends July 30, 2026; Node.js 24 LTS recommended)**
3. **OpenAI API Key** - You MUST have a valid OpenAI API key to run this example
## Environment Setup
You need to set the OpenAI API key. Choose one of these methods:
### Option 1: Environment Variable (Recommended)
```bash
export OPENAI_API_KEY=your-api-key-here
```
### Option 2: .env File
Create a `.env` file in this directory:
```dotenv
OPENAI_API_KEY=your-api-key-here
```
If using a `.env` file, uncomment `python-dotenv` in `requirements.txt` and reinstall dependencies.
## Installation
Install Python packages:
```bash
pip install -r requirements.txt
```
Note: The openai package and other dependencies (langchain, pydantic, etc.) will be automatically installed as dependencies of crewai.
Install promptfoo CLI:
```bash
npm install -g promptfoo
```
## Files
- `agent.py`: Contains the CrewAI agent setup and promptfoo provider interface
- `promptfooconfig.yaml`: Configures prompts, providers, and tests for evaluation
### Note on Reliability
When using a real LLM, you may notice that the agent's output is not always reliable, especially for more complex queries. For example, the agent may fail to return valid JSON or may not return a response at all. This is a common challenge when working with LLMs.
## Running the Evaluation
Run the evaluation:
```bash
promptfoo eval
```
Explore results in browser:
```bash
promptfoo view
```
## Troubleshooting
If you see authentication errors:
- Ensure your OpenAI API key is set correctly
- Verify the key is valid and has sufficient quota
- Check that the environment variable is accessible to the Python process
+144
View File
@@ -0,0 +1,144 @@
import asyncio
import json
import os
import re
import textwrap
from typing import Any, Dict
from crewai import Agent, Crew, Task
# ✅ Load the OpenAI API key from the environment
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
def get_recruitment_agent(model: str = "openai:gpt-4.1") -> Crew:
"""
Creates a CrewAI recruitment agent setup.
This agent's goal: find the best Ruby on Rails + React candidates.
"""
agent = Agent(
role="Senior Recruiter specializing in technical roles",
goal="Find the best candidates for a given set of job requirements and return the results in a valid JSON format.",
backstory=textwrap.dedent("""
You are an expert recruiter with years of experience in sourcing top talent for the tech industry.
You have a keen eye for detail and are a master at following instructions to the letter, especially when it comes to output formats.
You never fail to return a valid JSON object as your final answer.
""").strip(),
verbose=False,
model=model,
api_key=OPENAI_API_KEY, # ✅ Make sure to pass the API key
)
task = Task(
description="Find the top 3 candidates based on the following job requirements: {job_requirements}",
expected_output=textwrap.dedent("""
A single valid JSON object. The JSON object must have a single key called "candidates".
The value of the "candidates" key must be an array of JSON objects.
Each object in the array must have the following keys: "name", "experience", and "skills".
- "name" must be a string representing the candidate's name.
- "experience" must be a string summarizing the candidate's relevant experience.
- "skills" must be an array of strings listing the candidate's skills.
Example of the expected final output:
{
"candidates": [
{
"name": "Jane Doe",
"experience": "8 years of experience in Ruby on Rails and React, with a strong focus on building scalable web applications.",
"skills": ["Ruby on Rails", "React", "JavaScript", "PostgreSQL", "TDD"]
}
]
}
""").strip(),
agent=agent,
)
# ✅ Combine agent + task into a Crew setup
crew = Crew(agents=[agent], tasks=[task])
return crew
async def run_recruitment_agent(prompt, model="openai:gpt-4.1"):
"""
Runs the recruitment agent with a given job requirements prompt.
Returns a structured JSON-like dictionary with candidate info.
"""
# Check if API key is set
if not OPENAI_API_KEY:
return {
"error": "OpenAI API key not found. Please set the OPENAI_API_KEY environment variable or create a .env file with your API key."
}
crew = get_recruitment_agent(model)
try:
# ⚡ Trigger the agent to start working
crew.kickoff(inputs={"job_requirements": prompt})
# The result might be a string, or an object with a 'raw' attribute.
output_text = ""
if result:
if hasattr(result, "raw") and result.raw:
output_text = result.raw
elif isinstance(result, str):
output_text = result
if not output_text:
return {"error": "CrewAI agent returned an empty response."}
# Use regex to find the JSON block, even with markdown
json_match = re.search(r"```json\s*([\s\S]*?)\s*```|({[\s\S]*})", output_text)
if not json_match:
return {
"error": "No valid JSON block found in the agent's output.",
"raw_output": output_text,
}
json_string = json_match.group(1) or json_match.group(2)
try:
return json.loads(json_string)
except json.JSONDecodeError as e:
return {
"error": f"Failed to parse JSON from agent output: {str(e)}",
"raw_output": json_string,
}
except Exception as e:
# 🔥 Catch and report any error as part of the output
return {"error": f"An unexpected error occurred: {str(e)}"}
def call_api(
prompt: str, options: Dict[str, Any], context: Dict[str, Any]
) -> Dict[str, Any]:
"""
Calls the CrewAI recruitment agent with the provided prompt.
Wraps the async function in a synchronous call for Promptfoo.
"""
try:
# ✅ Run the async recruitment agent synchronously
config = options.get("config", {})
model = config.get("model", "openai:gpt-4.1")
result = asyncio.run(run_recruitment_agent(prompt, model=model))
if "error" in result:
return {"error": result["error"], "raw": result.get("raw_output", "")}
return {"output": result}
except Exception as e:
# 🔥 Catch and return any error as part of the output
return {"error": f"An error occurred in call_api: {str(e)}"}
if __name__ == "__main__":
# 🧪 Simple test block to check provider behavior standalone
print("✅ Testing CrewAI provider...")
# 🔧 Example test prompt
test_prompt = "We need a Ruby on Rails and React engineer with at least 5 years of experience."
# ⚡ Call the API function with test inputs
result = call_api(test_prompt, {}, {})
# 📦 Print the result to console
print("Provider result:", json.dumps(result, indent=2))
@@ -0,0 +1,78 @@
# yaml-language-server: $schema=https://promptfoo.dev/config-schema.json
description: 'CrewAI Recruitment Agent Evaluation'
prompts:
- 'Find top candidates for the following role: {{role}}'
providers:
- id: 'file://./agent.py'
label: 'CrewAI Recruitment Agent'
config:
model: 'openai:gpt-4.1'
# It's a good practice to have a defaultTest that applies to all test cases.
defaultTest:
assert:
# We expect the agent to always return a valid JSON object
- type: is-json
# We expect the output to contain a "candidates" key
- type: javascript
value: 'output.hasOwnProperty("candidates")'
tests:
- description: 'Senior Full-Stack Engineer'
vars:
role: 'A Senior Full-Stack Engineer with 8+ years of experience in Python, Django, and React.'
assert:
- type: javascript
value: |
// Check that there are at least 2 candidates
return output.candidates.length >= 2;
- type: python
value: |
# Check that all candidates have relevant skills
required_skills = ['python', 'django', 'react']
all_have_skills = all(
any(req_skill in skill.lower() for skill in candidate.get('skills', []) for req_skill in required_skills)
for candidate in output.get('candidates', [])
)
return all_have_skills
- description: 'Data Scientist with Machine Learning and Cloud'
vars:
role: 'A Data Scientist with machine learning, Python, and cloud (AWS or GCP) experience.'
assert:
- type: javascript
value: |
// Check that there are at least 2 candidates
return output.candidates.length >= 2;
- type: python
value: |
# Check for relevant data science and cloud skills
required_skills = ['machine learning', 'python', 'aws', 'gcp', 'tensorflow', 'pytorch']
all_have_skills = all(
any(req_skill in skill.lower() for skill in candidate.get('skills', []) for req_skill in required_skills)
for candidate in output.get('candidates', [])
)
return all_have_skills
- description: 'Junior UX/UI Designer'
vars:
role: 'A junior UX/UI designer with Figma and Adobe Creative Suite experience.'
assert:
- type: javascript
value: |
// Check that there are at least 2 candidates
return output.candidates.length >= 2;
- type: python
value: |
# Check for relevant design tool skills
required_skills = ['figma', 'adobe', 'ux', 'ui', 'user experience', 'user interface']
all_have_skills = all(
any(req_skill in skill.lower() for skill in candidate.get('skills', []) for req_skill in required_skills)
for candidate in output.get('candidates', [])
)
return all_have_skills
@@ -0,0 +1,6 @@
# CrewAI - Main framework used in this example
crewai>=0.203.0
# Optional: Environment variable management from .env files
# Uncomment if you want to use .env files for API keys
# python-dotenv>=1.0.1