Skip to content

Commit a993816

Browse files
fix: update how control decorator can be used for llm or tool call (#40)
## Describe your changes What did you change? Why did you change it? ## Shortcut ticket https://app.shortcut.com/galileo/story/66827/update-agent-control-docs-to-clarify-usage-on-tool-spans
1 parent 87897c1 commit a993816

1 file changed

Lines changed: 77 additions & 3 deletions

File tree

how-to/decorate-llm-tool-calls.mdx

Lines changed: 77 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -192,14 +192,88 @@ uv run python main.py
192192
2. **Execution** — the LLM call runs normally.
193193
3. **Post-stage** — after the function returns, the decorator sends the output to the server. Controls scoped to `"post"` evaluate it. If denied, the output is blocked.
194194

195-
## Decorate tool calls the same way
195+
## Decorate LLM steps
196+
197+
Decorate the function that performs the LLM call. By default, `@control()` registers the function as an `llm` step and uses the function name as the step name.
196198

197199
```python
200+
from agent_control import control
201+
202+
198203
@control()
199-
async def execute_query(query: str) -> str:
200-
return await db.run(query)
204+
async def generate_answer(message: str) -> str:
205+
response = client.chat.completions.create(
206+
model="gpt-4.1",
207+
messages=[{"role": "user", "content": message}],
208+
)
209+
return response.choices[0].message.content
210+
```
211+
212+
Use `step_name` when you want the code to map to a specific step name in Agent Control.
213+
214+
```python
215+
@control(step_name="customer-support-llm")
216+
async def generate_answer(message: str) -> str:
217+
response = client.chat.completions.create(
218+
model="gpt-4.1",
219+
messages=[{"role": "user", "content": message}],
220+
)
221+
return response.choices[0].message.content
222+
```
223+
224+
Controls scoped to LLM steps should use `step_types: ["llm"]`.
225+
226+
```python
227+
{
228+
"scope": {
229+
"step_types": ["llm"],
230+
"stages": ["pre", "post"],
231+
}
232+
}
201233
```
202234

235+
## Decorate tool steps
236+
237+
Decorate the function that executes the tool action, such as a database query, file write, API request, or business operation. For tool steps, make sure the function has tool metadata before `@control()` is applied. The current Python SDK uses `.name` or `.tool_name` to classify a decorated function as a `tool` step.
238+
239+
```python
240+
from agent_control import control
241+
242+
243+
async def _execute_query(sql: str) -> str:
244+
return await database.run(sql)
245+
246+
247+
_execute_query.name = "execute_query"
248+
_execute_query.tool_name = "execute_query"
249+
250+
execute_query = control()(_execute_query)
251+
```
252+
253+
If your framework has a tool decorator, apply the framework tool metadata first and `@control()` after that. In Python, decorators run from the bottom up, so `@tool` must be closest to the function and `@control()` must be above it.
254+
255+
```python
256+
@control()
257+
@tool("execute_query")
258+
async def execute_query(sql: str) -> str:
259+
return await database.run(sql)
260+
```
261+
262+
Controls scoped to tool steps should use `step_types: ["tool"]`.
263+
264+
```python
265+
{
266+
"scope": {
267+
"step_types": ["tool"],
268+
"stages": ["pre"],
269+
}
270+
}
271+
```
272+
273+
<Note>
274+
If you use `@control(step_name="execute_query")` without tool metadata, the step name changes, but the current Python SDK still treats the function as an `llm` step. Add `.name` or `.tool_name`, or apply your framework's tool decorator before `@control()`, when the control should run on a tool step.
275+
</Note>
276+
203277

204278
## Any LLM SDK works
205279

0 commit comments

Comments
 (0)