openai-agents 0.0.1__py3-none-any.whl → 0.0.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of openai-agents might be problematic. Click here for more details.
- agents/__init__.py +223 -0
- agents/_config.py +23 -0
- agents/_debug.py +17 -0
- agents/_run_impl.py +792 -0
- agents/_utils.py +61 -0
- agents/agent.py +159 -0
- agents/agent_output.py +144 -0
- agents/computer.py +107 -0
- agents/exceptions.py +63 -0
- agents/extensions/handoff_filters.py +67 -0
- agents/extensions/handoff_prompt.py +19 -0
- agents/function_schema.py +340 -0
- agents/guardrail.py +320 -0
- agents/handoffs.py +236 -0
- agents/items.py +246 -0
- agents/lifecycle.py +105 -0
- agents/logger.py +3 -0
- agents/model_settings.py +36 -0
- agents/models/__init__.py +0 -0
- agents/models/_openai_shared.py +34 -0
- agents/models/fake_id.py +5 -0
- agents/models/interface.py +107 -0
- agents/models/openai_chatcompletions.py +952 -0
- agents/models/openai_provider.py +65 -0
- agents/models/openai_responses.py +384 -0
- agents/result.py +220 -0
- agents/run.py +904 -0
- agents/run_context.py +26 -0
- agents/stream_events.py +58 -0
- agents/strict_schema.py +167 -0
- agents/tool.py +288 -0
- agents/tracing/__init__.py +97 -0
- agents/tracing/create.py +306 -0
- agents/tracing/logger.py +3 -0
- agents/tracing/processor_interface.py +69 -0
- agents/tracing/processors.py +261 -0
- agents/tracing/scope.py +45 -0
- agents/tracing/setup.py +211 -0
- agents/tracing/span_data.py +188 -0
- agents/tracing/spans.py +264 -0
- agents/tracing/traces.py +195 -0
- agents/tracing/util.py +17 -0
- agents/usage.py +22 -0
- agents/version.py +7 -0
- openai_agents-0.0.3.dist-info/METADATA +204 -0
- openai_agents-0.0.3.dist-info/RECORD +49 -0
- openai_agents-0.0.3.dist-info/licenses/LICENSE +21 -0
- openai-agents/example.py +0 -2
- openai_agents-0.0.1.dist-info/METADATA +0 -17
- openai_agents-0.0.1.dist-info/RECORD +0 -6
- openai_agents-0.0.1.dist-info/licenses/LICENSE +0 -20
- {openai-agents → agents/extensions}/__init__.py +0 -0
- {openai_agents-0.0.1.dist-info → openai_agents-0.0.3.dist-info}/WHEEL +0 -0
agents/_utils.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from collections.abc import Awaitable
|
|
5
|
+
from typing import Any, Literal, Union
|
|
6
|
+
|
|
7
|
+
from pydantic import TypeAdapter, ValidationError
|
|
8
|
+
from typing_extensions import TypeVar
|
|
9
|
+
|
|
10
|
+
from .exceptions import ModelBehaviorError
|
|
11
|
+
from .logger import logger
|
|
12
|
+
from .tracing import Span, SpanError, get_current_span
|
|
13
|
+
|
|
14
|
+
T = TypeVar("T")
|
|
15
|
+
|
|
16
|
+
MaybeAwaitable = Union[Awaitable[T], T]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def transform_string_function_style(name: str) -> str:
|
|
20
|
+
# Replace spaces with underscores
|
|
21
|
+
name = name.replace(" ", "_")
|
|
22
|
+
|
|
23
|
+
# Replace non-alphanumeric characters with underscores
|
|
24
|
+
name = re.sub(r"[^a-zA-Z0-9]", "_", name)
|
|
25
|
+
|
|
26
|
+
return name.lower()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def validate_json(json_str: str, type_adapter: TypeAdapter[T], partial: bool) -> T:
|
|
30
|
+
partial_setting: bool | Literal["off", "on", "trailing-strings"] = (
|
|
31
|
+
"trailing-strings" if partial else False
|
|
32
|
+
)
|
|
33
|
+
try:
|
|
34
|
+
validated = type_adapter.validate_json(json_str, experimental_allow_partial=partial_setting)
|
|
35
|
+
return validated
|
|
36
|
+
except ValidationError as e:
|
|
37
|
+
attach_error_to_current_span(
|
|
38
|
+
SpanError(
|
|
39
|
+
message="Invalid JSON provided",
|
|
40
|
+
data={},
|
|
41
|
+
)
|
|
42
|
+
)
|
|
43
|
+
raise ModelBehaviorError(
|
|
44
|
+
f"Invalid JSON when parsing {json_str} for {type_adapter}; {e}"
|
|
45
|
+
) from e
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def attach_error_to_span(span: Span[Any], error: SpanError) -> None:
|
|
49
|
+
span.set_error(error)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def attach_error_to_current_span(error: SpanError) -> None:
|
|
53
|
+
span = get_current_span()
|
|
54
|
+
if span:
|
|
55
|
+
attach_error_to_span(span, error)
|
|
56
|
+
else:
|
|
57
|
+
logger.warning(f"No span to add error {error} to")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
async def noop_coroutine() -> None:
|
|
61
|
+
pass
|
agents/agent.py
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import dataclasses
|
|
4
|
+
import inspect
|
|
5
|
+
from collections.abc import Awaitable
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from typing import TYPE_CHECKING, Any, Callable, Generic, cast
|
|
8
|
+
|
|
9
|
+
from . import _utils
|
|
10
|
+
from ._utils import MaybeAwaitable
|
|
11
|
+
from .guardrail import InputGuardrail, OutputGuardrail
|
|
12
|
+
from .handoffs import Handoff
|
|
13
|
+
from .items import ItemHelpers
|
|
14
|
+
from .logger import logger
|
|
15
|
+
from .model_settings import ModelSettings
|
|
16
|
+
from .models.interface import Model
|
|
17
|
+
from .run_context import RunContextWrapper, TContext
|
|
18
|
+
from .tool import Tool, function_tool
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from .lifecycle import AgentHooks
|
|
22
|
+
from .result import RunResult
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class Agent(Generic[TContext]):
|
|
27
|
+
"""An agent is an AI model configured with instructions, tools, guardrails, handoffs and more.
|
|
28
|
+
|
|
29
|
+
We strongly recommend passing `instructions`, which is the "system prompt" for the agent. In
|
|
30
|
+
addition, you can pass `description`, which is a human-readable description of the agent, used
|
|
31
|
+
when the agent is used inside tools/handoffs.
|
|
32
|
+
|
|
33
|
+
Agents are generic on the context type. The context is a (mutable) object you create. It is
|
|
34
|
+
passed to tool functions, handoffs, guardrails, etc.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
name: str
|
|
38
|
+
"""The name of the agent."""
|
|
39
|
+
|
|
40
|
+
instructions: (
|
|
41
|
+
str
|
|
42
|
+
| Callable[
|
|
43
|
+
[RunContextWrapper[TContext], Agent[TContext]],
|
|
44
|
+
MaybeAwaitable[str],
|
|
45
|
+
]
|
|
46
|
+
| None
|
|
47
|
+
) = None
|
|
48
|
+
"""The instructions for the agent. Will be used as the "system prompt" when this agent is
|
|
49
|
+
invoked. Describes what the agent should do, and how it responds.
|
|
50
|
+
|
|
51
|
+
Can either be a string, or a function that dynamically generates instructions for the agent. If
|
|
52
|
+
you provide a function, it will be called with the context and the agent instance. It must
|
|
53
|
+
return a string.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
handoff_description: str | None = None
|
|
57
|
+
"""A description of the agent. This is used when the agent is used as a handoff, so that an
|
|
58
|
+
LLM knows what it does and when to invoke it.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
handoffs: list[Agent[Any] | Handoff[TContext]] = field(default_factory=list)
|
|
62
|
+
"""Handoffs are sub-agents that the agent can delegate to. You can provide a list of handoffs,
|
|
63
|
+
and the agent can choose to delegate to them if relevant. Allows for separation of concerns and
|
|
64
|
+
modularity.
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
model: str | Model | None = None
|
|
68
|
+
"""The model implementation to use when invoking the LLM.
|
|
69
|
+
|
|
70
|
+
By default, if not set, the agent will use the default model configured in
|
|
71
|
+
`model_settings.DEFAULT_MODEL`.
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
model_settings: ModelSettings = field(default_factory=ModelSettings)
|
|
75
|
+
"""Configures model-specific tuning parameters (e.g. temperature, top_p).
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
tools: list[Tool] = field(default_factory=list)
|
|
79
|
+
"""A list of tools that the agent can use."""
|
|
80
|
+
|
|
81
|
+
input_guardrails: list[InputGuardrail[TContext]] = field(default_factory=list)
|
|
82
|
+
"""A list of checks that run in parallel to the agent's execution, before generating a
|
|
83
|
+
response. Runs only if the agent is the first agent in the chain.
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
output_guardrails: list[OutputGuardrail[TContext]] = field(default_factory=list)
|
|
87
|
+
"""A list of checks that run on the final output of the agent, after generating a response.
|
|
88
|
+
Runs only if the agent produces a final output.
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
output_type: type[Any] | None = None
|
|
92
|
+
"""The type of the output object. If not provided, the output will be `str`."""
|
|
93
|
+
|
|
94
|
+
hooks: AgentHooks[TContext] | None = None
|
|
95
|
+
"""A class that receives callbacks on various lifecycle events for this agent.
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
def clone(self, **kwargs: Any) -> Agent[TContext]:
|
|
99
|
+
"""Make a copy of the agent, with the given arguments changed. For example, you could do:
|
|
100
|
+
```
|
|
101
|
+
new_agent = agent.clone(instructions="New instructions")
|
|
102
|
+
```
|
|
103
|
+
"""
|
|
104
|
+
return dataclasses.replace(self, **kwargs)
|
|
105
|
+
|
|
106
|
+
def as_tool(
|
|
107
|
+
self,
|
|
108
|
+
tool_name: str | None,
|
|
109
|
+
tool_description: str | None,
|
|
110
|
+
custom_output_extractor: Callable[[RunResult], Awaitable[str]] | None = None,
|
|
111
|
+
) -> Tool:
|
|
112
|
+
"""Transform this agent into a tool, callable by other agents.
|
|
113
|
+
|
|
114
|
+
This is different from handoffs in two ways:
|
|
115
|
+
1. In handoffs, the new agent receives the conversation history. In this tool, the new agent
|
|
116
|
+
receives generated input.
|
|
117
|
+
2. In handoffs, the new agent takes over the conversation. In this tool, the new agent is
|
|
118
|
+
called as a tool, and the conversation is continued by the original agent.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
tool_name: The name of the tool. If not provided, the agent's name will be used.
|
|
122
|
+
tool_description: The description of the tool, which should indicate what it does and
|
|
123
|
+
when to use it.
|
|
124
|
+
custom_output_extractor: A function that extracts the output from the agent. If not
|
|
125
|
+
provided, the last message from the agent will be used.
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
@function_tool(
|
|
129
|
+
name_override=tool_name or _utils.transform_string_function_style(self.name),
|
|
130
|
+
description_override=tool_description or "",
|
|
131
|
+
)
|
|
132
|
+
async def run_agent(context: RunContextWrapper, input: str) -> str:
|
|
133
|
+
from .run import Runner
|
|
134
|
+
|
|
135
|
+
output = await Runner.run(
|
|
136
|
+
starting_agent=self,
|
|
137
|
+
input=input,
|
|
138
|
+
context=context.context,
|
|
139
|
+
)
|
|
140
|
+
if custom_output_extractor:
|
|
141
|
+
return await custom_output_extractor(output)
|
|
142
|
+
|
|
143
|
+
return ItemHelpers.text_message_outputs(output.new_items)
|
|
144
|
+
|
|
145
|
+
return run_agent
|
|
146
|
+
|
|
147
|
+
async def get_system_prompt(self, run_context: RunContextWrapper[TContext]) -> str | None:
|
|
148
|
+
"""Get the system prompt for the agent."""
|
|
149
|
+
if isinstance(self.instructions, str):
|
|
150
|
+
return self.instructions
|
|
151
|
+
elif callable(self.instructions):
|
|
152
|
+
if inspect.iscoroutinefunction(self.instructions):
|
|
153
|
+
return await cast(Awaitable[str], self.instructions(run_context, self))
|
|
154
|
+
else:
|
|
155
|
+
return cast(str, self.instructions(run_context, self))
|
|
156
|
+
elif self.instructions is not None:
|
|
157
|
+
logger.error(f"Instructions must be a string or a function, got {self.instructions}")
|
|
158
|
+
|
|
159
|
+
return None
|
agents/agent_output.py
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel, TypeAdapter
|
|
5
|
+
from typing_extensions import TypedDict, get_args, get_origin
|
|
6
|
+
|
|
7
|
+
from . import _utils
|
|
8
|
+
from .exceptions import ModelBehaviorError, UserError
|
|
9
|
+
from .strict_schema import ensure_strict_json_schema
|
|
10
|
+
from .tracing import SpanError
|
|
11
|
+
|
|
12
|
+
_WRAPPER_DICT_KEY = "response"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(init=False)
|
|
16
|
+
class AgentOutputSchema:
|
|
17
|
+
"""An object that captures the JSON schema of the output, as well as validating/parsing JSON
|
|
18
|
+
produced by the LLM into the output type.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
output_type: type[Any]
|
|
22
|
+
"""The type of the output."""
|
|
23
|
+
|
|
24
|
+
_type_adapter: TypeAdapter[Any]
|
|
25
|
+
"""A type adapter that wraps the output type, so that we can validate JSON."""
|
|
26
|
+
|
|
27
|
+
_is_wrapped: bool
|
|
28
|
+
"""Whether the output type is wrapped in a dictionary. This is generally done if the base
|
|
29
|
+
output type cannot be represented as a JSON Schema object.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
_output_schema: dict[str, Any]
|
|
33
|
+
"""The JSON schema of the output."""
|
|
34
|
+
|
|
35
|
+
strict_json_schema: bool
|
|
36
|
+
"""Whether the JSON schema is in strict mode. We **strongly** recommend setting this to True,
|
|
37
|
+
as it increases the likelihood of correct JSON input.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def __init__(self, output_type: type[Any], strict_json_schema: bool = True):
|
|
41
|
+
"""
|
|
42
|
+
Args:
|
|
43
|
+
output_type: The type of the output.
|
|
44
|
+
strict_json_schema: Whether the JSON schema is in strict mode. We **strongly** recommend
|
|
45
|
+
setting this to True, as it increases the likelihood of correct JSON input.
|
|
46
|
+
"""
|
|
47
|
+
self.output_type = output_type
|
|
48
|
+
self.strict_json_schema = strict_json_schema
|
|
49
|
+
|
|
50
|
+
if output_type is None or output_type is str:
|
|
51
|
+
self._is_wrapped = False
|
|
52
|
+
self._type_adapter = TypeAdapter(output_type)
|
|
53
|
+
self._output_schema = self._type_adapter.json_schema()
|
|
54
|
+
return
|
|
55
|
+
|
|
56
|
+
# We should wrap for things that are not plain text, and for things that would definitely
|
|
57
|
+
# not be a JSON Schema object.
|
|
58
|
+
self._is_wrapped = not _is_subclass_of_base_model_or_dict(output_type)
|
|
59
|
+
|
|
60
|
+
if self._is_wrapped:
|
|
61
|
+
OutputType = TypedDict(
|
|
62
|
+
"OutputType",
|
|
63
|
+
{
|
|
64
|
+
_WRAPPER_DICT_KEY: output_type, # type: ignore
|
|
65
|
+
},
|
|
66
|
+
)
|
|
67
|
+
self._type_adapter = TypeAdapter(OutputType)
|
|
68
|
+
self._output_schema = self._type_adapter.json_schema()
|
|
69
|
+
else:
|
|
70
|
+
self._type_adapter = TypeAdapter(output_type)
|
|
71
|
+
self._output_schema = self._type_adapter.json_schema()
|
|
72
|
+
|
|
73
|
+
if self.strict_json_schema:
|
|
74
|
+
self._output_schema = ensure_strict_json_schema(self._output_schema)
|
|
75
|
+
|
|
76
|
+
def is_plain_text(self) -> bool:
|
|
77
|
+
"""Whether the output type is plain text (versus a JSON object)."""
|
|
78
|
+
return self.output_type is None or self.output_type is str
|
|
79
|
+
|
|
80
|
+
def json_schema(self) -> dict[str, Any]:
|
|
81
|
+
"""The JSON schema of the output type."""
|
|
82
|
+
if self.is_plain_text():
|
|
83
|
+
raise UserError("Output type is plain text, so no JSON schema is available")
|
|
84
|
+
return self._output_schema
|
|
85
|
+
|
|
86
|
+
def validate_json(self, json_str: str, partial: bool = False) -> Any:
|
|
87
|
+
"""Validate a JSON string against the output type. Returns the validated object, or raises
|
|
88
|
+
a `ModelBehaviorError` if the JSON is invalid.
|
|
89
|
+
"""
|
|
90
|
+
validated = _utils.validate_json(json_str, self._type_adapter, partial)
|
|
91
|
+
if self._is_wrapped:
|
|
92
|
+
if not isinstance(validated, dict):
|
|
93
|
+
_utils.attach_error_to_current_span(
|
|
94
|
+
SpanError(
|
|
95
|
+
message="Invalid JSON",
|
|
96
|
+
data={"details": f"Expected a dict, got {type(validated)}"},
|
|
97
|
+
)
|
|
98
|
+
)
|
|
99
|
+
raise ModelBehaviorError(
|
|
100
|
+
f"Expected a dict, got {type(validated)} for JSON: {json_str}"
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
if _WRAPPER_DICT_KEY not in validated:
|
|
104
|
+
_utils.attach_error_to_current_span(
|
|
105
|
+
SpanError(
|
|
106
|
+
message="Invalid JSON",
|
|
107
|
+
data={"details": f"Could not find key {_WRAPPER_DICT_KEY} in JSON"},
|
|
108
|
+
)
|
|
109
|
+
)
|
|
110
|
+
raise ModelBehaviorError(
|
|
111
|
+
f"Could not find key {_WRAPPER_DICT_KEY} in JSON: {json_str}"
|
|
112
|
+
)
|
|
113
|
+
return validated[_WRAPPER_DICT_KEY]
|
|
114
|
+
return validated
|
|
115
|
+
|
|
116
|
+
def output_type_name(self) -> str:
|
|
117
|
+
"""The name of the output type."""
|
|
118
|
+
return _type_to_str(self.output_type)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _is_subclass_of_base_model_or_dict(t: Any) -> bool:
|
|
122
|
+
if not isinstance(t, type):
|
|
123
|
+
return False
|
|
124
|
+
|
|
125
|
+
# If it's a generic alias, 'origin' will be the actual type, e.g. 'list'
|
|
126
|
+
origin = get_origin(t)
|
|
127
|
+
|
|
128
|
+
allowed_types = (BaseModel, dict)
|
|
129
|
+
# If it's a generic alias e.g. list[str], then we should check the origin type i.e. list
|
|
130
|
+
return issubclass(origin or t, allowed_types)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _type_to_str(t: type[Any]) -> str:
|
|
134
|
+
origin = get_origin(t)
|
|
135
|
+
args = get_args(t)
|
|
136
|
+
|
|
137
|
+
if origin is None:
|
|
138
|
+
# It's a simple type like `str`, `int`, etc.
|
|
139
|
+
return t.__name__
|
|
140
|
+
elif args:
|
|
141
|
+
args_str = ", ".join(_type_to_str(arg) for arg in args)
|
|
142
|
+
return f"{origin.__name__}[{args_str}]"
|
|
143
|
+
else:
|
|
144
|
+
return str(t)
|
agents/computer.py
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
from typing import Literal
|
|
3
|
+
|
|
4
|
+
Environment = Literal["mac", "windows", "ubuntu", "browser"]
|
|
5
|
+
Button = Literal["left", "right", "wheel", "back", "forward"]
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Computer(abc.ABC):
|
|
9
|
+
"""A computer implemented with sync operations. The Computer interface abstracts the
|
|
10
|
+
operations needed to control a computer or browser."""
|
|
11
|
+
|
|
12
|
+
@property
|
|
13
|
+
@abc.abstractmethod
|
|
14
|
+
def environment(self) -> Environment:
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
@abc.abstractmethod
|
|
19
|
+
def dimensions(self) -> tuple[int, int]:
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
@abc.abstractmethod
|
|
23
|
+
def screenshot(self) -> str:
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
@abc.abstractmethod
|
|
27
|
+
def click(self, x: int, y: int, button: Button) -> None:
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
@abc.abstractmethod
|
|
31
|
+
def double_click(self, x: int, y: int) -> None:
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
@abc.abstractmethod
|
|
35
|
+
def scroll(self, x: int, y: int, scroll_x: int, scroll_y: int) -> None:
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
@abc.abstractmethod
|
|
39
|
+
def type(self, text: str) -> None:
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
@abc.abstractmethod
|
|
43
|
+
def wait(self) -> None:
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
@abc.abstractmethod
|
|
47
|
+
def move(self, x: int, y: int) -> None:
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
@abc.abstractmethod
|
|
51
|
+
def keypress(self, keys: list[str]) -> None:
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
@abc.abstractmethod
|
|
55
|
+
def drag(self, path: list[tuple[int, int]]) -> None:
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class AsyncComputer(abc.ABC):
|
|
60
|
+
"""A computer implemented with async operations. The Computer interface abstracts the
|
|
61
|
+
operations needed to control a computer or browser."""
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
@abc.abstractmethod
|
|
65
|
+
def environment(self) -> Environment:
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
@abc.abstractmethod
|
|
70
|
+
def dimensions(self) -> tuple[int, int]:
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
@abc.abstractmethod
|
|
74
|
+
async def screenshot(self) -> str:
|
|
75
|
+
pass
|
|
76
|
+
|
|
77
|
+
@abc.abstractmethod
|
|
78
|
+
async def click(self, x: int, y: int, button: Button) -> None:
|
|
79
|
+
pass
|
|
80
|
+
|
|
81
|
+
@abc.abstractmethod
|
|
82
|
+
async def double_click(self, x: int, y: int) -> None:
|
|
83
|
+
pass
|
|
84
|
+
|
|
85
|
+
@abc.abstractmethod
|
|
86
|
+
async def scroll(self, x: int, y: int, scroll_x: int, scroll_y: int) -> None:
|
|
87
|
+
pass
|
|
88
|
+
|
|
89
|
+
@abc.abstractmethod
|
|
90
|
+
async def type(self, text: str) -> None:
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
@abc.abstractmethod
|
|
94
|
+
async def wait(self) -> None:
|
|
95
|
+
pass
|
|
96
|
+
|
|
97
|
+
@abc.abstractmethod
|
|
98
|
+
async def move(self, x: int, y: int) -> None:
|
|
99
|
+
pass
|
|
100
|
+
|
|
101
|
+
@abc.abstractmethod
|
|
102
|
+
async def keypress(self, keys: list[str]) -> None:
|
|
103
|
+
pass
|
|
104
|
+
|
|
105
|
+
@abc.abstractmethod
|
|
106
|
+
async def drag(self, path: list[tuple[int, int]]) -> None:
|
|
107
|
+
pass
|
agents/exceptions.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
if TYPE_CHECKING:
|
|
4
|
+
from .guardrail import InputGuardrailResult, OutputGuardrailResult
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class AgentsException(Exception):
|
|
8
|
+
"""Base class for all exceptions in the Agents SDK."""
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class MaxTurnsExceeded(AgentsException):
|
|
12
|
+
"""Exception raised when the maximum number of turns is exceeded."""
|
|
13
|
+
|
|
14
|
+
message: str
|
|
15
|
+
|
|
16
|
+
def __init__(self, message: str):
|
|
17
|
+
self.message = message
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ModelBehaviorError(AgentsException):
|
|
21
|
+
"""Exception raised when the model does something unexpected, e.g. calling a tool that doesn't
|
|
22
|
+
exist, or providing malformed JSON.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
message: str
|
|
26
|
+
|
|
27
|
+
def __init__(self, message: str):
|
|
28
|
+
self.message = message
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class UserError(AgentsException):
|
|
32
|
+
"""Exception raised when the user makes an error using the SDK."""
|
|
33
|
+
|
|
34
|
+
message: str
|
|
35
|
+
|
|
36
|
+
def __init__(self, message: str):
|
|
37
|
+
self.message = message
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class InputGuardrailTripwireTriggered(AgentsException):
|
|
41
|
+
"""Exception raised when a guardrail tripwire is triggered."""
|
|
42
|
+
|
|
43
|
+
guardrail_result: "InputGuardrailResult"
|
|
44
|
+
"""The result data of the guardrail that was triggered."""
|
|
45
|
+
|
|
46
|
+
def __init__(self, guardrail_result: "InputGuardrailResult"):
|
|
47
|
+
self.guardrail_result = guardrail_result
|
|
48
|
+
super().__init__(
|
|
49
|
+
f"Guardrail {guardrail_result.guardrail.__class__.__name__} triggered tripwire"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class OutputGuardrailTripwireTriggered(AgentsException):
|
|
54
|
+
"""Exception raised when a guardrail tripwire is triggered."""
|
|
55
|
+
|
|
56
|
+
guardrail_result: "OutputGuardrailResult"
|
|
57
|
+
"""The result data of the guardrail that was triggered."""
|
|
58
|
+
|
|
59
|
+
def __init__(self, guardrail_result: "OutputGuardrailResult"):
|
|
60
|
+
self.guardrail_result = guardrail_result
|
|
61
|
+
super().__init__(
|
|
62
|
+
f"Guardrail {guardrail_result.guardrail.__class__.__name__} triggered tripwire"
|
|
63
|
+
)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from ..handoffs import HandoffInputData
|
|
4
|
+
from ..items import (
|
|
5
|
+
HandoffCallItem,
|
|
6
|
+
HandoffOutputItem,
|
|
7
|
+
RunItem,
|
|
8
|
+
ToolCallItem,
|
|
9
|
+
ToolCallOutputItem,
|
|
10
|
+
TResponseInputItem,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
"""Contains common handoff input filters, for convenience. """
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def remove_all_tools(handoff_input_data: HandoffInputData) -> HandoffInputData:
|
|
17
|
+
"""Filters out all tool items: file search, web search and function calls+output."""
|
|
18
|
+
|
|
19
|
+
history = handoff_input_data.input_history
|
|
20
|
+
new_items = handoff_input_data.new_items
|
|
21
|
+
|
|
22
|
+
filtered_history = (
|
|
23
|
+
_remove_tool_types_from_input(history) if isinstance(history, tuple) else history
|
|
24
|
+
)
|
|
25
|
+
filtered_pre_handoff_items = _remove_tools_from_items(handoff_input_data.pre_handoff_items)
|
|
26
|
+
filtered_new_items = _remove_tools_from_items(new_items)
|
|
27
|
+
|
|
28
|
+
return HandoffInputData(
|
|
29
|
+
input_history=filtered_history,
|
|
30
|
+
pre_handoff_items=filtered_pre_handoff_items,
|
|
31
|
+
new_items=filtered_new_items,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _remove_tools_from_items(items: tuple[RunItem, ...]) -> tuple[RunItem, ...]:
|
|
36
|
+
filtered_items = []
|
|
37
|
+
for item in items:
|
|
38
|
+
if (
|
|
39
|
+
isinstance(item, HandoffCallItem)
|
|
40
|
+
or isinstance(item, HandoffOutputItem)
|
|
41
|
+
or isinstance(item, ToolCallItem)
|
|
42
|
+
or isinstance(item, ToolCallOutputItem)
|
|
43
|
+
):
|
|
44
|
+
continue
|
|
45
|
+
filtered_items.append(item)
|
|
46
|
+
return tuple(filtered_items)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _remove_tool_types_from_input(
|
|
50
|
+
items: tuple[TResponseInputItem, ...],
|
|
51
|
+
) -> tuple[TResponseInputItem, ...]:
|
|
52
|
+
tool_types = [
|
|
53
|
+
"function_call",
|
|
54
|
+
"function_call_output",
|
|
55
|
+
"computer_call",
|
|
56
|
+
"computer_call_output",
|
|
57
|
+
"file_search_call",
|
|
58
|
+
"web_search_call",
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
filtered_items: list[TResponseInputItem] = []
|
|
62
|
+
for item in items:
|
|
63
|
+
itype = item.get("type")
|
|
64
|
+
if itype in tool_types:
|
|
65
|
+
continue
|
|
66
|
+
filtered_items.append(item)
|
|
67
|
+
return tuple(filtered_items)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# A recommended prompt prefix for agents that use handoffs. We recommend including this or
|
|
2
|
+
# similar instructions in any agents that use handoffs.
|
|
3
|
+
RECOMMENDED_PROMPT_PREFIX = (
|
|
4
|
+
"# System context\n"
|
|
5
|
+
"You are part of a multi-agent system called the Agents SDK, designed to make agent "
|
|
6
|
+
"coordination and execution easy. Agents uses two primary abstraction: **Agents** and "
|
|
7
|
+
"**Handoffs**. An agent encompasses instructions and tools and can hand off a "
|
|
8
|
+
"conversation to another agent when appropriate. "
|
|
9
|
+
"Handoffs are achieved by calling a handoff function, generally named "
|
|
10
|
+
"`transfer_to_<agent_name>`. Transfers between agents are handled seamlessly in the background;"
|
|
11
|
+
" do not mention or draw attention to these transfers in your conversation with the user.\n"
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def prompt_with_handoff_instructions(prompt: str) -> str:
|
|
16
|
+
"""
|
|
17
|
+
Add recommended instructions to the prompt for agents that use handoffs.
|
|
18
|
+
"""
|
|
19
|
+
return f"{RECOMMENDED_PROMPT_PREFIX}\n\n{prompt}"
|