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
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:
@@ -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))
|
||||
Reference in New Issue
Block a user