mirror of https://github.com/aliasrobotics/cai.git
fix agent and add primary test
This commit is contained in:
parent
30782863d6
commit
016c27c4af
|
|
@ -19,7 +19,7 @@ from cai.sdk.agents.models._openai_shared import set_use_responses_by_default
|
||||||
|
|
||||||
# Load environment variables
|
# Load environment variables
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
#set_tracing_disabled(True)
|
#set_tracing_disabled(True) #disable tracing or OPENAI_AGENTS_DISABLE_TRACING=1
|
||||||
|
|
||||||
# NOTE: This is needed when using LiteLLM Proxy Server
|
# NOTE: This is needed when using LiteLLM Proxy Server
|
||||||
#
|
#
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,10 @@ SSH_HOST
|
||||||
SSH_USER
|
SSH_USER
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
from cai.sdk.agents import Agent # pylint: disable=import-error
|
from openai import AsyncOpenAI
|
||||||
|
from cai.sdk.agents import Agent, OpenAIChatCompletionsModel # pylint: disable=import-error
|
||||||
from cai.util import load_prompt_template
|
from cai.util import load_prompt_template
|
||||||
|
from dotenv import load_dotenv
|
||||||
from cai.tools.command_and_control.sshpass import ( # pylint: disable=import-error # noqa: E501
|
from cai.tools.command_and_control.sshpass import ( # pylint: disable=import-error # noqa: E501
|
||||||
run_ssh_command_with_credentials
|
run_ssh_command_with_credentials
|
||||||
)
|
)
|
||||||
|
|
@ -14,7 +16,7 @@ from cai.tools.reconnaissance.generic_linux_command import ( # pylint: disable=
|
||||||
generic_linux_command
|
generic_linux_command
|
||||||
)
|
)
|
||||||
from cai.tools.web.search_web import ( # pylint: disable=import-error # noqa: E501
|
from cai.tools.web.search_web import ( # pylint: disable=import-error # noqa: E501
|
||||||
search_web
|
make_web_search_with_explanation
|
||||||
)
|
)
|
||||||
|
|
||||||
from cai.tools.reconnaissance.exec_code import ( # pylint: disable=import-error # noqa: E501
|
from cai.tools.reconnaissance.exec_code import ( # pylint: disable=import-error # noqa: E501
|
||||||
|
|
@ -23,23 +25,26 @@ from cai.tools.reconnaissance.exec_code import ( # pylint: disable=import-error
|
||||||
|
|
||||||
# Prompts
|
# Prompts
|
||||||
blueteam_agent_system_prompt = load_prompt_template("prompts/system_blue_team_agent.md")
|
blueteam_agent_system_prompt = load_prompt_template("prompts/system_blue_team_agent.md")
|
||||||
# Define functions list based on available API keys
|
# Define tools list based on available API keys
|
||||||
functions = [
|
tools = [
|
||||||
generic_linux_command,
|
generic_linux_command,
|
||||||
run_ssh_command_with_credentials,
|
run_ssh_command_with_credentials,
|
||||||
execute_code,
|
execute_code,
|
||||||
]
|
]
|
||||||
|
|
||||||
# Add search_web function if PERPLEXITY_API_KEY environment variable is set
|
load_dotenv()
|
||||||
|
# Add search_web tools if PERPLEXITY_API_KEY environment variable is set
|
||||||
if os.getenv('PERPLEXITY_API_KEY'):
|
if os.getenv('PERPLEXITY_API_KEY'):
|
||||||
functions.append(search_web)
|
tools.append(make_web_search_with_explanation)
|
||||||
|
|
||||||
blueteam_agent = Agent(
|
blueteam_agent = Agent(
|
||||||
name="Blue Team Agent",
|
name="Blue Team Agent",
|
||||||
instructions=blueteam_agent_system_prompt,
|
instructions=blueteam_agent_system_prompt,
|
||||||
description="""Agent that specializes in system defense and security monitoring.
|
description="""Agent that specializes in system defense and security monitoring.
|
||||||
Expert in cybersecurity protection and incident response.""",
|
Expert in cybersecurity protection and incident response.""",
|
||||||
model=os.getenv('CAI_MODEL', "qwen2.5:14b"),
|
model=OpenAIChatCompletionsModel(
|
||||||
functions=functions,
|
model=os.getenv('CAI_MODEL', "qwen2.5:14b"),
|
||||||
parallel_tool_calls=False,
|
openai_client=AsyncOpenAI(),
|
||||||
|
),
|
||||||
|
tools=tools,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -31,8 +31,9 @@ from cai.agents.meta.local_python_executor import (
|
||||||
fix_final_answer_code,
|
fix_final_answer_code,
|
||||||
truncate_content,
|
truncate_content,
|
||||||
)
|
)
|
||||||
from cai.sdk.agents import Agent, Result
|
from cai.sdk.agents import Agent, Result, OpenAIChatCompletionsModel
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
from openai import AsyncOpenAI
|
||||||
|
|
||||||
class CodeAgentException(Exception):
|
class CodeAgentException(Exception):
|
||||||
"""Base exception class for CodeAgent-related errors."""
|
"""Base exception class for CodeAgent-related errors."""
|
||||||
|
|
@ -185,7 +186,7 @@ class CodeAgent(Agent):
|
||||||
name: str = "CodeAgent",
|
name: str = "CodeAgent",
|
||||||
model: str = "qwen2.5:14b",
|
model: str = "qwen2.5:14b",
|
||||||
instructions: Union[str, Callable[[], str]] = None,
|
instructions: Union[str, Callable[[], str]] = None,
|
||||||
functions: List[Callable] = None,
|
tools: List[Callable] = None,
|
||||||
additional_authorized_imports: Optional[List[str]] = None,
|
additional_authorized_imports: Optional[List[str]] = None,
|
||||||
description: str = """Agent focused on writing and executing code.
|
description: str = """Agent focused on writing and executing code.
|
||||||
State-of-the-art in code production.""",
|
State-of-the-art in code production.""",
|
||||||
|
|
@ -201,7 +202,7 @@ class CodeAgent(Agent):
|
||||||
name: Name of the agent
|
name: Name of the agent
|
||||||
model: Model to use for the agent
|
model: Model to use for the agent
|
||||||
instructions: Instructions for the agent
|
instructions: Instructions for the agent
|
||||||
functions: List of functions available to the agent
|
tools: List of tools available to the agent
|
||||||
additional_authorized_imports: List of additional imports to allow
|
additional_authorized_imports: List of additional imports to allow
|
||||||
max_print_outputs_length: Maximum length of print outputs
|
max_print_outputs_length: Maximum length of print outputs
|
||||||
reasoning_effort: Level of reasoning effort (low, medium, high)
|
reasoning_effort: Level of reasoning effort (low, medium, high)
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,13 @@
|
||||||
Mail Agent module for checking email configuration security.
|
Mail Agent module for checking email configuration security.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
import os
|
||||||
|
from openai import AsyncOpenAI
|
||||||
import dns.resolver # pylint: disable=import-error
|
import dns.resolver # pylint: disable=import-error
|
||||||
from cai.sdk.agents import Agent
|
from cai.sdk.agents import Agent, OpenAIChatCompletionsModel
|
||||||
from cai.tools.misc.cli_utils import execute_cli_command
|
from cai.tools.misc.cli_utils import execute_cli_command
|
||||||
|
from cai.sdk.agents import function_tool
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def get_txt_record(domain, record_type='TXT'):
|
def get_txt_record(domain, record_type='TXT'):
|
||||||
|
|
@ -58,7 +62,7 @@ def check_dkim(domain: str, selector: str = "default"):
|
||||||
return txt_records[0]
|
return txt_records[0]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@function_tool
|
||||||
def check_mail_spoofing_vulnerability(
|
def check_mail_spoofing_vulnerability(
|
||||||
domain: str,
|
domain: str,
|
||||||
dkim_selector: str = "default") -> dict:
|
dkim_selector: str = "default") -> dict:
|
||||||
|
|
@ -100,7 +104,6 @@ def check_mail_spoofing_vulnerability(
|
||||||
|
|
||||||
|
|
||||||
dns_smtp_agent = Agent(
|
dns_smtp_agent = Agent(
|
||||||
model="gpt-4o",
|
|
||||||
name="DNS_SMTP_Agent",
|
name="DNS_SMTP_Agent",
|
||||||
description="Agent focused on assessing spoofing DMARC.",
|
description="Agent focused on assessing spoofing DMARC.",
|
||||||
instructions=(
|
instructions=(
|
||||||
|
|
@ -110,5 +113,9 @@ dns_smtp_agent = Agent(
|
||||||
"detailed reports. Use execute_cli_command for basic scans. "
|
"detailed reports. Use execute_cli_command for basic scans. "
|
||||||
"USE ONLY TOOL CALLS, DONT RETURN REASON."
|
"USE ONLY TOOL CALLS, DONT RETURN REASON."
|
||||||
),
|
),
|
||||||
tools=[check_mail_spoofing_vulnerability, execute_cli_command]
|
tools=[check_mail_spoofing_vulnerability, execute_cli_command],
|
||||||
|
model=OpenAIChatCompletionsModel(
|
||||||
|
model=os.getenv('CAI_MODEL', "qwen2.5:14b"),
|
||||||
|
openai_client=AsyncOpenAI(),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ Environment Variables enabling the episodic memory store
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from cai.sdk.agents import Agent
|
from cai.sdk.agents import Agent, OpenAIChatCompletionsModel
|
||||||
from cai.tools.misc.rag import add_to_memory_semantic, add_to_memory_episodic
|
from cai.tools.misc.rag import add_to_memory_semantic, add_to_memory_episodic
|
||||||
from cai.rag.vector_db import get_previous_memory
|
from cai.rag.vector_db import get_previous_memory
|
||||||
|
|
||||||
|
|
@ -145,32 +145,35 @@ QUERY_PROMPT = """INSTRUCTIONS:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
semantic_builder = Agent(
|
semantic_builder = Agent(
|
||||||
model=model,
|
|
||||||
name="Semantic_Builder",
|
name="Semantic_Builder",
|
||||||
instructions=ADD_MEMORY_PROMPT,
|
instructions=ADD_MEMORY_PROMPT,
|
||||||
description="""Agent that stores semantic memories from security assessments
|
description="""Agent that stores semantic memories from security assessments
|
||||||
and CTF exercises in semantic format.""",
|
and CTF exercises in semantic format.""",
|
||||||
tool_choice="required",
|
tool_choice="required",
|
||||||
temperature=0,
|
temperature=0,
|
||||||
functions=[add_to_memory_semantic],
|
tools=[add_to_memory_semantic],
|
||||||
parallel_tool_calls=True
|
model=OpenAIChatCompletionsModel(
|
||||||
|
model=model_name,
|
||||||
|
openai_client=AsyncOpenAI(),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
episodic_builder = Agent(
|
episodic_builder = Agent(
|
||||||
model=model,
|
|
||||||
name="Episodic_Builder",
|
name="Episodic_Builder",
|
||||||
instructions=ADD_MEMORY_PROMPT,
|
instructions=ADD_MEMORY_PROMPT,
|
||||||
description="""Agent that stores episodic memories from security assessments
|
description="""Agent that stores episodic memories from security assessments
|
||||||
and CTF exercises in episodic format.""",
|
and CTF exercises in episodic format.""",
|
||||||
tool_choice="required",
|
tool_choice="required",
|
||||||
temperature=0,
|
temperature=0,
|
||||||
functions=[add_to_memory_episodic],
|
tools=[add_to_memory_episodic],
|
||||||
parallel_tool_calls=True
|
model=OpenAIChatCompletionsModel(
|
||||||
|
model=model_name,
|
||||||
|
openai_client=AsyncOpenAI(),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
query_agent = Agent(
|
query_agent = Agent(
|
||||||
model=model,
|
|
||||||
name="Query_Agent",
|
name="Query_Agent",
|
||||||
description="""Agent that queries the memory system to retrieve relevant
|
description="""Agent that queries the memory system to retrieve relevant
|
||||||
historical information from previous security assessments
|
historical information from previous security assessments
|
||||||
|
|
@ -178,4 +181,8 @@ query_agent = Agent(
|
||||||
instructions=QUERY_PROMPT,
|
instructions=QUERY_PROMPT,
|
||||||
tool_choice="required",
|
tool_choice="required",
|
||||||
temperature=0,
|
temperature=0,
|
||||||
|
model=OpenAIChatCompletionsModel(
|
||||||
|
model=model_name,
|
||||||
|
openai_client=AsyncOpenAI(),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,9 @@ CLI utilities module for executing shell commands and processing their output.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from cai.tools.common import run_command # pylint: disable=E0401
|
from cai.tools.common import run_command # pylint: disable=E0401
|
||||||
|
from cai.sdk.agents import function_tool
|
||||||
|
|
||||||
|
@function_tool
|
||||||
def execute_cli_command(command: str) -> str:
|
def execute_cli_command(command: str) -> str:
|
||||||
"""
|
"""
|
||||||
Execute a CLI command and return the output.
|
Execute a CLI command and return the output.
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,12 @@ querying and adding data to vector databases.
|
||||||
import os
|
import os
|
||||||
import uuid
|
import uuid
|
||||||
from cai.rag.vector_db import QdrantConnector
|
from cai.rag.vector_db import QdrantConnector
|
||||||
|
from cai.sdk.agents import function_tool
|
||||||
|
|
||||||
# CTF BASED MEMORY
|
# CTF BASED MEMORY
|
||||||
collection_name = os.getenv('CAI_MEMORY_COLLECTION', "default")
|
collection_name = os.getenv('CAI_MEMORY_COLLECTION', "default")
|
||||||
|
|
||||||
|
@function_tool
|
||||||
def query_memory(query: str, top_k: int = 3, **kwargs) -> str: # pylint: disable=unused-argument,line-too-long # noqa: E501
|
def query_memory(query: str, top_k: int = 3, **kwargs) -> str: # pylint: disable=unused-argument,line-too-long # noqa: E501
|
||||||
"""
|
"""
|
||||||
Query memory to retrieve relevant context. From Previous CTFs executions.
|
Query memory to retrieve relevant context. From Previous CTFs executions.
|
||||||
|
|
@ -42,7 +42,7 @@ def query_memory(query: str, top_k: int = 3, **kwargs) -> str: # pylint: disabl
|
||||||
except Exception: # pylint: disable=broad-exception-caught
|
except Exception: # pylint: disable=broad-exception-caught
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
@function_tool
|
||||||
def add_to_memory_episodic(texts: str, step: int = 0, **kwargs) -> str: # pylint: disable=unused-argument,line-too-long # noqa: E501
|
def add_to_memory_episodic(texts: str, step: int = 0, **kwargs) -> str: # pylint: disable=unused-argument,line-too-long # noqa: E501
|
||||||
"""
|
"""
|
||||||
This is a persistent memory to add relevant context to our memory.
|
This is a persistent memory to add relevant context to our memory.
|
||||||
|
|
@ -76,7 +76,7 @@ def add_to_memory_episodic(texts: str, step: int = 0, **kwargs) -> str: # pylin
|
||||||
except Exception as e: # pylint: disable=broad-exception-caught
|
except Exception as e: # pylint: disable=broad-exception-caught
|
||||||
return f"Error adding documents to vector database: {str(e)}"
|
return f"Error adding documents to vector database: {str(e)}"
|
||||||
|
|
||||||
|
@function_tool
|
||||||
def add_to_memory_semantic(texts: str, step: int = 0, **kwargs) -> str: # pylint: disable=unused-argument,line-too-long # noqa: E501
|
def add_to_memory_semantic(texts: str, step: int = 0, **kwargs) -> str: # pylint: disable=unused-argument,line-too-long # noqa: E501
|
||||||
"""
|
"""
|
||||||
This is a persistent memory to add relevant context to our memory.
|
This is a persistent memory to add relevant context to our memory.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,101 @@
|
||||||
|
import os
|
||||||
|
import pytest
|
||||||
|
import pytest
|
||||||
|
from cai.sdk.agents import Runner
|
||||||
|
from cai.agents import get_agent_by_name
|
||||||
|
from cai.agents.bug_bounter import bug_bounter_agent
|
||||||
|
from cai.agents.flag_discriminator import flag_discriminator
|
||||||
|
from cai.agents.mail import dns_smtp_agent
|
||||||
|
from cai.agents.one_tool import one_tool_agent
|
||||||
|
from cai.agents.red_teamer import redteam_agent
|
||||||
|
from cai.agents.thought import thought_agent
|
||||||
|
|
||||||
|
@pytest.mark.allow_call_model_methods
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_blue_team_agent_inference():
|
||||||
|
"""
|
||||||
|
Non-streaming inference test for the blueteam_agent.
|
||||||
|
Verifies that Runner.run returns the final output containing the expected text.
|
||||||
|
"""
|
||||||
|
prompt = "monitor login attempts for suspicious activity"
|
||||||
|
result = await Runner.run(get_agent_by_name("blueteam_agent"), prompt)
|
||||||
|
final_output = result.final_output or ""
|
||||||
|
assert final_output, "Expected non-empty final output"
|
||||||
|
assert "login" in final_output.lower(), f"Expected 'login' in output, got: {final_output}"
|
||||||
|
|
||||||
|
@pytest.mark.allow_call_model_methods
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_bug_bounter_agent_inference():
|
||||||
|
"""
|
||||||
|
Non-streaming inference test for the bug_bounter_agent.
|
||||||
|
"""
|
||||||
|
prompt = "find vulnerabilities in web application sample.com"
|
||||||
|
result = await Runner.run(get_agent_by_name("bug_bounter_agent"), prompt)
|
||||||
|
final_output = result.final_output or ""
|
||||||
|
assert final_output, "Expected non-empty final output"
|
||||||
|
assert "sample.com" in final_output.lower(), f"Expected 'sample.com' in output, got: {final_output}"
|
||||||
|
|
||||||
|
@pytest.mark.allow_call_model_methods
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_flag_discriminator_inference():
|
||||||
|
"""
|
||||||
|
Non-streaming inference test for the flag_discriminator.
|
||||||
|
"""
|
||||||
|
prompt = "is 'flag{test_flag}' a valid CTF flag?"
|
||||||
|
result = await Runner.run(get_agent_by_name("flag_discriminator"), prompt)
|
||||||
|
final_output = result.final_output or ""
|
||||||
|
assert final_output, "Expected non-empty final output"
|
||||||
|
assert "valid" in final_output.lower(), f"Expected 'valid' in output, got: {final_output}"
|
||||||
|
|
||||||
|
@pytest.mark.allow_call_model_methods
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_dns_smtp_agent_inference():
|
||||||
|
"""
|
||||||
|
Non-streaming inference test for the dns_smtp_agent.
|
||||||
|
"""
|
||||||
|
prompt = "check DKIM record for example.com"
|
||||||
|
result = await Runner.run(get_agent_by_name("dns_smtp_agent"), prompt)
|
||||||
|
final_output = result.final_output or ""
|
||||||
|
assert final_output, "Expected non-empty final output"
|
||||||
|
assert "dkim" in final_output.lower(), f"Expected 'dkim' in output, got: {final_output}"
|
||||||
|
|
||||||
|
@pytest.mark.allow_call_model_methods
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_one_tool_agent_inference():
|
||||||
|
"""
|
||||||
|
Non-streaming inference test for the one_tool_agent.
|
||||||
|
Verifies that Runner.run returns the final output containing the expected text.
|
||||||
|
"""
|
||||||
|
prompt = "use nmap to scan 192.168.2.0/24"
|
||||||
|
# Run the agent in non-streaming mode
|
||||||
|
result = await Runner.run(get_agent_by_name("one_tool_agent"), prompt)
|
||||||
|
final_output = result.final_output or ""
|
||||||
|
assert final_output, "Expected non-empty final output"
|
||||||
|
assert "generic_linux_command" in final_output.lower(), f"Expected 'generic_linux_command' in output, got: {final_output}"
|
||||||
|
assert "nmap" in final_output.lower(), f"Expected 'nmap' in output, got: {final_output}"
|
||||||
|
|
||||||
|
@pytest.mark.allow_call_model_methods
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_red_team_agent_inference():
|
||||||
|
"""
|
||||||
|
Non-streaming inference test for the redteam_agent.
|
||||||
|
"""
|
||||||
|
prompt = "perform penetration test on example.com ssh service"
|
||||||
|
result = await Runner.run(get_agent_by_name("redteam_agent"), prompt)
|
||||||
|
final_output = result.final_output or ""
|
||||||
|
assert final_output, "Expected non-empty final output"
|
||||||
|
assert "ssh" in final_output.lower(), f"Expected 'ssh' in output, got: {final_output}"
|
||||||
|
|
||||||
|
@pytest.mark.allow_call_model_methods
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_thought_agent_inference():
|
||||||
|
"""
|
||||||
|
Non-streaming inference test for the thought_agent.
|
||||||
|
"""
|
||||||
|
prompt = "what are the next steps for network security assessment?"
|
||||||
|
result = await Runner.run(get_agent_by_name("thought_agent"), prompt)
|
||||||
|
final_output = result.final_output or ""
|
||||||
|
assert final_output, "Expected non-empty final output"
|
||||||
|
assert (
|
||||||
|
"plan" in final_output.lower() or "analysis" in final_output.lower()
|
||||||
|
), f"Expected 'plan' or 'analysis' in output, got: {final_output}"
|
||||||
Loading…
Reference in New Issue