pyagentic-core 1.0.0__tar.gz

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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Ryan Mikulec
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,112 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyagentic-core
3
+ Version: 1.0.0
4
+ Summary: Build LLM Agents in a Pythonic way
5
+ Author-email: Ryan Mikulec <rmikulec.dev@gmail.com>
6
+ License: MIT
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: License :: OSI Approved :: MIT License
9
+ Classifier: Operating System :: OS Independent
10
+ Requires-Python: >=3.13
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Requires-Dist: colorlog>=6.9.0
14
+ Requires-Dist: ipykernel>=6.29.5
15
+ Requires-Dist: openai>=1.93.2
16
+ Requires-Dist: typeguard>=4.4.4
17
+ Dynamic: license-file
18
+
19
+ # PyAgentic
20
+
21
+ [![Python Version](https://img.shields.io/badge/python-3.11%2B-blue.svg)](https://www.python.org/downloads/)
22
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
23
+ [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
24
+ [![Tests](https://github.com/rmikulec/pyagentic/workflows/Tests/badge.svg?branch=main)](https://github.com/rmikulec/pyAgentic/actions/workflows/testing.yml?query=branch%3Amain)
25
+
26
+ A declarative framework for building AI agents with OpenAI integration. PyAgentic provides a clean, type-safe way to create intelligent agents using Python's metaclass system and modern async patterns.
27
+
28
+ ## Features
29
+
30
+ - **Declarative Agent Definition** - Define agents using simple class-based syntax
31
+ - **Type Safety** - Full typing support with Pydantic integration
32
+ - **Tool Integration** - Easy function decoration for agent capabilities
33
+ - **Context Management** - Sophisticated context handling with lifecycle management
34
+ - **OpenAI Integration** - Native support for OpenAI's API with automatic schema generation
35
+ - **Async Support** - Built-in async/await support for scalable applications
36
+ - **Extensible** - Clean architecture for custom tools, context types, and validations
37
+
38
+ ## πŸš€ Quick Start
39
+
40
+ ### Installation
41
+
42
+ ```bash
43
+ pip install pyagentic-core
44
+ ```
45
+
46
+ ### Basic Example
47
+
48
+ ```python
49
+ from pyagentic import Agent, tool, ContextItem
50
+ from typing import List
51
+
52
+ class WeatherAgent(Agent):
53
+ """An agent that provides weather information."""
54
+
55
+ location: str = ContextItem(description="Current location")
56
+
57
+ @tool
58
+ def get_weather(self, city: str) -> str:
59
+ """Get current weather for a city."""
60
+ # Your weather API logic here
61
+ return f"The weather in {city} is sunny and 75Β°F"
62
+
63
+ @tool
64
+ def get_forecast(self, city: str, days: int = 5) -> List[str]:
65
+ """Get weather forecast for multiple days."""
66
+ return [f"Day {i+1}: Partly cloudy" for i in range(days)]
67
+
68
+ # Create and use the agent
69
+ agent = WeatherAgent(location="San Francisco")
70
+ response = await agent.run("What's the weather like in New York?")
71
+ print(response)
72
+ ```
73
+
74
+
75
+ ## Project Structure
76
+
77
+ ```
78
+ pyagentic/
79
+ β”œβ”€β”€ pyagentic/ # Core framework code
80
+ β”‚ β”œβ”€β”€ _base/ # Internal implementation
81
+ β”‚ └── __init__.py # Public API
82
+ β”œβ”€β”€ tests/ # Test suite
83
+ β”‚ β”œβ”€β”€ _base/ # Core tests
84
+ β”‚ β”œβ”€β”€ integration/ # Integration tests
85
+ β”‚ └── performance/ # Performance tests
86
+ β”œβ”€β”€ examples/ # Example agents
87
+ β”œβ”€β”€ templates/ # Agent templates
88
+ β”œβ”€β”€ docs/ # Documentation
89
+ β”œβ”€β”€ scripts/ # Utility scripts
90
+ └── notebooks/ # Jupyter notebooks
91
+ ```
92
+
93
+ ## Contributing
94
+
95
+ Contributions are welcome! Details coming soon.
96
+
97
+ ### Development Setup
98
+
99
+ ```bash
100
+ # Install dependencies
101
+ uv sync --group dev
102
+
103
+ # Formatting
104
+ uv run black -l99 pyagentic
105
+
106
+ # Linting
107
+ uv run flake8 --max-line-length 99 pyagentic
108
+ ```
109
+
110
+ ## πŸ“„ License
111
+
112
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
@@ -0,0 +1,94 @@
1
+ # PyAgentic
2
+
3
+ [![Python Version](https://img.shields.io/badge/python-3.11%2B-blue.svg)](https://www.python.org/downloads/)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+ [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
6
+ [![Tests](https://github.com/rmikulec/pyagentic/workflows/Tests/badge.svg?branch=main)](https://github.com/rmikulec/pyAgentic/actions/workflows/testing.yml?query=branch%3Amain)
7
+
8
+ A declarative framework for building AI agents with OpenAI integration. PyAgentic provides a clean, type-safe way to create intelligent agents using Python's metaclass system and modern async patterns.
9
+
10
+ ## Features
11
+
12
+ - **Declarative Agent Definition** - Define agents using simple class-based syntax
13
+ - **Type Safety** - Full typing support with Pydantic integration
14
+ - **Tool Integration** - Easy function decoration for agent capabilities
15
+ - **Context Management** - Sophisticated context handling with lifecycle management
16
+ - **OpenAI Integration** - Native support for OpenAI's API with automatic schema generation
17
+ - **Async Support** - Built-in async/await support for scalable applications
18
+ - **Extensible** - Clean architecture for custom tools, context types, and validations
19
+
20
+ ## πŸš€ Quick Start
21
+
22
+ ### Installation
23
+
24
+ ```bash
25
+ pip install pyagentic-core
26
+ ```
27
+
28
+ ### Basic Example
29
+
30
+ ```python
31
+ from pyagentic import Agent, tool, ContextItem
32
+ from typing import List
33
+
34
+ class WeatherAgent(Agent):
35
+ """An agent that provides weather information."""
36
+
37
+ location: str = ContextItem(description="Current location")
38
+
39
+ @tool
40
+ def get_weather(self, city: str) -> str:
41
+ """Get current weather for a city."""
42
+ # Your weather API logic here
43
+ return f"The weather in {city} is sunny and 75Β°F"
44
+
45
+ @tool
46
+ def get_forecast(self, city: str, days: int = 5) -> List[str]:
47
+ """Get weather forecast for multiple days."""
48
+ return [f"Day {i+1}: Partly cloudy" for i in range(days)]
49
+
50
+ # Create and use the agent
51
+ agent = WeatherAgent(location="San Francisco")
52
+ response = await agent.run("What's the weather like in New York?")
53
+ print(response)
54
+ ```
55
+
56
+
57
+ ## Project Structure
58
+
59
+ ```
60
+ pyagentic/
61
+ β”œβ”€β”€ pyagentic/ # Core framework code
62
+ β”‚ β”œβ”€β”€ _base/ # Internal implementation
63
+ β”‚ └── __init__.py # Public API
64
+ β”œβ”€β”€ tests/ # Test suite
65
+ β”‚ β”œβ”€β”€ _base/ # Core tests
66
+ β”‚ β”œβ”€β”€ integration/ # Integration tests
67
+ β”‚ └── performance/ # Performance tests
68
+ β”œβ”€β”€ examples/ # Example agents
69
+ β”œβ”€β”€ templates/ # Agent templates
70
+ β”œβ”€β”€ docs/ # Documentation
71
+ β”œβ”€β”€ scripts/ # Utility scripts
72
+ └── notebooks/ # Jupyter notebooks
73
+ ```
74
+
75
+ ## Contributing
76
+
77
+ Contributions are welcome! Details coming soon.
78
+
79
+ ### Development Setup
80
+
81
+ ```bash
82
+ # Install dependencies
83
+ uv sync --group dev
84
+
85
+ # Formatting
86
+ uv run black -l99 pyagentic
87
+
88
+ # Linting
89
+ uv run flake8 --max-line-length 99 pyagentic
90
+ ```
91
+
92
+ ## πŸ“„ License
93
+
94
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
@@ -0,0 +1,6 @@
1
+ from pyagentic._base._agent import Agent
2
+ from pyagentic._base._params import Param, ParamInfo
3
+ from pyagentic._base._tool import tool
4
+ from pyagentic._base._context import computed_context, ContextRef, ContextItem
5
+
6
+ __all__ = ["Agent", "Param", "ParamInfo", "tool", "computed_context", "ContextRef", "ContextItem"]
File without changes
@@ -0,0 +1,190 @@
1
+ import inspect
2
+ import json
3
+ import openai
4
+ from typing import Callable, Any, TypeVar, ClassVar
5
+
6
+ from pyagentic.logging import get_logger
7
+ from pyagentic._base._tool import _ToolDefinition
8
+ from pyagentic._base._context import ContextItem
9
+ from pyagentic._base._metaclasses import AgentMeta
10
+ from pyagentic.updates import AiUpdate, Status, EmitUpdate, ToolUpdate
11
+
12
+ logger = get_logger(__name__)
13
+
14
+
15
+ async def _safe_run(fn, *args, **kwargs):
16
+ """
17
+ Helper function to always run a function, async or not
18
+ """
19
+ if inspect.iscoroutinefunction(fn):
20
+ result = await fn(*args, **kwargs)
21
+ else:
22
+ result = fn(*args, **kwargs)
23
+ return result
24
+
25
+
26
+ class Agent(metaclass=AgentMeta):
27
+ __abstract_base__ = True
28
+ """
29
+ Base agent class to be extended in order to define a new Agent
30
+
31
+ Agent defintion requires the use of special function decorators in order to define the
32
+ behavior of the agent.
33
+
34
+ - @tool: Declares a method as a tool, allowing the agent to use it
35
+
36
+ Agents also have default arguements that can be declared on initiation
37
+
38
+ Args:
39
+ - model (str): The OpenAI model that will be used for inference. Defaults to value
40
+ found in `geo_assistant.config`
41
+ - emitter (Callable): A function that will be called to recieve intermittant information
42
+ about the agent's process. A common use case is that of a websocket, to be able
43
+ to recieve information about the process as it is happening
44
+ """
45
+ # Class Attributes
46
+ __tool_defs__: ClassVar[dict[str, _ToolDefinition]]
47
+ __context_attrs__: ClassVar[dict[str, tuple[TypeVar, ContextItem]]]
48
+ __system_message__: ClassVar[str]
49
+ __input_template__: ClassVar[str] = None
50
+
51
+ # Base Attributes
52
+ model: str
53
+ api_key: str
54
+ emitter: Callable[[Any], str] = None
55
+
56
+ def __post_init__(self):
57
+ self.client: openai.AsyncOpenAI = openai.AsyncOpenAI(api_key=self.api_key)
58
+
59
+ async def _process_tool_call(self, tool_call) -> bool:
60
+ if tool_call.type != "function_call":
61
+ return False
62
+ self.context._messages.append(tool_call)
63
+ logger.info(f"Calling {tool_call.name} with kwargs: {tool_call.arguments}")
64
+ # Lookup the bound method
65
+ try:
66
+ tool_def = self.__tool_defs__[tool_call.name]
67
+ handler = getattr(self, tool_call.name)
68
+ except KeyError:
69
+ return f"Tool {tool_call.name} not found"
70
+ kwargs = json.loads(tool_call.arguments)
71
+
72
+ # Run the tool, emitting updates
73
+ try:
74
+ if self.emitter:
75
+ await _safe_run(
76
+ self.emitter,
77
+ ToolUpdate(
78
+ status=Status.PROCESSING, tool_call=tool_call.name, tool_args=kwargs
79
+ ),
80
+ )
81
+
82
+ compiled_args = tool_def.compile_args(**kwargs)
83
+ result = await _safe_run(handler, **compiled_args)
84
+ except Exception as e:
85
+ logger.exception(e)
86
+ result = f"Tool `{tool_call.name}` failed: {e}. Please kindly state to the user that is failed, provide context, and ask if they want to try again." # noqa E501
87
+ if self.emitter:
88
+ await _safe_run(
89
+ self.emitter,
90
+ ToolUpdate(status=Status.ERROR, tool_call=tool_call.name, tool_args=kwargs),
91
+ )
92
+
93
+ # Record output for LLM
94
+ self.context._messages.append(
95
+ {"type": "function_call_output", "call_id": tool_call.call_id, "output": result}
96
+ )
97
+ return True
98
+
99
+ async def _build_tool_defs(self) -> list[dict]:
100
+ tool_defs = []
101
+ # iterate through registered tools
102
+ for tool_def in self.__tool_defs__.values():
103
+ # Check if any of the tool params use a ContextRef
104
+ # convert to openai schema
105
+ tool_defs.append(tool_def.to_openai(self.context))
106
+ return tool_defs
107
+
108
+ async def run(self, input_: str) -> str:
109
+ """
110
+ Run the agent with any given input
111
+
112
+ Parameters:
113
+ input_(str): The user input for the agent to process
114
+
115
+ Returns:
116
+ str: The output of the agent
117
+ """
118
+
119
+ # Generate and insert the new system message
120
+ self.context.add_user_message(input_)
121
+
122
+ # Create the tool list
123
+ tool_defs = await self._build_tool_defs()
124
+
125
+ # Begin the first pass on generating a response from openai
126
+ if self.emitter:
127
+ await _safe_run(
128
+ self.emitter,
129
+ EmitUpdate(
130
+ status=Status.GENERATING,
131
+ ),
132
+ )
133
+ try:
134
+ response = await self.client.responses.create(
135
+ model=self.model,
136
+ input=self.context.messages,
137
+ tools=tool_defs,
138
+ )
139
+ reasoning = [rx.to_dict() for rx in response.output if rx.type == "reasoning"]
140
+ tool_calls = [rx for rx in response.output if rx.type == "function_call"]
141
+ except Exception as e:
142
+ logger.exception(e)
143
+ # On failure, emit an udpate, update the messages, and return a standard message
144
+ if self.emitter:
145
+ await _safe_run(
146
+ self.emitter,
147
+ EmitUpdate(
148
+ status=Status.ERROR,
149
+ ),
150
+ )
151
+ self.context._messages.append(
152
+ {"role": "assistant", "content": "Failed to generate a response"}
153
+ )
154
+ return f"OpenAI failed to generate a response: {e}"
155
+
156
+ if reasoning:
157
+ self.context._messages.extend(reasoning)
158
+
159
+ # Dispatch any tool calls
160
+ made_calls = False
161
+ for tool_call in tool_calls:
162
+ made_calls = made_calls or (await self._process_tool_call(tool_call))
163
+
164
+ # If tools ran, re-invoke LLM for natural reply
165
+ if made_calls:
166
+ try:
167
+ response = await self.client.responses.create(
168
+ model=self.model,
169
+ input=self.context.messages,
170
+ )
171
+ except Exception as e:
172
+ logger.exception(e)
173
+ if self.emitter:
174
+ await _safe_run(
175
+ self.emitter, EmitUpdate(status=Status.ERROR, message="Generation failed")
176
+ )
177
+ self.context._messages.append(
178
+ {"role": "assistant", "content": "Failed to generate a response"}
179
+ )
180
+ return f"OpenAI failed to generate a response: {e}"
181
+
182
+ # Parse and finalize the Ai Response
183
+ ai_message = response.output_text
184
+
185
+ self.context._messages.append({"role": "assistant", "content": ai_message})
186
+
187
+ if self.emitter:
188
+ await _safe_run(self.emitter, AiUpdate(status=Status.SUCCEDED, message=ai_message))
189
+
190
+ return ai_message
@@ -0,0 +1,216 @@
1
+ import functools
2
+ from typing import Any, Callable, Type, Self
3
+ from dataclasses import dataclass, make_dataclass, field, asdict
4
+
5
+ from pyagentic._base._exceptions import InvalidContextRefNotFoundInContext
6
+
7
+
8
+ @dataclass
9
+ class ContextItem:
10
+ """
11
+ A `ContextItem` is used to signal that a class attribute can be used in the context
12
+ of an agent. Any of these values can be referenced in:
13
+ - the agent's `instructions`
14
+ - the agent's `input_template`
15
+ - any `ContextRef` used in the Agent (e.g., in a `ParamInfo`)
16
+ - the constructor of the Agent itself
17
+
18
+ Args:
19
+ default (Any, optional):
20
+ The default value for this context item if no explicit value is provided.
21
+ Defaults to `None`.
22
+ default_factory (Callable[[], Any], optional):
23
+ A zero-argument factory function that produces a default value.
24
+ If provided, its return value takes precedence over `default`.
25
+ Defaults to `None`.
26
+ """
27
+
28
+ default: Any = None
29
+ default_factory: Callable = None
30
+
31
+ def __post_init__(self):
32
+ if not (self.default or self.default_factory):
33
+ raise AttributeError("default or default_factory must be given")
34
+
35
+ def get_default_value(self):
36
+ if self.default_factory:
37
+ return self.default_factory()
38
+ else:
39
+ return self.default
40
+
41
+
42
+ class computed_context:
43
+ """
44
+ Descriptor used to mark a method in an Agent as a computed context.
45
+
46
+ Computed contexts work very similarly to Python's `@property` descriptor: they are
47
+ re-computed each time they're accessed. When a computed context appears in:
48
+
49
+ - the agent's `instructions`, its value will be refreshed on every call to the agent,
50
+ updating the system message with the latest value.
51
+ - the agent's `input_template`, its value will be refreshed each time a new user message is
52
+ added, updating the prompt accordingly.
53
+
54
+ Args:
55
+ func (Callable[[Agent], Any]):
56
+ The method on the Agent class that computes and returns the context value.
57
+ It will be called with the agent instance each time the context is accessed.
58
+ """
59
+
60
+ def __init__(self, fget):
61
+ functools.update_wrapper(self, fget)
62
+ self.fget = fget
63
+ self._is_context = True
64
+
65
+ def __set_name__(self, owner, name):
66
+ self.name = name
67
+
68
+ def __get__(self, instance, owner=None):
69
+ # when accessed on the class, return the descriptor itself
70
+ if instance is None:
71
+ return self
72
+ # when accessed on the instance, run the function against the *context* object
73
+ # (we’ll inject this descriptor onto the Context class)
74
+ return self.fget(instance)
75
+
76
+
77
+ @dataclass(repr=True)
78
+ class _AgentContext:
79
+ """
80
+ Base context class for agents; uses dataclass for auto-generated init/signature.
81
+ """
82
+
83
+ instructions: str
84
+ input_template: str = None
85
+ _messages: list = field(default_factory=list)
86
+
87
+ def as_dict(self) -> dict:
88
+ """
89
+ Exports the context as a dictionary. This dictionary is not serialized, so
90
+ any `ContextItem` or `computed_context` remains their original type.
91
+
92
+ Returns:
93
+ - dict: A dictionary containing all `ContextItem` and `computed_context`
94
+ for later processing.
95
+ """
96
+
97
+ data = asdict(self)
98
+
99
+ # tinject every computed_context value
100
+ for name, attr in type(self).__dict__.items():
101
+ if getattr(attr, "_is_context", False):
102
+ data[name] = getattr(self, name)
103
+ return data
104
+
105
+ @property
106
+ def system_message(self) -> str:
107
+ """
108
+ The current formatted system_message
109
+ """
110
+ # start with all the normal dataclass fields
111
+
112
+ # now format your instruction template
113
+ return self.instructions.format(**self.as_dict())
114
+
115
+ @property
116
+ def messages(self) -> list[dict[str, str]]:
117
+ """
118
+ List of openai-ready messages with the most up-to-date system message
119
+ """
120
+ messages = self._messages.copy()
121
+ messages.insert(0, {"role": "system", "content": self.system_message})
122
+ return messages
123
+
124
+ def add_user_message(self, message: str):
125
+ """
126
+ Add a user message to the message list. If a `input_template` is given then
127
+ the message will be formatted in it as well as any context used in the template.
128
+
129
+ To use the user message in the template, place the key `user_message`.
130
+
131
+ Args:
132
+ message(str): The user message to be added.
133
+ """
134
+
135
+ if self.input_template:
136
+ data = self.as_dict()
137
+ data["user_message"] = message
138
+ content = self.input_template.format(**data)
139
+ else:
140
+ content = message
141
+ self._messages.append({"role": "user", "content": content})
142
+
143
+ def get(self, name: str) -> Any:
144
+ """
145
+ Retrieves an item from the context.
146
+
147
+ Args:
148
+ name(str): The name of the item
149
+
150
+ Returns:
151
+ Any: The item. If it is a computed context item, then it is computed upon retrieval.
152
+ """
153
+ try:
154
+ return self.as_dict()[name]
155
+ except KeyError:
156
+ raise InvalidContextRefNotFoundInContext(name)
157
+
158
+ @classmethod
159
+ def make_ctx_class(cls, name: str, ctx_map: dict[str, tuple[Type[Any], Any]]) -> Type[Self]:
160
+ """
161
+ Dynamically create a dataclass subclass with typed context fields.
162
+
163
+ Args:
164
+ name: base name for the new class (e.g. 'MyAgent').
165
+ ctx_map: mapping of field name to (type, ContextItem).
166
+
167
+ Returns:
168
+ A new dataclass type 'NameContext'.
169
+ """
170
+ dc_fields = [] # for actual dataclass fields (ContextItem)
171
+ namespace: dict[str, Any] = {"__module__": cls.__module__}
172
+
173
+ for field_name, (type_, info) in ctx_map.items():
174
+ if isinstance(info, ContextItem):
175
+ # ---- your existing logic for setting defaults ----
176
+ if info.default_factory is not None:
177
+ dc_def = field(default_factory=info.default_factory)
178
+ else:
179
+ dc_def = field(default=info.default)
180
+ dc_fields.append((field_name, type_, dc_def))
181
+
182
+ elif isinstance(info, computed_context):
183
+ # stick the descriptor straight into the namespace
184
+ namespace[field_name] = info
185
+ # also record its type for annotation
186
+ namespace.setdefault("__annotations__", {})[field_name] = type_
187
+
188
+ else:
189
+ raise RuntimeError(f"Unexpected ctx_map entry for {field_name!r}: {info!r}")
190
+
191
+ # now build the dataclass
192
+ return make_dataclass(
193
+ cls_name=f"{name}Context",
194
+ fields=dc_fields,
195
+ bases=(cls,),
196
+ namespace=namespace,
197
+ )
198
+
199
+
200
+ class ContextRef:
201
+ """
202
+ A placeholder pointing at some attribute or method
203
+ on the agent’s context, to be resolved at schema-build time.
204
+ """
205
+
206
+ def __init__(self, path: str):
207
+ self.path = path # dot-notation into agent.context
208
+
209
+ def resolve(self, context: _AgentContext) -> Any:
210
+ val = context
211
+ for part in self.path.split("."):
212
+ val = getattr(val, part)
213
+ # if it’s wrapped in our Context helper, drill into .value
214
+ if hasattr(val, "value"):
215
+ val = val.value
216
+ return val() if callable(val) else val
@@ -0,0 +1,39 @@
1
+ class ToolDeclarationFailed(Exception):
2
+
3
+ def __init__(self, tool_name, message):
4
+ message = f"Tool declaration failed for {tool_name}" f"{message}"
5
+ super().__init__(message)
6
+
7
+
8
+ class SystemMessageNotDeclared(Exception):
9
+ def __init__(self):
10
+ super().__init__(
11
+ "System message not declared on agent. Agent must be declared with `__system_message__`" # noqa E501
12
+ )
13
+
14
+
15
+ class UnexpectedContextItemType(Exception):
16
+ def __init__(self, name, expected, recieved):
17
+ message = (
18
+ f"Unexpected value provided for `{name}`. "
19
+ f"Expected: {expected} - Recieved: {recieved}"
20
+ )
21
+ super().__init__(message)
22
+
23
+
24
+ class InvalidContextRefNotFoundInContext(Exception):
25
+ def __init__(self, name):
26
+ message = (
27
+ f"'{name}' not found in context. "
28
+ "Make sure it is either declared as a `ContextItem` or using `computed_context`"
29
+ )
30
+ super().__init__(message)
31
+
32
+
33
+ class InvalidContextRefMismatchTyping(Exception):
34
+ def __init__(self, ref_path, field_name, recieved_type, expected_type):
35
+ message = (
36
+ f"ContextRef('{ref_path}') for {self.__class__.__name__}.{field_name} "
37
+ f"is of type {recieved_type}, expected {expected_type}"
38
+ )
39
+ super().__init__(message)