Files
wehub-resource-sync 0d3cb498a3
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
chore: import upstream snapshot with attribution
2026-07-13 13:24:08 +08:00

145 lines
5.4 KiB
Python

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))