cortex-agent-sdk 0.0.3__tar.gz → 0.0.4__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.
- {cortex_agent_sdk-0.0.3 → cortex_agent_sdk-0.0.4}/PKG-INFO +43 -2
- {cortex_agent_sdk-0.0.3 → cortex_agent_sdk-0.0.4}/README.md +42 -1
- {cortex_agent_sdk-0.0.3 → cortex_agent_sdk-0.0.4}/pyproject.toml +1 -1
- {cortex_agent_sdk-0.0.3 → cortex_agent_sdk-0.0.4}/src/cortex_agent_sdk/tools/contracts.py +136 -23
- cortex_agent_sdk-0.0.4/src/cortex_agent_sdk/tools/decorators.py +65 -0
- {cortex_agent_sdk-0.0.3 → cortex_agent_sdk-0.0.4}/src/cortex_agent_sdk/tools/execution.py +6 -1
- {cortex_agent_sdk-0.0.3 → cortex_agent_sdk-0.0.4}/src/cortex_agent_sdk/tools/models.py +2 -1
- cortex_agent_sdk-0.0.3/src/cortex_agent_sdk/tools/decorators.py +0 -15
- {cortex_agent_sdk-0.0.3 → cortex_agent_sdk-0.0.4}/.gitignore +0 -0
- {cortex_agent_sdk-0.0.3 → cortex_agent_sdk-0.0.4}/LICENSE +0 -0
- {cortex_agent_sdk-0.0.3 → cortex_agent_sdk-0.0.4}/src/cortex_agent_sdk/__init__.py +0 -0
- {cortex_agent_sdk-0.0.3 → cortex_agent_sdk-0.0.4}/src/cortex_agent_sdk/agent.py +0 -0
- {cortex_agent_sdk-0.0.3 → cortex_agent_sdk-0.0.4}/src/cortex_agent_sdk/engine.py +0 -0
- {cortex_agent_sdk-0.0.3 → cortex_agent_sdk-0.0.4}/src/cortex_agent_sdk/errores/__init__.py +0 -0
- {cortex_agent_sdk-0.0.3 → cortex_agent_sdk-0.0.4}/src/cortex_agent_sdk/errores/catalogo.py +0 -0
- {cortex_agent_sdk-0.0.3 → cortex_agent_sdk-0.0.4}/src/cortex_agent_sdk/errores/excepcion.py +0 -0
- {cortex_agent_sdk-0.0.3 → cortex_agent_sdk-0.0.4}/src/cortex_agent_sdk/gateway.py +0 -0
- {cortex_agent_sdk-0.0.3 → cortex_agent_sdk-0.0.4}/src/cortex_agent_sdk/google/__init__.py +0 -0
- {cortex_agent_sdk-0.0.3 → cortex_agent_sdk-0.0.4}/src/cortex_agent_sdk/history/__init__.py +0 -0
- {cortex_agent_sdk-0.0.3 → cortex_agent_sdk-0.0.4}/src/cortex_agent_sdk/history/models.py +0 -0
- {cortex_agent_sdk-0.0.3 → cortex_agent_sdk-0.0.4}/src/cortex_agent_sdk/history/pipeline.py +0 -0
- {cortex_agent_sdk-0.0.3 → cortex_agent_sdk-0.0.4}/src/cortex_agent_sdk/history/transform.py +0 -0
- {cortex_agent_sdk-0.0.3 → cortex_agent_sdk-0.0.4}/src/cortex_agent_sdk/hooks.py +0 -0
- {cortex_agent_sdk-0.0.3 → cortex_agent_sdk-0.0.4}/src/cortex_agent_sdk/immutable.py +0 -0
- {cortex_agent_sdk-0.0.3 → cortex_agent_sdk-0.0.4}/src/cortex_agent_sdk/lifecycle.py +0 -0
- {cortex_agent_sdk-0.0.3 → cortex_agent_sdk-0.0.4}/src/cortex_agent_sdk/openai/__init__.py +0 -0
- {cortex_agent_sdk-0.0.3 → cortex_agent_sdk-0.0.4}/src/cortex_agent_sdk/openai/engine.py +0 -0
- {cortex_agent_sdk-0.0.3 → cortex_agent_sdk-0.0.4}/src/cortex_agent_sdk/openai/models.py +0 -0
- {cortex_agent_sdk-0.0.3 → cortex_agent_sdk-0.0.4}/src/cortex_agent_sdk/openai/options.py +0 -0
- {cortex_agent_sdk-0.0.3 → cortex_agent_sdk-0.0.4}/src/cortex_agent_sdk/postgres/__init__.py +0 -0
- {cortex_agent_sdk-0.0.3 → cortex_agent_sdk-0.0.4}/src/cortex_agent_sdk/postgres/store.py +0 -0
- {cortex_agent_sdk-0.0.3 → cortex_agent_sdk-0.0.4}/src/cortex_agent_sdk/py.typed +0 -0
- {cortex_agent_sdk-0.0.3 → cortex_agent_sdk-0.0.4}/src/cortex_agent_sdk/redis/__init__.py +0 -0
- {cortex_agent_sdk-0.0.3 → cortex_agent_sdk-0.0.4}/src/cortex_agent_sdk/redis/scripts.py +0 -0
- {cortex_agent_sdk-0.0.3 → cortex_agent_sdk-0.0.4}/src/cortex_agent_sdk/redis/store.py +0 -0
- {cortex_agent_sdk-0.0.3 → cortex_agent_sdk-0.0.4}/src/cortex_agent_sdk/results.py +0 -0
- {cortex_agent_sdk-0.0.3 → cortex_agent_sdk-0.0.4}/src/cortex_agent_sdk/runtime.py +0 -0
- {cortex_agent_sdk-0.0.3 → cortex_agent_sdk-0.0.4}/src/cortex_agent_sdk/sessions/__init__.py +0 -0
- {cortex_agent_sdk-0.0.3 → cortex_agent_sdk-0.0.4}/src/cortex_agent_sdk/sessions/codec.py +0 -0
- {cortex_agent_sdk-0.0.3 → cortex_agent_sdk-0.0.4}/src/cortex_agent_sdk/sessions/lease.py +0 -0
- {cortex_agent_sdk-0.0.3 → cortex_agent_sdk-0.0.4}/src/cortex_agent_sdk/sessions/memory.py +0 -0
- {cortex_agent_sdk-0.0.3 → cortex_agent_sdk-0.0.4}/src/cortex_agent_sdk/sessions/models.py +0 -0
- {cortex_agent_sdk-0.0.3 → cortex_agent_sdk-0.0.4}/src/cortex_agent_sdk/sessions/store.py +0 -0
- {cortex_agent_sdk-0.0.3 → cortex_agent_sdk-0.0.4}/src/cortex_agent_sdk/tools/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cortex-agent-sdk
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.4
|
|
4
4
|
Summary: SDK async y multiproveedor para construir agentes con control explícito
|
|
5
5
|
Project-URL: Repository, https://github.com/epok200/cortex_agent_sdk
|
|
6
6
|
Project-URL: Issues, https://github.com/epok200/cortex_agent_sdk/issues
|
|
@@ -137,10 +137,51 @@ async def main() -> None:
|
|
|
137
137
|
asyncio.run(main())
|
|
138
138
|
```
|
|
139
139
|
|
|
140
|
+
Los type hints son la vía recomendada para tools sencillas. Cortex infiere el contrato visible y
|
|
141
|
+
valida los argumentos antes de ejecutar la función; no hace falta escribir JSON Schema ni
|
|
142
|
+
`ToolSpec` manualmente.
|
|
143
|
+
|
|
144
|
+
### Contratos Pydantic
|
|
145
|
+
|
|
146
|
+
Cuando los argumentos forman un contrato reutilizable o tienen validaciones entre campos, puede
|
|
147
|
+
usarse un `BaseModel` como fuente de verdad:
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
from datetime import datetime
|
|
151
|
+
from typing import Self
|
|
152
|
+
|
|
153
|
+
from pydantic import BaseModel, Field, model_validator
|
|
154
|
+
|
|
155
|
+
from cortex_agent_sdk import final_answer
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class ReminderArgs(BaseModel):
|
|
159
|
+
message: str = Field(min_length=1, description="Texto del recordatorio.")
|
|
160
|
+
when: datetime | None = None
|
|
161
|
+
cron: str | None = None
|
|
162
|
+
|
|
163
|
+
@model_validator(mode="after")
|
|
164
|
+
def validate_mode(self) -> Self:
|
|
165
|
+
if (self.when is None) == (self.cron is None):
|
|
166
|
+
raise ValueError("se requiere exactamente when o cron")
|
|
167
|
+
return self
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
@final_answer(input_model=ReminderArgs)
|
|
171
|
+
async def schedule_reminder(args: ReminderArgs) -> str:
|
|
172
|
+
"""Programa un recordatorio."""
|
|
173
|
+
return f"Recordatorio: {args.message}"
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Cortex genera el `ToolSpec` neutral a partir del modelo, rechaza propiedades adicionales y entrega a
|
|
177
|
+
la función una instancia ya validada. `ToolBinding(input_model=...)` ofrece la misma capacidad para
|
|
178
|
+
tools construidas en runtime. El `ToolSpec` manual continúa disponible como escape hatch cuando el
|
|
179
|
+
schema visible necesita construirse dinámicamente o requiere control de bajo nivel.
|
|
180
|
+
|
|
140
181
|
## Capacidades del alfa
|
|
141
182
|
|
|
142
183
|
- Loop async acotado.
|
|
143
|
-
- Tools async con schema inferido o
|
|
184
|
+
- Tools async con schema inferido, Pydantic explícito o `ToolSpec` manual.
|
|
144
185
|
- Historial y sesiones en memoria, Redis o PostgreSQL.
|
|
145
186
|
- Hooks locales.
|
|
146
187
|
- Timeouts para providers y tools.
|
|
@@ -103,10 +103,51 @@ async def main() -> None:
|
|
|
103
103
|
asyncio.run(main())
|
|
104
104
|
```
|
|
105
105
|
|
|
106
|
+
Los type hints son la vía recomendada para tools sencillas. Cortex infiere el contrato visible y
|
|
107
|
+
valida los argumentos antes de ejecutar la función; no hace falta escribir JSON Schema ni
|
|
108
|
+
`ToolSpec` manualmente.
|
|
109
|
+
|
|
110
|
+
### Contratos Pydantic
|
|
111
|
+
|
|
112
|
+
Cuando los argumentos forman un contrato reutilizable o tienen validaciones entre campos, puede
|
|
113
|
+
usarse un `BaseModel` como fuente de verdad:
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
from datetime import datetime
|
|
117
|
+
from typing import Self
|
|
118
|
+
|
|
119
|
+
from pydantic import BaseModel, Field, model_validator
|
|
120
|
+
|
|
121
|
+
from cortex_agent_sdk import final_answer
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class ReminderArgs(BaseModel):
|
|
125
|
+
message: str = Field(min_length=1, description="Texto del recordatorio.")
|
|
126
|
+
when: datetime | None = None
|
|
127
|
+
cron: str | None = None
|
|
128
|
+
|
|
129
|
+
@model_validator(mode="after")
|
|
130
|
+
def validate_mode(self) -> Self:
|
|
131
|
+
if (self.when is None) == (self.cron is None):
|
|
132
|
+
raise ValueError("se requiere exactamente when o cron")
|
|
133
|
+
return self
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@final_answer(input_model=ReminderArgs)
|
|
137
|
+
async def schedule_reminder(args: ReminderArgs) -> str:
|
|
138
|
+
"""Programa un recordatorio."""
|
|
139
|
+
return f"Recordatorio: {args.message}"
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Cortex genera el `ToolSpec` neutral a partir del modelo, rechaza propiedades adicionales y entrega a
|
|
143
|
+
la función una instancia ya validada. `ToolBinding(input_model=...)` ofrece la misma capacidad para
|
|
144
|
+
tools construidas en runtime. El `ToolSpec` manual continúa disponible como escape hatch cuando el
|
|
145
|
+
schema visible necesita construirse dinámicamente o requiere control de bajo nivel.
|
|
146
|
+
|
|
106
147
|
## Capacidades del alfa
|
|
107
148
|
|
|
108
149
|
- Loop async acotado.
|
|
109
|
-
- Tools async con schema inferido o
|
|
150
|
+
- Tools async con schema inferido, Pydantic explícito o `ToolSpec` manual.
|
|
110
151
|
- Historial y sesiones en memoria, Redis o PostgreSQL.
|
|
111
152
|
- Hooks locales.
|
|
112
153
|
- Timeouts para providers y tools.
|
|
@@ -21,6 +21,7 @@ class ToolDefinition:
|
|
|
21
21
|
private_arguments: Mapping[str, object]
|
|
22
22
|
is_final_answer: bool
|
|
23
23
|
schema_validator: Draft202012Validator | None
|
|
24
|
+
input_parameter: str | None = None
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
def build_definition(item: ToolFunction | ToolBinding) -> ToolDefinition:
|
|
@@ -38,42 +39,68 @@ def build_definition(item: ToolFunction | ToolBinding) -> ToolDefinition:
|
|
|
38
39
|
if not inspect.iscoroutinefunction(function):
|
|
39
40
|
raise AppError(CodigoError.CONFIG_INVALIDA, f"{function.__name__} debe ser async")
|
|
40
41
|
|
|
41
|
-
|
|
42
|
+
declared_model = _declared_input_model(binding, function)
|
|
43
|
+
if declared_model is None:
|
|
44
|
+
input_model, inferred_spec = _infer_contract(function, binding.private_arguments)
|
|
45
|
+
input_parameter = None
|
|
46
|
+
else:
|
|
47
|
+
input_model, inferred_spec, input_parameter = _model_contract(
|
|
48
|
+
function,
|
|
49
|
+
declared_model,
|
|
50
|
+
binding.private_arguments,
|
|
51
|
+
)
|
|
52
|
+
|
|
42
53
|
spec = binding.spec or inferred_spec
|
|
43
54
|
schema_validator = _explicit_validator(binding.spec, inferred_spec)
|
|
44
55
|
private_arguments = MappingProxyType(dict(binding.private_arguments))
|
|
45
56
|
is_final = bool(getattr(function, "__cortex_final_answer__", False))
|
|
46
57
|
return ToolDefinition(
|
|
47
|
-
spec,
|
|
48
|
-
binding.function,
|
|
49
|
-
input_model,
|
|
50
|
-
private_arguments,
|
|
51
|
-
is_final,
|
|
52
|
-
schema_validator,
|
|
58
|
+
spec=spec,
|
|
59
|
+
function=binding.function,
|
|
60
|
+
input_model=input_model,
|
|
61
|
+
private_arguments=private_arguments,
|
|
62
|
+
is_final_answer=is_final,
|
|
63
|
+
schema_validator=schema_validator,
|
|
64
|
+
input_parameter=input_parameter,
|
|
53
65
|
)
|
|
54
66
|
|
|
55
67
|
|
|
68
|
+
def _declared_input_model(
|
|
69
|
+
binding: ToolBinding,
|
|
70
|
+
function: ToolFunction,
|
|
71
|
+
) -> type[BaseModel] | None:
|
|
72
|
+
decorated = getattr(binding.function, "__cortex_input_model__", None)
|
|
73
|
+
if decorated is None:
|
|
74
|
+
decorated = getattr(function, "__cortex_input_model__", None)
|
|
75
|
+
explicit = binding.input_model
|
|
76
|
+
if explicit is not None and decorated is not None and explicit is not decorated:
|
|
77
|
+
raise AppError(
|
|
78
|
+
CodigoError.CONFIG_INVALIDA,
|
|
79
|
+
f"contratos Pydantic incompatibles en {function.__name__}",
|
|
80
|
+
)
|
|
81
|
+
model = explicit or decorated
|
|
82
|
+
if model is None:
|
|
83
|
+
return None
|
|
84
|
+
if not isinstance(model, type) or not issubclass(model, BaseModel):
|
|
85
|
+
raise AppError(
|
|
86
|
+
CodigoError.CONFIG_INVALIDA,
|
|
87
|
+
f"input_model inválido en {function.__name__}",
|
|
88
|
+
)
|
|
89
|
+
return model
|
|
90
|
+
|
|
91
|
+
|
|
56
92
|
def _infer_contract(
|
|
57
93
|
function: ToolFunction,
|
|
58
94
|
private_arguments: Mapping[str, object],
|
|
59
95
|
) -> tuple[type[BaseModel], ToolSpec]:
|
|
60
96
|
signature = inspect.signature(function)
|
|
61
|
-
|
|
62
|
-
annotations = get_type_hints(function, include_extras=True)
|
|
63
|
-
except (NameError, TypeError) as error:
|
|
64
|
-
raise AppError(
|
|
65
|
-
CodigoError.CONFIG_INVALIDA,
|
|
66
|
-
f"no se resolvieron tipos de {function.__name__}",
|
|
67
|
-
) from error
|
|
97
|
+
annotations = _resolved_annotations(function)
|
|
68
98
|
|
|
69
99
|
fields: dict[str, Any] = {}
|
|
70
100
|
injected_names: set[str] = set()
|
|
71
101
|
for name, parameter in signature.parameters.items():
|
|
72
102
|
annotation = annotations.get(name, parameter.annotation)
|
|
73
|
-
|
|
74
|
-
raise AppError(CodigoError.CONFIG_INVALIDA, f"falta tipo en {function.__name__}.{name}")
|
|
75
|
-
if parameter.kind in {parameter.VAR_POSITIONAL, parameter.VAR_KEYWORD}:
|
|
76
|
-
raise AppError(CodigoError.CONFIG_INVALIDA, f"{function.__name__} no admite variádicos")
|
|
103
|
+
_validate_parameter(function, name, parameter, annotation)
|
|
77
104
|
if _is_injected(annotation):
|
|
78
105
|
injected_names.add(name)
|
|
79
106
|
continue
|
|
@@ -82,24 +109,110 @@ def _infer_contract(
|
|
|
82
109
|
default = ...
|
|
83
110
|
fields[name] = (annotation, default)
|
|
84
111
|
|
|
112
|
+
_validate_injected(function, injected_names, private_arguments)
|
|
113
|
+
|
|
114
|
+
config = ConfigDict(extra="forbid")
|
|
115
|
+
model_name = f"{function.__name__.title().replace('_', '')}Arguments"
|
|
116
|
+
input_model = create_model(model_name, __config__=config, **fields)
|
|
117
|
+
return input_model, _spec_from_model(function, input_model)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _model_contract(
|
|
121
|
+
function: ToolFunction,
|
|
122
|
+
input_model: type[BaseModel],
|
|
123
|
+
private_arguments: Mapping[str, object],
|
|
124
|
+
) -> tuple[type[BaseModel], ToolSpec, str]:
|
|
125
|
+
signature = inspect.signature(function)
|
|
126
|
+
annotations = _resolved_annotations(function)
|
|
127
|
+
public_names: list[str] = []
|
|
128
|
+
injected_names: set[str] = set()
|
|
129
|
+
input_parameter: str | None = None
|
|
130
|
+
|
|
131
|
+
for name, parameter in signature.parameters.items():
|
|
132
|
+
annotation = annotations.get(name, parameter.annotation)
|
|
133
|
+
_validate_parameter(function, name, parameter, annotation)
|
|
134
|
+
if _is_injected(annotation):
|
|
135
|
+
injected_names.add(name)
|
|
136
|
+
continue
|
|
137
|
+
public_names.append(name)
|
|
138
|
+
if _annotation_base(annotation) is input_model:
|
|
139
|
+
input_parameter = name
|
|
140
|
+
|
|
141
|
+
_validate_injected(function, injected_names, private_arguments)
|
|
142
|
+
if len(public_names) != 1 or input_parameter is None:
|
|
143
|
+
raise AppError(
|
|
144
|
+
CodigoError.CONFIG_INVALIDA,
|
|
145
|
+
f"{function.__name__} debe recibir un único argumento público {input_model.__name__}",
|
|
146
|
+
)
|
|
147
|
+
return input_model, _spec_from_model(function, input_model), input_parameter
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _resolved_annotations(function: ToolFunction) -> dict[str, Any]:
|
|
151
|
+
try:
|
|
152
|
+
return get_type_hints(function, include_extras=True)
|
|
153
|
+
except (NameError, TypeError) as error:
|
|
154
|
+
raise AppError(
|
|
155
|
+
CodigoError.CONFIG_INVALIDA,
|
|
156
|
+
f"no se resolvieron tipos de {function.__name__}",
|
|
157
|
+
) from error
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _validate_parameter(
|
|
161
|
+
function: ToolFunction,
|
|
162
|
+
name: str,
|
|
163
|
+
parameter: inspect.Parameter,
|
|
164
|
+
annotation: object,
|
|
165
|
+
) -> None:
|
|
166
|
+
if annotation is inspect.Signature.empty:
|
|
167
|
+
raise AppError(CodigoError.CONFIG_INVALIDA, f"falta tipo en {function.__name__}.{name}")
|
|
168
|
+
if parameter.kind in {parameter.VAR_POSITIONAL, parameter.VAR_KEYWORD}:
|
|
169
|
+
raise AppError(CodigoError.CONFIG_INVALIDA, f"{function.__name__} no admite variádicos")
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _validate_injected(
|
|
173
|
+
function: ToolFunction,
|
|
174
|
+
injected_names: set[str],
|
|
175
|
+
private_arguments: Mapping[str, object],
|
|
176
|
+
) -> None:
|
|
85
177
|
if injected_names != set(private_arguments):
|
|
86
178
|
raise AppError(
|
|
87
179
|
CodigoError.CONFIG_INVALIDA,
|
|
88
180
|
f"inyección inválida en {function.__name__}",
|
|
89
181
|
)
|
|
90
182
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
input_model = create_model(model_name, __config__=config, **fields)
|
|
183
|
+
|
|
184
|
+
def _spec_from_model(function: ToolFunction, input_model: type[BaseModel]) -> ToolSpec:
|
|
94
185
|
schema = input_model.model_json_schema()
|
|
95
186
|
schema.pop("title", None)
|
|
187
|
+
_forbid_object_extras(schema)
|
|
188
|
+
if schema.get("type") != "object" or not isinstance(schema.get("properties"), dict):
|
|
189
|
+
raise AppError(
|
|
190
|
+
CodigoError.CONFIG_INVALIDA,
|
|
191
|
+
f"{input_model.__name__} debe describir argumentos de tipo object",
|
|
192
|
+
)
|
|
96
193
|
description = (inspect.getdoc(function) or "").split("\n", maxsplit=1)[0]
|
|
97
|
-
|
|
194
|
+
return ToolSpec(
|
|
98
195
|
name=function.__name__,
|
|
99
196
|
description=description,
|
|
100
197
|
parameters=schema,
|
|
101
198
|
)
|
|
102
|
-
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def _forbid_object_extras(value: object) -> None:
|
|
202
|
+
if isinstance(value, dict):
|
|
203
|
+
if value.get("type") == "object" and isinstance(value.get("properties"), dict):
|
|
204
|
+
value["additionalProperties"] = False
|
|
205
|
+
for nested in value.values():
|
|
206
|
+
_forbid_object_extras(nested)
|
|
207
|
+
elif isinstance(value, list):
|
|
208
|
+
for nested in value:
|
|
209
|
+
_forbid_object_extras(nested)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _annotation_base(annotation: object) -> object:
|
|
213
|
+
if get_origin(annotation) is Annotated:
|
|
214
|
+
return get_args(annotation)[0]
|
|
215
|
+
return annotation
|
|
103
216
|
|
|
104
217
|
|
|
105
218
|
def _is_injected(annotation: object) -> bool:
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from typing import overload
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel
|
|
5
|
+
|
|
6
|
+
from cortex_agent_sdk.tools.models import ToolFunction
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@overload
|
|
10
|
+
def tool[F: ToolFunction](function: F, /) -> F: ...
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@overload
|
|
14
|
+
def tool[F: ToolFunction](
|
|
15
|
+
*,
|
|
16
|
+
input_model: type[BaseModel] | None = None,
|
|
17
|
+
) -> Callable[[F], F]: ...
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def tool(
|
|
21
|
+
function: ToolFunction | None = None,
|
|
22
|
+
*,
|
|
23
|
+
input_model: type[BaseModel] | None = None,
|
|
24
|
+
) -> ToolFunction | Callable[[ToolFunction], ToolFunction]:
|
|
25
|
+
"""Marca una tool y permite declarar un contrato Pydantic explícito."""
|
|
26
|
+
|
|
27
|
+
def decorate(inner: ToolFunction) -> ToolFunction:
|
|
28
|
+
inner.__cortex_tool__ = True # type: ignore[attr-defined]
|
|
29
|
+
if input_model is not None:
|
|
30
|
+
inner.__cortex_input_model__ = input_model # type: ignore[attr-defined]
|
|
31
|
+
return inner
|
|
32
|
+
|
|
33
|
+
if function is None:
|
|
34
|
+
return decorate
|
|
35
|
+
return decorate(function)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@overload
|
|
39
|
+
def final_answer[F: ToolFunction](function: F, /) -> F: ...
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@overload
|
|
43
|
+
def final_answer[F: ToolFunction](
|
|
44
|
+
*,
|
|
45
|
+
input_model: type[BaseModel] | None = None,
|
|
46
|
+
) -> Callable[[F], F]: ...
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def final_answer(
|
|
50
|
+
function: ToolFunction | None = None,
|
|
51
|
+
*,
|
|
52
|
+
input_model: type[BaseModel] | None = None,
|
|
53
|
+
) -> ToolFunction | Callable[[ToolFunction], ToolFunction]:
|
|
54
|
+
"""Marca una tool terminal y permite un contrato Pydantic explícito."""
|
|
55
|
+
|
|
56
|
+
def decorate(inner: ToolFunction) -> ToolFunction:
|
|
57
|
+
inner.__cortex_tool__ = True # type: ignore[attr-defined]
|
|
58
|
+
inner.__cortex_final_answer__ = True # type: ignore[attr-defined]
|
|
59
|
+
if input_model is not None:
|
|
60
|
+
inner.__cortex_input_model__ = input_model # type: ignore[attr-defined]
|
|
61
|
+
return inner
|
|
62
|
+
|
|
63
|
+
if function is None:
|
|
64
|
+
return decorate
|
|
65
|
+
return decorate(function)
|
|
@@ -142,7 +142,7 @@ def _validate_arguments(
|
|
|
142
142
|
definition.is_final_answer,
|
|
143
143
|
)
|
|
144
144
|
try:
|
|
145
|
-
|
|
145
|
+
validated = definition.input_model.model_validate(public_input, extra="forbid")
|
|
146
146
|
except ValidationError:
|
|
147
147
|
return _failed_outcome(
|
|
148
148
|
call,
|
|
@@ -150,6 +150,11 @@ def _validate_arguments(
|
|
|
150
150
|
"Los argumentos de la herramienta son inválidos.",
|
|
151
151
|
definition.is_final_answer,
|
|
152
152
|
)
|
|
153
|
+
|
|
154
|
+
if definition.input_parameter is not None:
|
|
155
|
+
public_arguments: dict[str, object] = {definition.input_parameter: validated}
|
|
156
|
+
else:
|
|
157
|
+
public_arguments = validated.model_dump()
|
|
153
158
|
return {**public_arguments, **definition.private_arguments}
|
|
154
159
|
|
|
155
160
|
|
|
@@ -46,11 +46,12 @@ ToolFunction = Callable[..., Awaitable[object]]
|
|
|
46
46
|
|
|
47
47
|
@dataclass(frozen=True, slots=True)
|
|
48
48
|
class ToolBinding:
|
|
49
|
-
"""Une callable,
|
|
49
|
+
"""Une callable, contrato opcional y argumentos privados."""
|
|
50
50
|
|
|
51
51
|
function: ToolFunction
|
|
52
52
|
spec: ToolSpec | None = None
|
|
53
53
|
private_arguments: Mapping[str, object] = field(default_factory=dict)
|
|
54
|
+
input_model: type[BaseModel] | None = None
|
|
54
55
|
|
|
55
56
|
def __post_init__(self) -> None:
|
|
56
57
|
frozen_arguments = MappingProxyType(dict(self.private_arguments))
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
from cortex_agent_sdk.tools.models import ToolFunction
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
def tool(function: ToolFunction) -> ToolFunction:
|
|
5
|
-
"""Marca un callable async para registrarlo en un Agent."""
|
|
6
|
-
function.__cortex_tool__ = True # type: ignore[attr-defined]
|
|
7
|
-
return function
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def final_answer(function: ToolFunction) -> ToolFunction:
|
|
11
|
-
"""Marca una tool cuyo str exitoso termina el turno."""
|
|
12
|
-
function.__cortex_tool__ = True # type: ignore[attr-defined]
|
|
13
|
-
function.__cortex_final_answer__ = True # type: ignore[attr-defined]
|
|
14
|
-
return function
|
|
15
|
-
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|