Skip to content

Commit 005e92d

Browse files
authored
feat: parallel subagent invocations (#69)
1 parent 277a83e commit 005e92d

27 files changed

Lines changed: 3495 additions & 1851 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
__pycache__/
55
*.egg-info
66
dist
7+
build
78
.env
89

910
# Directory for local CDK context files

README.md

Lines changed: 91 additions & 39 deletions
Large diffs are not rendered by default.

examples/multi_agent.ipynb

Lines changed: 104 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"\n",
1919
"We can implement the multi-agent architecture by simply registering subordinate agents as tools with a higher-level agent. To be able to use an agent as tool, the agent must have a `name` and a `description`. Providing an `input_schema` is optional, but can be used to signal to the supervisor agent that it should send certain fields to the subordinate agent.\n",
2020
"\n",
21-
"In the example here we just have a 2-level hierarchy: one supervisor, and 2 subordinate agents. You could add more levels if you want; one of the subordinate agents could itself be a supervisor to its own set of subordinate agents."
21+
"In the example here we just have a 2-level hierarchy: one supervisor, and 2 subordinate agents. You could add more levels if you want; one of the subordinate agents could itself be a supervisor to its own set of subordinate agents.\n"
2222
]
2323
},
2424
{
@@ -39,7 +39,7 @@
3939
"id": "00d4232c",
4040
"metadata": {},
4141
"source": [
42-
"## Weather agent"
42+
"## Weather agent\n"
4343
]
4444
},
4545
{
@@ -75,7 +75,7 @@
7575
"id": "b8a8e867",
7676
"metadata": {},
7777
"source": [
78-
"## Events agent"
78+
"## Events agent\n"
7979
]
8080
},
8181
{
@@ -130,7 +130,7 @@
130130
"id": "415af9d9",
131131
"metadata": {},
132132
"source": [
133-
"## Supervisor agent"
133+
"## Supervisor agent\n"
134134
]
135135
},
136136
{
@@ -178,7 +178,7 @@
178178
"source": [
179179
"## Chat with the supervisor agent\n",
180180
"\n",
181-
"The supervisor agent will use the subordinate agents to help the user:"
181+
"The supervisor agent will use the subordinate agents to help the user:\n"
182182
]
183183
},
184184
{
@@ -202,7 +202,9 @@
202202
"source": [
203203
"## Tracing\n",
204204
"\n",
205-
"Each agent has its own set of traces that you can inspect"
205+
"### All traces from supervisor (includes subagent traces)\n",
206+
"\n",
207+
"The supervisor's traces include all traces from subagent invocations:\n"
206208
]
207209
},
208210
{
@@ -216,32 +218,127 @@
216218
"supervisor_ui.launch()"
217219
]
218220
},
221+
{
222+
"cell_type": "markdown",
223+
"id": "subcontext_explanation",
224+
"metadata": {},
225+
"source": [
226+
"### Accessing individual subagent traces\n",
227+
"\n",
228+
"When agents are used as tools, each invocation gets its own **subcontext ID** to maintain separate conversational memory. This enables:\n",
229+
"\n",
230+
"- **Parallel invocations**: The supervisor could call both agents simultaneously, each with separate memory\n",
231+
"- **Sequential invocations without memory carryover**: Calling the weather agent twice won't leak context between invocations\n",
232+
"- **Follow-up messages**: You can send additional messages to a specific subagent invocation by reusing its subcontext ID\n",
233+
"\n",
234+
"To view traces for a specific subagent invocation, we need to:\n",
235+
"\n",
236+
"1. Extract the subcontext ID from the supervisor's traces\n",
237+
"2. Set the correct conversation ID and subcontext ID on the subagent\n",
238+
"3. Then access the subagent's traces\n",
239+
"\n",
240+
"First, let's add a helper function to extract subcontext IDs:\n"
241+
]
242+
},
243+
{
244+
"cell_type": "code",
245+
"execution_count": null,
246+
"id": "helper_function",
247+
"metadata": {},
248+
"outputs": [],
249+
"source": [
250+
"from collections import defaultdict\n",
251+
"\n",
252+
"\n",
253+
"def get_subagent_subcontext_ids(traces):\n",
254+
" \"\"\"\n",
255+
" Extract subcontext IDs used for each subagent invocation from traces.\n",
256+
"\n",
257+
" Returns a dict mapping subagent name -> set of subcontext IDs used\n",
258+
" \"\"\"\n",
259+
" result = defaultdict(set)\n",
260+
"\n",
261+
" for trace in traces:\n",
262+
" if (\n",
263+
" trace.attributes.get(\"ai.trace.type\") == \"tool-invocation\"\n",
264+
" and \"ai.tool.subagent.subcontext.id\" in trace.attributes\n",
265+
" and \"ai.tool.name\" in trace.attributes\n",
266+
" ):\n",
267+
" subagent_name = trace.attributes[\"ai.tool.name\"]\n",
268+
" subcontext_id = trace.attributes[\"ai.tool.subagent.subcontext.id\"]\n",
269+
" result[subagent_name].add(subcontext_id)\n",
270+
"\n",
271+
" return result"
272+
]
273+
},
274+
{
275+
"cell_type": "markdown",
276+
"id": "weather_agent_traces_explanation",
277+
"metadata": {},
278+
"source": [
279+
"Now let's access the weather agent's traces for this specific invocation:\n"
280+
]
281+
},
219282
{
220283
"cell_type": "code",
221284
"execution_count": null,
222285
"id": "995050bc",
223286
"metadata": {},
224287
"outputs": [],
225288
"source": [
289+
"# Store the conversation ID\n",
290+
"conversation_id = supervisor.conversation_id\n",
291+
"\n",
292+
"# Extract subcontext IDs from the supervisor's traces\n",
293+
"subcontext_ids = get_subagent_subcontext_ids(supervisor.traces)\n",
294+
"\n",
295+
"# Get the specific subcontext_id used for weather_agent\n",
296+
"weather_subcontext_id = subcontext_ids[\"transfer_to_weather_agent\"].pop()\n",
297+
"\n",
298+
"# Set the weather agent to the correct conversation and subcontext\n",
299+
"weather_agent.set_conversation_id(conversation_id, subcontext_id=weather_subcontext_id)\n",
300+
"\n",
301+
"# Now weather_agent.traces returns the correct traces for this invocation\n",
302+
"print(f\"Weather agent subcontext ID: {weather_subcontext_id}\")\n",
303+
"print(f\"Number of traces: {len(weather_agent.traces)}\")\n",
304+
"\n",
226305
"weather_agent_ui = traces_ui(weather_agent.traces)\n",
227306
"weather_agent_ui.launch()"
228307
]
229308
},
309+
{
310+
"cell_type": "markdown",
311+
"id": "events_agent_traces_explanation",
312+
"metadata": {},
313+
"source": [
314+
"And the events agent's traces:\n"
315+
]
316+
},
230317
{
231318
"cell_type": "code",
232319
"execution_count": null,
233320
"id": "bc2a53db",
234321
"metadata": {},
235322
"outputs": [],
236323
"source": [
324+
"# Get the specific subcontext_id used for events_agent\n",
325+
"events_subcontext_id = subcontext_ids[\"transfer_to_events_agent\"].pop()\n",
326+
"\n",
327+
"# Set the events agent to the correct conversation and subcontext\n",
328+
"events_agent.set_conversation_id(conversation_id, subcontext_id=events_subcontext_id)\n",
329+
"\n",
330+
"# Now events_agent.traces returns the correct traces for this invocation\n",
331+
"print(f\"Events agent subcontext ID: {events_subcontext_id}\")\n",
332+
"print(f\"Number of traces: {len(events_agent.traces)}\")\n",
333+
"\n",
237334
"events_agent_ui = traces_ui(events_agent.traces)\n",
238335
"events_agent_ui.launch()"
239336
]
240337
}
241338
],
242339
"metadata": {
243340
"kernelspec": {
244-
"display_name": ".venv",
341+
"display_name": "generative-ai-toolkit",
245342
"language": "python",
246343
"name": "python3"
247344
},

examples/tracing101.ipynb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,8 @@
152152
"\n",
153153
"for trace in in_memory_tracer.get_traces(\n",
154154
" attribute_filter={\n",
155-
" \"ai.conversation.id\": conversation_id # filter traces by conversation id\n",
155+
" \"ai.conversation.id\": conversation_id, # filter traces by conversation id\n",
156+
" \"ai.subcontext.id\": None\n",
156157
" }\n",
157158
"):\n",
158159
" print(trace.as_human_readable())\n",
@@ -361,6 +362,7 @@
361362
" attribute_filter={\n",
362363
" \"ai.conversation.id\": conversation_id,\n",
363364
" \"ai.auth.context\": auth_context,\n",
365+
" \"ai.subcontext.id\": None\n",
364366
" }\n",
365367
"):\n",
366368
" print(trace.as_human_readable())\n",
@@ -681,6 +683,7 @@
681683
" attribute_filter={\n",
682684
" \"ai.conversation.id\": conversation_id,\n",
683685
" \"ai.auth.context\": auth_context,\n",
686+
" \"ai.subcontext.id\": None\n",
684687
" }\n",
685688
"):\n",
686689
" print(trace.as_human_readable())\n",

pyproject.toml

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -37,39 +37,26 @@ run-agent = [
3737
"opentelemetry-proto>=1.31,<2.0",
3838
"pydantic>=2.10,<3.0",
3939
]
40-
evaluate = ["nltk>=3.9,<4.0", "scikit-learn>=1.5,<2.0"]
40+
evaluate = [
41+
"nltk>=3.9,<4.0",
42+
"scikit-learn>=1.5,<2.0",
43+
]
4144
all = [
45+
"generative-ai-toolkit[run-agent,evaluate]",
4246
"boto3-stubs[bedrock-runtime,dynamodb]>=1.37,<2.0",
43-
"flask>=3.1,<4.0",
4447
"gradio>=5.23,<6.0",
45-
"gunicorn>=23.0,<24.0",
4648
"ipython>=8.30,<9.0",
47-
"nltk>=3.9,<4.0",
4849
"mcp>=1.8,<2.0",
49-
"opentelemetry-proto>=1.31,<2.0",
5050
"pandas>=2.2,<3.0",
51-
"pydantic>=2.10,<3.0",
52-
"scikit-learn>=1.5,<2.0",
5351
"tabulate>=0.9,<1.0",
5452
]
5553
dev = [
56-
"boto3-stubs[bedrock-runtime,dynamodb]>=1.37,<2.0",
57-
"flask>=3.1,<4.0",
58-
"gradio>=5.23,<6.0",
59-
"gunicorn>=23.0,<24.0",
60-
"ipython>=8.30,<9.0",
61-
"mcp>=1.8,<2.0",
62-
"nltk>=3.9,<4.0",
63-
"opentelemetry-proto>=1.31,<2.0",
64-
"pandas>=2.2,<3.0",
54+
"generative-ai-toolkit[all]",
6555
"playwright>=1.52,<2.0",
66-
"pydantic>=2.10,<3.0",
56+
"pytest>=8.4,<9.0",
6757
"pytest-cov>=6.2,<7.0",
6858
"pytest-playwright>=0.7,<0.8",
69-
"pytest>=8.4,<9.0",
7059
"ruff>=0.12,<0.13",
71-
"scikit-learn>=1.5,<2.0",
72-
"tabulate>=0.9,<1.0",
7360
]
7461

7562
[tool.setuptools.packages.find]

src/generative_ai_toolkit/agent/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
from generative_ai_toolkit.agent.agent import Agent, BedrockConverseAgent
15+
from generative_ai_toolkit.agent.agent import Agent
16+
from generative_ai_toolkit.agent.bedrock_converse_agent import BedrockConverseAgent
1617
from generative_ai_toolkit.agent.tool import BedrockConverseTool, Tool
1718

1819
__all__ = ["BedrockConverseAgent", "BedrockConverseTool", "Agent", "Tool"]

0 commit comments

Comments
 (0)