VibecoderMcSwaggins commited on
Commit
02a3c53
Β·
unverified Β·
1 Parent(s): ac752cb

fix(deps): SPEC-18 Agent Framework Core Upgrade to 1.0.0b251204 (#131)

Browse files

* fix(deps): Upgrade agent-framework-core to 1.0.0b251204 (SPEC-18)

Breaking API changes in upstream require event class migration:
- MagenticAgentDeltaEvent β†’ AgentRunUpdateEvent
- MagenticAgentMessageEvent β†’ ExecutorCompletedEvent
- MagenticFinalResultEvent β†’ (removed, use WorkflowOutputEvent)

Upstream fixed the repr bug we worked around in SPEC-17.
Keep Accumulator Pattern as safety net for now.

Closes P0: HuggingFace Spaces deployment blocked

* docs: Add SPEC-18 documentation and fix test docstring

- Add docs/specs/SPEC_18_AGENT_FRAMEWORK_UPGRADE.md with full migration guide
- Update test_accumulator_pattern.py docstring to reference new event classes

Part of SPEC-18: Agent Framework Core Upgrade

* refactor: Address CodeRabbit review feedback

- Use defensive getattr for event.data.text (consistency)
- Remove redundant isinstance check in _handle_final_event
- Update sync dates in requirements.txt and workflow-diagrams.md
- Add language specifier to SPEC-18 code block

Note: CodeRabbit's "critical" issue about agent= vs manager= was
verified as INCORRECT - the API accepts both parameters.

.pre-commit-config.yaml CHANGED
@@ -18,5 +18,5 @@ repos:
18
  - pydantic-settings>=2.2
19
  - tenacity>=8.2
20
  - pydantic-ai>=0.0.16
21
- - agent-framework-core>=1.0.0b251120
22
  args: [--ignore-missing-imports]
 
18
  - pydantic-settings>=2.2
19
  - tenacity>=8.2
20
  - pydantic-ai>=0.0.16
21
+ - agent-framework-core==1.0.0b251204
22
  args: [--ignore-missing-imports]
docs/specs/SPEC_18_AGENT_FRAMEWORK_UPGRADE.md ADDED
@@ -0,0 +1,459 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPEC-18: Agent Framework Core Upgrade Strategy
2
+
3
+ **Status**: APPROVED - Senior Review Complete
4
+ **Created**: 2025-12-04
5
+ **Priority**: P0 (Blocking HuggingFace Spaces deployment)
6
+ **Estimated Effort**: 2-4 hours (implementation + testing)
7
+
8
+ ---
9
+
10
+ ## Executive Summary
11
+
12
+ The `agent-framework-core` package released version `1.0.0b251204` on 2025-12-04 with breaking API changes. HuggingFace Spaces pulls this new version, breaking our app with:
13
+
14
+ ```text
15
+ cannot import name 'MagenticAgentDeltaEvent' from 'agent_framework'
16
+ ```
17
+
18
+ **Key Insight**: The upstream release **FIXED the repr bug** that we reported and worked around with our Accumulator Pattern (SPEC-17). This creates an opportunity to simplify our codebase.
19
+
20
+ **Recommendation**: UPGRADE to latest version (not pin to old) because:
21
+ 1. The repr bug fix means we can simplify our Accumulator Pattern
22
+ 2. Beta versions move fast - staying current is better long-term
23
+ 3. The migration is smaller than initially estimated (~30 lines to change)
24
+
25
+ ---
26
+
27
+ ## Root Cause Analysis
28
+
29
+ ### Why This Happened
30
+
31
+ 1. **Dependency drift**: Our `requirements.txt` used `>=1.0.0b251120,<2.0.0` (range)
32
+ 2. **New release**: Microsoft released `1.0.0b251204` on the SAME DAY (Dec 4, 2025)
33
+ 3. **HuggingFace behavior**: Fresh installs pull latest matching version
34
+ 4. **Breaking changes**: New version removed 3 event classes we depend on
35
+
36
+ ### Pattern Recognition
37
+
38
+ This is the SECOND dependency-related P0 today:
39
+ 1. **MCP ToolUseContent** - Same pattern (requirements.txt drift)
40
+ 2. **Agent Framework events** - Same pattern (requirements.txt drift)
41
+
42
+ **Lesson**: Beta dependencies need EXACT pinning, not ranges.
43
+
44
+ ---
45
+
46
+ ## Version Comparison
47
+
48
+ | Version | Date | Status |
49
+ |---------|------|--------|
50
+ | `1.0.0b251120` | Nov 20, 2025 | Our current version (has repr bug) |
51
+ | `1.0.0b251204` | Dec 4, 2025 | Latest (fixes repr bug, breaking API) |
52
+
53
+ ### What Microsoft Changed
54
+
55
+ From [GitHub releases](https://github.com/microsoft/agent-framework/releases):
56
+
57
+ 1. **"Standardized orchestration outputs"** - Unified event system
58
+ 2. **"Fixed MagenticAgentExecutor from producing repr string"** - Our bug is fixed!
59
+ 3. **Event class refactoring** - Magentic-specific β†’ Generic events
60
+
61
+ ---
62
+
63
+ ## API Changes (Complete Analysis)
64
+
65
+ ### Classes REMOVED (Breaking)
66
+
67
+ ```python
68
+ # OLD (1.0.0b251120) - These no longer exist
69
+ MagenticAgentDeltaEvent # Streaming text chunks
70
+ MagenticAgentMessageEvent # Agent turn completion
71
+ MagenticFinalResultEvent # Final result
72
+ ```
73
+
74
+ ### Classes ADDED (Replacements)
75
+
76
+ ```python
77
+ # NEW (1.0.0b251204) - Use these instead
78
+ AgentRunUpdateEvent # Streaming updates
79
+ └── .data: AgentRunResponseUpdate
80
+ └── .text: str # Streaming text (same data!)
81
+ └── .contents: list # Content blocks
82
+ └── .author_name: str # Agent ID (was .agent_id)
83
+ └── .role: str # Role (assistant, etc.)
84
+
85
+ ExecutorCompletedEvent # Agent turn complete (CRITICAL!)
86
+ └── .executor_id: str # Which agent completed
87
+ └── .data: Any # Completion data
88
+
89
+ WorkflowOutputEvent # Workflow finished (unchanged)
90
+ ```
91
+
92
+ ### Classes UNCHANGED
93
+
94
+ ```python
95
+ # These work the same in both versions
96
+ MagenticBuilder βœ…
97
+ MagenticContext βœ…
98
+ MagenticOrchestratorMessageEvent βœ… # Still exists!
99
+ WorkflowOutputEvent βœ…
100
+ BaseChatClient βœ…
101
+ ChatAgent βœ…
102
+ ai_function βœ…
103
+ ```
104
+
105
+ ### Migration Mapping
106
+
107
+ | OLD | NEW | Notes |
108
+ |-----|-----|-------|
109
+ | `MagenticAgentDeltaEvent.text` | `AgentRunUpdateEvent.data.text` | Same data, extra `.data` level |
110
+ | `MagenticAgentDeltaEvent.agent_id` | `AgentRunUpdateEvent.data.author_name` | Renamed field |
111
+ | `MagenticAgentMessageEvent` | `ExecutorCompletedEvent` | **CRITICAL**: Agent turn complete signal |
112
+ | `MagenticAgentMessageEvent.agent_id` | `ExecutorCompletedEvent.executor_id` | Agent identifier |
113
+ | `MagenticFinalResultEvent` | `WorkflowOutputEvent` | Workflow complete |
114
+ | `MagenticOrchestratorMessageEvent` | `MagenticOrchestratorMessageEvent` | UNCHANGED βœ… |
115
+
116
+ ---
117
+
118
+ ## Files Requiring Changes
119
+
120
+ ### Critical (Must Change)
121
+
122
+ | File | Lines | Changes Required |
123
+ |------|-------|------------------|
124
+ | `src/orchestrators/advanced.py` | 24-31, 327-385 | Import updates + event handling |
125
+ | `tests/unit/orchestrators/test_accumulator_pattern.py` | 17-94 | Mock class updates |
126
+ | `tests/unit/orchestrators/test_advanced_events.py` | 4 | Import update |
127
+ | `requirements.txt` | 49 | Pin exact version |
128
+ | `pyproject.toml` | 64 | Pin exact version |
129
+
130
+ ### Documentation (Update References)
131
+
132
+ | File | Status | Action |
133
+ |------|--------|--------|
134
+ | `docs/workflow-diagrams.md` | Has old class names | Update diagram labels |
135
+ | `docs/specs/archive/SPEC_17_ACCUMULATOR_PATTERN.md` | Archived | Add "Superseded" note |
136
+ | `docs/bugs/archive/P0_REPR_BUG_ROOT_CAUSE_ANALYSIS.md` | Archived | Add "Fixed upstream" note |
137
+ | `docs/architecture/system_registry.md` | Has outdated refs | Update tool inventory |
138
+
139
+ ### Config Files (Pin Versions)
140
+
141
+ | File | Current | Change To |
142
+ |------|---------|-----------|
143
+ | `requirements.txt` | `>=1.0.0b251120,<2.0.0` | `==1.0.0b251204` |
144
+ | `pyproject.toml` | `>=1.0.0b251120,<2.0.0` | `==1.0.0b251204` |
145
+ | `uv.lock` | Auto-generated | Run `uv lock` after |
146
+
147
+ ---
148
+
149
+ ## Implementation Plan
150
+
151
+ ### Phase 1: Version Pinning (5 min)
152
+ ```bash
153
+ # Update requirements.txt and pyproject.toml
154
+ agent-framework-core==1.0.0b251204
155
+
156
+ # Regenerate lock file
157
+ uv lock
158
+ uv sync
159
+ ```
160
+
161
+ ### Phase 2: Import Updates (10 min)
162
+
163
+ **`src/orchestrators/advanced.py` lines 24-31:**
164
+ ```python
165
+ # OLD
166
+ from agent_framework import (
167
+ MagenticAgentDeltaEvent,
168
+ MagenticAgentMessageEvent,
169
+ MagenticBuilder,
170
+ MagenticFinalResultEvent,
171
+ MagenticOrchestratorMessageEvent,
172
+ WorkflowOutputEvent,
173
+ )
174
+
175
+ # NEW
176
+ from agent_framework import (
177
+ AgentRunUpdateEvent, # Replaces MagenticAgentDeltaEvent
178
+ ExecutorCompletedEvent, # Replaces MagenticAgentMessageEvent (CRITICAL!)
179
+ MagenticBuilder,
180
+ MagenticOrchestratorMessageEvent, # UNCHANGED
181
+ WorkflowOutputEvent, # Replaces MagenticFinalResultEvent
182
+ )
183
+ ```
184
+
185
+ ### Phase 3: Event Handling Refactor (30 min)
186
+
187
+ **`src/orchestrators/advanced.py` lines 326-385:**
188
+
189
+ ```python
190
+ # ============================================
191
+ # 1. STREAMING: AgentRunUpdateEvent
192
+ # ============================================
193
+ # OLD:
194
+ if isinstance(event, MagenticAgentDeltaEvent):
195
+ if event.text:
196
+ state.current_message_buffer += event.text
197
+ yield AgentEvent(type="streaming", message=event.text, ...)
198
+
199
+ # NEW:
200
+ if isinstance(event, AgentRunUpdateEvent) and event.data:
201
+ author = getattr(event.data, 'author_name', None)
202
+ if author != state.current_agent_id:
203
+ state.current_message_buffer = ""
204
+ state.current_agent_id = author
205
+
206
+ text = event.data.text
207
+ if text:
208
+ state.current_message_buffer += text
209
+ yield AgentEvent(type="streaming", message=text, data={"agent_id": author}, ...)
210
+ continue
211
+
212
+ # ============================================
213
+ # 2. COMPLETION SIGNAL: ExecutorCompletedEvent (CRITICAL!)
214
+ # ============================================
215
+ # OLD:
216
+ if isinstance(event, MagenticAgentMessageEvent):
217
+ state.iteration += 1
218
+ # ... handle completion ...
219
+
220
+ # NEW:
221
+ if isinstance(event, ExecutorCompletedEvent):
222
+ state.iteration += 1
223
+ agent_name = event.executor_id or ""
224
+
225
+ # Track if ReportAgent ran (for P1 forced synthesis)
226
+ if REPORTER_AGENT_ID in agent_name.lower():
227
+ state.reporter_ran = True
228
+
229
+ # Use accumulated buffer (or trust event.data.text since repr bug is fixed)
230
+ comp_event, prog_event = self._handle_completion_event(
231
+ event, state.current_message_buffer, state.iteration
232
+ )
233
+ yield comp_event
234
+ yield prog_event
235
+ state.current_message_buffer = ""
236
+ continue
237
+
238
+ # ============================================
239
+ # 3. FINAL EVENT: WorkflowOutputEvent (unchanged pattern)
240
+ # ============================================
241
+ if isinstance(event, WorkflowOutputEvent):
242
+ # ... same as before, MagenticFinalResultEvent removed ...
243
+ ```
244
+
245
+ **Key Simplification**: Since the repr bug is fixed upstream:
246
+ 1. We MAY be able to trust `event.data.text` directly
247
+ 2. Keep the buffer as safety net for this PR
248
+ 3. Mark Accumulator Pattern for potential removal in next sprint
249
+
250
+ ### Phase 4: Test Updates (30 min)
251
+
252
+ **`tests/unit/orchestrators/test_accumulator_pattern.py`:**
253
+ - Update mock classes to match new API
254
+ - Verify Accumulator Pattern still works OR
255
+ - Update tests if we simplify the pattern
256
+
257
+ **`tests/unit/orchestrators/test_advanced_events.py`:**
258
+ - Update import from `MagenticOrchestratorMessageEvent` (still exists)
259
+
260
+ ### Phase 5: Verification (30 min)
261
+
262
+ ```bash
263
+ # Run full test suite
264
+ make check
265
+
266
+ # Verify specific tests
267
+ uv run pytest tests/unit/orchestrators/ -v
268
+
269
+ # Manual smoke test
270
+ uv run python src/app.py
271
+ # Test free tier (HuggingFace)
272
+ # Test paid tier (OpenAI) if key available
273
+ ```
274
+
275
+ ---
276
+
277
+ ## Risk Assessment
278
+
279
+ | Risk | Likelihood | Impact | Mitigation |
280
+ |------|------------|--------|------------|
281
+ | New bugs in event handling | MEDIUM | HIGH | Comprehensive test coverage |
282
+ | Breaking streaming | LOW | HIGH | Keep buffer pattern as safety net |
283
+ | Other API changes we missed | LOW | MEDIUM | Check all imports against new version |
284
+ | HuggingFace caching old version | LOW | LOW | Use `--upgrade` flag |
285
+
286
+ ---
287
+
288
+ ## Rollback Plan
289
+
290
+ If upgrade fails:
291
+ 1. Pin to OLD version: `agent-framework-core==1.0.0b251120`
292
+ 2. Keep existing Accumulator Pattern
293
+ 3. File issue with Microsoft if blocking bugs found
294
+
295
+ ---
296
+
297
+ ## Decision: Upgrade vs Pin Old
298
+
299
+ ### Option A: Upgrade to `1.0.0b251204` βœ… RECOMMENDED
300
+
301
+ | Pros | Cons |
302
+ |------|------|
303
+ | Repr bug fixed upstream | Requires code changes |
304
+ | Simpler codebase (maybe remove workaround) | Testing effort |
305
+ | Aligned with upstream | Risk of new bugs |
306
+ | Future-proof | |
307
+
308
+ ### Option B: Pin to `1.0.0b251120`
309
+
310
+ | Pros | Cons |
311
+ |------|------|
312
+ | Zero code changes | Technical debt (keep workaround forever) |
313
+ | Immediate fix | Missing bug fixes |
314
+ | Known behavior | Eventually unsupported |
315
+
316
+ **Recommendation**: **UPGRADE** - The effort is ~2-4 hours, but we get:
317
+ 1. Upstream repr bug fix (no more workaround needed)
318
+ 2. Cleaner codebase
319
+ 3. Alignment with Microsoft's direction
320
+
321
+ ---
322
+
323
+ ## Success Criteria
324
+
325
+ - [ ] All 302 tests pass
326
+ - [ ] `make check` passes (lint, type, test)
327
+ - [ ] App starts on local
328
+ - [ ] Free tier works (HuggingFace backend)
329
+ - [ ] Paid tier works (OpenAI backend)
330
+ - [ ] Streaming works correctly
331
+ - [ ] No repr garbage in output
332
+ - [ ] HuggingFace Spaces deployment works
333
+
334
+ ---
335
+
336
+ ## Post-Upgrade Cleanup
337
+
338
+ After successful upgrade:
339
+ 1. Update `docs/specs/archive/SPEC_17_ACCUMULATOR_PATTERN.md` - Mark as "Superseded by upstream fix"
340
+ 2. Consider removing Accumulator Pattern if no longer needed
341
+ 3. Update `docs/architecture/system_registry.md` with new event classes
342
+ 4. Add regression test to prevent future dependency drift
343
+
344
+ ---
345
+
346
+ ## Appendix A: Full File Diff Preview
347
+
348
+ ### `src/orchestrators/advanced.py` (estimated diff)
349
+
350
+ ```diff
351
+ from agent_framework import (
352
+ - MagenticAgentDeltaEvent,
353
+ - MagenticAgentMessageEvent,
354
+ + AgentRunUpdateEvent,
355
+ + ExecutorCompletedEvent,
356
+ MagenticBuilder,
357
+ - MagenticFinalResultEvent,
358
+ MagenticOrchestratorMessageEvent,
359
+ WorkflowOutputEvent,
360
+ )
361
+
362
+ # ... lines 300-325 unchanged ...
363
+
364
+ # 1. Handle Streaming
365
+ - if isinstance(event, MagenticAgentDeltaEvent):
366
+ - if event.agent_id != state.current_agent_id:
367
+ + if isinstance(event, AgentRunUpdateEvent) and event.data:
368
+ + author = getattr(event.data, 'author_name', None)
369
+ + if author != state.current_agent_id:
370
+ state.current_message_buffer = ""
371
+ - state.current_agent_id = event.agent_id
372
+ -
373
+ - if event.text:
374
+ - state.current_message_buffer += event.text
375
+ + state.current_agent_id = author
376
+ +
377
+ + text = event.data.text
378
+ + if text:
379
+ + state.current_message_buffer += text
380
+ yield AgentEvent(
381
+ type="streaming",
382
+ - message=event.text,
383
+ - data={"agent_id": event.agent_id},
384
+ + message=text,
385
+ + data={"agent_id": author},
386
+ iteration=state.iteration,
387
+ )
388
+ continue
389
+
390
+ # 2. Handle Completion Signal
391
+ - if isinstance(event, MagenticAgentMessageEvent):
392
+ + if isinstance(event, ExecutorCompletedEvent):
393
+ state.iteration += 1
394
+ - agent_name = (event.agent_id or "").lower()
395
+ + agent_name = (event.executor_id or "").lower()
396
+ if REPORTER_AGENT_ID in agent_name:
397
+ state.reporter_ran = True
398
+ # ... rest of completion handling ...
399
+ continue
400
+
401
+ # 3. Handle Final Events
402
+ - if isinstance(event, (MagenticFinalResultEvent, WorkflowOutputEvent)):
403
+ + if isinstance(event, WorkflowOutputEvent):
404
+ # ... final event handling (remove MagenticFinalResultEvent check) ...
405
+ ```
406
+
407
+ ---
408
+
409
+ ## Appendix B: Lessons Learned
410
+
411
+ ### Dependency Management Best Practices
412
+
413
+ 1. **Beta packages need EXACT pins**: `==1.0.0b251204` not `>=1.0.0b251120`
414
+ 2. **Sync requirements.txt and pyproject.toml**: HuggingFace uses requirements.txt
415
+ 3. **Lock files are critical**: `uv.lock` captures exact versions
416
+ 4. **Monitor upstream releases**: Set up GitHub watch on critical deps
417
+
418
+ ### Why This Pattern Will Recur
419
+
420
+ Microsoft Agent Framework is in **active beta** development. Expect:
421
+ - Frequent releases (weekly/biweekly)
422
+ - Breaking changes without major version bumps
423
+ - API refactoring as they stabilize
424
+
425
+ **Strategy**: Pin exact versions, upgrade deliberately, not automatically.
426
+
427
+ ---
428
+
429
+ ## Senior Agent Review: COMPLETE
430
+
431
+ **Review conducted**: 2025-12-04
432
+
433
+ ### Questions & Answers:
434
+
435
+ 1. **Is the upgrade approach correct?**
436
+ βœ… YES - Upgrade is recommended. Pinning to old version would accumulate tech debt.
437
+
438
+ 2. **Are there any API changes we missed?**
439
+ βœ… FOUND: `ExecutorCompletedEvent` was missing from original spec.
440
+ - This is the replacement for `MagenticAgentMessageEvent` (agent turn completion)
441
+ - **CRITICAL** - Must be handled for proper workflow control
442
+
443
+ 3. **Should we keep the Accumulator Pattern as a safety net?**
444
+ βœ… YES for this PR - Keep buffer as safety net, mark for potential removal in next sprint.
445
+ - Upstream claims repr bug is fixed
446
+ - Verify with smoke test before removing workaround
447
+
448
+ 4. **Testing strategy for streaming behavior?**
449
+ βœ… Must include manual smoke test in addition to unit tests.
450
+ - Unit tests mock events, may not catch runtime issues
451
+ - Run `python src/app.py` and verify tokens stream correctly
452
+
453
+ ### Reviewer Notes:
454
+
455
+ > "The analysis is accurate. The move to 'Standardized orchestration outputs' by Microsoft
456
+ > explains the removal of Magentic-specific events in favor of generic AgentRun* events.
457
+ > Proceed with the upgrade, ensuring ExecutorCompletedEvent is properly handled."
458
+
459
+ **Status**: APPROVED FOR IMPLEMENTATION
docs/workflow-diagrams.md CHANGED
@@ -499,9 +499,9 @@ graph TD
499
 
500
  Submit -.->|Triggers| Workflow[Magentic Workflow]
501
  Workflow -.->|MagenticOrchestratorMessageEvent| Log
502
- Workflow -.->|MagenticAgentDeltaEvent| Log
503
- Workflow -.->|MagenticAgentMessageEvent| Log
504
- Workflow -.->|MagenticFinalResultEvent| Tab4
505
 
506
  style App fill:#e1f5e1
507
  style Input fill:#fff4e6
@@ -656,7 +656,7 @@ No separate Judge Agent needed - manager does it all!
656
  ---
657
 
658
  **Document Version**: 2.0 (Magentic Simplified)
659
- **Last Updated**: 2025-11-24
660
  **Architecture**: Microsoft Magentic Orchestration Pattern
661
  **Agents**: 4 (Hypothesis, Search, Analysis, Report) + 1 Manager
662
  **License**: MIT
 
499
 
500
  Submit -.->|Triggers| Workflow[Magentic Workflow]
501
  Workflow -.->|MagenticOrchestratorMessageEvent| Log
502
+ Workflow -.->|AgentRunUpdateEvent| Log
503
+ Workflow -.->|ExecutorCompletedEvent| Log
504
+ Workflow -.->|WorkflowOutputEvent| Tab4
505
 
506
  style App fill:#e1f5e1
507
  style Input fill:#fff4e6
 
656
  ---
657
 
658
  **Document Version**: 2.0 (Magentic Simplified)
659
+ **Last Updated**: 2025-12-05
660
  **Architecture**: Microsoft Magentic Orchestration Pattern
661
  **Agents**: 4 (Hypothesis, Search, Analysis, Report) + 1 Manager
662
  **License**: MIT
pyproject.toml CHANGED
@@ -61,7 +61,7 @@ dev = [
61
  "pip-audit>=2.7.0",
62
  ]
63
  magentic = [
64
- "agent-framework-core>=1.0.0b251120,<2.0.0", # Microsoft Agent Framework (PyPI)
65
  ]
66
  rag = [
67
  # LlamaIndex RAG support (chromadb already in core deps)
 
61
  "pip-audit>=2.7.0",
62
  ]
63
  magentic = [
64
+ "agent-framework-core==1.0.0b251204", # Microsoft Agent Framework (PyPI)
65
  ]
66
  rag = [
67
  # LlamaIndex RAG support (chromadb already in core deps)
requirements.txt CHANGED
@@ -2,7 +2,7 @@
2
  # requirements.txt - HuggingFace Spaces Dependencies
3
  # ============================================================
4
  # This file MUST stay in sync with pyproject.toml dependencies.
5
- # Last synced: 2025-12-04
6
  # ============================================================
7
 
8
  # Core
@@ -46,7 +46,7 @@ langgraph-checkpoint-sqlite>=3.0.0,<4.0
46
  urllib3>=2.5.0
47
 
48
  # Multi-agent orchestration (Advanced mode) - from [magentic] optional
49
- agent-framework-core>=1.0.0b251120,<2.0.0
50
 
51
  # LlamaIndex RAG support - from [rag] optional
52
  llama-index>=0.11.0
 
2
  # requirements.txt - HuggingFace Spaces Dependencies
3
  # ============================================================
4
  # This file MUST stay in sync with pyproject.toml dependencies.
5
+ # Last synced: 2025-12-05
6
  # ============================================================
7
 
8
  # Core
 
46
  urllib3>=2.5.0
47
 
48
  # Multi-agent orchestration (Advanced mode) - from [magentic] optional
49
+ agent-framework-core==1.0.0b251204
50
 
51
  # LlamaIndex RAG support - from [rag] optional
52
  llama-index>=0.11.0
src/agents/graph/workflow.py CHANGED
@@ -25,7 +25,7 @@ def create_research_graph(
25
  llm: BaseChatModel | None = None,
26
  checkpointer: BaseCheckpointSaver[Any] | None = None,
27
  embedding_service: EmbeddingServiceProtocol | None = None,
28
- ) -> CompiledStateGraph[Any]:
29
  """Build the research state graph.
30
 
31
  Args:
 
25
  llm: BaseChatModel | None = None,
26
  checkpointer: BaseCheckpointSaver[Any] | None = None,
27
  embedding_service: EmbeddingServiceProtocol | None = None,
28
+ ) -> CompiledStateGraph[Any, Any, Any, Any]:
29
  """Build the research state graph.
30
 
31
  Args:
src/orchestrators/advanced.py CHANGED
@@ -22,11 +22,11 @@ from typing import TYPE_CHECKING, Any, Literal
22
 
23
  import structlog
24
  from agent_framework import (
25
- MagenticAgentDeltaEvent,
26
- MagenticAgentMessageEvent,
 
 
27
  MagenticBuilder,
28
- MagenticFinalResultEvent,
29
- MagenticOrchestratorMessageEvent,
30
  WorkflowOutputEvent,
31
  )
32
 
@@ -150,6 +150,7 @@ class AdvancedOrchestrator(OrchestratorProtocol):
150
 
151
  # Manager chat client (orchestrates the agents)
152
  manager_client = self._chat_client
 
153
 
154
  return (
155
  MagenticBuilder()
@@ -160,7 +161,7 @@ class AdvancedOrchestrator(OrchestratorProtocol):
160
  reporter=report_agent,
161
  )
162
  .with_standard_manager(
163
- chat_client=manager_client,
164
  max_round_count=self._max_rounds,
165
  max_stall_count=3,
166
  max_reset_count=2,
@@ -324,30 +325,33 @@ The final output should be a structured research report."""
324
  async with asyncio.timeout(self._timeout_seconds):
325
  async for event in workflow.run_stream(task):
326
  # 1. Handle Streaming (Source of Truth for Content)
327
- if isinstance(event, MagenticAgentDeltaEvent):
 
328
  # Detect agent switch to clear buffer
329
- if event.agent_id != state.current_agent_id:
330
  state.current_message_buffer = ""
331
- state.current_agent_id = event.agent_id
332
 
333
- if event.text:
334
- state.current_message_buffer += event.text
 
335
  yield AgentEvent(
336
  type="streaming",
337
- message=event.text,
338
- data={"agent_id": event.agent_id},
339
  iteration=state.iteration,
340
  )
341
  continue
342
 
343
  # 2. Handle Completion Signal
344
- # We use our accumulated buffer instead of the corrupted event.message
345
- if isinstance(event, MagenticAgentMessageEvent):
346
  state.iteration += 1
347
 
348
  # P1 FIX: Track if ReportAgent produced output
349
- agent_name = (event.agent_id or "").lower()
350
- if REPORTER_AGENT_ID in agent_name:
 
 
351
  state.reporter_ran = True
352
 
353
  comp_event, prog_event = self._handle_completion_event(
@@ -363,7 +367,7 @@ The final output should be a structured research report."""
363
  continue
364
 
365
  # 3. Handle Final Events Inline (P2 Duplicate Report Fix + P1 Forced Synthesis)
366
- if isinstance(event, (MagenticFinalResultEvent, WorkflowOutputEvent)):
367
  if state.final_event_received:
368
  continue # Skip duplicate final events
369
  state.final_event_received = True
@@ -430,21 +434,19 @@ The final output should be a structured research report."""
430
  )
431
 
432
  def _handle_completion_event(
433
- self, event: MagenticAgentMessageEvent, buffer: str, iteration: int
434
  ) -> tuple[AgentEvent, AgentEvent]:
435
  """Handle an agent completion event using the accumulated buffer."""
436
  # Use buffer if available, otherwise fall back cautiously
437
  # (Only fall back if buffer empty, which implies tool-only turn)
438
  text_content = buffer
439
  if not text_content:
 
440
  # Try extraction but ignore repr strings AND empty strings
441
- raw_text = self._extract_text(event.message)
442
- if raw_text and not (raw_text.startswith("<") and "object at" in raw_text):
443
- text_content = raw_text
444
- else:
445
- text_content = "Action completed (Tool Call)"
446
 
447
- agent_name = event.agent_id or "unknown"
448
  event_type = self._get_event_type_for_agent(agent_name)
449
 
450
  completion_event = AgentEvent(
@@ -470,7 +472,7 @@ The final output should be a structured research report."""
470
 
471
  def _handle_final_event(
472
  self,
473
- event: MagenticFinalResultEvent | WorkflowOutputEvent,
474
  iteration: int,
475
  last_streamed_length: int,
476
  ) -> AgentEvent:
@@ -489,11 +491,7 @@ The final output should be a structured research report."""
489
  )
490
 
491
  # NO: Final event must carry the payload (tool-only turn, cache hit)
492
- text = ""
493
- if isinstance(event, MagenticFinalResultEvent):
494
- text = self._extract_text(event.message) if event.message else "No result"
495
- elif isinstance(event, WorkflowOutputEvent):
496
- text = self._extract_text(event.data) if event.data else "Research complete"
497
 
498
  return AgentEvent(
499
  type="complete",
@@ -589,14 +587,19 @@ The final output should be a structured research report."""
589
 
590
  def _process_event(self, event: Any, iteration: int) -> AgentEvent | None:
591
  """Process workflow event into AgentEvent."""
592
- if isinstance(event, MagenticOrchestratorMessageEvent):
 
 
 
 
 
593
  # FILTERING: Skip internal framework bookkeeping
594
- if event.kind in ("task_ledger", "instruction"):
595
  return None
596
 
597
  # TRANSFORMATION: Handle user_task BEFORE text extraction
598
  # (user_task uses static message, doesn't need text content)
599
- if event.kind == "user_task":
600
  return AgentEvent(
601
  type="progress",
602
  message="Manager assigning research task to agents...",
@@ -604,23 +607,22 @@ The final output should be a structured research report."""
604
  )
605
 
606
  # For other manager events, extract and validate text
607
- text = self._extract_text(event.message)
608
  if not text:
609
  return None
610
 
611
  # Default fallback for other manager events
612
  return AgentEvent(
613
  type="judging",
614
- message=f"Manager ({event.kind}): {self._smart_truncate(text)}",
615
  iteration=iteration,
616
  )
617
 
618
  # NOTE: The following event types are handled inline in run() loop and never reach
619
  # this method due to `continue` statements:
620
- # - MagenticAgentMessageEvent: Accumulator Pattern (lines 322-335)
621
- # - MagenticAgentDeltaEvent: Accumulator Pattern (lines 306-320)
622
- # - MagenticFinalResultEvent: P2 Duplicate Fix via _handle_final_event() (lines 343-347)
623
- # - WorkflowOutputEvent: P2 Duplicate Fix via _handle_final_event() (lines 343-347)
624
 
625
  return None
626
 
 
22
 
23
  import structlog
24
  from agent_framework import (
25
+ MAGENTIC_EVENT_TYPE_ORCHESTRATOR,
26
+ AgentRunUpdateEvent,
27
+ ChatAgent,
28
+ ExecutorCompletedEvent,
29
  MagenticBuilder,
 
 
30
  WorkflowOutputEvent,
31
  )
32
 
 
150
 
151
  # Manager chat client (orchestrates the agents)
152
  manager_client = self._chat_client
153
+ manager_agent = ChatAgent(chat_client=manager_client)
154
 
155
  return (
156
  MagenticBuilder()
 
161
  reporter=report_agent,
162
  )
163
  .with_standard_manager(
164
+ agent=manager_agent,
165
  max_round_count=self._max_rounds,
166
  max_stall_count=3,
167
  max_reset_count=2,
 
325
  async with asyncio.timeout(self._timeout_seconds):
326
  async for event in workflow.run_stream(task):
327
  # 1. Handle Streaming (Source of Truth for Content)
328
+ if isinstance(event, AgentRunUpdateEvent) and event.data:
329
+ author = getattr(event.data, "author_name", None)
330
  # Detect agent switch to clear buffer
331
+ if author != state.current_agent_id:
332
  state.current_message_buffer = ""
333
+ state.current_agent_id = author
334
 
335
+ text = getattr(event.data, "text", None)
336
+ if text:
337
+ state.current_message_buffer += text
338
  yield AgentEvent(
339
  type="streaming",
340
+ message=text,
341
+ data={"agent_id": author},
342
  iteration=state.iteration,
343
  )
344
  continue
345
 
346
  # 2. Handle Completion Signal
347
+ if isinstance(event, ExecutorCompletedEvent):
 
348
  state.iteration += 1
349
 
350
  # P1 FIX: Track if ReportAgent produced output
351
+ # Note: ExecutorCompletedEvent might not have agent_id directly accessible
352
+ # The executor_id usually maps to the agent name
353
+ agent_name = getattr(event, "executor_id", "") or "unknown"
354
+ if REPORTER_AGENT_ID in agent_name.lower():
355
  state.reporter_ran = True
356
 
357
  comp_event, prog_event = self._handle_completion_event(
 
367
  continue
368
 
369
  # 3. Handle Final Events Inline (P2 Duplicate Report Fix + P1 Forced Synthesis)
370
+ if isinstance(event, WorkflowOutputEvent):
371
  if state.final_event_received:
372
  continue # Skip duplicate final events
373
  state.final_event_received = True
 
434
  )
435
 
436
  def _handle_completion_event(
437
+ self, event: ExecutorCompletedEvent, buffer: str, iteration: int
438
  ) -> tuple[AgentEvent, AgentEvent]:
439
  """Handle an agent completion event using the accumulated buffer."""
440
  # Use buffer if available, otherwise fall back cautiously
441
  # (Only fall back if buffer empty, which implies tool-only turn)
442
  text_content = buffer
443
  if not text_content:
444
+ # ExecutorCompletedEvent doesn't carry the message directly in the same way
445
  # Try extraction but ignore repr strings AND empty strings
446
+ # The result is often in event.result or similar, but buffering is safer
447
+ text_content = "Action completed (Tool Call)"
 
 
 
448
 
449
+ agent_name = getattr(event, "executor_id", "unknown") or "unknown"
450
  event_type = self._get_event_type_for_agent(agent_name)
451
 
452
  completion_event = AgentEvent(
 
472
 
473
  def _handle_final_event(
474
  self,
475
+ event: WorkflowOutputEvent,
476
  iteration: int,
477
  last_streamed_length: int,
478
  ) -> AgentEvent:
 
491
  )
492
 
493
  # NO: Final event must carry the payload (tool-only turn, cache hit)
494
+ text = self._extract_text(event.data) if event.data else "Research complete"
 
 
 
 
495
 
496
  return AgentEvent(
497
  type="complete",
 
587
 
588
  def _process_event(self, event: Any, iteration: int) -> AgentEvent | None:
589
  """Process workflow event into AgentEvent."""
590
+ # Handle orchestrator messages (formerly MagenticOrchestratorMessageEvent)
591
+ # We check the event type string directly
592
+ if getattr(event, "type", "") == MAGENTIC_EVENT_TYPE_ORCHESTRATOR:
593
+ kind = getattr(event, "kind", "")
594
+ message = getattr(event, "message", "")
595
+
596
  # FILTERING: Skip internal framework bookkeeping
597
+ if kind in ("task_ledger", "instruction"):
598
  return None
599
 
600
  # TRANSFORMATION: Handle user_task BEFORE text extraction
601
  # (user_task uses static message, doesn't need text content)
602
+ if kind == "user_task":
603
  return AgentEvent(
604
  type="progress",
605
  message="Manager assigning research task to agents...",
 
607
  )
608
 
609
  # For other manager events, extract and validate text
610
+ text = self._extract_text(message)
611
  if not text:
612
  return None
613
 
614
  # Default fallback for other manager events
615
  return AgentEvent(
616
  type="judging",
617
+ message=f"Manager ({kind}): {self._smart_truncate(text)}",
618
  iteration=iteration,
619
  )
620
 
621
  # NOTE: The following event types are handled inline in run() loop and never reach
622
  # this method due to `continue` statements:
623
+ # - ExecutorCompletedEvent: Accumulator Pattern
624
+ # - AgentRunUpdateEvent: Accumulator Pattern
625
+ # - WorkflowOutputEvent: P2 Duplicate Fix via _handle_final_event()
 
626
 
627
  return None
628
 
tests/unit/orchestrators/test_accumulator_pattern.py CHANGED
@@ -1,8 +1,13 @@
1
  """
2
  Test the Accumulator Pattern for Microsoft Agent Framework event handling.
3
 
4
- This tests SPEC 17: We use MagenticAgentDeltaEvent.text as the sole source of content,
5
- and MagenticAgentMessageEvent as a signal only (ignoring .message to avoid repr bug).
 
 
 
 
 
6
  """
7
 
8
  import importlib
@@ -14,49 +19,41 @@ import pytest
14
 
15
 
16
  # --- Create real event classes ---
17
- class MockDeltaEvent:
18
- """Simulates MagenticAgentDeltaEvent with streaming text."""
19
 
20
- def __init__(self, text: str, agent_id: str = "TestAgent"):
21
- self.text = text
22
- self.agent_id = agent_id
 
23
 
24
 
25
- class MockMessageEvent:
26
- """Simulates MagenticAgentMessageEvent with potentially corrupted .message."""
27
 
28
- def __init__(self, message_text: str, agent_id: str = "TestAgent"):
29
- self.message = MagicMock()
30
- self.message.text = message_text # This could be repr garbage
31
- self.agent_id = agent_id
32
- self.text = None # No top-level .text on MessageEvent
33
 
34
 
35
- class MockFinalResultEvent:
36
- """Simulates MagenticFinalResultEvent."""
37
 
38
- def __init__(self, text: str):
39
- self.message = MagicMock()
40
- self.message.text = text
41
- self.text = None
42
 
43
 
44
  class MockOrchestratorMessageEvent:
45
- """Simulates MagenticOrchestratorMessageEvent."""
46
 
47
  def __init__(self, kind: str = "user_task", message: str = "test"):
 
 
 
48
  self.kind = kind
49
  self.message = MagicMock()
50
  self.message.text = message
51
 
52
 
53
- class MockWorkflowOutputEvent:
54
- """Simulates WorkflowOutputEvent."""
55
-
56
- def __init__(self, data=None):
57
- self.data = data
58
-
59
-
60
  # Pass-through decorators
61
  def mock_use_function_invocation(func=None):
62
  return func if func else lambda f: f
@@ -87,11 +84,12 @@ def mock_agent_framework():
87
  mock_af.observability = mock_af_observability
88
 
89
  # Assign our REAL event classes as the module-level types
90
- mock_af.MagenticAgentDeltaEvent = MockDeltaEvent
91
- mock_af.MagenticAgentMessageEvent = MockMessageEvent
92
- mock_af.MagenticFinalResultEvent = MockFinalResultEvent
93
- mock_af.MagenticOrchestratorMessageEvent = MockOrchestratorMessageEvent
94
  mock_af.WorkflowOutputEvent = MockWorkflowOutputEvent
 
 
 
95
 
96
  # Mock other classes
97
  mock_af.MagenticBuilder = MagicMock
@@ -173,13 +171,13 @@ def mock_orchestrator(mock_agent_framework):
173
  async def test_accumulator_pattern_scenario_a_standard_text(mock_orchestrator):
174
  """
175
  Scenario A: Standard Text Message
176
- Input: Deltas ("Hello", " World") -> MessageEvent (Poisoned Repr)
177
- Expected: AgentEvent with "Hello World", NOT the repr string
178
  """
179
  events = [
180
- MockDeltaEvent("Hello", agent_id="ChatBot"),
181
- MockDeltaEvent(" World", agent_id="ChatBot"),
182
- MockMessageEvent("<ChatMessage object at 0xDEADBEEF>", agent_id="ChatBot"),
183
  ]
184
 
185
  async def mock_stream(*args, **kwargs):
@@ -204,9 +202,8 @@ async def test_accumulator_pattern_scenario_a_standard_text(mock_orchestrator):
204
  )
205
  final_event = chat_events[0]
206
 
207
- # CRITICAL: Must contain accumulated text, NOT repr
208
  assert "Hello World" in final_event.message or "Hello" in final_event.message
209
- assert "<ChatMessage" not in final_event.message, f"Repr bug! Got: {final_event.message}"
210
 
211
 
212
  @pytest.mark.unit
@@ -214,11 +211,11 @@ async def test_accumulator_pattern_scenario_a_standard_text(mock_orchestrator):
214
  async def test_accumulator_pattern_scenario_b_tool_call(mock_orchestrator):
215
  """
216
  Scenario B: Tool Call (No Text Deltas)
217
- Input: No Deltas -> MessageEvent (Poisoned Repr)
218
- Expected: AgentEvent with fallback text, NOT the repr string
219
  """
220
  events = [
221
- MockMessageEvent("<ChatMessage object at 0xDEADBEEF>", agent_id="SearchAgent"),
222
  ]
223
 
224
  async def mock_stream(*args, **kwargs):
@@ -243,8 +240,6 @@ async def test_accumulator_pattern_scenario_b_tool_call(mock_orchestrator):
243
  )
244
  final_event = search_events[0]
245
 
246
- # CRITICAL: Should use fallback, NOT repr
247
- assert "<ChatMessage" not in final_event.message, f"Repr bug! Got: {final_event.message}"
248
  # Should contain fallback or tool indicator
249
  assert "Action completed" in final_event.message or "Tool" in final_event.message
250
 
@@ -257,10 +252,10 @@ async def test_accumulator_pattern_buffer_clearing(mock_orchestrator):
257
  Agent B should NOT inherit Agent A's accumulated text.
258
  """
259
  events = [
260
- MockDeltaEvent("Agent A says hi", agent_id="AgentA"),
261
- MockMessageEvent("irrelevant", agent_id="AgentA"),
262
- MockDeltaEvent("Agent B responds", agent_id="AgentB"),
263
- MockMessageEvent("irrelevant", agent_id="AgentB"),
264
  ]
265
 
266
  async def mock_stream(*args, **kwargs):
 
1
  """
2
  Test the Accumulator Pattern for Microsoft Agent Framework event handling.
3
 
4
+ This tests SPEC-17 (updated for SPEC-18): We use AgentRunUpdateEvent.data.text as the
5
+ sole source of streaming content, and ExecutorCompletedEvent as a completion signal.
6
+
7
+ Event mapping (SPEC-18 migration):
8
+ - MagenticAgentDeltaEvent β†’ AgentRunUpdateEvent
9
+ - MagenticAgentMessageEvent β†’ ExecutorCompletedEvent
10
+ - MagenticFinalResultEvent β†’ WorkflowOutputEvent
11
  """
12
 
13
  import importlib
 
19
 
20
 
21
  # --- Create real event classes ---
22
+ class MockAgentRunUpdateEvent:
23
+ """Simulates AgentRunUpdateEvent with streaming data."""
24
 
25
+ def __init__(self, text: str, author_name: str = "TestAgent"):
26
+ self.data = MagicMock()
27
+ self.data.text = text
28
+ self.data.author_name = author_name
29
 
30
 
31
+ class MockExecutorCompletedEvent:
32
+ """Simulates ExecutorCompletedEvent signaling agent turn completion."""
33
 
34
+ def __init__(self, executor_id: str = "TestAgent"):
35
+ self.executor_id = executor_id
 
 
 
36
 
37
 
38
+ class MockWorkflowOutputEvent:
39
+ """Simulates WorkflowOutputEvent."""
40
 
41
+ def __init__(self, data=None):
42
+ self.data = data
 
 
43
 
44
 
45
  class MockOrchestratorMessageEvent:
46
+ """Simulates orchestrator message event (formerly MagenticOrchestratorMessageEvent)."""
47
 
48
  def __init__(self, kind: str = "user_task", message: str = "test"):
49
+ from agent_framework import MAGENTIC_EVENT_TYPE_ORCHESTRATOR
50
+
51
+ self.type = MAGENTIC_EVENT_TYPE_ORCHESTRATOR
52
  self.kind = kind
53
  self.message = MagicMock()
54
  self.message.text = message
55
 
56
 
 
 
 
 
 
 
 
57
  # Pass-through decorators
58
  def mock_use_function_invocation(func=None):
59
  return func if func else lambda f: f
 
84
  mock_af.observability = mock_af_observability
85
 
86
  # Assign our REAL event classes as the module-level types
87
+ mock_af.AgentRunUpdateEvent = MockAgentRunUpdateEvent
88
+ mock_af.ExecutorCompletedEvent = MockExecutorCompletedEvent
 
 
89
  mock_af.WorkflowOutputEvent = MockWorkflowOutputEvent
90
+ mock_af.MagenticOrchestratorMessageEvent = MockOrchestratorMessageEvent
91
+ mock_af.AgentRunResponse = MagicMock
92
+ mock_af.MAGENTIC_EVENT_TYPE_ORCHESTRATOR = "orchestrator_message"
93
 
94
  # Mock other classes
95
  mock_af.MagenticBuilder = MagicMock
 
171
  async def test_accumulator_pattern_scenario_a_standard_text(mock_orchestrator):
172
  """
173
  Scenario A: Standard Text Message
174
+ Input: Updates ("Hello", " World") -> Completed
175
+ Expected: AgentEvent with "Hello World"
176
  """
177
  events = [
178
+ MockAgentRunUpdateEvent("Hello", author_name="ChatBot"),
179
+ MockAgentRunUpdateEvent(" World", author_name="ChatBot"),
180
+ MockExecutorCompletedEvent(executor_id="ChatBot"),
181
  ]
182
 
183
  async def mock_stream(*args, **kwargs):
 
202
  )
203
  final_event = chat_events[0]
204
 
205
+ # Must contain accumulated text
206
  assert "Hello World" in final_event.message or "Hello" in final_event.message
 
207
 
208
 
209
  @pytest.mark.unit
 
211
  async def test_accumulator_pattern_scenario_b_tool_call(mock_orchestrator):
212
  """
213
  Scenario B: Tool Call (No Text Deltas)
214
+ Input: No Deltas -> Completed
215
+ Expected: AgentEvent with fallback text
216
  """
217
  events = [
218
+ MockExecutorCompletedEvent(executor_id="SearchAgent"),
219
  ]
220
 
221
  async def mock_stream(*args, **kwargs):
 
240
  )
241
  final_event = search_events[0]
242
 
 
 
243
  # Should contain fallback or tool indicator
244
  assert "Action completed" in final_event.message or "Tool" in final_event.message
245
 
 
252
  Agent B should NOT inherit Agent A's accumulated text.
253
  """
254
  events = [
255
+ MockAgentRunUpdateEvent("Agent A says hi", author_name="AgentA"),
256
+ MockExecutorCompletedEvent(executor_id="AgentA"),
257
+ MockAgentRunUpdateEvent("Agent B responds", author_name="AgentB"),
258
+ MockExecutorCompletedEvent(executor_id="AgentB"),
259
  ]
260
 
261
  async def mock_stream(*args, **kwargs):
tests/unit/orchestrators/test_advanced_events.py CHANGED
@@ -1,11 +1,23 @@
1
  """Test for AdvancedOrchestrator event processing (P1 Bug)."""
2
 
 
 
3
  import pytest
4
- from agent_framework import MagenticOrchestratorMessageEvent
5
 
6
  from src.orchestrators.advanced import AdvancedOrchestrator
7
 
8
 
 
 
 
 
 
 
 
 
 
 
9
  @pytest.mark.unit
10
  class TestAdvancedEventProcessing:
11
  """Test event processing logic in AdvancedOrchestrator."""
@@ -28,7 +40,7 @@ class TestAdvancedEventProcessing:
28
  Desired behavior: Returns None (filtered)
29
  """
30
  # Create a raw internal framework event
31
- raw_event = MagenticOrchestratorMessageEvent(
32
  kind="task_ledger",
33
  message="We are working to address the following user request: Research sildenafil...",
34
  )
@@ -46,7 +58,7 @@ class TestAdvancedEventProcessing:
46
  Current behavior: Returns AgentEvent(type='judging', message='Manager (instruction): ...')
47
  Desired behavior: Returns None (filtered)
48
  """
49
- raw_event = MagenticOrchestratorMessageEvent(
50
  kind="instruction", message="Conduct targeted searches on PubMed..."
51
  )
52
 
@@ -61,7 +73,7 @@ class TestAdvancedEventProcessing:
61
  Current behavior: 'Manager (user_task): Research...' (truncated, type='judging')
62
  Desired behavior: 'Manager assigning research task...' (type='progress')
63
  """
64
- raw_event = MagenticOrchestratorMessageEvent(
65
  kind="user_task",
66
  message="Research sexual health and wellness interventions for: sildenafil mechanism",
67
  )
 
1
  """Test for AdvancedOrchestrator event processing (P1 Bug)."""
2
 
3
+ from unittest.mock import MagicMock
4
+
5
  import pytest
6
+ from agent_framework import MAGENTIC_EVENT_TYPE_ORCHESTRATOR
7
 
8
  from src.orchestrators.advanced import AdvancedOrchestrator
9
 
10
 
11
+ class MockOrchestratorEvent:
12
+ """Mock event that mimics the new orchestrator event structure."""
13
+
14
+ def __init__(self, kind: str, message: str):
15
+ self.type = MAGENTIC_EVENT_TYPE_ORCHESTRATOR
16
+ self.kind = kind
17
+ self.message = MagicMock()
18
+ self.message.text = message
19
+
20
+
21
  @pytest.mark.unit
22
  class TestAdvancedEventProcessing:
23
  """Test event processing logic in AdvancedOrchestrator."""
 
40
  Desired behavior: Returns None (filtered)
41
  """
42
  # Create a raw internal framework event
43
+ raw_event = MockOrchestratorEvent(
44
  kind="task_ledger",
45
  message="We are working to address the following user request: Research sildenafil...",
46
  )
 
58
  Current behavior: Returns AgentEvent(type='judging', message='Manager (instruction): ...')
59
  Desired behavior: Returns None (filtered)
60
  """
61
+ raw_event = MockOrchestratorEvent(
62
  kind="instruction", message="Conduct targeted searches on PubMed..."
63
  )
64
 
 
73
  Current behavior: 'Manager (user_task): Research...' (truncated, type='judging')
74
  Desired behavior: 'Manager assigning research task...' (type='progress')
75
  """
76
+ raw_event = MockOrchestratorEvent(
77
  kind="user_task",
78
  message="Research sexual health and wellness interventions for: sildenafil mechanism",
79
  )
uv.lock CHANGED
@@ -21,7 +21,7 @@ wheels = [
21
 
22
  [[package]]
23
  name = "agent-framework-core"
24
- version = "1.0.0b251120"
25
  source = { registry = "https://pypi.org/simple" }
26
  dependencies = [
27
  { name = "azure-identity" },
@@ -36,9 +36,9 @@ dependencies = [
36
  { name = "pydantic-settings" },
37
  { name = "typing-extensions" },
38
  ]
39
- sdist = { url = "https://files.pythonhosted.org/packages/00/78/c53cb3a657ca5e000ce196c6ca776eb5b5cd95cbd8ac691872b8cc4ea23c/agent_framework_core-1.0.0b251120.tar.gz", hash = "sha256:eb327c123ce54ff36ccc828bcc4f5162248f768f2438be0550644f1b3c28927f", size = 281920 }
40
  wheels = [
41
- { url = "https://files.pythonhosted.org/packages/0c/60/7e40edfa60372cf0d56500b19c6bb322ca483fd4e7b558c1bf0ff88d4540/agent_framework_core-1.0.0b251120-py3-none-any.whl", hash = "sha256:65618ad496fa6805992980f2801b8d1a226e8edc59974945bb903df917fda105", size = 326665 },
42
  ]
43
 
44
  [[package]]
@@ -1130,7 +1130,7 @@ rag = [
1130
 
1131
  [package.metadata]
1132
  requires-dist = [
1133
- { name = "agent-framework-core", marker = "extra == 'magentic'", specifier = ">=1.0.0b251120,<2.0.0" },
1134
  { name = "bandit", marker = "extra == 'dev'", specifier = ">=1.7.0" },
1135
  { name = "beautifulsoup4", specifier = ">=4.12" },
1136
  { name = "chromadb", specifier = ">=0.4.22" },
@@ -2684,7 +2684,7 @@ wheels = [
2684
 
2685
  [[package]]
2686
  name = "logfire"
2687
- version = "4.15.1"
2688
  source = { registry = "https://pypi.org/simple" }
2689
  dependencies = [
2690
  { name = "executing" },
@@ -2695,9 +2695,9 @@ dependencies = [
2695
  { name = "rich" },
2696
  { name = "typing-extensions" },
2697
  ]
2698
- sdist = { url = "https://files.pythonhosted.org/packages/26/7b/3cdbfbd8fc085912d7322afc5f16ffd396197574fc9c786422b0ce3f5232/logfire-4.15.1.tar.gz", hash = "sha256:fac8463c0319af6d1bf66788802ff0a04b481dac006564f6837f64c7404f474a", size = 549319 }
2699
  wheels = [
2700
- { url = "https://files.pythonhosted.org/packages/21/44/45c39998cb3920a11498455f3f62f173bd3f5d9a15bd0db40f88bedb9e1f/logfire-4.15.1-py3-none-any.whl", hash = "sha256:b931d2becf937c08d7c89f1ab68ab05298095b010dfaf29cbd22f7bcacbaa2bb", size = 228716 },
2701
  ]
2702
 
2703
  [package.optional-dependencies]
@@ -3671,32 +3671,32 @@ wheels = [
3671
 
3672
  [[package]]
3673
  name = "opentelemetry-api"
3674
- version = "1.38.0"
3675
  source = { registry = "https://pypi.org/simple" }
3676
  dependencies = [
3677
  { name = "importlib-metadata" },
3678
  { name = "typing-extensions" },
3679
  ]
3680
- sdist = { url = "https://files.pythonhosted.org/packages/08/d8/0f354c375628e048bd0570645b310797299754730079853095bf000fba69/opentelemetry_api-1.38.0.tar.gz", hash = "sha256:f4c193b5e8acb0912b06ac5b16321908dd0843d75049c091487322284a3eea12", size = 65242 }
3681
  wheels = [
3682
- { url = "https://files.pythonhosted.org/packages/ae/a2/d86e01c28300bd41bab8f18afd613676e2bd63515417b77636fc1add426f/opentelemetry_api-1.38.0-py3-none-any.whl", hash = "sha256:2891b0197f47124454ab9f0cf58f3be33faca394457ac3e09daba13ff50aa582", size = 65947 },
3683
  ]
3684
 
3685
  [[package]]
3686
  name = "opentelemetry-exporter-otlp-proto-common"
3687
- version = "1.38.0"
3688
  source = { registry = "https://pypi.org/simple" }
3689
  dependencies = [
3690
  { name = "opentelemetry-proto" },
3691
  ]
3692
- sdist = { url = "https://files.pythonhosted.org/packages/19/83/dd4660f2956ff88ed071e9e0e36e830df14b8c5dc06722dbde1841accbe8/opentelemetry_exporter_otlp_proto_common-1.38.0.tar.gz", hash = "sha256:e333278afab4695aa8114eeb7bf4e44e65c6607d54968271a249c180b2cb605c", size = 20431 }
3693
  wheels = [
3694
- { url = "https://files.pythonhosted.org/packages/a7/9e/55a41c9601191e8cd8eb626b54ee6827b9c9d4a46d736f32abc80d8039fc/opentelemetry_exporter_otlp_proto_common-1.38.0-py3-none-any.whl", hash = "sha256:03cb76ab213300fe4f4c62b7d8f17d97fcfd21b89f0b5ce38ea156327ddda74a", size = 18359 },
3695
  ]
3696
 
3697
  [[package]]
3698
  name = "opentelemetry-exporter-otlp-proto-grpc"
3699
- version = "1.38.0"
3700
  source = { registry = "https://pypi.org/simple" }
3701
  dependencies = [
3702
  { name = "googleapis-common-protos" },
@@ -3707,14 +3707,14 @@ dependencies = [
3707
  { name = "opentelemetry-sdk" },
3708
  { name = "typing-extensions" },
3709
  ]
3710
- sdist = { url = "https://files.pythonhosted.org/packages/a2/c0/43222f5b97dc10812bc4f0abc5dc7cd0a2525a91b5151d26c9e2e958f52e/opentelemetry_exporter_otlp_proto_grpc-1.38.0.tar.gz", hash = "sha256:2473935e9eac71f401de6101d37d6f3f0f1831db92b953c7dcc912536158ebd6", size = 24676 }
3711
  wheels = [
3712
- { url = "https://files.pythonhosted.org/packages/28/f0/bd831afbdba74ca2ce3982142a2fad707f8c487e8a3b6fef01f1d5945d1b/opentelemetry_exporter_otlp_proto_grpc-1.38.0-py3-none-any.whl", hash = "sha256:7c49fd9b4bd0dbe9ba13d91f764c2d20b0025649a6e4ac35792fb8d84d764bc7", size = 19695 },
3713
  ]
3714
 
3715
  [[package]]
3716
  name = "opentelemetry-exporter-otlp-proto-http"
3717
- version = "1.38.0"
3718
  source = { registry = "https://pypi.org/simple" }
3719
  dependencies = [
3720
  { name = "googleapis-common-protos" },
@@ -3725,14 +3725,14 @@ dependencies = [
3725
  { name = "requests" },
3726
  { name = "typing-extensions" },
3727
  ]
3728
- sdist = { url = "https://files.pythonhosted.org/packages/81/0a/debcdfb029fbd1ccd1563f7c287b89a6f7bef3b2902ade56797bfd020854/opentelemetry_exporter_otlp_proto_http-1.38.0.tar.gz", hash = "sha256:f16bd44baf15cbe07633c5112ffc68229d0edbeac7b37610be0b2def4e21e90b", size = 17282 }
3729
  wheels = [
3730
- { url = "https://files.pythonhosted.org/packages/e5/77/154004c99fb9f291f74aa0822a2f5bbf565a72d8126b3a1b63ed8e5f83c7/opentelemetry_exporter_otlp_proto_http-1.38.0-py3-none-any.whl", hash = "sha256:84b937305edfc563f08ec69b9cb2298be8188371217e867c1854d77198d0825b", size = 19579 },
3731
  ]
3732
 
3733
  [[package]]
3734
  name = "opentelemetry-instrumentation"
3735
- version = "0.59b0"
3736
  source = { registry = "https://pypi.org/simple" }
3737
  dependencies = [
3738
  { name = "opentelemetry-api" },
@@ -3740,14 +3740,14 @@ dependencies = [
3740
  { name = "packaging" },
3741
  { name = "wrapt" },
3742
  ]
3743
- sdist = { url = "https://files.pythonhosted.org/packages/04/ed/9c65cd209407fd807fa05be03ee30f159bdac8d59e7ea16a8fe5a1601222/opentelemetry_instrumentation-0.59b0.tar.gz", hash = "sha256:6010f0faaacdaf7c4dff8aac84e226d23437b331dcda7e70367f6d73a7db1adc", size = 31544 }
3744
  wheels = [
3745
- { url = "https://files.pythonhosted.org/packages/10/f5/7a40ff3f62bfe715dad2f633d7f1174ba1a7dd74254c15b2558b3401262a/opentelemetry_instrumentation-0.59b0-py3-none-any.whl", hash = "sha256:44082cc8fe56b0186e87ee8f7c17c327c4c2ce93bdbe86496e600985d74368ee", size = 33020 },
3746
  ]
3747
 
3748
  [[package]]
3749
  name = "opentelemetry-instrumentation-httpx"
3750
- version = "0.59b0"
3751
  source = { registry = "https://pypi.org/simple" }
3752
  dependencies = [
3753
  { name = "opentelemetry-api" },
@@ -3756,48 +3756,48 @@ dependencies = [
3756
  { name = "opentelemetry-util-http" },
3757
  { name = "wrapt" },
3758
  ]
3759
- sdist = { url = "https://files.pythonhosted.org/packages/18/6b/1bdf36b68cace9b4eae3cbbade4150c71c90aa392b127dda5bb5c2a49307/opentelemetry_instrumentation_httpx-0.59b0.tar.gz", hash = "sha256:a1cb9b89d9f05a82701cc9ab9cfa3db54fd76932489449778b350bc1b9f0e872", size = 19886 }
3760
  wheels = [
3761
- { url = "https://files.pythonhosted.org/packages/58/16/c1e0745d20af392ec9060693531d7f01239deb2d81e460d0c379719691b8/opentelemetry_instrumentation_httpx-0.59b0-py3-none-any.whl", hash = "sha256:7dc9f66aef4ca3904d877f459a70c78eafd06131dc64d713b9b1b5a7d0a48f05", size = 15197 },
3762
  ]
3763
 
3764
  [[package]]
3765
  name = "opentelemetry-proto"
3766
- version = "1.38.0"
3767
  source = { registry = "https://pypi.org/simple" }
3768
  dependencies = [
3769
  { name = "protobuf" },
3770
  ]
3771
- sdist = { url = "https://files.pythonhosted.org/packages/51/14/f0c4f0f6371b9cb7f9fa9ee8918bfd59ac7040c7791f1e6da32a1839780d/opentelemetry_proto-1.38.0.tar.gz", hash = "sha256:88b161e89d9d372ce723da289b7da74c3a8354a8e5359992be813942969ed468", size = 46152 }
3772
  wheels = [
3773
- { url = "https://files.pythonhosted.org/packages/b6/6a/82b68b14efca5150b2632f3692d627afa76b77378c4999f2648979409528/opentelemetry_proto-1.38.0-py3-none-any.whl", hash = "sha256:b6ebe54d3217c42e45462e2a1ae28c3e2bf2ec5a5645236a490f55f45f1a0a18", size = 72535 },
3774
  ]
3775
 
3776
  [[package]]
3777
  name = "opentelemetry-sdk"
3778
- version = "1.38.0"
3779
  source = { registry = "https://pypi.org/simple" }
3780
  dependencies = [
3781
  { name = "opentelemetry-api" },
3782
  { name = "opentelemetry-semantic-conventions" },
3783
  { name = "typing-extensions" },
3784
  ]
3785
- sdist = { url = "https://files.pythonhosted.org/packages/85/cb/f0eee1445161faf4c9af3ba7b848cc22a50a3d3e2515051ad8628c35ff80/opentelemetry_sdk-1.38.0.tar.gz", hash = "sha256:93df5d4d871ed09cb4272305be4d996236eedb232253e3ab864c8620f051cebe", size = 171942 }
3786
  wheels = [
3787
- { url = "https://files.pythonhosted.org/packages/2f/2e/e93777a95d7d9c40d270a371392b6d6f1ff170c2a3cb32d6176741b5b723/opentelemetry_sdk-1.38.0-py3-none-any.whl", hash = "sha256:1c66af6564ecc1553d72d811a01df063ff097cdc82ce188da9951f93b8d10f6b", size = 132349 },
3788
  ]
3789
 
3790
  [[package]]
3791
  name = "opentelemetry-semantic-conventions"
3792
- version = "0.59b0"
3793
  source = { registry = "https://pypi.org/simple" }
3794
  dependencies = [
3795
  { name = "opentelemetry-api" },
3796
  { name = "typing-extensions" },
3797
  ]
3798
- sdist = { url = "https://files.pythonhosted.org/packages/40/bc/8b9ad3802cd8ac6583a4eb7de7e5d7db004e89cb7efe7008f9c8a537ee75/opentelemetry_semantic_conventions-0.59b0.tar.gz", hash = "sha256:7a6db3f30d70202d5bf9fa4b69bc866ca6a30437287de6c510fb594878aed6b0", size = 129861 }
3799
  wheels = [
3800
- { url = "https://files.pythonhosted.org/packages/24/7d/c88d7b15ba8fe5c6b8f93be50fc11795e9fc05386c44afaf6b76fe191f9b/opentelemetry_semantic_conventions-0.59b0-py3-none-any.whl", hash = "sha256:35d3b8833ef97d614136e253c1da9342b4c3c083bbaf29ce31d572a1c3825eed", size = 207954 },
3801
  ]
3802
 
3803
  [[package]]
@@ -3811,11 +3811,11 @@ wheels = [
3811
 
3812
  [[package]]
3813
  name = "opentelemetry-util-http"
3814
- version = "0.59b0"
3815
  source = { registry = "https://pypi.org/simple" }
3816
- sdist = { url = "https://files.pythonhosted.org/packages/34/f7/13cd081e7851c42520ab0e96efb17ffbd901111a50b8252ec1e240664020/opentelemetry_util_http-0.59b0.tar.gz", hash = "sha256:ae66ee91be31938d832f3b4bc4eb8a911f6eddd38969c4a871b1230db2a0a560", size = 9412 }
3817
  wheels = [
3818
- { url = "https://files.pythonhosted.org/packages/20/56/62282d1d4482061360449dacc990c89cad0fc810a2ed937b636300f55023/opentelemetry_util_http-0.59b0-py3-none-any.whl", hash = "sha256:6d036a07563bce87bf521839c0671b507a02a0d39d7ea61b88efa14c6e25355d", size = 7648 },
3819
  ]
3820
 
3821
  [[package]]
 
21
 
22
  [[package]]
23
  name = "agent-framework-core"
24
+ version = "1.0.0b251204"
25
  source = { registry = "https://pypi.org/simple" }
26
  dependencies = [
27
  { name = "azure-identity" },
 
36
  { name = "pydantic-settings" },
37
  { name = "typing-extensions" },
38
  ]
39
+ sdist = { url = "https://files.pythonhosted.org/packages/09/6c/0dc008b80e04814e68b89f843d910f75d05e0fe28094cad311af802f1bcf/agent_framework_core-1.0.0b251204.tar.gz", hash = "sha256:846f87b83b0772de4b9812b2659d1d0e3185a86329ae9347ead201fb15161e51", size = 290590 }
40
  wheels = [
41
+ { url = "https://files.pythonhosted.org/packages/e5/87/4381b35da5a6b1388ad714f83a2f5ad3b382ccdd397beb1a3e2f75b62a5b/agent_framework_core-1.0.0b251204-py3-none-any.whl", hash = "sha256:feb06ce3a146ab4e7793a389b5f74982391ed03feb98954fa9991a22a48cbdb5", size = 334273 },
42
  ]
43
 
44
  [[package]]
 
1130
 
1131
  [package.metadata]
1132
  requires-dist = [
1133
+ { name = "agent-framework-core", marker = "extra == 'magentic'", specifier = "==1.0.0b251204" },
1134
  { name = "bandit", marker = "extra == 'dev'", specifier = ">=1.7.0" },
1135
  { name = "beautifulsoup4", specifier = ">=4.12" },
1136
  { name = "chromadb", specifier = ">=0.4.22" },
 
2684
 
2685
  [[package]]
2686
  name = "logfire"
2687
+ version = "4.16.0"
2688
  source = { registry = "https://pypi.org/simple" }
2689
  dependencies = [
2690
  { name = "executing" },
 
2695
  { name = "rich" },
2696
  { name = "typing-extensions" },
2697
  ]
2698
+ sdist = { url = "https://files.pythonhosted.org/packages/e2/60/b8040db3598a55da64c45e3e689f2baa87389a4648a6f46ba80be3329f23/logfire-4.16.0.tar.gz", hash = "sha256:03a3ab8fdc13399309cb55d69cba7a6fcbad3526cfad85fc4f72e7d75e22b654", size = 550759 }
2699
  wheels = [
2700
+ { url = "https://files.pythonhosted.org/packages/53/f7/ffcf81eb4aea75e40c0646b9519947d2070626c5d533922df92975045181/logfire-4.16.0-py3-none-any.whl", hash = "sha256:8f895f6c2efa593ad6d49e1b06d8e6e351d3dd0cad61ce5def0c3d401f8ea707", size = 229122 },
2701
  ]
2702
 
2703
  [package.optional-dependencies]
 
3671
 
3672
  [[package]]
3673
  name = "opentelemetry-api"
3674
+ version = "1.39.0"
3675
  source = { registry = "https://pypi.org/simple" }
3676
  dependencies = [
3677
  { name = "importlib-metadata" },
3678
  { name = "typing-extensions" },
3679
  ]
3680
+ sdist = { url = "https://files.pythonhosted.org/packages/c0/0b/e5428c009d4d9af0515b0a8371a8aaae695371af291f45e702f7969dce6b/opentelemetry_api-1.39.0.tar.gz", hash = "sha256:6130644268c5ac6bdffaf660ce878f10906b3e789f7e2daa5e169b047a2933b9", size = 65763 }
3681
  wheels = [
3682
+ { url = "https://files.pythonhosted.org/packages/05/85/d831a9bc0a9e0e1a304ff3d12c1489a5fbc9bf6690a15dcbdae372bbca45/opentelemetry_api-1.39.0-py3-none-any.whl", hash = "sha256:3c3b3ca5c5687b1b5b37e5c5027ff68eacea8675241b29f13110a8ffbb8f0459", size = 66357 },
3683
  ]
3684
 
3685
  [[package]]
3686
  name = "opentelemetry-exporter-otlp-proto-common"
3687
+ version = "1.39.0"
3688
  source = { registry = "https://pypi.org/simple" }
3689
  dependencies = [
3690
  { name = "opentelemetry-proto" },
3691
  ]
3692
+ sdist = { url = "https://files.pythonhosted.org/packages/11/cb/3a29ce606b10c76d413d6edd42d25a654af03e73e50696611e757d2602f3/opentelemetry_exporter_otlp_proto_common-1.39.0.tar.gz", hash = "sha256:a135fceed1a6d767f75be65bd2845da344dd8b9258eeed6bc48509d02b184409", size = 20407 }
3693
  wheels = [
3694
+ { url = "https://files.pythonhosted.org/packages/ef/c6/215edba62d13a3948c718b289539f70e40965bc37fc82ecd55bb0b749c1a/opentelemetry_exporter_otlp_proto_common-1.39.0-py3-none-any.whl", hash = "sha256:3d77be7c4bdf90f1a76666c934368b8abed730b5c6f0547a2ec57feb115849ac", size = 18367 },
3695
  ]
3696
 
3697
  [[package]]
3698
  name = "opentelemetry-exporter-otlp-proto-grpc"
3699
+ version = "1.39.0"
3700
  source = { registry = "https://pypi.org/simple" }
3701
  dependencies = [
3702
  { name = "googleapis-common-protos" },
 
3707
  { name = "opentelemetry-sdk" },
3708
  { name = "typing-extensions" },
3709
  ]
3710
+ sdist = { url = "https://files.pythonhosted.org/packages/7e/62/4db083ee9620da3065eeb559e9fc128f41a1d15e7c48d7c83aafbccd354c/opentelemetry_exporter_otlp_proto_grpc-1.39.0.tar.gz", hash = "sha256:7e7bb3f436006836c0e0a42ac619097746ad5553ad7128a5bd4d3e727f37fc06", size = 24650 }
3711
  wheels = [
3712
+ { url = "https://files.pythonhosted.org/packages/56/e8/d420b94ffddfd8cff85bb4aa5d98da26ce7935dc3cf3eca6b83cd39ab436/opentelemetry_exporter_otlp_proto_grpc-1.39.0-py3-none-any.whl", hash = "sha256:758641278050de9bb895738f35ff8840e4a47685b7e6ef4a201fe83196ba7a05", size = 19765 },
3713
  ]
3714
 
3715
  [[package]]
3716
  name = "opentelemetry-exporter-otlp-proto-http"
3717
+ version = "1.39.0"
3718
  source = { registry = "https://pypi.org/simple" }
3719
  dependencies = [
3720
  { name = "googleapis-common-protos" },
 
3725
  { name = "requests" },
3726
  { name = "typing-extensions" },
3727
  ]
3728
+ sdist = { url = "https://files.pythonhosted.org/packages/81/dc/1e9bf3f6a28e29eba516bc0266e052996d02bc7e92675f3cd38169607609/opentelemetry_exporter_otlp_proto_http-1.39.0.tar.gz", hash = "sha256:28d78fc0eb82d5a71ae552263d5012fa3ebad18dfd189bf8d8095ba0e65ee1ed", size = 17287 }
3729
  wheels = [
3730
+ { url = "https://files.pythonhosted.org/packages/bc/46/e4a102e17205bb05a50dbf24ef0e92b66b648cd67db9a68865af06a242fd/opentelemetry_exporter_otlp_proto_http-1.39.0-py3-none-any.whl", hash = "sha256:5789cb1375a8b82653328c0ce13a054d285f774099faf9d068032a49de4c7862", size = 19639 },
3731
  ]
3732
 
3733
  [[package]]
3734
  name = "opentelemetry-instrumentation"
3735
+ version = "0.60b0"
3736
  source = { registry = "https://pypi.org/simple" }
3737
  dependencies = [
3738
  { name = "opentelemetry-api" },
 
3740
  { name = "packaging" },
3741
  { name = "wrapt" },
3742
  ]
3743
+ sdist = { url = "https://files.pythonhosted.org/packages/55/3c/bd53dbb42eff93d18e3047c7be11224aa9966ce98ac4cc5bfb860a32c95a/opentelemetry_instrumentation-0.60b0.tar.gz", hash = "sha256:4e9fec930f283a2677a2217754b40aaf9ef76edae40499c165bc7f1d15366a74", size = 31707 }
3744
  wheels = [
3745
+ { url = "https://files.pythonhosted.org/packages/5c/7b/5b5b9f8cfe727a28553acf9cd287b1d7f706f5c0a00d6e482df55b169483/opentelemetry_instrumentation-0.60b0-py3-none-any.whl", hash = "sha256:aaafa1483543a402819f1bdfb06af721c87d60dd109501f9997332862a35c76a", size = 33096 },
3746
  ]
3747
 
3748
  [[package]]
3749
  name = "opentelemetry-instrumentation-httpx"
3750
+ version = "0.60b0"
3751
  source = { registry = "https://pypi.org/simple" }
3752
  dependencies = [
3753
  { name = "opentelemetry-api" },
 
3756
  { name = "opentelemetry-util-http" },
3757
  { name = "wrapt" },
3758
  ]
3759
+ sdist = { url = "https://files.pythonhosted.org/packages/09/71/9dc0bc5ab14122251f520ffe9fc8dd892ca688d7d591482b5b1843d685d2/opentelemetry_instrumentation_httpx-0.60b0.tar.gz", hash = "sha256:fcf349a92fb0b941a2a18bec65141f4ba62cbf7a457a1aa580794bad44dc477c", size = 20612 }
3760
  wheels = [
3761
+ { url = "https://files.pythonhosted.org/packages/3a/22/a340c8bfad6f31bfd6a15b0b24d5e68e05e3975e4dbdc3cea6ec4f96e060/opentelemetry_instrumentation_httpx-0.60b0-py3-none-any.whl", hash = "sha256:3f5e6fc4ddf1d9de2aaddb5255110827154dbd4de9187da906c8c2a3cc2219e9", size = 15702 },
3762
  ]
3763
 
3764
  [[package]]
3765
  name = "opentelemetry-proto"
3766
+ version = "1.39.0"
3767
  source = { registry = "https://pypi.org/simple" }
3768
  dependencies = [
3769
  { name = "protobuf" },
3770
  ]
3771
+ sdist = { url = "https://files.pythonhosted.org/packages/48/b5/64d2f8c3393cd13ea2092106118f7b98461ba09333d40179a31444c6f176/opentelemetry_proto-1.39.0.tar.gz", hash = "sha256:c1fa48678ad1a1624258698e59be73f990b7fc1f39e73e16a9d08eef65dd838c", size = 46153 }
3772
  wheels = [
3773
+ { url = "https://files.pythonhosted.org/packages/e3/4d/d500e1862beed68318705732d1976c390f4a72ca8009c4983ff627acff20/opentelemetry_proto-1.39.0-py3-none-any.whl", hash = "sha256:1e086552ac79acb501485ff0ce75533f70f3382d43d0a30728eeee594f7bf818", size = 72534 },
3774
  ]
3775
 
3776
  [[package]]
3777
  name = "opentelemetry-sdk"
3778
+ version = "1.39.0"
3779
  source = { registry = "https://pypi.org/simple" }
3780
  dependencies = [
3781
  { name = "opentelemetry-api" },
3782
  { name = "opentelemetry-semantic-conventions" },
3783
  { name = "typing-extensions" },
3784
  ]
3785
+ sdist = { url = "https://files.pythonhosted.org/packages/51/e3/7cd989003e7cde72e0becfe830abff0df55c69d237ee7961a541e0167833/opentelemetry_sdk-1.39.0.tar.gz", hash = "sha256:c22204f12a0529e07aa4d985f1bca9d6b0e7b29fe7f03e923548ae52e0e15dde", size = 171322 }
3786
  wheels = [
3787
+ { url = "https://files.pythonhosted.org/packages/a4/b4/2adc8bc83eb1055ecb592708efb6f0c520cc2eb68970b02b0f6ecda149cf/opentelemetry_sdk-1.39.0-py3-none-any.whl", hash = "sha256:90cfb07600dfc0d2de26120cebc0c8f27e69bf77cd80ef96645232372709a514", size = 132413 },
3788
  ]
3789
 
3790
  [[package]]
3791
  name = "opentelemetry-semantic-conventions"
3792
+ version = "0.60b0"
3793
  source = { registry = "https://pypi.org/simple" }
3794
  dependencies = [
3795
  { name = "opentelemetry-api" },
3796
  { name = "typing-extensions" },
3797
  ]
3798
+ sdist = { url = "https://files.pythonhosted.org/packages/71/0e/176a7844fe4e3cb5de604212094dffaed4e18b32f1c56b5258bcbcba85c2/opentelemetry_semantic_conventions-0.60b0.tar.gz", hash = "sha256:227d7aa73cbb8a2e418029d6b6465553aa01cf7e78ec9d0bc3255c7b3ac5bf8f", size = 137935 }
3799
  wheels = [
3800
+ { url = "https://files.pythonhosted.org/packages/d0/56/af0306666f91bae47db14d620775604688361f0f76a872e0005277311131/opentelemetry_semantic_conventions-0.60b0-py3-none-any.whl", hash = "sha256:069530852691136018087b52688857d97bba61cd641d0f8628d2d92788c4f78a", size = 219981 },
3801
  ]
3802
 
3803
  [[package]]
 
3811
 
3812
  [[package]]
3813
  name = "opentelemetry-util-http"
3814
+ version = "0.60b0"
3815
  source = { registry = "https://pypi.org/simple" }
3816
+ sdist = { url = "https://files.pythonhosted.org/packages/38/0d/786a713445cf338131fef3a84fab1378e4b2ef3c3ea348eeb0c915eb804a/opentelemetry_util_http-0.60b0.tar.gz", hash = "sha256:e42b7bb49bba43b6f34390327d97e5016eb1c47949ceaf37c4795472a4e3a82d", size = 10576 }
3817
  wheels = [
3818
+ { url = "https://files.pythonhosted.org/packages/53/5d/a448862f6d10c95685ed0e703596b6bd1784074e7ad90bffdc550abb7b68/opentelemetry_util_http-0.60b0-py3-none-any.whl", hash = "sha256:4f366f1a48adb74ffa6f80aee26f96882e767e01b03cd1cfb948b6e1020341fe", size = 8742 },
3819
  ]
3820
 
3821
  [[package]]