agent-framework-core 1.1.0__tar.gz → 1.2.0__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_core-1.1.0 → agent_framework_core-1.2.0}/PKG-INFO +1 -1
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/__init__.py +18 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/_evaluation.py +19 -1
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/_feature_stage.py +1 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/_telemetry.py +57 -22
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/_tools.py +88 -12
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/_workflows/_events.py +7 -0
- agent_framework_core-1.2.0/agent_framework/_workflows/_functional.py +1551 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/_workflows/_workflow.py +5 -5
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/a2a/__init__.py +2 -1
- agent_framework_core-1.2.0/agent_framework/a2a/__init__.pyi +5 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/foundry/__init__.py +1 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/github/__init__.py +2 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/github/__init__.pyi +2 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/pyproject.toml +1 -1
- agent_framework_core-1.1.0/agent_framework/a2a/__init__.pyi +0 -9
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/LICENSE +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/README.md +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/_agents.py +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/_clients.py +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/_compaction.py +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/_docstrings.py +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/_mcp.py +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/_middleware.py +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/_serialization.py +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/_sessions.py +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/_settings.py +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/_skills.py +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/_types.py +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/_workflows/__init__.py +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/_workflows/_agent.py +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/_workflows/_agent_executor.py +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/_workflows/_agent_utils.py +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/_workflows/_checkpoint.py +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/_workflows/_checkpoint_encoding.py +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/_workflows/_const.py +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/_workflows/_conversation_history.py +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/_workflows/_edge.py +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/_workflows/_edge_runner.py +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/_workflows/_executor.py +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/_workflows/_function_executor.py +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/_workflows/_message_utils.py +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/_workflows/_model_utils.py +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/_workflows/_request_info_mixin.py +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/_workflows/_runner.py +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/_workflows/_runner_context.py +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/_workflows/_state.py +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/_workflows/_typing_utils.py +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/_workflows/_validation.py +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/_workflows/_viz.py +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/_workflows/_workflow_builder.py +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/_workflows/_workflow_context.py +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/_workflows/_workflow_executor.py +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/ag_ui/__init__.py +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/ag_ui/__init__.pyi +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/amazon/__init__.py +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/amazon/__init__.pyi +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/anthropic/__init__.py +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/anthropic/__init__.pyi +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/azure/__init__.py +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/azure/__init__.pyi +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/chatkit/__init__.py +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/chatkit/__init__.pyi +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/declarative/__init__.py +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/declarative/__init__.pyi +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/devui/__init__.py +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/devui/__init__.pyi +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/exceptions.py +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/foundry/__init__.pyi +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/google/__init__.py +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/google/__init__.pyi +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/lab/__init__.py +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/mem0/__init__.py +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/mem0/__init__.pyi +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/microsoft/__init__.py +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/microsoft/__init__.pyi +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/observability.py +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/ollama/__init__.py +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/ollama/__init__.pyi +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/openai/__init__.py +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/openai/__init__.pyi +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/orchestrations/__init__.py +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/orchestrations/__init__.pyi +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/py.typed +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/redis/__init__.py +0 -0
- {agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/redis/__init__.pyi +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agent-framework-core
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: Microsoft Agent Framework for building AI Agents with Python. This is the core package that has all the core abstractions and implementations.
|
|
5
5
|
Author-email: Microsoft <af-support@microsoft.com>
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -125,6 +125,7 @@ from ._telemetry import (
|
|
|
125
125
|
prepend_agent_framework_to_user_agent,
|
|
126
126
|
)
|
|
127
127
|
from ._tools import (
|
|
128
|
+
SKIP_PARSING,
|
|
128
129
|
FunctionInvocationConfiguration,
|
|
129
130
|
FunctionInvocationLayer,
|
|
130
131
|
FunctionTool,
|
|
@@ -212,6 +213,15 @@ from ._workflows._executor import (
|
|
|
212
213
|
handler,
|
|
213
214
|
)
|
|
214
215
|
from ._workflows._function_executor import FunctionExecutor, executor
|
|
216
|
+
from ._workflows._functional import (
|
|
217
|
+
FunctionalWorkflow,
|
|
218
|
+
FunctionalWorkflowAgent,
|
|
219
|
+
RunContext,
|
|
220
|
+
StepWrapper,
|
|
221
|
+
get_run_context,
|
|
222
|
+
step,
|
|
223
|
+
workflow,
|
|
224
|
+
)
|
|
215
225
|
from ._workflows._request_info_mixin import response_handler
|
|
216
226
|
from ._workflows._runner import Runner
|
|
217
227
|
from ._workflows._runner_context import (
|
|
@@ -258,6 +268,7 @@ __all__ = [
|
|
|
258
268
|
"GROUP_INDEX_KEY",
|
|
259
269
|
"GROUP_KIND_KEY",
|
|
260
270
|
"GROUP_TOKEN_COUNT_KEY",
|
|
271
|
+
"SKIP_PARSING",
|
|
261
272
|
"SUMMARIZED_BY_SUMMARY_ID_KEY",
|
|
262
273
|
"SUMMARY_OF_GROUP_IDS_KEY",
|
|
263
274
|
"SUMMARY_OF_MESSAGE_IDS_KEY",
|
|
@@ -330,6 +341,8 @@ __all__ = [
|
|
|
330
341
|
"FunctionMiddleware",
|
|
331
342
|
"FunctionMiddlewareTypes",
|
|
332
343
|
"FunctionTool",
|
|
344
|
+
"FunctionalWorkflow",
|
|
345
|
+
"FunctionalWorkflowAgent",
|
|
333
346
|
"GeneratedEmbeddings",
|
|
334
347
|
"GraphConnectivityError",
|
|
335
348
|
"HistoryProvider",
|
|
@@ -352,6 +365,7 @@ __all__ = [
|
|
|
352
365
|
"ResponseStream",
|
|
353
366
|
"Role",
|
|
354
367
|
"RoleLiteral",
|
|
368
|
+
"RunContext",
|
|
355
369
|
"Runner",
|
|
356
370
|
"RunnerContext",
|
|
357
371
|
"SecretString",
|
|
@@ -364,6 +378,7 @@ __all__ = [
|
|
|
364
378
|
"SkillScriptRunner",
|
|
365
379
|
"SkillsProvider",
|
|
366
380
|
"SlidingWindowStrategy",
|
|
381
|
+
"StepWrapper",
|
|
367
382
|
"SubWorkflowRequestMessage",
|
|
368
383
|
"SubWorkflowResponseMessage",
|
|
369
384
|
"SummarizationStrategy",
|
|
@@ -422,6 +437,7 @@ __all__ = [
|
|
|
422
437
|
"evaluator",
|
|
423
438
|
"executor",
|
|
424
439
|
"function_middleware",
|
|
440
|
+
"get_run_context",
|
|
425
441
|
"handler",
|
|
426
442
|
"included_messages",
|
|
427
443
|
"included_token_count",
|
|
@@ -437,6 +453,7 @@ __all__ = [
|
|
|
437
453
|
"register_state_type",
|
|
438
454
|
"resolve_agent_id",
|
|
439
455
|
"response_handler",
|
|
456
|
+
"step",
|
|
440
457
|
"tool",
|
|
441
458
|
"tool_call_args_match",
|
|
442
459
|
"tool_called_check",
|
|
@@ -445,4 +462,5 @@ __all__ = [
|
|
|
445
462
|
"validate_tool_mode",
|
|
446
463
|
"validate_tools",
|
|
447
464
|
"validate_workflow_graph",
|
|
465
|
+
"workflow",
|
|
448
466
|
]
|
|
@@ -1659,6 +1659,7 @@ async def evaluate_workflow(
|
|
|
1659
1659
|
workflow: Workflow,
|
|
1660
1660
|
workflow_result: WorkflowRunResult | None = None,
|
|
1661
1661
|
queries: str | Sequence[str] | None = None,
|
|
1662
|
+
expected_output: str | Sequence[str] | None = None,
|
|
1662
1663
|
evaluators: Evaluator | Callable[..., Any] | Sequence[Evaluator | Callable[..., Any]],
|
|
1663
1664
|
eval_name: str | None = None,
|
|
1664
1665
|
include_overall: bool = True,
|
|
@@ -1683,6 +1684,11 @@ async def evaluate_workflow(
|
|
|
1683
1684
|
workflow: The workflow instance.
|
|
1684
1685
|
workflow_result: A completed ``WorkflowRunResult``.
|
|
1685
1686
|
queries: Test queries to run through the workflow.
|
|
1687
|
+
expected_output: Ground-truth expected output(s), one per query. A
|
|
1688
|
+
single string is wrapped into a one-element list. When provided,
|
|
1689
|
+
must be the same length as ``queries``. Each value is stamped on
|
|
1690
|
+
the corresponding ``EvalItem.expected_output`` for evaluators
|
|
1691
|
+
that compare against a reference answer (e.g. similarity).
|
|
1686
1692
|
evaluators: One or more ``Evaluator`` instances.
|
|
1687
1693
|
eval_name: Display name for the evaluation.
|
|
1688
1694
|
include_overall: Whether to evaluate the workflow's final output.
|
|
@@ -1720,10 +1726,20 @@ async def evaluate_workflow(
|
|
|
1720
1726
|
# Normalize singular query to list
|
|
1721
1727
|
if isinstance(queries, str):
|
|
1722
1728
|
queries = [queries]
|
|
1729
|
+
if isinstance(expected_output, str):
|
|
1730
|
+
expected_output = [expected_output]
|
|
1723
1731
|
|
|
1724
1732
|
if workflow_result is None and queries is None:
|
|
1725
1733
|
raise ValueError("Provide either 'workflow_result' or 'queries'.")
|
|
1726
1734
|
|
|
1735
|
+
if expected_output is not None and queries is None:
|
|
1736
|
+
raise ValueError(
|
|
1737
|
+
"Provide 'queries' when using 'expected_output';"
|
|
1738
|
+
" 'expected_output' is not supported with 'workflow_result' only."
|
|
1739
|
+
)
|
|
1740
|
+
if expected_output is not None and queries is not None and len(expected_output) != len(queries):
|
|
1741
|
+
raise ValueError(f"Got {len(queries)} queries but {len(expected_output)} expected_output values.")
|
|
1742
|
+
|
|
1727
1743
|
if num_repetitions < 1:
|
|
1728
1744
|
raise ValueError(f"num_repetitions must be >= 1, got {num_repetitions}.")
|
|
1729
1745
|
|
|
@@ -1737,7 +1753,7 @@ async def evaluate_workflow(
|
|
|
1737
1753
|
if queries is not None:
|
|
1738
1754
|
results_list: list[WRR] = []
|
|
1739
1755
|
for _rep in range(num_repetitions):
|
|
1740
|
-
for q in queries:
|
|
1756
|
+
for qi, q in enumerate(queries):
|
|
1741
1757
|
result = await workflow.run(q)
|
|
1742
1758
|
if not isinstance(result, WRR):
|
|
1743
1759
|
raise TypeError(f"Expected WorkflowRunResult from workflow.run(), got {type(result).__name__}.")
|
|
@@ -1746,6 +1762,8 @@ async def evaluate_workflow(
|
|
|
1746
1762
|
if include_overall:
|
|
1747
1763
|
overall_item = _build_overall_item(q, result)
|
|
1748
1764
|
if overall_item:
|
|
1765
|
+
if expected_output is not None:
|
|
1766
|
+
overall_item.expected_output = expected_output[qi]
|
|
1749
1767
|
overall_items.append(overall_item)
|
|
1750
1768
|
else:
|
|
1751
1769
|
assert workflow_result is not None # noqa: S101 # nosec B101
|
|
@@ -2,11 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import contextlib
|
|
5
6
|
import logging
|
|
6
7
|
import os
|
|
7
|
-
from collections.abc import Generator
|
|
8
|
-
from contextlib import contextmanager
|
|
9
|
-
from contextvars import ContextVar
|
|
10
8
|
from typing import Any, Final
|
|
11
9
|
|
|
12
10
|
from . import __version__ as version_info
|
|
@@ -29,34 +27,71 @@ USER_AGENT_KEY: Final[str] = "User-Agent"
|
|
|
29
27
|
HTTP_USER_AGENT: Final[str] = "agent-framework-python"
|
|
30
28
|
AGENT_FRAMEWORK_USER_AGENT = f"{HTTP_USER_AGENT}/{version_info}" # type: ignore[has-type]
|
|
31
29
|
|
|
32
|
-
|
|
30
|
+
# This environment variable is reserved by the Foundry hosting environment to
|
|
31
|
+
# indicate that the agent is running in a hosted environment.
|
|
32
|
+
_FOUNDRY_HOSTING_ENV_VAR = "FOUNDRY_HOSTING_ENVIRONMENT"
|
|
33
|
+
# This prefix is added to the user agent string when the agent is running in a hosted environment.
|
|
34
|
+
_HOSTED_USER_AGENT_PREFIX = "foundry-hosting"
|
|
33
35
|
|
|
36
|
+
_user_agent_prefixes: set[str] = set()
|
|
37
|
+
_hosted_env_detected: bool = False
|
|
34
38
|
|
|
35
|
-
@contextmanager
|
|
36
|
-
def user_agent_prefix(prefix: str) -> Generator[None]:
|
|
37
|
-
"""Context manager that adds a prefix to the user agent string for the current scope.
|
|
38
39
|
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
def _add_user_agent_prefix(prefix: str) -> None:
|
|
41
|
+
"""Permanently add a prefix to the user agent string.
|
|
42
|
+
|
|
43
|
+
This is used by hosting layers to identify themselves in telemetry.
|
|
44
|
+
Once added, the prefix applies to all subsequent user agent strings.
|
|
41
45
|
|
|
42
46
|
Args:
|
|
43
47
|
prefix: The prefix to add (e.g. "foundry-hosting").
|
|
44
48
|
"""
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
_user_agent_prefixes.reset(token)
|
|
49
|
+
if prefix:
|
|
50
|
+
_user_agent_prefixes.add(prefix)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _detect_hosted_environment() -> None:
|
|
54
|
+
"""Detect if running in a hosted environment and add the user agent prefix.
|
|
52
55
|
|
|
56
|
+
Checks the ``FOUNDRY_HOSTING_ENVIRONMENT`` env var first, then falls back
|
|
57
|
+
to checking whether the agent server SDK is installed (via
|
|
58
|
+
``importlib.util.find_spec``) before importing it, to avoid unnecessary
|
|
59
|
+
import overhead for non-hosted scenarios.
|
|
60
|
+
"""
|
|
61
|
+
global _hosted_env_detected
|
|
62
|
+
if _hosted_env_detected:
|
|
63
|
+
return
|
|
64
|
+
|
|
65
|
+
if (env_value := os.environ.get(_FOUNDRY_HOSTING_ENV_VAR)) is not None:
|
|
66
|
+
# Env var exists — trust its value and skip the fallback.
|
|
67
|
+
if env_value:
|
|
68
|
+
_add_user_agent_prefix(_HOSTED_USER_AGENT_PREFIX)
|
|
69
|
+
_hosted_env_detected = True
|
|
70
|
+
return
|
|
53
71
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
72
|
+
# Env var not set — fall back to AgentConfig as a second layer of defense.
|
|
73
|
+
# Use find_spec to avoid the cost of a full import when the SDK is not installed.
|
|
74
|
+
import importlib.util
|
|
75
|
+
|
|
76
|
+
try:
|
|
77
|
+
if importlib.util.find_spec("azure.ai.agentserver.core") is None:
|
|
78
|
+
return
|
|
79
|
+
except (ModuleNotFoundError, ValueError):
|
|
80
|
+
return
|
|
81
|
+
with contextlib.suppress(ImportError, AttributeError):
|
|
82
|
+
from azure.ai.agentserver.core import AgentConfig # pyright: ignore[reportMissingImports]
|
|
83
|
+
|
|
84
|
+
if AgentConfig.from_env().is_hosted:
|
|
85
|
+
_add_user_agent_prefix(_HOSTED_USER_AGENT_PREFIX)
|
|
86
|
+
_hosted_env_detected = True
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def get_user_agent() -> str:
|
|
90
|
+
"""Return the full user agent string including any registered prefixes."""
|
|
91
|
+
_detect_hosted_environment()
|
|
92
|
+
if not _user_agent_prefixes:
|
|
58
93
|
return AGENT_FRAMEWORK_USER_AGENT
|
|
59
|
-
return f"{'/'.join(
|
|
94
|
+
return f"{'/'.join(sorted(_user_agent_prefixes))}/{AGENT_FRAMEWORK_USER_AGENT}"
|
|
60
95
|
|
|
61
96
|
|
|
62
97
|
def prepend_agent_framework_to_user_agent(headers: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
@@ -89,7 +124,7 @@ def prepend_agent_framework_to_user_agent(headers: dict[str, Any] | None = None)
|
|
|
89
124
|
"""
|
|
90
125
|
if not IS_TELEMETRY_ENABLED:
|
|
91
126
|
return headers or {}
|
|
92
|
-
user_agent =
|
|
127
|
+
user_agent = get_user_agent()
|
|
93
128
|
if not headers:
|
|
94
129
|
return {USER_AGENT_KEY: user_agent}
|
|
95
130
|
headers[USER_AGENT_KEY] = f"{user_agent} {headers[USER_AGENT_KEY]}" if USER_AGENT_KEY in headers else user_agent
|
|
@@ -94,6 +94,33 @@ ApprovalMode: TypeAlias = Literal["always_require", "never_require"]
|
|
|
94
94
|
ChatClientT = TypeVar("ChatClientT", bound="SupportsChatGetResponse[Any]")
|
|
95
95
|
ResponseModelBoundT = TypeVar("ResponseModelBoundT", bound=BaseModel)
|
|
96
96
|
|
|
97
|
+
|
|
98
|
+
class _SkipParsingSentinel:
|
|
99
|
+
"""Sentinel signaling that :meth:`FunctionTool.invoke` should return the raw value.
|
|
100
|
+
|
|
101
|
+
When passed as ``result_parser`` to :class:`FunctionTool` (or the ``@tool`` decorator),
|
|
102
|
+
the default :meth:`FunctionTool.parse_result` is bypassed and the wrapped function's
|
|
103
|
+
return value is returned unchanged from :meth:`FunctionTool.invoke`. Callers may also
|
|
104
|
+
request the raw value on a per-call basis by passing ``skip_parsing=True`` to
|
|
105
|
+
:meth:`FunctionTool.invoke`.
|
|
106
|
+
|
|
107
|
+
Use the module-level ``SKIP_PARSING`` singleton — do not instantiate this class.
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
_instance: ClassVar[_SkipParsingSentinel | None] = None
|
|
111
|
+
|
|
112
|
+
def __new__(cls) -> _SkipParsingSentinel:
|
|
113
|
+
if cls._instance is None:
|
|
114
|
+
cls._instance = super().__new__(cls)
|
|
115
|
+
return cls._instance
|
|
116
|
+
|
|
117
|
+
def __repr__(self) -> str:
|
|
118
|
+
return "SKIP_PARSING"
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
SKIP_PARSING: Final[_SkipParsingSentinel] = _SkipParsingSentinel()
|
|
122
|
+
"""Sentinel for ``FunctionTool(result_parser=...)`` meaning "do not parse the result"."""
|
|
123
|
+
|
|
97
124
|
# region Helpers
|
|
98
125
|
|
|
99
126
|
|
|
@@ -279,7 +306,7 @@ class FunctionTool(SerializationMixin):
|
|
|
279
306
|
additional_properties: dict[str, Any] | None = None,
|
|
280
307
|
func: Callable[..., Any] | None = None,
|
|
281
308
|
input_model: type[BaseModel] | Mapping[str, Any] | None = None,
|
|
282
|
-
result_parser: Callable[[Any], str | list[Content]] | None = None,
|
|
309
|
+
result_parser: Callable[[Any], str | list[Content]] | _SkipParsingSentinel | None = None,
|
|
283
310
|
**kwargs: Any,
|
|
284
311
|
) -> None:
|
|
285
312
|
"""Initialize the FunctionTool.
|
|
@@ -327,9 +354,11 @@ class FunctionTool(SerializationMixin):
|
|
|
327
354
|
result_parser: An optional callable with signature ``Callable[[Any], str]`` that
|
|
328
355
|
overrides the default result parsing behavior. When provided, this callable
|
|
329
356
|
is used to convert the raw function return value to a string instead of the
|
|
330
|
-
built-in :meth:`parse_result` logic.
|
|
331
|
-
|
|
332
|
-
|
|
357
|
+
built-in :meth:`parse_result` logic. Pass the :data:`SKIP_PARSING` sentinel
|
|
358
|
+
instead of a callable to opt out of parsing entirely; in that case
|
|
359
|
+
:meth:`invoke` returns the wrapped function's raw return value. Depending
|
|
360
|
+
on your function, it may be easiest to just do the serialization directly
|
|
361
|
+
in the function body rather than providing a custom ``result_parser``.
|
|
333
362
|
**kwargs: Additional keyword arguments.
|
|
334
363
|
"""
|
|
335
364
|
# Core attributes (formerly from BaseTool)
|
|
@@ -508,31 +537,65 @@ class FunctionTool(SerializationMixin):
|
|
|
508
537
|
self.invocation_exception_count += 1
|
|
509
538
|
raise
|
|
510
539
|
|
|
540
|
+
@overload
|
|
511
541
|
async def invoke(
|
|
512
542
|
self,
|
|
513
543
|
*,
|
|
514
544
|
arguments: BaseModel | Mapping[str, Any] | None = None,
|
|
515
545
|
context: FunctionInvocationContext | None = None,
|
|
516
546
|
tool_call_id: str | None = None,
|
|
547
|
+
skip_parsing: Literal[True],
|
|
517
548
|
**kwargs: Any,
|
|
518
|
-
) ->
|
|
549
|
+
) -> Any: ...
|
|
550
|
+
|
|
551
|
+
@overload
|
|
552
|
+
async def invoke(
|
|
553
|
+
self,
|
|
554
|
+
*,
|
|
555
|
+
arguments: BaseModel | Mapping[str, Any] | None = None,
|
|
556
|
+
context: FunctionInvocationContext | None = None,
|
|
557
|
+
tool_call_id: str | None = None,
|
|
558
|
+
skip_parsing: Literal[False] = False,
|
|
559
|
+
**kwargs: Any,
|
|
560
|
+
) -> list[Content]: ...
|
|
561
|
+
|
|
562
|
+
async def invoke(
|
|
563
|
+
self,
|
|
564
|
+
*,
|
|
565
|
+
arguments: BaseModel | Mapping[str, Any] | None = None,
|
|
566
|
+
context: FunctionInvocationContext | None = None,
|
|
567
|
+
tool_call_id: str | None = None,
|
|
568
|
+
skip_parsing: bool = False,
|
|
569
|
+
**kwargs: Any,
|
|
570
|
+
) -> list[Content] | Any:
|
|
519
571
|
"""Run the AI function with the provided arguments as a Pydantic model.
|
|
520
572
|
|
|
521
573
|
The raw return value of the wrapped function is automatically parsed into a
|
|
522
574
|
``list[Content]`` using :meth:`parse_result` or the custom ``result_parser``
|
|
523
|
-
|
|
524
|
-
is represented uniformly as Content items.
|
|
575
|
+
configured on the tool. Every result — text, rich media, or serialized
|
|
576
|
+
objects — is represented uniformly as Content items.
|
|
577
|
+
|
|
578
|
+
Parsing can be skipped in two ways: configure the tool with
|
|
579
|
+
``result_parser=SKIP_PARSING`` to always skip parsing, or pass
|
|
580
|
+
``skip_parsing=True`` per call. Either way the wrapped function's raw value
|
|
581
|
+
is returned. This is intended for callers (e.g. sandboxed runtimes) that
|
|
582
|
+
consume the value from Python directly and would otherwise undo the
|
|
583
|
+
``Content`` wrapping.
|
|
525
584
|
|
|
526
585
|
Keyword Args:
|
|
527
586
|
arguments: A mapping or model instance containing the arguments for the function.
|
|
528
587
|
context: Explicit function invocation context carrying runtime kwargs.
|
|
529
588
|
tool_call_id: Optional tool call identifier used for telemetry and tracing.
|
|
589
|
+
skip_parsing: When ``True``, bypass parsing and return the wrapped function's
|
|
590
|
+
raw value instead of a ``list[Content]``. Defaults to ``False``.
|
|
530
591
|
kwargs: Direct function argument values. When provided, every keyword
|
|
531
592
|
must match a declared tool parameter. Runtime data must be passed
|
|
532
593
|
via ``context``.
|
|
533
594
|
|
|
534
595
|
Returns:
|
|
535
|
-
|
|
596
|
+
``list[Content]`` by default. The raw function return value (``Any``) when
|
|
597
|
+
``skip_parsing=True`` (or the tool was constructed with
|
|
598
|
+
``result_parser=SKIP_PARSING``).
|
|
536
599
|
|
|
537
600
|
Raises:
|
|
538
601
|
TypeError: If arguments is not mapping-like or fails schema checks.
|
|
@@ -544,7 +607,9 @@ class FunctionTool(SerializationMixin):
|
|
|
544
607
|
from ._types import Content
|
|
545
608
|
from .observability import OBSERVABILITY_SETTINGS
|
|
546
609
|
|
|
547
|
-
|
|
610
|
+
configured_parser = self.result_parser
|
|
611
|
+
skip_parsing = skip_parsing or configured_parser is SKIP_PARSING
|
|
612
|
+
parser = configured_parser if callable(configured_parser) else FunctionTool.parse_result
|
|
548
613
|
|
|
549
614
|
parameter_names = set(self.parameters().get("properties", {}).keys())
|
|
550
615
|
direct_argument_kwargs = (
|
|
@@ -616,6 +681,10 @@ class FunctionTool(SerializationMixin):
|
|
|
616
681
|
logger.debug(f"Function arguments: {observable_kwargs}")
|
|
617
682
|
res = self.__call__(**call_kwargs)
|
|
618
683
|
result = await res if inspect.isawaitable(res) else res
|
|
684
|
+
if skip_parsing:
|
|
685
|
+
logger.info(f"Function {self.name} succeeded.")
|
|
686
|
+
logger.debug(f"Function result: {type(result).__name__}")
|
|
687
|
+
return result
|
|
619
688
|
try:
|
|
620
689
|
parsed = parser(result)
|
|
621
690
|
except Exception:
|
|
@@ -671,6 +740,13 @@ class FunctionTool(SerializationMixin):
|
|
|
671
740
|
logger.error(f"Function failed. Error: {exception}")
|
|
672
741
|
raise
|
|
673
742
|
else:
|
|
743
|
+
if skip_parsing:
|
|
744
|
+
logger.info(f"Function {self.name} succeeded.")
|
|
745
|
+
if OBSERVABILITY_SETTINGS.SENSITIVE_DATA_ENABLED: # type: ignore[name-defined]
|
|
746
|
+
result_str = str(result)
|
|
747
|
+
span.set_attribute(OtelAttr.TOOL_RESULT, result_str)
|
|
748
|
+
logger.debug(f"Function result: {result_str}")
|
|
749
|
+
return result
|
|
674
750
|
try:
|
|
675
751
|
parsed = parser(result)
|
|
676
752
|
except Exception:
|
|
@@ -1067,7 +1143,7 @@ def tool(
|
|
|
1067
1143
|
max_invocations: int | None = None,
|
|
1068
1144
|
max_invocation_exceptions: int | None = None,
|
|
1069
1145
|
additional_properties: dict[str, Any] | None = None,
|
|
1070
|
-
result_parser: Callable[[Any], str | list[Content]] | None = None,
|
|
1146
|
+
result_parser: Callable[[Any], str | list[Content]] | _SkipParsingSentinel | None = None,
|
|
1071
1147
|
) -> FunctionTool: ...
|
|
1072
1148
|
|
|
1073
1149
|
|
|
@@ -1083,7 +1159,7 @@ def tool(
|
|
|
1083
1159
|
max_invocations: int | None = None,
|
|
1084
1160
|
max_invocation_exceptions: int | None = None,
|
|
1085
1161
|
additional_properties: dict[str, Any] | None = None,
|
|
1086
|
-
result_parser: Callable[[Any], str | list[Content]] | None = None,
|
|
1162
|
+
result_parser: Callable[[Any], str | list[Content]] | _SkipParsingSentinel | None = None,
|
|
1087
1163
|
) -> Callable[[Callable[..., Any]], FunctionTool]: ...
|
|
1088
1164
|
|
|
1089
1165
|
|
|
@@ -1098,7 +1174,7 @@ def tool(
|
|
|
1098
1174
|
max_invocations: int | None = None,
|
|
1099
1175
|
max_invocation_exceptions: int | None = None,
|
|
1100
1176
|
additional_properties: dict[str, Any] | None = None,
|
|
1101
|
-
result_parser: Callable[[Any], str | list[Content]] | None = None,
|
|
1177
|
+
result_parser: Callable[[Any], str | list[Content]] | _SkipParsingSentinel | None = None,
|
|
1102
1178
|
) -> FunctionTool | Callable[[Callable[..., Any]], FunctionTool]:
|
|
1103
1179
|
"""Decorate a function to turn it into a FunctionTool that can be passed to models and executed automatically.
|
|
1104
1180
|
|
{agent_framework_core-1.1.0 → agent_framework_core-1.2.0}/agent_framework/_workflows/_events.py
RENAMED
|
@@ -120,6 +120,7 @@ WorkflowEventType = Literal[
|
|
|
120
120
|
"executor_invoked", # Executor handler was called (use .executor_id, .data)
|
|
121
121
|
"executor_completed", # Executor handler completed (use .executor_id, .data)
|
|
122
122
|
"executor_failed", # Executor handler raised error (use .executor_id, .details)
|
|
123
|
+
"executor_bypassed", # Executor skipped via cache hit during replay (use .executor_id, .data)
|
|
123
124
|
# Orchestration event types (use .data for typed payload)
|
|
124
125
|
"group_chat", # Group chat orchestrator events (use .data as GroupChatRequestSentEvent | GroupChatResponseReceivedEvent) # noqa: E501
|
|
125
126
|
"handoff_sent", # Handoff routing events (use .data as HandoffSentEvent)
|
|
@@ -148,6 +149,7 @@ class WorkflowEvent(Generic[DataT]):
|
|
|
148
149
|
- `WorkflowEvent.executor_invoked(executor_id)` - executor handler called
|
|
149
150
|
- `WorkflowEvent.executor_completed(executor_id)` - executor handler completed
|
|
150
151
|
- `WorkflowEvent.executor_failed(executor_id, details)` - executor handler failed
|
|
152
|
+
- `WorkflowEvent.executor_bypassed(executor_id)` - executor skipped via cache hit
|
|
151
153
|
|
|
152
154
|
The generic parameter DataT represents the type of the event's data payload:
|
|
153
155
|
- Lifecycle events: `WorkflowEvent[None]` (data is None)
|
|
@@ -318,6 +320,11 @@ class WorkflowEvent(Generic[DataT]):
|
|
|
318
320
|
"""Create an 'executor_failed' event when an executor handler raises an error."""
|
|
319
321
|
return WorkflowEvent("executor_failed", executor_id=executor_id, data=details, details=details)
|
|
320
322
|
|
|
323
|
+
@classmethod
|
|
324
|
+
def executor_bypassed(cls, executor_id: str, data: DataT | None = None) -> WorkflowEvent[DataT]:
|
|
325
|
+
"""Create an 'executor_bypassed' event when a step is skipped via cache hit during replay."""
|
|
326
|
+
return cls("executor_bypassed", executor_id=executor_id, data=data)
|
|
327
|
+
|
|
321
328
|
# ==========================================================================
|
|
322
329
|
# Property for type-safe access
|
|
323
330
|
# ==========================================================================
|