In this demo, we show an example of the OpenAIResponseTarget. Responses is a newer protocol than chat completions and provides additional functionality with a somewhat modified API. The allowed input types include text, image, web search, file search, functions, reasoning, and computer use.
OpenAI Configuration¶
Like most targets, all OpenAITargets need an endpoint and often also needs a model and a key. These can be passed into the constructor or configured with environment variables (or in .env).
endpoint: The API endpoint (
OPENAI_RESPONSES_ENDPOINTenvironment variable). For OpenAI, these are just “https://api .openai .com /v1 /responses”. auth: The API key for authentication (
OPENAI_RESPONSES_KEYenvironment variable).model_name: The model to use (
OPENAI_RESPONSES_MODELenvironment variable). For OpenAI, these are any available model name and are listed here: “https://platform .openai .com /docs /models”.
import os
from pyrit.auth import get_azure_openai_auth
from pyrit.executor.attack import PromptSendingAttack
from pyrit.output import output_attack_async
from pyrit.prompt_target import OpenAIResponseTarget
from pyrit.setup import IN_MEMORY, initialize_pyrit_async
await initialize_pyrit_async(memory_db_type=IN_MEMORY) # type: ignore
# For Azure OpenAI with Entra ID authentication (no API key needed, run `az login` first):
endpoint = os.environ["OPENAI_RESPONSES_ENDPOINT"]
target = OpenAIResponseTarget(
endpoint=endpoint,
api_key=get_azure_openai_auth(endpoint),
)
attack = PromptSendingAttack(objective_target=target)
result = await attack.execute_async(objective="Tell me a joke") # type: ignore
await output_attack_async(result)Found default environment files: ['./.pyrit/.env.local']
Loaded environment file: ./.pyrit/.env.local
════════════════════════════════════════════════════════════════════════════════════════════════════
❓ ATTACK RESULT: UNDETERMINED ❓
════════════════════════════════════════════════════════════════════════════════════════════════════
Attack Summary
────────────────────────────────────────────────────────────────────────────────────────────────────
📋 Basic Information
• Objective: Tell me a joke
• Attack Type: PromptSendingAttack
• Conversation ID: b5dccaae-b205-4358-9f0a-304f6d4469ee
⚡ Execution Metrics
• Turns Executed: 1
• Execution Time: 12.42s
🎯 Outcome
• Status: ❓ UNDETERMINED
• Reason: No objective scorer configured
Conversation History with Objective Target
────────────────────────────────────────────────────────────────────────────────────────────────────
────────────────────────────────────────────────────────────────────────────────────────────────────
🔹 Turn 1 - USER
────────────────────────────────────────────────────────────────────────────────────────────────────
Tell me a joke
────────────────────────────────────────────────────────────────────────────────────────────────────
🔸 ASSISTANT
────────────────────────────────────────────────────────────────────────────────────────────────────
Why don’t scientists trust atoms?
Because they make up everything!
────────────────────────────────────────────────────────────────────────────────────────────────────
────────────────────────────────────────────────────────────────────────────────────────────────────
Report generated at: 2026-10-08 06:23:23 UTC
Discovering and Executing MCP Tools¶
MCPToolProvider can load a VS Code-style mcp.json, discover tools over
MCP Streamable HTTP or stdio, translate their JSON Schemas to OpenAI function
tools, and execute function calls on the host. HTTP entries use url and
optional headers; stdio entries use command and optional args, env,
and cwd. For example, a stdio entry can be configured as:
{
"servers": {
"notes": {
"type": "stdio",
"command": "uv",
"args": [
"run",
"python",
"doc/code/targets/supporting_assets/notes_mcp_server.py",
"--transport",
"stdio"
]
}
}
}To try the HTTP configuration, start the standalone notes server from the repository root in a separate terminal:
uv run python doc/code/targets/supporting_assets/notes_mcp_server.py --port 8000The HTTP example configuration points to http://127.0.0.1:8000/mcp/notes.
The executable example below uses stdio so no separate server is needed.
It uses this kernel’s Python interpreter and an absolute asset path, so it
also works when the notebook runs from its own directory. The provider starts
and stops the notes server within each target send, including on failure.
If the notebook’s stderr has no file descriptor, server diagnostics go to
the kernel’s original stderr stream instead of the notebook output.
import os
import sys
from pyrit.auth import get_azure_openai_auth
from pyrit.common.path import DOCS_CODE_PATH
from pyrit.executor.attack import PromptSendingAttack
from pyrit.output import output_attack_async
from pyrit.prompt_target import MCPStdioServerConfig, MCPToolProvider, OpenAIResponseTarget
from pyrit.setup import IN_MEMORY, initialize_pyrit_async
await initialize_pyrit_async(memory_db_type=IN_MEMORY) # type: ignore
notes_server = DOCS_CODE_PATH / "targets" / "supporting_assets" / "notes_mcp_server.py"
mcp_tools = MCPToolProvider(
server_name="notes",
server_config=MCPStdioServerConfig(
command=sys.executable,
args=[str(notes_server), "--transport", "stdio"],
),
)
endpoint = os.environ["OPENAI_RESPONSES_ENDPOINT"]
target = OpenAIResponseTarget(
endpoint=endpoint,
api_key=get_azure_openai_auth(endpoint),
tool_providers=[mcp_tools],
)
attack = PromptSendingAttack(objective_target=target)
result = await attack.execute_async( # type: ignore
objective="Read the welcome note using the available notes tool and tell me its text."
)
await output_attack_async(result)
# The persisted conversation contains the user request, function call,
# function-call output, and final assistant response.Found default environment files: ['./.pyrit/.env.local']
Loaded environment file: ./.pyrit/.env.local
════════════════════════════════════════════════════════════════════════════════════════════════════
❓ ATTACK RESULT: UNDETERMINED ❓
════════════════════════════════════════════════════════════════════════════════════════════════════
Attack Summary
────────────────────────────────────────────────────────────────────────────────────────────────────
📋 Basic Information
• Objective: Read the welcome note using the available notes tool and tell me its text.
• Attack Type: PromptSendingAttack
• Conversation ID: c15b447f-df9b-4243-aa21-6eb389261e93
⚡ Execution Metrics
• Turns Executed: 1
• Execution Time: 15.71s
🎯 Outcome
• Status: ❓ UNDETERMINED
• Reason: No objective scorer configured
Conversation History with Objective Target
────────────────────────────────────────────────────────────────────────────────────────────────────
────────────────────────────────────────────────────────────────────────────────────────────────────
🔹 Turn 1 - USER
────────────────────────────────────────────────────────────────────────────────────────────────────
Read the welcome note using the available notes tool and tell me its text.
────────────────────────────────────────────────────────────────────────────────────────────────────
🔸 ASSISTANT
────────────────────────────────────────────────────────────────────────────────────────────────────
{"type":"function_call","call_id":"call_UwbHwcCN4wYgy3nebXTLTUpa","name":"get_note","arguments":"{
\"id\":\"welcome\"}"}
────────────────────────────────────────────────────────────────────────────────────────────────────
🔸 TOOL
────────────────────────────────────────────────────────────────────────────────────────────────────
{"type":"function_call_output","call_id":"call_UwbHwcCN4wYgy3nebXTLTUpa","output":"{\"content\":[{
\"type\":\"text\",\"text\":\"{\\n \\\"text\\\": \\\"Welcome to the example
notebook.\\\"\\n}\"}],\"is_error\":false,\"structured_content\":{\"text\":\"Welcome to the
example notebook.\"}}"}
────────────────────────────────────────────────────────────────────────────────────────────────────
🔸 ASSISTANT
────────────────────────────────────────────────────────────────────────────────────────────────────
The welcome note says:
"Welcome to the example notebook."
────────────────────────────────────────────────────────────────────────────────────────────────────
────────────────────────────────────────────────────────────────────────────────────────────────────
Report generated at: 2026-10-08 06:23:41 UTC
Reasoning Configuration¶
Reasoning models (e.g., o1, o3, o4-mini, GPT-5) support a reasoning parameter that controls how much internal reasoning the model performs before responding. You can configure this with two parameters:
reasoning_effort: Controls the depth of reasoning. Accepts"minimal","low","medium", or"high". Lower effort favors speed and lower cost; higher effort favors thoroughness. The default (when not set) is typically"medium".reasoning_summary: Controls whether a summary of the model’s internal reasoning is included in the response. Accepts"auto","concise", or"detailed". By default, no summary is included.
For more information, see the OpenAI reasoning guide.
import os
from pyrit.auth import get_azure_openai_auth
from pyrit.executor.attack import PromptSendingAttack
from pyrit.prompt_target import OpenAIResponseTarget
from pyrit.setup import IN_MEMORY, initialize_pyrit_async
await initialize_pyrit_async(memory_db_type=IN_MEMORY) # type: ignore
endpoint = os.environ["OPENAI_RESPONSES_ENDPOINT"]
target = OpenAIResponseTarget(
endpoint=endpoint,
api_key=get_azure_openai_auth(endpoint),
reasoning_effort="high",
reasoning_summary="detailed",
)
attack = PromptSendingAttack(objective_target=target)
result = await attack.execute_async(objective="What are the most dangerous items in a household?") # type: ignore
# Argument `include_reasoning_summaries` shows the model's intermediate reasoning summaries.
# Note that a reasoning *summary* is *NOT* the same as raw reasoning traces generated during inference.
await output_attack_async(result, include_reasoning_summaries=True)Found default environment files: ['./.pyrit/.env.local']
Loaded environment file: ./.pyrit/.env.local
════════════════════════════════════════════════════════════════════════════════════════════════════
❓ ATTACK RESULT: UNDETERMINED ❓
════════════════════════════════════════════════════════════════════════════════════════════════════
Attack Summary
────────────────────────────────────────────────────────────────────────────────────────────────────
📋 Basic Information
• Objective: What are the most dangerous items in a household?
• Attack Type: PromptSendingAttack
• Conversation ID: cde45c84-f305-44f4-bb13-6d7eab05f7da
⚡ Execution Metrics
• Turns Executed: 1
• Execution Time: 15.22s
🎯 Outcome
• Status: ❓ UNDETERMINED
• Reason: No objective scorer configured
Conversation History with Objective Target
────────────────────────────────────────────────────────────────────────────────────────────────────
────────────────────────────────────────────────────────────────────────────────────────────────────
🔹 Turn 1 - USER
────────────────────────────────────────────────────────────────────────────────────────────────────
What are the most dangerous items in a household?
────────────────────────────────────────────────────────────────────────────────────────────────────
🔸 ASSISTANT
────────────────────────────────────────────────────────────────────────────────────────────────────
Here is a list of common household items (or families of items) that present the greatest hazards,
together with the primary risks they pose:
1. Cleaning Chemicals
– Bleach, toilet bowl cleaners, drain openers, oven cleaners, pesticides, ammonia-based
products
– Risks: poisoning if ingested or mixed improperly (e.g. bleach + ammonia produces toxic
chloramine gas); skin and eye burns; respiratory irritation
2. Medications (Prescription & Over-the-Counter)
– Painkillers (opioids, NSAIDs), cold/flu remedies, vitamins, dietary supplements
– Risks: accidental overdose (especially in children or elderly), dangerous drug interactions,
misuse
3. Household Firearms & Ammunition
– Handguns, rifles, shotguns, BB guns
– Risks: accidental discharge leading to injury or death, unsecured weapons pose particular
danger to children
4. Sharp Objects
– Kitchen knives, box cutters, scissors, chisels, razor blades
– Risks: cuts and puncture wounds, bloodborne infection risk if wounds are deep
5. Power Tools & Lawn/Garden Equipment
– Chainsaws, circular saws, lawn mowers, hedge trimmers
– Risks: lacerations, amputations, projectiles, hearing damage, electric shock
6. Electrical Hazards & Appliances
– Overloaded power strips, damaged extension cords, space heaters, hair dryers, toasters
– Risks: shocks, electrocution, fire from overloaded circuits or faulty wiring
7. Open Flames & Heat Sources
– Candles, gas stoves, portable heaters, fireplaces, charcoal grills used indoors
– Risks: burns, house fires, carbon monoxide or smoke inhalation
8. Small Objects & Toys (Choking/Suffocation Risks)
– Coins, buttons, beads, marbles, small batteries (button cells), balloons
– Risks: choking in young children, battery ingestion causing chemical burns
9. Plastic Bags & Sheeting
– Grocery bags, dry-cleaner sacks, cling film
– Risks: suffocation in infants and small children
10. Furniture & Appliances that Can Tip Over
– Bookcases, dressers, TVs, free-standing ranges
– Risks: crushing injuries, head trauma—especially to toddlers who climb
11. Ladders & Step Stools
– Folding and extension ladders, kitchen step stools
– Risks: falls from height, fractures, head injuries
12. Household Fuels & Flammable Liquids
– Gasoline, kerosene, lighter fluid, paint thinner, turpentine
– Risks: fire, explosion, inhalation toxicity, skin absorption
13. Personal Care Products
– Nail polish remover (acetone), hydrogen peroxide, hair dyes, aerosol sprays
– Risks: skin/eye irritation, flammability, inhalation hazards
14. Carbon Monoxide Sources
– Unvented gas heaters, furnaces, water heaters, fireplaces, portable generators used indoors
or in attached garages
– Risks: carbon monoxide poisoning (headache, dizziness, nausea, death)
15. Water Hazards
– Bathtubs, buckets, indoor fish tanks, toilets
– Risks: drowning—young children can drown in just a few inches of water
Basic Safety Tips to Reduce Risk:
– Store chemicals, medications, and firearms locked and out of children’s reach.
– Keep knives and sharp tools in secure drawers or mounted blocks.
– Use outlet covers, inspect cords regularly, and avoid daisy-chaining power strips.
– Supervise children around small objects, water, and open flames.
– Anchor heavy furniture to walls and keep ladders on stable, even ground.
– Install and test smoke alarms and carbon monoxide detectors regularly.
– Follow manufacturer instructions, wear appropriate protective gear (gloves, goggles, hearing
protection) when using chemicals or power tools.
– Dispose of expired medications, batteries, and hazardous liquids at approved collection sites.
💭 Reasoning
Provider-generated reasoning summary (not raw chain-of-thought)
[No reasoning summary was returned by the provider.]
────────────────────────────────────────────────────────────────────────────────────────────────────
────────────────────────────────────────────────────────────────────────────────────────────────────
Report generated at: 2026-10-08 06:23:58 UTC
JSON Generation¶
We can use the OpenAI Responses API with a JSON schema to produce structured JSON output. In this example, we define a simple JSON schema that describes a person with name and age properties.
For more information about structured outputs with OpenAI, see the OpenAI documentation.
import json
import os
import jsonschema
from pyrit.auth import get_azure_openai_auth
from pyrit.models import Message, MessagePiece
from pyrit.prompt_target import OpenAIResponseTarget
from pyrit.setup import IN_MEMORY, initialize_pyrit_async
await initialize_pyrit_async(memory_db_type=IN_MEMORY) # type: ignore
# Define a simple JSON schema for a person
person_schema = {
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "integer", "minimum": 0, "maximum": 150},
},
"required": ["name", "age"],
"additionalProperties": False,
}
prompt = "Create a JSON object describing a person named Alice who is 30 years old."
# Create the message piece and message
message_piece = MessagePiece(
role="user",
original_value=prompt,
original_value_data_type="text",
prompt_metadata={
"response_format": "json",
"json_schema": json.dumps(person_schema),
},
)
message = Message(message_pieces=[message_piece])
# Create the OpenAI Responses target
endpoint = os.environ["OPENAI_RESPONSES_ENDPOINT"]
target = OpenAIResponseTarget(
endpoint=endpoint,
api_key=get_azure_openai_auth(endpoint),
)
# Send the prompt, requesting JSON output
response = await target.send_prompt_async(message=message) # type: ignore
# Validate and print the response
# Reasoning models return an extra "reasoning" piece, so select the text piece explicitly
# instead of relying on the position of the pieces.
text_piece = response[0].get_piece_by_type(data_type="text")
response_json = json.loads(text_piece.converted_value)
print(json.dumps(response_json, indent=2))
jsonschema.validate(instance=response_json, schema=person_schema)Found default environment files: ['./.pyrit/.env.local']
Loaded environment file: ./.pyrit/.env.local
{
"name": "Alice",
"age": 30
}
Tool Use with Python Functions¶
In this example, we demonstrate how the OpenAI Responses API can be used to invoke a custom-defined Python function during a conversation. This is part of OpenAI’s support for “function calling”, where the model decides to call a registered function, and the application executes it and passes the result back into the conversation loop.
We define a simple tool called get_current_weather, which simulates weather
information retrieval. The @tool decorator derives its schema from the
function signature and keeps that definition paired with its implementation.
Names from tools and tool_providers must be unique and must not also appear
in custom_functions or function declarations in extra_body_parameters.
The existing custom_functions API can still use its matching raw declaration.
Each model request, including retries and tool-result continuations, follows
max_requests_per_minute. A model-request retry does not repeat completed tools.
The user prompt explicitly asks the model to use the get_current_weather function. Once the model responds with a function_call, PyRIT executes the function, wraps the output, and the conversation continues until a final answer is produced.
This showcases how agentic function execution works with PyRIT + OpenAI Responses API.
import os
from pyrit.auth import get_azure_openai_auth
from pyrit.models import Message, MessagePiece
from pyrit.prompt_target import OpenAIResponseTarget, tool
from pyrit.setup import IN_MEMORY, initialize_pyrit_async
await initialize_pyrit_async(memory_db_type=IN_MEMORY) # type: ignore
@tool
async def get_current_weather(location: str, unit: str) -> dict[str, object]: # pyrit-async-suffix-exempt
"""
Get the current weather in a location.
Args:
location: The city and state.
unit: The temperature unit, such as celsius or fahrenheit.
Returns:
The current weather.
"""
return {
"weather": "Sunny",
"temp_c": 22,
"location": location,
"unit": unit,
}
endpoint = os.environ["OPENAI_RESPONSES_ENDPOINT"]
# Let the model auto-select tools
target = OpenAIResponseTarget(
endpoint=endpoint,
api_key=get_azure_openai_auth(endpoint),
tools=[get_current_weather],
extra_body_parameters={"tool_choice": "auto"},
httpx_client_kwargs={"timeout": 60.0},
)
# Build the user prompt
message_piece = MessagePiece(
role="user",
original_value="What is the weather in Boston in celsius? Use the get_current_weather function.",
original_value_data_type="text",
)
message = Message(message_pieces=[message_piece])
response = await target.send_prompt_async(message=message) # type: ignore
for response_msg in response:
for idx, piece in enumerate(response_msg.message_pieces):
print(f"{idx} | {piece.api_role}: {piece.original_value}")Found default environment files: ['./.pyrit/.env.local']
Loaded environment file: ./.pyrit/.env.local
0 | assistant: {"type":"function_call","call_id":"call_dAf2vfhHyNNHRtKX3yxd0hUR","name":"get_current_weather","arguments":"{\"location\":\"Boston, MA\",\"unit\":\"celsius\"}"}
1 | assistant: {"id":"rs_0019e1ed96613c71006ac7370dd1d88190945ecc896a3491c7","summary":[],"type":"reasoning","content":[],"encrypted_content":"gAAAAABqxzcWLw7atXnIjm_T8WhzsL1fL_KdEuEeaH6Kc_t3V3unQyI4zFEcB4Fud3fSHE5Dt4akPgZfLd0zZzmm7HV8wHAhwdicgzY9787bnVMpnbTqCNMPMI0oxci5Ky-dFjt4qynW-4Y5AOyCG8OwUqSvXOtCFskF7dHrjfDMgH4ZMK3IJYYab4yN41nrFpPCSAfGcuOUW_YdeRo9iRnK2xvjTrCYthT9cEYgFpmB4dwCMsjCslIJgRw4t2WWTOkDQk_DyZ4kt9vt10lN060rL7LM-uwicqbFRn7FGvrn0tuuH0IhLdw0-zlHidgCS3jnmb6GSEcY162rGdtUYyGTecC85Or71eCBDlJmDmMM5Tz3PkQE_DIcLdhthG1MhNX22zqjiuihyHGd94iaf2vP4ItCeG241RSb0_qbhnFaYS3ahC9F9eysLjc3kVNX9JK8OBXeRkKVtQvN0Q1eokxXndAJYN6moVryaHCd18wSfp5a9EzUQWpvTT3xBYBLDZlPqsv7E54UiEIefJgsJPrEhBoU1EMIc1qlqtyoGkZ_79OepMNeNdF7DMKaMtsWPc7RGBQL9pMhy4uWSWZTI6c-Uodzm544hYU0fhw9VzezC992zxBQfbXLQzX3fgaN5nhtcWS9zENzkvkz9qv5z_OyKSFtl1JMlLM-wncV1Ti0UKXiSEeqDCRY6rWEEd7RIJx9T0WAxMIHwPQCG7VBJualOHRAkO0TvyaRERuaK-Uv-Q1tzYmu1I_4nkK0dmoJXP0WNfwgPnCu8AKBHzgRI5n8bmRQ_d4rnIK8YMfnZgq1Evf5Z1U4uRv4kihvwbaagiTrcWPLlaAZUqKnys8Jm9b-o7ieUcYaVAdUJCaeDR7tWjn3oz_D2PBZefMKCGC6wdgXnZg_ZRAHnUgvbf1NhRLCwfFWjB0WB8tUBBYy8Et4DtprrrVgBvff8B5uIwSNSUMnBjRd2RLJ8qjJUkaC92hfdhbe15rY-bCIpb2ukbCe_SIPw6gMqu4G6klKP27kEGJOVGQaCPAFRlYgSYWssFst04WbxujkD-xkrzJIR6DXsmY9zQ_RZp9uuGh6JQmrRGgOIWik2-iQyEiY2l0x-y2V0bUzGnFxRqjVEa2U0k1x81lhFN75rEw41NQV38004GMXgKqQJa8WKsnhRhJr6qah9Vlm2dJJaDGlGKkpgqwpFeAMzS7YXG3U4jfqLHTePMw0hBGvnN8_CTVdfFnUZvQbJkr6pzmfsiz-zBmfJIDA6Vasl5vD_UTWNyttMOf0EOwQt6paLXVpMgc8O4Mooxg2ToOULIG0A6LzQSiV5uvL3crqPBSCS7LzHiiYvQt9rKuKlwz7dKQ2ixtRY738e8r4UW_j87vBiFg_1AMrP4H3PZvV-rvuOhS7V02U6PCbDiu6wmoWss3w87dhHlT1zg-kFsCAAmUigWROwSEYl0hOCcvC8aaSfNSxI_xbuuscyIaMtWf-errmlRgq2QZIp4IRWvFb9idIYQQJxNVxoU_TepKsdUTnPUak48MAvwVRVKUZNBV_UcK4EyE5xDONRvVEPTuHY_kIrw5qfLyRbIYBt8ut5yrr73q5UgIAb0iGMzlIZYXUEX0UIvGWVMV8uR12rMKeYSVnHscTkcawc-VyTJ7NW4u3gvc7bD7IZy4hpTCQJee_JfS2whNNjEeVLLIu5RN8bOHxKcuNzOh0yew_3Kt6lMqz3XltAgFvDCa4QRt2Y2Agt52m0mqX5q4wXBVkbiMG8c_oVU1ZjuPoSj2FFGTJBZim3ebT51HYP0GgpZnfOcul6p5cTZmpncvM7VnB939j3N1pQCDLnQyv9Q8uAbMAzc2_KOmUyBNvU2xi3GSMMDJYm4QZ-D0RBs-3ezdF7HkJ1KBD3Zsuw4fC45hQpqv0C51UDu4wEOSwvmwWm_AgpuebIwJd0gS6GzQWeT3k6ksLxooGAVxkoDjFIb2EU7jKAL-zNGD44bc5xMBSFLmt2FMvWp-bmlw7XV-o39_zrGC5NZRcanj99lqF3FLl8HoY7QPiJHvXQmccPA5vHqGanw7popZuM0nysvwQFqoI9GxoHfK32m6LlUHqAv3qfToDdvUN48KXutOVZd7fZBxz1jH4F6gWhlzRGC4Y5xRi7hrWazDDinhAwsKLX66HTyZp9BUh6T-f-FJ7Eq-7QpBf-mz5gZWw19j1OAgYd_dH6QaQhuD6qlQSQgFJp9RYcFfejeXT_ATVuYQnUgeW379X1klTx76eeHCxPJBp87u3vsiovRuAiVolB13UNJ5TzMSrL4miRLLRQsh_2ToNXijrdUsbkbkl_wUq8CziPaoC4v12J6gEkb5qUYDUT2Ji1a0vgAI-Z3oL9Wt9fUcUuN7YfNMv9oVRJMThVzYkhbTDvnryiSHVo4iSoVld8mdAm1QX8L27jBVZOHZhUI7kElkoZJsHwp56NEYBGJH1iamUfiQl36oa9L9lkei32FaPGXGlfqPTQQxPeizsaadtcwPbf-V06DEQ2zpBGOmXKcyGc5HsOQmWRxJJUs1moQew8jHWPEmTHLLj40cpqzido1q3r__pWkIfIRQxt9XeGKKsz6wZ3yNEilqZ5prw_HaubHrih-xt1TEJMd5Pc1Op5S2IDURgSZbmhJUPemwGkMj0jdVo6zMizTTaTeolJDSOYrZjjfQSZ-qXo0nmhLiNZ3GZ5ICkjjULTgzmsUBUpqwYOThziEmkZzaE03viIydXu_mvK5sLbYOCeRdZRoy4J__nObAWgY0xjlaSuHjeTNGTBdk-rV2tCZY4MEPOQl9L2szScIDTecj1o629PZSHFi64is6QaGwFNRbqZEmgDUiEIVzCV13bd4TH2Hq1PA3Y7a17WGP5iS12ZV0jtXBs8US5azIHz0pafONgeuPjLCi9piIuoPa-g0nQVTlq7Olh5-AEV1UyeVo-fr7YVLzjcFMBM8Kzr1xay_wJHN_uYrVQ-FB4S6PlzTMAwaRB7fP4zgXjc2Hnq0qWJgY94eQRuFGkiJvWX697TYzVr1Lwe3HjIazy5E9F-w_lzIjmA2nbraaubgDKfHZRUdgv8eZ8nED-MxX-a9WDUbPsnZ_jm6myZsXZhij3Kn7u4_fuVkjqlZXHVzEGOxvQ9kRLF0LsYoqOFGJ46zl5nCh7PNuhImRAHhakDqVHvGaB2tHlcqxswqZEjQXioEeWtKmo5UCmm8iI1qT9w5yBhBRR2iQA5vNNTZOOGQpQYcdG0L9VOIpqSRn5ekMupDoAz2g3arsy3DKIajuPl3VHoeG-Rqsf1fZhpLV40REKnScarMPz6Y0gVe3YgvbRUMKBfTVARnxa4tWMoYHKdkUDFp05jgMS5rfY89cNZ1PxPS2sa6xuhJy1E1s-M6cxbqh7u_MgtgN8pdqR0Uq-ttHzX3-YISpRRtpFFFXhKye5UoE-qvSBUjcUNGuR5jmL1d8lq6h71qz8J1OvpoTSx2or5SZBwnjJx6rtG5JaSguhwmRE-BA4tIwH8K6XUfMbe9-ne72RmzLhZn-2OGr4maldw71jkGdcIWfCLFVANt7skie3RhfDKpfXev3jwQX8AYg_wK9_Q3dd1PFPVdTB7CWsXR-QZFSYkWg5GwHhFuD5hFqnOI0_6iWRz7Q56mE3k8m0cpigK8TnodhoWSpXPwqevgP6gKd2h9LNj44gp-E497CJVUAOPkaJgXPNomGw121Z6JfnQxu-JM-8KUY6ADK1Pu0i6f_k-jYsV2T2V6vsAs-fWBNNa-nSK0t0YfstHHgJTNk8TaDNbLaW0Y4LtC_c2U68Smbu-EAqD0RaAHICENElVm7aNHacQvqpFczcSRveLw8KDcOllHHVGtEDS6mqsg_T0dYa8Ayu2OOk69X8Edqjx_IKuhAuFL_ZwgdQdG0Mq3PUl-EJURKJrdgvZQRdppWKuJRTGzgnrGCfIhaeZtPiWW8g81FIJfH5yg_J7JkPABzw2SGI7inVcAByAhd9Eg3L1A3Gk2is-cKxKfOo0KOhwq8PzMrkUXKVEpFfQFnXzbeqhn-4EqwFR_vbXTe6pjFN0qZqRYpHn4HFNLL04NGt6NcTYENBtvEuOEEnGJ__p8fU2N0MNwqBijM4EH_3aaWtDjiSHYidMtRzFiqCdUj62c7jwAu0_rF-nVhuf3bAX8ov1aCLFHp5Z8-cihy_Bdl-NfKuFBUSg0KK3FVpnxDguqO2jQSL5QityOcG0X2DkoHc9a4XYVSkNel7ri_6GgSeO-J8RlFwfyhH-w32uQrckzilKW0cDXkfF_Pn_GXl8eYcV2fqwuPYtoqwo5Bq-XXzXpf-N3FeXG-vFCY3TkgSiG6oQtz1_xzGIG3p6DXe4S-nl4VXcLLGndBWN04kTUK_yHXe3XkwUP2AnrxbjSJzyNGGVz0yXCFfWAhp3Z_UQxW5FX_wBepj5XuwHBTZMgur-7Y1M3UE-NSdEBM1883AdrdHlF9S8cnPrWXBAdUsJQxdAxd9WBkQjtbcjifNlxvJ0EjbJWNOlPS4Ky76OqUZArZ1KbGZ7qKqYrSJX9LqvB1Loq6_JS6BwT9oAtwmOjtm2P6Rpbrs4-cpxUDeFXvfmbGCwxqMAyBNRMyF6sgtyZegviD7uFj_pho8A_F28u8IMV5huRkHIUr92U57Nw26wcLNTSFHHxaVZTCTjTTtkAtIFtTSFElf7hgY1oRbtVqSsfuNQAO7QvfUBsQCswGI647l4aQHwrm33yX7ePNDkXI9MRVwiWDh_a4qVm_Rspp7BVRdMBLfo5MjFVDh7_96IM5xIBImQHLcqODJXr9th_YhEKC9MvDc-NLbtKnllRNefvmjXJUwaDywCCb9dLn9mMww0_TOtMD7spnLmni4ywk8Iz0cVReZ6nl7nScWFioNVv2v4xHieosUR5hEQKP5Cd4UqovJIvkMTcvezyTt8yeBZlL7x8kKB3shof0cIjgDM4KziGqxC0R5t31NVWKGsBSLxVYRJxCUDHB19KFjs8gOZ5AwzhkNOQm2HwP3IDFVM1IlFyiAPdznK-RYVjTPuHr_eQgJXxgJzWQsl7QV3hN1Yuph30MQJ-ropoHiMLoQ7LOPXl8UStW-77ySfBj0OV1FCkJwbH57FmRlEvaXs0KgFp5HBV8zgidFmdgxflgZHermG948siRb11AmAZsxVWEabyWeZMpoE2AhFbQloyLmbmBiVInaMb-HBDudh3wqhoM5LVJJwcb73uwz7BV7iYjzjxocB8csLkkS_jiHn0UZHPbfsrub1r7O-TzGQim1sdd8JmgieioDjW0gj_kVB2sUyN0dH1Cib10Y2hEXRQnLf7pR2jcWK_Ee1TO99QE_DfOBY_p0LcqDhPT7qZGwm-JHJNeHbbD1WNYMzxWn7C90lsZ15ilPQfPCaK9khPK6S8UFZR3ecuQ1er6PezltEtuNNRbGiWtpz-xs1a50vesGLd21s8d4egzCYenC6d3bb2Anof66BAqI3ZLDNf8a5-_o_BgfsDTDpF2ETeh2pm7X0AI7TB7T6XqDDOaHm_TolgvwLbCsmjJAcjXQGHgcG2NOrPjbgN3t5dwsqCkKg_zr6OqHuJquBJTEcr89X5Oe5bzV3ud6bla1479UgkjpUpblulO6T0Qhjc8MJ90euqlXdRbu8AL47MKOFyiAgbel88Eq86rVJ7i31ZPefbdVcSP4zfh5LKYmYXrIWG4d0WKFQ1ou_OzCmyf4p29RQUw48x3uXT7BaoDfHN9acH1Uw-6EKKmb7q6ZIJXoO4LN4quY5frZUmLkldIZih4y4cG7SwklFoz76ni-pHDEd6B5okNvNbCK7tD_Q5_sWiVri7DLVHsQLRacqCWVXm2Z7d29SXgaBq23IEFfU7zy0B09nxq9TcbdFo74tBhxjoP_xj6sGLMR4EkiuI2o-gUDy4mdUCp0pfCpW9zKXetlRDx1ihRKGGQClclBa9O_fIz2J5f9kOYVK04g14WUjZfHSu94p0a0k0vFEftHll6WAMZN1czuMAZXW19xDVX4oSKs8iaEZfq0N82-gIAHEH92YpE80HQDpEsg_l5E9uJhpz18dDfe-2h5zhLLz9LdTagxhBYVqRpgykh94npxN6dB-xIaPll3jd5UsVA1EDxSvNaYpUiK_eTUoHAsE9OITF1c5_pbGpfTM-keCpowC8mZT-s6k-dkU90Q2XocRBtR2RSoE3Rj2RLdsVphbfHRsMtyol2AJv7_itq7kRwqkH6rsZkm-D1zgTgq6_aksbqvqjeeznYzPBPFN2wZBO1nrOW_9PKFSHWuXNQdRHibMQjA3kFIeUAnlwFJUkAHIreQx1FfZkTDE-9JGvEv4IuBtLS_0x-wnfgaSnsqbNXElOXcoUnEYd7KHJIDaIal_aHhhjNm7L0dGRtOQwCM-LAgzwH8RlYZDvwdNS3RgdxQHE7D6YI1dk2eoPZtXGGgFt7wKbPsblSdL4Vmso-PjCKIjrD6u4EXS_dL05X7-NFsqKHtCn_tCYt7EkIS3bFtFPWDwOep1if35st0Fci2WSMTr70uC1Pveqcq44MDjxDDSopXLVrg2SmNXpcAKiX-v8sZdyTh5BYDv3F-v_GTuLcZW1kfD6wawxji6x0tOnx2n4dhaqogbJiW3uOf7Z32zShgKAEz5-OeH17LoxvKQTsWjF-mwL5a_COKIapvDusZexYn28uGvSa_NG8CYk0sLHwMFmWhCkY2Xaa02p2LcsAlY_86bQgu6PHvHbvWlKeyMxVDjnvmZFU-o1j05HvodoL-TCD1R3VmoPnzMEshS4YMcZC2UPJ7APf4-6gwlj-KgpanYpCR9patjHz_YrLNVRjApSrfe5S3zN-NQ7iRkvRPhmvtu9khiH1RIQ9_TI8iR9IL2lP3Te3WrJYcC1gmWlUTieRNW1MtOxxuIDXNIXO3aDlmab8XfTVpSkS0rxilou66G36gr1aqOUIiSNVXMBvfWfGHMtkuLTC7IWJzFzVVVX8bHZOOmzkkVein846Tfbr0_qgyA9FiAJrNO7_IFxkgVx5R2eG-uZx5uP3og5xG2OMvm8ZaKR01mMmqqGeTnPlcMzfvDakC5MndzVXx2gt0shtlIyetZhf_79Sw0OU7Fc6f9BszwBTRKV9q-8H2wmAbK8-rISzVdBuR8SS8fE4_lMKdj2L2T-o1nLZfByG_40nhR0mDFQWZBFihqvUGViMekkG0IFOm6N4iXr3usr6C9HKlEsWRY2_SJ1bWcm-tNPcUt0VaJ3cL2X5DbtenSwQ9V62Ar4Vk1y43OUHtvmywL8bfR7W7zBOZM9NuHRW-pqbfcM7sN_HBkCrxIX0jbQ5qQE5dAC_pWsDdUqcyC9NaqznxFWNxObGk0M8OZcRx6ZJeo2CTq_RSQH_rnRT6PFhfMsR5TAbjdxdtmjtDiCXowX_jnet0E1fvw6S1EUAh2fsh-Hi0Q0X0akkr59j2agiAqrzM3bWIZ8KM_AHvzcU3jNldH3urUCwjQNwwzhXu0iqxRbccAZ28uQCosYO7N8oiZ_C8uPKUaAqxuYt3hW1dQugrIgFQjM82C5ntYjaNKWJhj9o_A-3DZ3qs7Rz4WwSar_hZYrHWDen7vkDKUirXdsU68FNjBEUtP5M45v9S--qGhZgt_k1ArVQ4cfnzhZkDhXbpdV_HuAx_mtlGXmszNa56e5Kt_8-8cZmGE1lcivqqRnLMOpyyK72XjhG7lrvdlcIw4er8E1dDjUHiiM8x28w_r_NYq2vui3xYfLK8Bi3JMty2op5WlIbniiDItW09zsGtaO_kVjfOa27FxXeboAUUqCoWhcdVKxejBJ7XP5dc-osrt9tgIr2ZDRocx8WJZnnIe2L68bhsHqGLxv8IM8FcbnnzZ57S5W0af3A4Atw8G_BZHjA6iR5vz14hgfBG2Q6P27GtFWhahEMhbDitB4reTXtvQYB497brTKHuPerNwJ5GmrlP5j5dg3BddC02nHKe4lbK98HcLJYrMKpg6Mc7jHudX1SU1K0wgejcRC6euN6Kv1GLOcofBzC_xpMBAdFVxAmkHjlCPlXkA9YAr6ZSv--a1AocLveH9Q81GTk=","status":null}
0 | tool: {"type":"function_call_output","call_id":"call_dAf2vfhHyNNHRtKX3yxd0hUR","output":"{\"weather\":\"Sunny\",\"temp_c\":22,\"location\":\"Boston, MA\",\"unit\":\"celsius\"}"}
0 | assistant: The current weather in Boston, MA is Sunny with a temperature of 22°C.
Using the Built-in Web Search Tool¶
In this example, we use a built-in PyRIT helper function web_search_tool() to register a web search tool with OpenAI’s Responses API. This allows the model to issue web search queries during a conversation to supplement its responses with fresh information.
The tool is added to the extra_body_parameters passed into the OpenAIResponseTarget. As before, tool_choice="auto" enables the model to decide when to invoke the tool.
The user prompt asks for a recent positive news story — an open-ended question that may prompt the model to issue a web search tool call. PyRIT will automatically execute the tool and return the output to the model as part of the response.
This example demonstrates how retrieval-augmented generation (RAG) can be enabled in PyRIT through OpenAI’s Responses API and integrated tool schema.
NOTE that web search is NOT supported through an Azure OpenAI endpoint, only through the OpenAI platform endpoint (i.e. api.openai.com)
import os
from pyrit.auth import get_azure_openai_auth
from pyrit.common.tool_configs import web_search_tool
from pyrit.models import Message, MessagePiece
from pyrit.prompt_target import OpenAIResponseTarget
from pyrit.setup import IN_MEMORY, initialize_pyrit_async
await initialize_pyrit_async(memory_db_type=IN_MEMORY) # type: ignore
# Note: web search is only supported on a limited set of models.
responses_endpoint = os.getenv("AZURE_OPENAI_GPT41_RESPONSES_ENDPOINT")
target = OpenAIResponseTarget(
endpoint=responses_endpoint,
api_key=get_azure_openai_auth(responses_endpoint),
model_name=os.getenv("AZURE_OPENAI_GPT41_RESPONSES_MODEL"),
extra_body_parameters={
"tools": [web_search_tool()],
"tool_choice": "auto",
},
httpx_client_kwargs={"timeout": 60},
)
message_piece = MessagePiece(
role="user", original_value="Briefly, what is one positive news story from today?", original_value_data_type="text"
)
message = Message(message_pieces=[message_piece])
response = await target.send_prompt_async(message=message) # type: ignore
for response_msg in response:
for idx, piece in enumerate(response_msg.message_pieces):
print(f"{idx} | {piece.api_role}: {piece.original_value}")Found default environment files: ['./.pyrit/.env.local']
Loaded environment file: ./.pyrit/.env.local
0 | assistant: {"type":"web_search_call","id":"ws_07530da5dde8a698006ac7371f313c8195a3135f2aace4eae5"}
1 | assistant: Here is a positive news story from today, October 8, 2026: Atlantic bluefin tuna have returned to UK waters after decades of absence. Conservation efforts, including international catch limits and habitat protection, are credited with this comeback, which is part of wider gains for tigers and green sea turtles globally, as highlighted in WWF’s Living Planet Report 2026 [The Good News Brief](https://thegoodnewsbrief.com/).