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 +1 -1
- docs/specs/SPEC_18_AGENT_FRAMEWORK_UPGRADE.md +459 -0
- docs/workflow-diagrams.md +4 -4
- pyproject.toml +1 -1
- requirements.txt +2 -2
- src/agents/graph/workflow.py +1 -1
- src/orchestrators/advanced.py +41 -39
- tests/unit/orchestrators/test_accumulator_pattern.py +43 -48
- tests/unit/orchestrators/test_advanced_events.py +16 -4
- uv.lock +37 -37
|
@@ -18,5 +18,5 @@ repos:
|
|
| 18 |
- pydantic-settings>=2.2
|
| 19 |
- tenacity>=8.2
|
| 20 |
- pydantic-ai>=0.0.16
|
| 21 |
-
- agent-framework-core
|
| 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]
|
|
@@ -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
|
|
@@ -499,9 +499,9 @@ graph TD
|
|
| 499 |
|
| 500 |
Submit -.->|Triggers| Workflow[Magentic Workflow]
|
| 501 |
Workflow -.->|MagenticOrchestratorMessageEvent| Log
|
| 502 |
-
Workflow -.->|
|
| 503 |
-
Workflow -.->|
|
| 504 |
-
Workflow -.->|
|
| 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-
|
| 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
|
|
@@ -61,7 +61,7 @@ dev = [
|
|
| 61 |
"pip-audit>=2.7.0",
|
| 62 |
]
|
| 63 |
magentic = [
|
| 64 |
-
"agent-framework-core
|
| 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)
|
|
@@ -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-
|
| 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
|
| 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
|
|
@@ -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:
|
|
@@ -22,11 +22,11 @@ from typing import TYPE_CHECKING, Any, Literal
|
|
| 22 |
|
| 23 |
import structlog
|
| 24 |
from agent_framework import (
|
| 25 |
-
|
| 26 |
-
|
|
|
|
|
|
|
| 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 |
-
|
| 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,
|
|
|
|
| 328 |
# Detect agent switch to clear buffer
|
| 329 |
-
if
|
| 330 |
state.current_message_buffer = ""
|
| 331 |
-
state.current_agent_id =
|
| 332 |
|
| 333 |
-
|
| 334 |
-
|
|
|
|
| 335 |
yield AgentEvent(
|
| 336 |
type="streaming",
|
| 337 |
-
message=
|
| 338 |
-
data={"agent_id":
|
| 339 |
iteration=state.iteration,
|
| 340 |
)
|
| 341 |
continue
|
| 342 |
|
| 343 |
# 2. Handle Completion Signal
|
| 344 |
-
|
| 345 |
-
if isinstance(event, MagenticAgentMessageEvent):
|
| 346 |
state.iteration += 1
|
| 347 |
|
| 348 |
# P1 FIX: Track if ReportAgent produced output
|
| 349 |
-
|
| 350 |
-
|
|
|
|
|
|
|
| 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,
|
| 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:
|
| 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 |
-
|
| 442 |
-
|
| 443 |
-
text_content = raw_text
|
| 444 |
-
else:
|
| 445 |
-
text_content = "Action completed (Tool Call)"
|
| 446 |
|
| 447 |
-
agent_name = event
|
| 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:
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 593 |
# FILTERING: Skip internal framework bookkeeping
|
| 594 |
-
if
|
| 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
|
| 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(
|
| 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 ({
|
| 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 |
-
# -
|
| 621 |
-
# -
|
| 622 |
-
# -
|
| 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 |
|
|
@@ -1,8 +1,13 @@
|
|
| 1 |
"""
|
| 2 |
Test the Accumulator Pattern for Microsoft Agent Framework event handling.
|
| 3 |
|
| 4 |
-
This tests SPEC
|
| 5 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
"""
|
| 7 |
|
| 8 |
import importlib
|
|
@@ -14,49 +19,41 @@ import pytest
|
|
| 14 |
|
| 15 |
|
| 16 |
# --- Create real event classes ---
|
| 17 |
-
class
|
| 18 |
-
"""Simulates
|
| 19 |
|
| 20 |
-
def __init__(self, text: str,
|
| 21 |
-
self.
|
| 22 |
-
self.
|
|
|
|
| 23 |
|
| 24 |
|
| 25 |
-
class
|
| 26 |
-
"""Simulates
|
| 27 |
|
| 28 |
-
def __init__(self,
|
| 29 |
-
self.
|
| 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
|
| 36 |
-
"""Simulates
|
| 37 |
|
| 38 |
-
def __init__(self,
|
| 39 |
-
self.
|
| 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.
|
| 91 |
-
mock_af.
|
| 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:
|
| 177 |
-
Expected: AgentEvent with "Hello World"
|
| 178 |
"""
|
| 179 |
events = [
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 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 |
-
#
|
| 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 ->
|
| 218 |
-
Expected: AgentEvent with fallback text
|
| 219 |
"""
|
| 220 |
events = [
|
| 221 |
-
|
| 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 |
-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
| 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):
|
|
@@ -1,11 +1,23 @@
|
|
| 1 |
"""Test for AdvancedOrchestrator event processing (P1 Bug)."""
|
| 2 |
|
|
|
|
|
|
|
| 3 |
import pytest
|
| 4 |
-
from agent_framework import
|
| 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 =
|
| 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 =
|
| 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 =
|
| 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 |
)
|
|
@@ -21,7 +21,7 @@ wheels = [
|
|
| 21 |
|
| 22 |
[[package]]
|
| 23 |
name = "agent-framework-core"
|
| 24 |
-
version = "1.0.
|
| 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/
|
| 40 |
wheels = [
|
| 41 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 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 = "
|
| 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.
|
| 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/
|
| 2699 |
wheels = [
|
| 2700 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 2701 |
]
|
| 2702 |
|
| 2703 |
[package.optional-dependencies]
|
|
@@ -3671,32 +3671,32 @@ wheels = [
|
|
| 3671 |
|
| 3672 |
[[package]]
|
| 3673 |
name = "opentelemetry-api"
|
| 3674 |
-
version = "1.
|
| 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/
|
| 3681 |
wheels = [
|
| 3682 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 3683 |
]
|
| 3684 |
|
| 3685 |
[[package]]
|
| 3686 |
name = "opentelemetry-exporter-otlp-proto-common"
|
| 3687 |
-
version = "1.
|
| 3688 |
source = { registry = "https://pypi.org/simple" }
|
| 3689 |
dependencies = [
|
| 3690 |
{ name = "opentelemetry-proto" },
|
| 3691 |
]
|
| 3692 |
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
| 3693 |
wheels = [
|
| 3694 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 3695 |
]
|
| 3696 |
|
| 3697 |
[[package]]
|
| 3698 |
name = "opentelemetry-exporter-otlp-proto-grpc"
|
| 3699 |
-
version = "1.
|
| 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/
|
| 3711 |
wheels = [
|
| 3712 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 3713 |
]
|
| 3714 |
|
| 3715 |
[[package]]
|
| 3716 |
name = "opentelemetry-exporter-otlp-proto-http"
|
| 3717 |
-
version = "1.
|
| 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/
|
| 3729 |
wheels = [
|
| 3730 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 3731 |
]
|
| 3732 |
|
| 3733 |
[[package]]
|
| 3734 |
name = "opentelemetry-instrumentation"
|
| 3735 |
-
version = "0.
|
| 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/
|
| 3744 |
wheels = [
|
| 3745 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 3746 |
]
|
| 3747 |
|
| 3748 |
[[package]]
|
| 3749 |
name = "opentelemetry-instrumentation-httpx"
|
| 3750 |
-
version = "0.
|
| 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/
|
| 3760 |
wheels = [
|
| 3761 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 3762 |
]
|
| 3763 |
|
| 3764 |
[[package]]
|
| 3765 |
name = "opentelemetry-proto"
|
| 3766 |
-
version = "1.
|
| 3767 |
source = { registry = "https://pypi.org/simple" }
|
| 3768 |
dependencies = [
|
| 3769 |
{ name = "protobuf" },
|
| 3770 |
]
|
| 3771 |
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
| 3772 |
wheels = [
|
| 3773 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 3774 |
]
|
| 3775 |
|
| 3776 |
[[package]]
|
| 3777 |
name = "opentelemetry-sdk"
|
| 3778 |
-
version = "1.
|
| 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/
|
| 3786 |
wheels = [
|
| 3787 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 3788 |
]
|
| 3789 |
|
| 3790 |
[[package]]
|
| 3791 |
name = "opentelemetry-semantic-conventions"
|
| 3792 |
-
version = "0.
|
| 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/
|
| 3799 |
wheels = [
|
| 3800 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 3801 |
]
|
| 3802 |
|
| 3803 |
[[package]]
|
|
@@ -3811,11 +3811,11 @@ wheels = [
|
|
| 3811 |
|
| 3812 |
[[package]]
|
| 3813 |
name = "opentelemetry-util-http"
|
| 3814 |
-
version = "0.
|
| 3815 |
source = { registry = "https://pypi.org/simple" }
|
| 3816 |
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
| 3817 |
wheels = [
|
| 3818 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 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]]
|