@aws/nx-plugin 1.0.0-rc.78 → 1.0.0-rc.79
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.
- package/migrations.json +6 -1
- package/package.json +1 -1
- package/src/migrations/latest/py-agent-ag-ui-hooks/metadata.json +3 -0
- package/src/migrations/latest/py-agent-ag-ui-hooks/migration.d.ts +6 -0
- package/src/migrations/latest/py-agent-ag-ui-hooks/migration.js +113 -0
- package/src/migrations/latest/py-agent-ag-ui-hooks/migration.js.map +1 -0
- package/src/py/agent/__snapshots__/generator.constructs.spec.ts.snap +5 -1
- package/src/py/agent/files/strands/ag-ui/main.py.template +5 -1
- package/src/py/agent/files/strands/common/agent.py.template +5 -1
package/migrations.json
CHANGED
|
@@ -37,6 +37,11 @@
|
|
|
37
37
|
"description": "Surface enableWaf, encryption, encryptionKey and enableKeyRotation on the vended StaticWebsite construct/module",
|
|
38
38
|
"implementation": "./src/migrations/latest/static-website-configurable-waf-encryption/migration"
|
|
39
39
|
},
|
|
40
|
+
"latest-py-agent-ag-ui-hooks": {
|
|
41
|
+
"version": "1.0.0-rc.79",
|
|
42
|
+
"description": "Forward the py#agent Strands hooks to the AG-UI adapter so model and tool errors are reported",
|
|
43
|
+
"implementation": "./src/migrations/latest/py-agent-ag-ui-hooks/migration"
|
|
44
|
+
},
|
|
40
45
|
"v1.0.0-rc.50-0001-modernize-function-props-cast": {
|
|
41
46
|
"version": "1.0.0-rc.50",
|
|
42
47
|
"description": "Replace the legacy angle-bracket FunctionProps type assertion with the modern as syntax in generated API constructs",
|
|
@@ -158,7 +163,7 @@
|
|
|
158
163
|
"implementation": "./src/migrations/v1.0.0-rc.72/0001-py-agent-session-management-support/migration"
|
|
159
164
|
},
|
|
160
165
|
"sync-vended-versions": {
|
|
161
|
-
"version": "1.0.0-rc.
|
|
166
|
+
"version": "1.0.0-rc.79",
|
|
162
167
|
"description": "Sync vended dependency versions and the tracked plugin version to those vended by this release",
|
|
163
168
|
"implementation": "./src/utils/version-upgrade-migration/migration"
|
|
164
169
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
import { type MigrationReturnObject, type Tree } from '@nx/devkit';
|
|
6
|
+
export default function migration(tree: Tree): Promise<MigrationReturnObject>;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/ import { getProjects, joinPathFragments } from "@nx/devkit";
|
|
5
|
+
import { PY_AGENT_GENERATOR_INFO } from "../../../py/agent/generator.js";
|
|
6
|
+
import { addPythonDestructuredImport, applyGritQL, captureGritQLVariable, GRIT_INSERT_PLACEHOLDER, insertViaGritQL, matchGritQL } from "../../../utils/ast.js";
|
|
7
|
+
import { formatFilesInSubtree } from "../../../utils/format.js";
|
|
8
|
+
/**
|
|
9
|
+
* Forward a Strands py#agent's hooks to the AG-UI adapter.
|
|
10
|
+
*
|
|
11
|
+
* `StrandsAgent` builds a fresh `Agent` per `thread_id` by copying the template
|
|
12
|
+
* Agent's kwargs, but deliberately skips `hooks` - Strands keeps only the built
|
|
13
|
+
* `HookRegistry`, so the providers can't be read back off it. An AG-UI agent's
|
|
14
|
+
* `log_model_errors` / `log_tool_errors` therefore never fired for served
|
|
15
|
+
* requests, and model/tool failures were reported as successful runs.
|
|
16
|
+
*
|
|
17
|
+
* The fix hoists the hooks list out of the `Agent(...)` call into an
|
|
18
|
+
* `AGENT_HOOKS` module constant in `agent.py` - whatever the user has put in it,
|
|
19
|
+
* so custom hooks come along - and passes that to `StrandsAgent(...)` in
|
|
20
|
+
* `main.py` as well. `agent.py` is hoisted for every protocol so the constant
|
|
21
|
+
* matches what the generator vends today; only AG-UI needs the `main.py` half.
|
|
22
|
+
*/ // `Agent`'s `hooks` list is invariant, so the hoisted constant has to declare
|
|
23
|
+
// the parameter's own type - an inline literal was inferred from it instead.
|
|
24
|
+
const HOOKS_TYPE = 'list[HookProvider | HookCallback]';
|
|
25
|
+
const HOOKS_MODULE = 'strands.hooks';
|
|
26
|
+
// Anchored on the `get_agent` context manager - the one shape shared by every
|
|
27
|
+
// generated protocol and connection combination - so the constant lands
|
|
28
|
+
// directly above its only use, where the generator vends it.
|
|
29
|
+
const HOIST_HOOKS_PATTERN = `language python
|
|
30
|
+
\`@contextmanager
|
|
31
|
+
def get_agent($params):
|
|
32
|
+
$body\` => raw\`AGENT_HOOKS: ${HOOKS_TYPE} = ${GRIT_INSERT_PLACEHOLDER}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@contextmanager
|
|
36
|
+
def get_agent($params):
|
|
37
|
+
$body\` where { $program <: not contains \`AGENT_HOOKS\` }`;
|
|
38
|
+
// Scoped to the Agent constructor so a hooks list the user passes elsewhere in
|
|
39
|
+
// agent.py is left alone.
|
|
40
|
+
const HOOKS_LIST_CAPTURE_PATTERN = 'language python\n`hooks=[$hooks]` where { $hooks <: within `Agent($_)` }';
|
|
41
|
+
const HOOKS_LIST_REWRITE_PATTERN = 'language python\n`hooks=[$_]` as $kwarg where { $kwarg <: within `Agent($_)`, $kwarg => `hooks=AGENT_HOOKS` }';
|
|
42
|
+
const AGUI_CONSTRUCTOR_MATCH_PATTERN = 'language python\n`StrandsAgent($args)` where { $args <: contains `agent=$_` }';
|
|
43
|
+
const AGUI_HOOKS_WIRED_PATTERN = 'language python\n`StrandsAgent($args)` where { $args <: contains `agent=$_`, $args <: contains `hooks=$_` }';
|
|
44
|
+
// Appends after the last argument rather than re-emitting the argument list,
|
|
45
|
+
// both to land `hooks=` where the template puts it and to leave the other
|
|
46
|
+
// arguments' formatting and comments untouched.
|
|
47
|
+
const AGUI_HOOKS_APPEND_PATTERN = 'language python\n`StrandsAgent($args)` where { $args <: contains `agent=$_`, $args <: not contains `hooks=$_`, $args <: [$..., $last], $last += `, hooks=AGENT_HOOKS` }';
|
|
48
|
+
const findAgentComponents = (components)=>(components ?? []).filter((component)=>component.generator === PY_AGENT_GENERATOR_INFO.id);
|
|
49
|
+
/**
|
|
50
|
+
* Hoists `agent.py`'s `hooks=[...]` list into an `AGENT_HOOKS` module constant,
|
|
51
|
+
* returning whether the file ends up exporting one.
|
|
52
|
+
*/ const hoistAgentHooks = async (tree, agentPath)=>{
|
|
53
|
+
if ((tree.read(agentPath, 'utf-8') ?? '').includes('AGENT_HOOKS')) {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
// Nothing to hoist unless the Agent is constructed with an inline hooks list.
|
|
57
|
+
const hooks = await captureGritQLVariable(tree, agentPath, HOOKS_LIST_CAPTURE_PATTERN, 'hooks');
|
|
58
|
+
if (!hooks) return false;
|
|
59
|
+
if (!await insertViaGritQL(tree, agentPath, HOIST_HOOKS_PATTERN, `[${hooks}]`)) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
await applyGritQL(tree, agentPath, HOOKS_LIST_REWRITE_PATTERN);
|
|
63
|
+
await addPythonDestructuredImport(tree, agentPath, [
|
|
64
|
+
'HookCallback',
|
|
65
|
+
'HookProvider'
|
|
66
|
+
], HOOKS_MODULE);
|
|
67
|
+
return true;
|
|
68
|
+
};
|
|
69
|
+
const migrateAgent = async (tree, agentDir, component, nextSteps)=>{
|
|
70
|
+
// LangChain agents pass no hooks - their AG-UI adapter is handed the graph
|
|
71
|
+
// itself rather than rebuilding one, so nothing is dropped.
|
|
72
|
+
if (component.framework === 'langchain') return;
|
|
73
|
+
const agentPath = joinPathFragments(agentDir, 'agent.py');
|
|
74
|
+
const mainPath = joinPathFragments(agentDir, 'main.py');
|
|
75
|
+
const isAgUi = component.protocol === 'ag-ui';
|
|
76
|
+
if (!tree.exists(agentPath)) return;
|
|
77
|
+
if (!await hoistAgentHooks(tree, agentPath)) {
|
|
78
|
+
if (isAgUi) {
|
|
79
|
+
nextSteps.push(`${agentPath}: the hooks passed to \`Agent(...)\` have diverged from the generated shape - left untouched. Hoist them into an \`AGENT_HOOKS\` module constant and pass it to \`StrandsAgent(...)\` in ${mainPath} too, or AG-UI will not register them (see the py#agent generator's template).`);
|
|
80
|
+
}
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
// Only AG-UI rebuilds the Agent, so only its main.py needs the hooks.
|
|
84
|
+
if (!isAgUi || !tree.exists(mainPath)) return;
|
|
85
|
+
// Already passing hooks, whether from a previous run or the user's own wiring.
|
|
86
|
+
if (await matchGritQL(tree, mainPath, AGUI_HOOKS_WIRED_PATTERN)) return;
|
|
87
|
+
if (!await matchGritQL(tree, mainPath, AGUI_CONSTRUCTOR_MATCH_PATTERN)) {
|
|
88
|
+
nextSteps.push(`${mainPath}: the \`StrandsAgent(...)\` constructor has diverged from the generated shape - left untouched. Pass \`hooks=AGENT_HOOKS\` to it, importing \`AGENT_HOOKS\` from \`.agent\`, or AG-UI will not register the agent's hooks (see the py#agent generator's template).`);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
await applyGritQL(tree, mainPath, AGUI_HOOKS_APPEND_PATTERN);
|
|
92
|
+
await addPythonDestructuredImport(tree, mainPath, [
|
|
93
|
+
'AGENT_HOOKS'
|
|
94
|
+
], '.agent');
|
|
95
|
+
};
|
|
96
|
+
export default async function migration(tree) {
|
|
97
|
+
const nextSteps = [];
|
|
98
|
+
for (const project of getProjects(tree).values()){
|
|
99
|
+
const components = findAgentComponents(project.metadata?.components);
|
|
100
|
+
for (const component of components){
|
|
101
|
+
if (!component.path) continue;
|
|
102
|
+
// Legacy component paths point at agent.py rather than its directory.
|
|
103
|
+
const componentDir = component.path.endsWith('/agent.py') ? component.path.slice(0, -'/agent.py'.length) : component.path;
|
|
104
|
+
await migrateAgent(tree, joinPathFragments(project.root, componentDir), component, nextSteps);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
await formatFilesInSubtree(tree);
|
|
108
|
+
return {
|
|
109
|
+
nextSteps
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
//# sourceMappingURL=migration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../packages/nx-plugin/src/migrations/latest/py-agent-ag-ui-hooks/migration.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport {\n getProjects,\n joinPathFragments,\n type MigrationReturnObject,\n type Tree,\n} from '@nx/devkit';\nimport { PY_AGENT_GENERATOR_INFO } from '../../../py/agent/generator';\nimport {\n addPythonDestructuredImport,\n applyGritQL,\n captureGritQLVariable,\n GRIT_INSERT_PLACEHOLDER,\n insertViaGritQL,\n matchGritQL,\n} from '../../../utils/ast';\nimport { formatFilesInSubtree } from '../../../utils/format';\nimport type { ComponentMetadata } from '../../../utils/nx';\n\n/**\n * Forward a Strands py#agent's hooks to the AG-UI adapter.\n *\n * `StrandsAgent` builds a fresh `Agent` per `thread_id` by copying the template\n * Agent's kwargs, but deliberately skips `hooks` - Strands keeps only the built\n * `HookRegistry`, so the providers can't be read back off it. An AG-UI agent's\n * `log_model_errors` / `log_tool_errors` therefore never fired for served\n * requests, and model/tool failures were reported as successful runs.\n *\n * The fix hoists the hooks list out of the `Agent(...)` call into an\n * `AGENT_HOOKS` module constant in `agent.py` - whatever the user has put in it,\n * so custom hooks come along - and passes that to `StrandsAgent(...)` in\n * `main.py` as well. `agent.py` is hoisted for every protocol so the constant\n * matches what the generator vends today; only AG-UI needs the `main.py` half.\n */\n\n// `Agent`'s `hooks` list is invariant, so the hoisted constant has to declare\n// the parameter's own type - an inline literal was inferred from it instead.\nconst HOOKS_TYPE = 'list[HookProvider | HookCallback]';\nconst HOOKS_MODULE = 'strands.hooks';\n\n// Anchored on the `get_agent` context manager - the one shape shared by every\n// generated protocol and connection combination - so the constant lands\n// directly above its only use, where the generator vends it.\nconst HOIST_HOOKS_PATTERN = `language python\n\\`@contextmanager\ndef get_agent($params):\n $body\\` => raw\\`AGENT_HOOKS: ${HOOKS_TYPE} = ${GRIT_INSERT_PLACEHOLDER}\n\n\n@contextmanager\ndef get_agent($params):\n $body\\` where { $program <: not contains \\`AGENT_HOOKS\\` }`;\n\n// Scoped to the Agent constructor so a hooks list the user passes elsewhere in\n// agent.py is left alone.\nconst HOOKS_LIST_CAPTURE_PATTERN =\n 'language python\\n`hooks=[$hooks]` where { $hooks <: within `Agent($_)` }';\nconst HOOKS_LIST_REWRITE_PATTERN =\n 'language python\\n`hooks=[$_]` as $kwarg where { $kwarg <: within `Agent($_)`, $kwarg => `hooks=AGENT_HOOKS` }';\n\nconst AGUI_CONSTRUCTOR_MATCH_PATTERN =\n 'language python\\n`StrandsAgent($args)` where { $args <: contains `agent=$_` }';\nconst AGUI_HOOKS_WIRED_PATTERN =\n 'language python\\n`StrandsAgent($args)` where { $args <: contains `agent=$_`, $args <: contains `hooks=$_` }';\n// Appends after the last argument rather than re-emitting the argument list,\n// both to land `hooks=` where the template puts it and to leave the other\n// arguments' formatting and comments untouched.\nconst AGUI_HOOKS_APPEND_PATTERN =\n 'language python\\n`StrandsAgent($args)` where { $args <: contains `agent=$_`, $args <: not contains `hooks=$_`, $args <: [$..., $last], $last += `, hooks=AGENT_HOOKS` }';\n\nconst findAgentComponents = (\n components: ComponentMetadata[] | undefined,\n): ComponentMetadata[] =>\n (components ?? []).filter(\n (component) => component.generator === PY_AGENT_GENERATOR_INFO.id,\n );\n\n/**\n * Hoists `agent.py`'s `hooks=[...]` list into an `AGENT_HOOKS` module constant,\n * returning whether the file ends up exporting one.\n */\nconst hoistAgentHooks = async (\n tree: Tree,\n agentPath: string,\n): Promise<boolean> => {\n if ((tree.read(agentPath, 'utf-8') ?? '').includes('AGENT_HOOKS')) {\n return true;\n }\n\n // Nothing to hoist unless the Agent is constructed with an inline hooks list.\n const hooks = await captureGritQLVariable(\n tree,\n agentPath,\n HOOKS_LIST_CAPTURE_PATTERN,\n 'hooks',\n );\n if (!hooks) return false;\n\n if (\n !(await insertViaGritQL(tree, agentPath, HOIST_HOOKS_PATTERN, `[${hooks}]`))\n ) {\n return false;\n }\n await applyGritQL(tree, agentPath, HOOKS_LIST_REWRITE_PATTERN);\n await addPythonDestructuredImport(\n tree,\n agentPath,\n ['HookCallback', 'HookProvider'],\n HOOKS_MODULE,\n );\n return true;\n};\n\nconst migrateAgent = async (\n tree: Tree,\n agentDir: string,\n component: ComponentMetadata,\n nextSteps: string[],\n): Promise<void> => {\n // LangChain agents pass no hooks - their AG-UI adapter is handed the graph\n // itself rather than rebuilding one, so nothing is dropped.\n if (component.framework === 'langchain') return;\n\n const agentPath = joinPathFragments(agentDir, 'agent.py');\n const mainPath = joinPathFragments(agentDir, 'main.py');\n const isAgUi = component.protocol === 'ag-ui';\n\n if (!tree.exists(agentPath)) return;\n\n if (!(await hoistAgentHooks(tree, agentPath))) {\n if (isAgUi) {\n nextSteps.push(\n `${agentPath}: the hooks passed to \\`Agent(...)\\` have diverged from the generated shape - left untouched. Hoist them into an \\`AGENT_HOOKS\\` module constant and pass it to \\`StrandsAgent(...)\\` in ${mainPath} too, or AG-UI will not register them (see the py#agent generator's template).`,\n );\n }\n return;\n }\n\n // Only AG-UI rebuilds the Agent, so only its main.py needs the hooks.\n if (!isAgUi || !tree.exists(mainPath)) return;\n\n // Already passing hooks, whether from a previous run or the user's own wiring.\n if (await matchGritQL(tree, mainPath, AGUI_HOOKS_WIRED_PATTERN)) return;\n\n if (!(await matchGritQL(tree, mainPath, AGUI_CONSTRUCTOR_MATCH_PATTERN))) {\n nextSteps.push(\n `${mainPath}: the \\`StrandsAgent(...)\\` constructor has diverged from the generated shape - left untouched. Pass \\`hooks=AGENT_HOOKS\\` to it, importing \\`AGENT_HOOKS\\` from \\`.agent\\`, or AG-UI will not register the agent's hooks (see the py#agent generator's template).`,\n );\n return;\n }\n\n await applyGritQL(tree, mainPath, AGUI_HOOKS_APPEND_PATTERN);\n await addPythonDestructuredImport(tree, mainPath, ['AGENT_HOOKS'], '.agent');\n};\n\nexport default async function migration(\n tree: Tree,\n): Promise<MigrationReturnObject> {\n const nextSteps: string[] = [];\n\n for (const project of getProjects(tree).values()) {\n const components = findAgentComponents(\n (project.metadata as { components?: ComponentMetadata[] })?.components,\n );\n\n for (const component of components) {\n if (!component.path) continue;\n\n // Legacy component paths point at agent.py rather than its directory.\n const componentDir = component.path.endsWith('/agent.py')\n ? component.path.slice(0, -'/agent.py'.length)\n : component.path;\n\n await migrateAgent(\n tree,\n joinPathFragments(project.root, componentDir),\n component,\n nextSteps,\n );\n }\n }\n\n await formatFilesInSubtree(tree);\n\n return { nextSteps };\n}\n"],"names":["getProjects","joinPathFragments","PY_AGENT_GENERATOR_INFO","addPythonDestructuredImport","applyGritQL","captureGritQLVariable","GRIT_INSERT_PLACEHOLDER","insertViaGritQL","matchGritQL","formatFilesInSubtree","HOOKS_TYPE","HOOKS_MODULE","HOIST_HOOKS_PATTERN","HOOKS_LIST_CAPTURE_PATTERN","HOOKS_LIST_REWRITE_PATTERN","AGUI_CONSTRUCTOR_MATCH_PATTERN","AGUI_HOOKS_WIRED_PATTERN","AGUI_HOOKS_APPEND_PATTERN","findAgentComponents","components","filter","component","generator","id","hoistAgentHooks","tree","agentPath","read","includes","hooks","migrateAgent","agentDir","nextSteps","framework","mainPath","isAgUi","protocol","exists","push","migration","project","values","metadata","path","componentDir","endsWith","slice","length","root"],"mappings":"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"}
|
|
@@ -665,6 +665,7 @@ exports[`py#agent generator > should match snapshot for generated files > agent-
|
|
|
665
665
|
|
|
666
666
|
from proj_agent_connection import log_model_errors, log_tool_errors
|
|
667
667
|
from strands import Agent, tool
|
|
668
|
+
from strands.hooks import HookCallback, HookProvider
|
|
668
669
|
from strands_tools import current_time
|
|
669
670
|
|
|
670
671
|
from .session import get_session_manager
|
|
@@ -675,6 +676,9 @@ def subtract(a: int, b: int) -> int:
|
|
|
675
676
|
return a - b
|
|
676
677
|
|
|
677
678
|
|
|
679
|
+
AGENT_HOOKS: list[HookProvider | HookCallback] = [log_model_errors, log_tool_errors]
|
|
680
|
+
|
|
681
|
+
|
|
678
682
|
@contextmanager
|
|
679
683
|
def get_agent():
|
|
680
684
|
yield Agent(
|
|
@@ -686,7 +690,7 @@ Use your tools for mathematical tasks.
|
|
|
686
690
|
Refer to tools as your 'spellbook'.
|
|
687
691
|
""",
|
|
688
692
|
tools=[subtract, current_time],
|
|
689
|
-
hooks=
|
|
693
|
+
hooks=AGENT_HOOKS,
|
|
690
694
|
session_manager=get_session_manager(),
|
|
691
695
|
)
|
|
692
696
|
"
|
|
@@ -10,7 +10,7 @@ from fastapi.middleware.cors import CORSMiddleware
|
|
|
10
10
|
from fastapi.responses import StreamingResponse
|
|
11
11
|
from <%- agentConnectionModuleName %> import get_current_session_id, session_id_context
|
|
12
12
|
|
|
13
|
-
from .agent import get_agent
|
|
13
|
+
from .agent import AGENT_HOOKS, get_agent
|
|
14
14
|
from .middleware.session_id_middleware import SESSION_ID_HEADER, SessionIdMiddleware
|
|
15
15
|
from .session import get_session_manager
|
|
16
16
|
|
|
@@ -27,6 +27,10 @@ async def lifespan(app: FastAPI):
|
|
|
27
27
|
# A per-thread session manager, not the template Agent's own, since
|
|
28
28
|
# AG-UI caches one Strands agent per thread_id.
|
|
29
29
|
config=StrandsAgentConfig(session_manager_provider=lambda _input_data: get_session_manager()),
|
|
30
|
+
# Required as well as on the template Agent: AG-UI keeps only the
|
|
31
|
+
# built HookRegistry, so hooks it can't read back are never
|
|
32
|
+
# registered and model/tool failures go unreported.
|
|
33
|
+
hooks=AGENT_HOOKS,
|
|
30
34
|
)
|
|
31
35
|
yield
|
|
32
36
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from contextlib import contextmanager
|
|
2
2
|
|
|
3
3
|
from strands import Agent, tool
|
|
4
|
+
from strands.hooks import HookCallback, HookProvider
|
|
4
5
|
from strands_tools import current_time
|
|
5
6
|
from <%- agentConnectionModuleName %> import log_model_errors, log_tool_errors
|
|
6
7
|
<%_ if (protocol !== 'ag-ui') { _%>
|
|
@@ -14,6 +15,9 @@ def subtract(a: int, b: int) -> int:
|
|
|
14
15
|
return a - b
|
|
15
16
|
|
|
16
17
|
|
|
18
|
+
AGENT_HOOKS: list[HookProvider | HookCallback] = [log_model_errors, log_tool_errors]
|
|
19
|
+
|
|
20
|
+
|
|
17
21
|
@contextmanager
|
|
18
22
|
def get_agent():
|
|
19
23
|
yield Agent(
|
|
@@ -25,7 +29,7 @@ Use your tools for mathematical tasks.
|
|
|
25
29
|
Refer to tools as your 'spellbook'.
|
|
26
30
|
""",
|
|
27
31
|
tools=[subtract, current_time],
|
|
28
|
-
hooks=
|
|
32
|
+
hooks=AGENT_HOOKS,
|
|
29
33
|
<%_ if (protocol !== 'ag-ui') { _%>
|
|
30
34
|
session_manager=get_session_manager(),
|
|
31
35
|
<%_ } _%>
|