agentjit 0.1.0__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.
agentjit/__init__.py ADDED
@@ -0,0 +1,43 @@
1
+ """AgentJIT: Just-In-Time Compiler for AI Agent Trajectories.
2
+
3
+ Compiles non-deterministic, expensive multi-step LLM workflows into ultra-fast
4
+ deterministic Python code with zero token costs and automatic speculative fallbacks.
5
+ """
6
+
7
+ from agentjit.codegen import GuardViolation
8
+ from agentjit.compiler import compile_trajectory, create_compiled_pipeline
9
+ from agentjit.decorators import jit, JITWrapper
10
+ from agentjit.integrations.base import ToolDispatcher
11
+ from agentjit.runtime import CompiledPipeline
12
+ from agentjit.tracer import Tracer, trace_tool
13
+ from agentjit.types import (
14
+ CompilationResult,
15
+ ExecutionMetrics,
16
+ ExecutionNode,
17
+ Guard,
18
+ SourceType,
19
+ TraceStep,
20
+ Trajectory,
21
+ ValueSource,
22
+ )
23
+
24
+ __version__ = "0.1.0"
25
+ __all__ = [
26
+ "jit",
27
+ "JITWrapper",
28
+ "Tracer",
29
+ "trace_tool",
30
+ "compile_trajectory",
31
+ "create_compiled_pipeline",
32
+ "CompiledPipeline",
33
+ "GuardViolation",
34
+ "Trajectory",
35
+ "TraceStep",
36
+ "ExecutionNode",
37
+ "Guard",
38
+ "ValueSource",
39
+ "SourceType",
40
+ "CompilationResult",
41
+ "ExecutionMetrics",
42
+ "ToolDispatcher",
43
+ ]
agentjit/analyzer.py ADDED
@@ -0,0 +1,302 @@
1
+ """Data-flow dependency analyzer and graph synthesis for agent trajectories."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import math
6
+ from typing import Any, Dict, List, Optional, Tuple, Union
7
+
8
+ from agentjit.types import (
9
+ ExecutionNode,
10
+ Guard,
11
+ SourceType,
12
+ TraceStep,
13
+ Trajectory,
14
+ ValueSource,
15
+ )
16
+
17
+
18
+ class TrajectoryAnalyzer:
19
+ """Analyzes a recorded agent trajectory to construct a deterministic execution DAG."""
20
+
21
+ def __init__(self, trajectory: Trajectory) -> None:
22
+ self.trajectory = trajectory
23
+ self.entry_args = trajectory.entry_args
24
+ self.steps = trajectory.steps
25
+
26
+ def analyze(self) -> Tuple[List[ExecutionNode], List[Guard], ValueSource]:
27
+ """Perform data-flow analysis and return execution nodes, guards, and final output source."""
28
+ nodes: List[ExecutionNode] = []
29
+ step_outputs: Dict[int, Any] = {}
30
+
31
+ # 1. Analyze each step and determine argument provenance
32
+ for step in self.steps:
33
+ bindings: Dict[str, ValueSource] = {}
34
+ for param_name, param_val in step.inputs.items():
35
+ source = self._resolve_value_source(param_val, step.step_id, step_outputs)
36
+ bindings[param_name] = source
37
+
38
+ node = ExecutionNode(
39
+ node_id=step.step_id,
40
+ tool_name=step.tool_name,
41
+ argument_bindings=bindings,
42
+ output_var_name=f"step_{step.step_id}_out",
43
+ )
44
+ nodes.append(node)
45
+ step_outputs[step.step_id] = step.output
46
+
47
+ # 2. Determine where the final result comes from
48
+ final_source = self._resolve_final_output(step_outputs)
49
+
50
+ # 3. Generate speculative execution guards
51
+ guards = self._generate_guards()
52
+
53
+ return nodes, guards, final_source
54
+
55
+ def _resolve_value_source(
56
+ self,
57
+ target_val: Any,
58
+ current_step_id: int,
59
+ step_outputs: Dict[int, Any],
60
+ ) -> ValueSource:
61
+ """Trace backwards to locate the origin of target_val."""
62
+ # 1. Direct match with entry argument
63
+ for arg_name, arg_val in self.entry_args.items():
64
+ if self._safe_equals(target_val, arg_val):
65
+ return ValueSource(source_type=SourceType.ENTRY_ARG, name=arg_name)
66
+
67
+ # 2. Direct match with output of an earlier step
68
+ for prev_id in range(current_step_id - 1, 0, -1):
69
+ if prev_id in step_outputs and self._safe_equals(target_val, step_outputs[prev_id]):
70
+ return ValueSource(source_type=SourceType.STEP_OUTPUT, step_id=prev_id)
71
+
72
+ # 3. Nested match within entry arguments (e.g. user['id'])
73
+ for arg_name, arg_val in self.entry_args.items():
74
+ found, path = self._search_nested(target_val, arg_val)
75
+ if found:
76
+ return ValueSource(
77
+ source_type=SourceType.NESTED_ACCESS,
78
+ name=arg_name,
79
+ key_path=path,
80
+ )
81
+
82
+ # 4. Nested match within previous step outputs (e.g. order['customer_id'])
83
+ for prev_id in range(current_step_id - 1, 0, -1):
84
+ if prev_id in step_outputs:
85
+ found, path = self._search_nested(target_val, step_outputs[prev_id])
86
+ if found:
87
+ return ValueSource(
88
+ source_type=SourceType.NESTED_ACCESS,
89
+ step_id=prev_id,
90
+ key_path=path,
91
+ )
92
+
93
+ # 5. Arithmetic binary operation synthesis (e.g. unit_price * quantity)
94
+ if isinstance(target_val, (int, float)) and not isinstance(target_val, bool):
95
+ bin_op = self._try_synthesize_arithmetic(target_val, current_step_id, step_outputs)
96
+ if bin_op is not None:
97
+ return bin_op
98
+
99
+ # 6. Nested dictionary structure check
100
+ if isinstance(target_val, dict):
101
+ dict_sources = {
102
+ k: self._resolve_value_source(v, current_step_id, step_outputs)
103
+ for k, v in target_val.items()
104
+ }
105
+ return ValueSource(
106
+ source_type=SourceType.DICT_SYNTHESIS,
107
+ dict_fields=dict_sources,
108
+ )
109
+
110
+ # 7. Nested list structure check
111
+ if isinstance(target_val, (list, tuple)):
112
+ list_sources = [
113
+ self._resolve_value_source(item, current_step_id, step_outputs)
114
+ for item in target_val
115
+ ]
116
+ return ValueSource(
117
+ source_type=SourceType.LIST_SYNTHESIS,
118
+ list_items=list_sources,
119
+ )
120
+
121
+ # 8. Invariant literal
122
+ return ValueSource(source_type=SourceType.LITERAL, literal_value=target_val)
123
+
124
+ def _try_synthesize_arithmetic(
125
+ self,
126
+ target_val: Union[int, float],
127
+ current_step_id: int,
128
+ step_outputs: Dict[int, Any],
129
+ ) -> Optional[ValueSource]:
130
+ """Attempt to synthesize a binary arithmetic operation matching target_val."""
131
+ candidates: List[Tuple[Union[int, float], ValueSource]] = []
132
+
133
+ # Collect numeric candidates from entry args
134
+ for arg_name, arg_val in self.entry_args.items():
135
+ if isinstance(arg_val, (int, float)) and not isinstance(arg_val, bool):
136
+ candidates.append(
137
+ (arg_val, ValueSource(source_type=SourceType.ENTRY_ARG, name=arg_name))
138
+ )
139
+
140
+ # Collect numeric candidates from previous step outputs
141
+ for prev_id in range(1, current_step_id):
142
+ out = step_outputs.get(prev_id)
143
+ if isinstance(out, (int, float)) and not isinstance(out, bool):
144
+ candidates.append(
145
+ (out, ValueSource(source_type=SourceType.STEP_OUTPUT, step_id=prev_id))
146
+ )
147
+ elif isinstance(out, dict):
148
+ for k, v in out.items():
149
+ if isinstance(v, (int, float)) and not isinstance(v, bool):
150
+ candidates.append(
151
+ (
152
+ v,
153
+ ValueSource(
154
+ source_type=SourceType.NESTED_ACCESS,
155
+ step_id=prev_id,
156
+ key_path=[k],
157
+ ),
158
+ )
159
+ )
160
+
161
+ # Test pairwise operations (prefer multiplication and addition)
162
+ ops = [
163
+ ("*", lambda a, b: a * b),
164
+ ("+", lambda a, b: a + b),
165
+ ("-", lambda a, b: a - b),
166
+ ("/", lambda a, b: (a / b) if b != 0 else None),
167
+ ]
168
+
169
+ for i in range(len(candidates)):
170
+ for j in range(len(candidates)):
171
+ if i == j:
172
+ continue
173
+ v1, src1 = candidates[i]
174
+ v2, src2 = candidates[j]
175
+
176
+ for op_symbol, op_fn in ops:
177
+ try:
178
+ computed = op_fn(v1, v2)
179
+ if computed is not None and math.isclose(
180
+ computed, target_val, rel_tol=1e-5, abs_tol=1e-5
181
+ ):
182
+ return ValueSource(
183
+ source_type=SourceType.BINARY_OP,
184
+ left=src1,
185
+ right=src2,
186
+ operator=op_symbol,
187
+ )
188
+ except Exception:
189
+ continue
190
+
191
+ return None
192
+
193
+ def _resolve_final_output(self, step_outputs: Dict[int, Any]) -> ValueSource:
194
+ """Find the source expression for the trajectory's final result."""
195
+ final_res = self.trajectory.final_result
196
+ if not self.steps:
197
+ return ValueSource(source_type=SourceType.LITERAL, literal_value=final_res)
198
+
199
+ # 1. Check if final result directly equals a step's output
200
+ for step_id in reversed(sorted(step_outputs.keys())):
201
+ if self._safe_equals(final_res, step_outputs[step_id]):
202
+ return ValueSource(source_type=SourceType.STEP_OUTPUT, step_id=step_id)
203
+
204
+ # 2. Check if final result is a dict composed of step outputs/entry args
205
+ if isinstance(final_res, dict):
206
+ dict_sources = {
207
+ k: self._resolve_value_source(v, len(self.steps) + 1, step_outputs)
208
+ for k, v in final_res.items()
209
+ }
210
+ return ValueSource(
211
+ source_type=SourceType.DICT_SYNTHESIS,
212
+ dict_fields=dict_sources,
213
+ )
214
+
215
+ # 3. Check if final result is a list composed of step outputs/entry args
216
+ if isinstance(final_res, (list, tuple)):
217
+ list_sources = [
218
+ self._resolve_value_source(item, len(self.steps) + 1, step_outputs)
219
+ for item in final_res
220
+ ]
221
+ return ValueSource(
222
+ source_type=SourceType.LIST_SYNTHESIS,
223
+ list_items=list_sources,
224
+ )
225
+
226
+ # 4. Check if nested in the last step output
227
+ last_id = self.steps[-1].step_id
228
+ if last_id in step_outputs:
229
+ found, path = self._search_nested(final_res, step_outputs[last_id])
230
+ if found:
231
+ return ValueSource(
232
+ source_type=SourceType.NESTED_ACCESS,
233
+ step_id=last_id,
234
+ key_path=path,
235
+ )
236
+
237
+ # Default to output of last step
238
+ return ValueSource(source_type=SourceType.STEP_OUTPUT, step_id=last_id)
239
+
240
+ def _generate_guards(self) -> List[Guard]:
241
+ """Generate speculative validation guards for the entry arguments."""
242
+ guards: List[Guard] = []
243
+ for arg_name, arg_val in self.entry_args.items():
244
+ if arg_val is not None:
245
+ # Non-null guard
246
+ guards.append(
247
+ Guard(
248
+ target_param=arg_name,
249
+ guard_type="non_null",
250
+ condition_code=f"{arg_name} is not None",
251
+ description=f"Argument '{arg_name}' must not be None",
252
+ )
253
+ )
254
+ # Type guard
255
+ type_name = type(arg_val).__name__
256
+ guards.append(
257
+ Guard(
258
+ target_param=arg_name,
259
+ guard_type="type_match",
260
+ condition_code=f"isinstance({arg_name}, {type_name})",
261
+ description=f"Argument '{arg_name}' must be of type {type_name}",
262
+ )
263
+ )
264
+ return guards
265
+
266
+ def _search_nested(
267
+ self,
268
+ target: Any,
269
+ haystack: Any,
270
+ max_depth: int = 4,
271
+ ) -> Tuple[bool, List[Union[str, int]]]:
272
+ """Recursively search for target inside a nested dictionary/list structure."""
273
+ if max_depth <= 0 or target is None or haystack is None:
274
+ return False, []
275
+
276
+ if isinstance(haystack, dict):
277
+ for k, v in haystack.items():
278
+ if self._safe_equals(target, v):
279
+ return True, [k]
280
+ found, subpath = self._search_nested(target, v, max_depth - 1)
281
+ if found:
282
+ return True, [k] + subpath
283
+
284
+ elif isinstance(haystack, (list, tuple)):
285
+ for idx, item in enumerate(haystack):
286
+ if self._safe_equals(target, item):
287
+ return True, [idx]
288
+ found, subpath = self._search_nested(target, item, max_depth - 1)
289
+ if found:
290
+ return True, [idx] + subpath
291
+
292
+ return False, []
293
+
294
+ @staticmethod
295
+ def _safe_equals(a: Any, b: Any) -> bool:
296
+ """Safe equality comparison avoiding numpy/pandas ambiguity."""
297
+ if a is b:
298
+ return True
299
+ try:
300
+ return bool(a == b)
301
+ except Exception:
302
+ return False
agentjit/codegen.py ADDED
@@ -0,0 +1,137 @@
1
+ """AST and Python source code generator for compiled agent trajectories."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import ast
6
+ from typing import Any, Callable, Dict, List, Optional
7
+
8
+ from agentjit.types import (
9
+ CompilationResult,
10
+ ExecutionNode,
11
+ Guard,
12
+ Trajectory,
13
+ ValueSource,
14
+ )
15
+
16
+
17
+ class GuardViolation(Exception):
18
+ """Raised at runtime when a compiled pipeline's speculative guard fails."""
19
+ def __init__(self, message: str, param: str = "") -> None:
20
+ super().__init__(message)
21
+ self.param = param
22
+
23
+
24
+ class CodeGenerator:
25
+ """Synthesizes human-readable, typed, and executable Python code from an execution DAG."""
26
+
27
+ def __init__(
28
+ self,
29
+ entry_params: List[str],
30
+ nodes: List[ExecutionNode],
31
+ guards: List[Guard],
32
+ final_source: ValueSource,
33
+ tool_registry: Dict[str, Callable],
34
+ func_name: str = "compiled_pipeline",
35
+ ) -> None:
36
+ self.entry_params = entry_params
37
+ self.nodes = nodes
38
+ self.guards = guards
39
+ self.final_source = final_source
40
+ self.tool_registry = tool_registry
41
+ self.func_name = func_name
42
+
43
+ def generate_source(self) -> str:
44
+ """Generate formatted Python source code for the compiled pipeline."""
45
+ lines: List[str] = []
46
+ params_str = ", ".join(self.entry_params)
47
+ lines.append(f"def {self.func_name}({params_str}):")
48
+ lines.append(' """JIT-compiled trajectory pipeline generated by AgentJIT.')
49
+ lines.append(" Executes deterministically in sub-millisecond time with zero token cost.")
50
+ lines.append(' """')
51
+
52
+ # 1. Speculative Guards
53
+ if self.guards:
54
+ lines.append(" # --- Speculative Guards ---")
55
+ for g in self.guards:
56
+ # Add validation statement
57
+ lines.append(f" if not ({g.condition_code}):")
58
+ escaped_desc = g.description.replace('"', '\\"')
59
+ lines.append(
60
+ f' raise GuardViolation("{escaped_desc}", param="{g.target_param}")'
61
+ )
62
+
63
+ lines.append("")
64
+ lines.append(" # --- Execution Steps ---")
65
+ var_map: Dict[int, str] = {}
66
+
67
+ # 2. Tool invocations
68
+ for node in self.nodes:
69
+ var_name = node.output_var_name
70
+ var_map[node.node_id] = var_name
71
+
72
+ args_parts: List[str] = []
73
+ for param_k, val_src in node.argument_bindings.items():
74
+ arg_expr = val_src.render_expression(var_map)
75
+ args_parts.append(f"{param_k}={arg_expr}")
76
+
77
+ args_str = ", ".join(args_parts)
78
+ lines.append(
79
+ f" {var_name} = _tools[{repr(node.tool_name)}]({args_str})"
80
+ )
81
+
82
+ # 3. Final return
83
+ lines.append("")
84
+ lines.append(" # --- Return Final Result ---")
85
+ return_expr = self.final_source.render_expression(var_map)
86
+ lines.append(f" return {return_expr}")
87
+
88
+ return "\n".join(lines)
89
+
90
+ def compile(self) -> CompilationResult:
91
+ """Generate source code, validate AST, and compile into an executable callable."""
92
+ source = self.generate_source()
93
+
94
+ # Validate syntax via AST
95
+ try:
96
+ tree = ast.parse(source)
97
+ except SyntaxError as e:
98
+ return CompilationResult(
99
+ success=False,
100
+ source_code=source,
101
+ error=f"Syntax error in synthesized code: {e}",
102
+ )
103
+
104
+ # Execute compilation in controlled namespace
105
+ exec_globals: Dict[str, Any] = {
106
+ "_tools": self.tool_registry,
107
+ "GuardViolation": GuardViolation,
108
+ "isinstance": isinstance,
109
+ "int": int,
110
+ "float": float,
111
+ "str": str,
112
+ "bool": bool,
113
+ "dict": dict,
114
+ "list": list,
115
+ }
116
+ exec_locals: Dict[str, Any] = {}
117
+
118
+ try:
119
+ code_obj = compile(tree, filename="<agentjit_compiled>", mode="exec")
120
+ exec(code_obj, exec_globals, exec_locals)
121
+ compiled_func = exec_locals.get(self.func_name)
122
+ except Exception as e:
123
+ return CompilationResult(
124
+ success=False,
125
+ source_code=source,
126
+ error=f"Execution error during module compilation: {e}",
127
+ )
128
+
129
+ return CompilationResult(
130
+ success=True,
131
+ source_code=source,
132
+ compiled_callable=compiled_func,
133
+ nodes=self.nodes,
134
+ guards=self.guards,
135
+ entry_parameters=self.entry_params,
136
+ output_expression=self.final_source.render_expression({}),
137
+ )
agentjit/compiler.py ADDED
@@ -0,0 +1,60 @@
1
+ """High-level compiler coordinating analysis, code generation, and pipeline assembly."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Callable, Dict, Optional
6
+
7
+ from agentjit.analyzer import TrajectoryAnalyzer
8
+ from agentjit.codegen import CodeGenerator
9
+ from agentjit.runtime import CompiledPipeline
10
+ from agentjit.types import CompilationResult, Trajectory
11
+
12
+
13
+ def compile_trajectory(
14
+ trajectory: Trajectory,
15
+ tool_registry: Dict[str, Callable],
16
+ function_name: str = "compiled_agent_pipeline",
17
+ ) -> CompilationResult:
18
+ """Compile a recorded agent trajectory into an optimized Python pipeline.
19
+
20
+ Args:
21
+ trajectory: The traced sequence of tool calls and entry parameters.
22
+ tool_registry: Mapping from tool names to their Python callable implementations.
23
+ function_name: Name of the generated Python function.
24
+
25
+ Returns:
26
+ CompilationResult containing synthesized source code and executable function.
27
+ """
28
+ analyzer = TrajectoryAnalyzer(trajectory)
29
+ nodes, guards, final_source = analyzer.analyze()
30
+
31
+ entry_params = list(trajectory.entry_args.keys())
32
+
33
+ generator = CodeGenerator(
34
+ entry_params=entry_params,
35
+ nodes=nodes,
36
+ guards=guards,
37
+ final_source=final_source,
38
+ tool_registry=tool_registry,
39
+ func_name=function_name,
40
+ )
41
+
42
+ return generator.compile()
43
+
44
+
45
+ def create_compiled_pipeline(
46
+ trajectory: Trajectory,
47
+ tool_registry: Dict[str, Callable],
48
+ fallback_fn: Optional[Callable] = None,
49
+ function_name: str = "compiled_agent_pipeline",
50
+ ) -> CompiledPipeline:
51
+ """Compile a trajectory and wrap it in an executable CompiledPipeline."""
52
+ compilation = compile_trajectory(trajectory, tool_registry, function_name)
53
+ if not compilation.success:
54
+ raise RuntimeError(f"Failed to compile trajectory: {compilation.error}")
55
+
56
+ return CompiledPipeline(
57
+ compilation=compilation,
58
+ fallback_fn=fallback_fn,
59
+ estimated_uncompiled_latency_ms=max(100.0, trajectory.total_duration_ms),
60
+ )
agentjit/decorators.py ADDED
@@ -0,0 +1,129 @@
1
+ """High-level @jit decorator for automatic tracing, compilation, and execution."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import functools
6
+ import inspect
7
+ from typing import Any, Callable, Dict, Optional
8
+
9
+ from agentjit.compiler import create_compiled_pipeline
10
+ from agentjit.runtime import CompiledPipeline
11
+ from agentjit.tracer import Tracer
12
+
13
+
14
+ class JITWrapper:
15
+ """Manages the lifecycle of a JIT-compiled agent function."""
16
+
17
+ def __init__(
18
+ self,
19
+ func: Callable,
20
+ warmup_runs: int = 1,
21
+ auto_compile: bool = True,
22
+ function_name: Optional[str] = None,
23
+ ) -> None:
24
+ self.func = func
25
+ self.warmup_runs = max(1, warmup_runs)
26
+ self.auto_compile = auto_compile
27
+ self.function_name = function_name or f"compiled_{func.__name__}"
28
+
29
+ self._runs_completed = 0
30
+ self._pipeline: Optional[CompiledPipeline] = None
31
+ self._last_trajectory = None
32
+
33
+ functools.update_wrapper(self, func)
34
+
35
+ @property
36
+ def is_compiled(self) -> bool:
37
+ """True if the agent workflow has been successfully compiled."""
38
+ return self._pipeline is not None
39
+
40
+ @property
41
+ def pipeline(self) -> Optional[CompiledPipeline]:
42
+ """Access the underlying CompiledPipeline instance."""
43
+ return self._pipeline
44
+
45
+ @property
46
+ def source_code(self) -> Optional[str]:
47
+ """View the compiled Python code."""
48
+ if self._pipeline:
49
+ return self._pipeline.source_code
50
+ return None
51
+
52
+ @property
53
+ def stats(self) -> Dict[str, Any]:
54
+ """Access performance and execution statistics."""
55
+ if self._pipeline:
56
+ return self._pipeline.stats
57
+ return {"status": "uncompiled", "warmup_progress": f"{self._runs_completed}/{self.warmup_runs}"}
58
+
59
+ def __call__(self, *args, **kwargs) -> Any:
60
+ # If already compiled, execute optimized pipeline
61
+ if self._pipeline is not None:
62
+ return self._pipeline(*args, **kwargs)
63
+
64
+ # Warmup / Tracing phase
65
+ sig = inspect.signature(self.func)
66
+ bound = sig.bind(*args, **kwargs)
67
+ bound.apply_defaults()
68
+ entry_args = dict(bound.arguments)
69
+
70
+ with Tracer(entry_args=entry_args) as tracer:
71
+ result = self.func(*args, **kwargs)
72
+ tracer.set_final_result(result)
73
+ self._last_trajectory = tracer.trajectory
74
+
75
+ self._runs_completed += 1
76
+
77
+ # Check if warmup threshold reached
78
+ if self.auto_compile and self._runs_completed >= self.warmup_runs:
79
+ self._compile_from_tracer(tracer)
80
+
81
+ return result
82
+
83
+ def _compile_from_tracer(self, tracer: Tracer) -> None:
84
+ """Trigger compilation using the captured trajectory and tools."""
85
+ try:
86
+ self._pipeline = create_compiled_pipeline(
87
+ trajectory=tracer.trajectory,
88
+ tool_registry=tracer._registered_tools,
89
+ fallback_fn=self.func,
90
+ function_name=self.function_name,
91
+ )
92
+ except Exception as err:
93
+ # If compilation fails, remain in uncompiled fallback mode
94
+ self._pipeline = None
95
+
96
+
97
+ def jit(
98
+ func: Optional[Callable] = None,
99
+ *,
100
+ warmup_runs: int = 1,
101
+ auto_compile: bool = True,
102
+ function_name: Optional[str] = None,
103
+ ) -> Any:
104
+ """Decorator to JIT-compile an AI agent workflow.
105
+
106
+ Usage:
107
+ @jit
108
+ def my_agent(order_id: int, country: str):
109
+ order = fetch_order(order_id)
110
+ tax = calculate_vat(order["amount"], country)
111
+ return update_db(order_id, tax)
112
+
113
+ # 1st run: Executes dynamic agent, records trajectory, compiles to pure Python
114
+ result1 = my_agent(402, "Germany")
115
+
116
+ # 2nd run: Executes in <1ms directly with ZERO LLM calls and zero token cost!
117
+ result2 = my_agent(403, "France")
118
+ """
119
+ def decorator(fn: Callable) -> JITWrapper:
120
+ return JITWrapper(
121
+ fn,
122
+ warmup_runs=warmup_runs,
123
+ auto_compile=auto_compile,
124
+ function_name=function_name,
125
+ )
126
+
127
+ if func is not None:
128
+ return decorator(func)
129
+ return decorator
@@ -0,0 +1,5 @@
1
+ """Integrations package for AgentJIT."""
2
+
3
+ from agentjit.integrations.base import ToolDispatcher
4
+
5
+ __all__ = ["ToolDispatcher"]