agent-framework-orchestrations 1.3.1__tar.gz → 1.3.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {agent_framework_orchestrations-1.3.1 → agent_framework_orchestrations-1.3.2}/PKG-INFO +4 -2
- {agent_framework_orchestrations-1.3.1 → agent_framework_orchestrations-1.3.2}/agent_framework_orchestrations/_concurrent.py +10 -1
- {agent_framework_orchestrations-1.3.1 → agent_framework_orchestrations-1.3.2}/agent_framework_orchestrations/_group_chat.py +6 -1
- {agent_framework_orchestrations-1.3.1 → agent_framework_orchestrations-1.3.2}/agent_framework_orchestrations/_handoff.py +8 -1
- {agent_framework_orchestrations-1.3.1 → agent_framework_orchestrations-1.3.2}/agent_framework_orchestrations/_magentic.py +40 -11
- {agent_framework_orchestrations-1.3.1 → agent_framework_orchestrations-1.3.2}/agent_framework_orchestrations/_orchestration_request_info.py +18 -0
- {agent_framework_orchestrations-1.3.1 → agent_framework_orchestrations-1.3.2}/agent_framework_orchestrations/_sequential.py +6 -1
- {agent_framework_orchestrations-1.3.1 → agent_framework_orchestrations-1.3.2}/pyproject.toml +3 -2
- {agent_framework_orchestrations-1.3.1 → agent_framework_orchestrations-1.3.2}/LICENSE +0 -0
- {agent_framework_orchestrations-1.3.1 → agent_framework_orchestrations-1.3.2}/README.md +0 -0
- {agent_framework_orchestrations-1.3.1 → agent_framework_orchestrations-1.3.2}/agent_framework_orchestrations/__init__.py +0 -0
- {agent_framework_orchestrations-1.3.1 → agent_framework_orchestrations-1.3.2}/agent_framework_orchestrations/_base_group_chat_orchestrator.py +0 -0
- {agent_framework_orchestrations-1.3.1 → agent_framework_orchestrations-1.3.2}/agent_framework_orchestrations/_feature_usage.py +0 -0
- {agent_framework_orchestrations-1.3.1 → agent_framework_orchestrations-1.3.2}/agent_framework_orchestrations/_orchestration_state.py +0 -0
- {agent_framework_orchestrations-1.3.1 → agent_framework_orchestrations-1.3.2}/agent_framework_orchestrations/_orchestrator_helpers.py +0 -0
- {agent_framework_orchestrations-1.3.1 → agent_framework_orchestrations-1.3.2}/agent_framework_orchestrations/_participant_output_config.py +0 -0
- {agent_framework_orchestrations-1.3.1 → agent_framework_orchestrations-1.3.2}/agent_framework_orchestrations/_workflow_builder.py +0 -0
- {agent_framework_orchestrations-1.3.1 → agent_framework_orchestrations-1.3.2}/agent_framework_orchestrations/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: agent-framework-orchestrations
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.2
|
|
4
4
|
Summary: Orchestration patterns for Microsoft Agent Framework. Includes SequentialBuilder, ConcurrentBuilder, HandoffBuilder, GroupChatBuilder, and MagenticBuilder.
|
|
5
5
|
Author-email: Microsoft <af-support@microsoft.com>
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -14,6 +14,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.12
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.13
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
17
18
|
Classifier: Typing :: Typed
|
|
18
19
|
License-File: LICENSE
|
|
19
20
|
Requires-Dist: agent-framework-core>=1.19.0,<2
|
|
@@ -21,6 +22,7 @@ Project-URL: homepage, https://aka.ms/agent-framework
|
|
|
21
22
|
Project-URL: issues, https://github.com/microsoft/agent-framework/issues
|
|
22
23
|
Project-URL: release_notes, https://github.com/microsoft/agent-framework/releases?q=tag%3Apython-1&expanded=true
|
|
23
24
|
Project-URL: source, https://github.com/microsoft/agent-framework/tree/main/python
|
|
25
|
+
Import-Name: agent_framework_orchestrations
|
|
24
26
|
|
|
25
27
|
# Agent Framework Orchestrations
|
|
26
28
|
|
|
@@ -18,7 +18,7 @@ from agent_framework._workflows._workflow_context import WorkflowContext
|
|
|
18
18
|
from typing_extensions import Never
|
|
19
19
|
|
|
20
20
|
from ._feature_usage import FeatureIndex
|
|
21
|
-
from ._orchestration_request_info import AgentApprovalExecutor
|
|
21
|
+
from ._orchestration_request_info import AgentApprovalExecutor, validate_request_info_filter
|
|
22
22
|
from ._participant_output_config import (
|
|
23
23
|
UNSET,
|
|
24
24
|
_coalesce_output_from, # pyright: ignore[reportPrivateUsage]
|
|
@@ -172,6 +172,10 @@ class _CallbackAggregator(Executor):
|
|
|
172
172
|
else:
|
|
173
173
|
ret = await asyncio.to_thread(self._callback, results)
|
|
174
174
|
|
|
175
|
+
# Async callable objects and wrappers returning a coroutine are not coroutine functions
|
|
176
|
+
if inspect.isawaitable(ret):
|
|
177
|
+
ret = await ret
|
|
178
|
+
|
|
175
179
|
# If the callback returned a value, finalize the workflow with it
|
|
176
180
|
if ret is not None:
|
|
177
181
|
await ctx.yield_output(ret)
|
|
@@ -382,6 +386,11 @@ class ConcurrentBuilder:
|
|
|
382
386
|
else:
|
|
383
387
|
raise TypeError(f"Participants must be SupportsAgentRun or Executor instances. Got {type(p).__name__}.")
|
|
384
388
|
|
|
389
|
+
if self._request_info_enabled:
|
|
390
|
+
validate_request_info_filter(
|
|
391
|
+
self._request_info_filter,
|
|
392
|
+
[resolve_agent_id(p) for p in participants if not isinstance(p, Executor)],
|
|
393
|
+
)
|
|
385
394
|
return executors
|
|
386
395
|
|
|
387
396
|
def build(self) -> Workflow:
|
|
@@ -51,7 +51,7 @@ from ._base_group_chat_orchestrator import (
|
|
|
51
51
|
TerminationCondition,
|
|
52
52
|
)
|
|
53
53
|
from ._feature_usage import FeatureIndex
|
|
54
|
-
from ._orchestration_request_info import AgentApprovalExecutor
|
|
54
|
+
from ._orchestration_request_info import AgentApprovalExecutor, validate_request_info_filter
|
|
55
55
|
from ._orchestrator_helpers import clean_conversation_for_handoff, extract_markdown_fence_bodies
|
|
56
56
|
from ._participant_output_config import (
|
|
57
57
|
UNSET,
|
|
@@ -1030,6 +1030,11 @@ class GroupChatBuilder:
|
|
|
1030
1030
|
f"Participants must be SupportsAgentRun or Executor instances. Got {type(participant).__name__}."
|
|
1031
1031
|
)
|
|
1032
1032
|
|
|
1033
|
+
if self._request_info_enabled:
|
|
1034
|
+
validate_request_info_filter(
|
|
1035
|
+
self._request_info_filter,
|
|
1036
|
+
[resolve_agent_id(p) for p in participants if not isinstance(p, Executor)],
|
|
1037
|
+
)
|
|
1033
1038
|
return executors
|
|
1034
1039
|
|
|
1035
1040
|
def build(self) -> Workflow:
|
|
@@ -275,6 +275,9 @@ class HandoffAgentExecutor(AgentExecutor):
|
|
|
275
275
|
autonomous_mode_prompt: Prompt to provide to the agent when continuing in autonomous mode.
|
|
276
276
|
This will guide the agent in the absence of user input.
|
|
277
277
|
autonomous_mode_turn_limit: Maximum number of autonomous turns before requesting user input.
|
|
278
|
+
``0`` returns control to the user as soon as the agent responds
|
|
279
|
+
without a handoff; the default limit is only used when this is
|
|
280
|
+
left as ``None``.
|
|
278
281
|
"""
|
|
279
282
|
self._auto_handoff_middleware = _AutoHandoffMiddleware(handoffs)
|
|
280
283
|
cloned_agent = self._prepare_agent_with_handoffs(agent, handoffs, self._auto_handoff_middleware)
|
|
@@ -288,7 +291,9 @@ class HandoffAgentExecutor(AgentExecutor):
|
|
|
288
291
|
# Autonomous mode members
|
|
289
292
|
self._autonomous_mode = autonomous_mode
|
|
290
293
|
self._autonomous_mode_prompt = autonomous_mode_prompt or _AUTONOMOUS_MODE_DEFAULT_PROMPT
|
|
291
|
-
self._autonomous_mode_turn_limit =
|
|
294
|
+
self._autonomous_mode_turn_limit = (
|
|
295
|
+
_DEFAULT_AUTONOMOUS_TURN_LIMIT if autonomous_mode_turn_limit is None else autonomous_mode_turn_limit
|
|
296
|
+
)
|
|
292
297
|
self._autonomous_mode_turns = 0
|
|
293
298
|
|
|
294
299
|
def _prepare_agent_with_handoffs(
|
|
@@ -938,6 +943,8 @@ class HandoffBuilder:
|
|
|
938
943
|
in autonomous mode. If not provided, a default prompt will be used.
|
|
939
944
|
turn_limits: Optional mapping of agent identifiers/factory names to maximum number of autonomous turns
|
|
940
945
|
before returning control to the user. If not provided, a default turn limit will be used.
|
|
946
|
+
A limit of ``0`` disables autonomous continuation for that agent instead of falling back
|
|
947
|
+
to the default.
|
|
941
948
|
"""
|
|
942
949
|
self._autonomous_mode = True
|
|
943
950
|
self._autonomous_mode_prompts = prompts or {}
|
|
@@ -427,8 +427,20 @@ def _extract_json(text: str) -> dict[str, Any]:
|
|
|
427
427
|
raise ValueError("No JSON object found.")
|
|
428
428
|
depth = 0
|
|
429
429
|
end = None
|
|
430
|
+
in_string = False
|
|
431
|
+
escaped = False
|
|
430
432
|
for i, ch in enumerate(text[start:], start=start):
|
|
431
|
-
|
|
433
|
+
# Braces inside string values, such as code in an instruction, do not count.
|
|
434
|
+
if in_string:
|
|
435
|
+
if escaped:
|
|
436
|
+
escaped = False
|
|
437
|
+
elif ch == "\\":
|
|
438
|
+
escaped = True
|
|
439
|
+
elif ch == '"':
|
|
440
|
+
in_string = False
|
|
441
|
+
elif ch == '"':
|
|
442
|
+
in_string = True
|
|
443
|
+
elif ch == "{":
|
|
432
444
|
depth += 1
|
|
433
445
|
elif ch == "}":
|
|
434
446
|
depth -= 1
|
|
@@ -517,7 +529,7 @@ class MagenticManagerBase(ABC):
|
|
|
517
529
|
|
|
518
530
|
|
|
519
531
|
class StandardMagenticManager(MagenticManagerBase):
|
|
520
|
-
"""Standard Magentic manager that performs real LLM calls via
|
|
532
|
+
"""Standard Magentic manager that performs real LLM calls via an Agent.
|
|
521
533
|
|
|
522
534
|
The manager constructs prompts that mirror the original Magentic One orchestration:
|
|
523
535
|
- Facts gathering
|
|
@@ -724,21 +736,37 @@ class StandardMagenticManager(MagenticManagerBase):
|
|
|
724
736
|
# Include full context to help the model decide current stage, with small retry loop
|
|
725
737
|
attempts = 0
|
|
726
738
|
last_error: Exception | None = None
|
|
739
|
+
unknown_speaker_ledger: MagenticProgressLedger | None = None
|
|
727
740
|
while attempts < self.progress_ledger_retry_count:
|
|
728
741
|
raw = await self._complete([*magentic_context.chat_history, user_message])
|
|
729
742
|
try:
|
|
730
743
|
ledger_dict = _extract_json(raw.text)
|
|
731
|
-
|
|
744
|
+
ledger = _coerce_model(MagenticProgressLedger, ledger_dict)
|
|
732
745
|
except Exception as ex:
|
|
733
746
|
last_error = ex
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
747
|
+
unknown_speaker_ledger = None
|
|
748
|
+
else:
|
|
749
|
+
next_speaker = ledger.next_speaker.answer
|
|
750
|
+
if (
|
|
751
|
+
ledger.is_request_satisfied.answer
|
|
752
|
+
or not isinstance(next_speaker, str)
|
|
753
|
+
or next_speaker in agent_names
|
|
754
|
+
):
|
|
755
|
+
return ledger
|
|
756
|
+
# Retry an unknown name, but keep the ledger so running out of attempts ends the run
|
|
757
|
+
# through the orchestrator's invalid speaker handling instead of another reset.
|
|
758
|
+
unknown_speaker_ledger = ledger
|
|
759
|
+
last_error = ValueError(f"Unknown next speaker '{next_speaker}'. Valid names: {names_csv}.")
|
|
760
|
+
attempts += 1
|
|
761
|
+
logger.warning(
|
|
762
|
+
f"Progress ledger attempt failed ({attempts}/{self.progress_ledger_retry_count}): {last_error}"
|
|
763
|
+
)
|
|
764
|
+
if attempts < self.progress_ledger_retry_count:
|
|
765
|
+
# brief backoff before next try
|
|
766
|
+
await asyncio.sleep(0.25 * attempts)
|
|
741
767
|
|
|
768
|
+
if unknown_speaker_ledger is not None:
|
|
769
|
+
return unknown_speaker_ledger
|
|
742
770
|
raise RuntimeError(
|
|
743
771
|
f"Progress ledger parse failed after {self.progress_ledger_retry_count} attempt(s): {last_error}"
|
|
744
772
|
)
|
|
@@ -1374,8 +1402,9 @@ class MagenticAgentExecutor(AgentExecutor):
|
|
|
1374
1402
|
# Request into related
|
|
1375
1403
|
self._pending_agent_requests.clear()
|
|
1376
1404
|
self._pending_responses_to_agent.clear()
|
|
1405
|
+
self._pending_request_order.clear()
|
|
1377
1406
|
# Reset sessions
|
|
1378
|
-
self.
|
|
1407
|
+
self._session = self._agent.create_session()
|
|
1379
1408
|
|
|
1380
1409
|
|
|
1381
1410
|
# endregion Magentic Agent Executor
|
|
@@ -41,6 +41,24 @@ def resolve_request_info_filter(agents: list[str | SupportsAgentRun] | None) ->
|
|
|
41
41
|
return result
|
|
42
42
|
|
|
43
43
|
|
|
44
|
+
def validate_request_info_filter(request_info_filter: set[str] | None, agent_ids: list[str]) -> None:
|
|
45
|
+
"""Raise if the request info filter names an agent that is not a participant.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
request_info_filter: Agent IDs passed to ``with_request_info(agents=...)``. Empty or None means all agents.
|
|
49
|
+
agent_ids: IDs of the agent participants in the orchestration.
|
|
50
|
+
|
|
51
|
+
Raises:
|
|
52
|
+
ValueError: If a name in the filter does not match any agent participant.
|
|
53
|
+
"""
|
|
54
|
+
unknown = sorted((request_info_filter or set()) - set(agent_ids))
|
|
55
|
+
if unknown:
|
|
56
|
+
raise ValueError(
|
|
57
|
+
f"with_request_info() names agents that are not agent participants: "
|
|
58
|
+
f"{', '.join(repr(name) for name in unknown)}. Agent participants: {', '.join(agent_ids)}."
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
44
62
|
@dataclass
|
|
45
63
|
class AgentRequestInfoResponse:
|
|
46
64
|
"""Response containing additional information requested from users for agents.
|
|
@@ -32,7 +32,7 @@ from agent_framework._workflows._workflow import Workflow
|
|
|
32
32
|
from agent_framework._workflows._workflow_context import WorkflowContext
|
|
33
33
|
|
|
34
34
|
from ._feature_usage import FeatureIndex
|
|
35
|
-
from ._orchestration_request_info import AgentApprovalExecutor
|
|
35
|
+
from ._orchestration_request_info import AgentApprovalExecutor, validate_request_info_filter
|
|
36
36
|
from ._participant_output_config import (
|
|
37
37
|
UNSET,
|
|
38
38
|
_coalesce_output_from, # pyright: ignore[reportPrivateUsage]
|
|
@@ -230,6 +230,11 @@ class SequentialBuilder:
|
|
|
230
230
|
else:
|
|
231
231
|
raise TypeError(f"Participants must be SupportsAgentRun or Executor instances. Got {type(p).__name__}.")
|
|
232
232
|
|
|
233
|
+
if self._request_info_enabled:
|
|
234
|
+
validate_request_info_filter(
|
|
235
|
+
self._request_info_filter,
|
|
236
|
+
[resolve_agent_id(p) for p in participants if not isinstance(p, Executor)],
|
|
237
|
+
)
|
|
233
238
|
return executors
|
|
234
239
|
|
|
235
240
|
def build(self) -> Workflow:
|
{agent_framework_orchestrations-1.3.1 → agent_framework_orchestrations-1.3.2}/pyproject.toml
RENAMED
|
@@ -4,7 +4,7 @@ description = "Orchestration patterns for Microsoft Agent Framework. Includes Se
|
|
|
4
4
|
authors = [{ name = "Microsoft", email = "af-support@microsoft.com"}]
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
7
|
-
version = "1.3.
|
|
7
|
+
version = "1.3.2"
|
|
8
8
|
license-files = ["LICENSE"]
|
|
9
9
|
urls.homepage = "https://aka.ms/agent-framework"
|
|
10
10
|
urls.source = "https://github.com/microsoft/agent-framework/tree/main/python"
|
|
@@ -20,6 +20,7 @@ classifiers = [
|
|
|
20
20
|
"Programming Language :: Python :: 3.12",
|
|
21
21
|
"Programming Language :: Python :: 3.13",
|
|
22
22
|
"Programming Language :: Python :: 3.14",
|
|
23
|
+
"Programming Language :: Python :: 3.15",
|
|
23
24
|
"Typing :: Typed",
|
|
24
25
|
]
|
|
25
26
|
dependencies = [
|
|
@@ -92,5 +93,5 @@ help = "Run the default unit test suite for this package."
|
|
|
92
93
|
cmd = 'pytest -m "not integration" --cov=agent_framework_orchestrations --cov-report=term-missing:skip-covered -n auto --dist worksteal tests'
|
|
93
94
|
|
|
94
95
|
[build-system]
|
|
95
|
-
requires = ["flit-core
|
|
96
|
+
requires = ["flit-core>=3.11,<5.0"]
|
|
96
97
|
build-backend = "flit_core.buildapi"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|