mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Python: [BREAKING] update to v1.0.0 (#5062)
* updates to final deprecated pieces and versions * fix mypy * fix readme links
This commit is contained in:
+85
-8
@@ -97,13 +97,20 @@ def __getattr__(name: str) -> Any:
|
||||
|
||||
**Important:** Do not create a new package unless approved by the core team.
|
||||
|
||||
### Initial Release (Preview)
|
||||
Every new package starts as `alpha`.
|
||||
|
||||
### Alpha package checklist
|
||||
|
||||
1. Create directory under `packages/` (e.g., `packages/my-connector/`)
|
||||
2. Add the package to `tool.uv.sources` in root `pyproject.toml`
|
||||
3. Include samples inside the package (e.g., `packages/my-connector/samples/`)
|
||||
4. Do **NOT** add to `[all]` extra in `packages/core/pyproject.toml`
|
||||
5. Do **NOT** create lazy loading in core yet
|
||||
3. Set the package version to the alpha pattern: `1.0.0a<date>`
|
||||
4. Set the package classifier to `Development Status :: 3 - Alpha`
|
||||
5. Include samples inside the package (e.g., `packages/my-connector/samples/`)
|
||||
6. Do **NOT** add to `[all]` extra in `packages/core/pyproject.toml`
|
||||
7. Do **NOT** create lazy loading in core yet
|
||||
8. Add the package to `python/PACKAGE_STATUS.md` and keep that file updated when packages are added,
|
||||
removed, renamed, or promoted. If the package exposes individually staged APIs, keep the feature list
|
||||
there current too.
|
||||
|
||||
Recommended dependency workflow during connector implementation:
|
||||
|
||||
@@ -116,17 +123,83 @@ Recommended dependency workflow during connector implementation:
|
||||
`uv run poe add-dependency-and-validate-bounds --package core --dependency "<dependency-spec>"`
|
||||
If compatibility checks are not in place yet, add the dependency first, then implement tests before running bound validation.
|
||||
|
||||
### Promotion to Stable
|
||||
### Promotion path
|
||||
|
||||
1. Move samples to root `samples/` folder
|
||||
2. Add to `[all]` extra in `packages/core/pyproject.toml`
|
||||
3. Create provider folder in `agent_framework/` with lazy loading `__init__.py`
|
||||
Promotion work is not isolated to the package being promoted. If a promotion changes dependency
|
||||
metadata for downstream packages, also update the dependent packages' own versions so they publish
|
||||
new metadata alongside the promoted dependency bounds.
|
||||
Apply the internal package dependency update rules from the versioning section below during
|
||||
promotions as well as standalone version update work.
|
||||
|
||||
#### Alpha -> Beta
|
||||
|
||||
Move a package to `beta` when it is stable enough to be part of the main install surface.
|
||||
|
||||
1. Update the package version to the beta pattern: `1.0.0b<date>`
|
||||
2. Update the classifier to `Development Status :: 4 - Beta`
|
||||
3. Add the package to `[all]` in `packages/core/pyproject.toml`
|
||||
4. Move samples to the root `samples/` tree and remove package-local samples
|
||||
5. Create or update the relevant lazy-loading namespace in core when the package belongs under one
|
||||
6. Update `python/PACKAGE_STATUS.md`
|
||||
|
||||
After `alpha`, there should be no samples left inside a package folder.
|
||||
|
||||
#### Beta -> RC
|
||||
|
||||
Move a package to `rc` when its API is close to the final released shape.
|
||||
|
||||
1. Update the package version to the release-candidate pattern: `1.0.0rc<number>`
|
||||
2. Keep the classifier at `Development Status :: 4 - Beta` because PyPI does not have a separate
|
||||
release-candidate classifier
|
||||
3. Keep the package in `core[all]`
|
||||
4. Keep samples only in the root `samples/` tree
|
||||
5. Update `python/PACKAGE_STATUS.md` to show the package as `rc`
|
||||
|
||||
#### RC -> Released
|
||||
|
||||
Move a package to `released` when it no longer carries a prerelease qualifier.
|
||||
|
||||
1. Update the package version to the stable pattern: `1.0.0`
|
||||
2. Update the classifier to `Development Status :: 5 - Production/Stable`
|
||||
3. Keep the package in `core[all]`
|
||||
4. Keep samples only in the root `samples/` tree
|
||||
5. Update `python/PACKAGE_STATUS.md` to show the package as `released`
|
||||
6. Update all `README.md` files that install that package with
|
||||
`pip install agent-framework-... --pre` so they use `pip install agent-framework-...` without
|
||||
the `--pre` suffix
|
||||
|
||||
## Versioning
|
||||
|
||||
### Internal package dependency updates
|
||||
|
||||
- If package A depends on package B within this repository, only update package A's dependency
|
||||
declaration when the work on package B actually affects package A.
|
||||
- If package A does not need anything from the package B change, leave package A's dependency
|
||||
declaration unchanged.
|
||||
- If package A does need something from the package B change, update package A's dependency
|
||||
declaration to the version or versioning scheme that matches what package A now requires.
|
||||
- If package B is promoted to a different lifecycle stage, update package A's dependency
|
||||
declaration to the new versioning scheme for package B even when the only change is the stage
|
||||
transition itself.
|
||||
- Use this guidance both for ordinary version updates and for package promotion work.
|
||||
|
||||
- All non-core packages declare a lower bound on `agent-framework-core`
|
||||
- When core version bumps with breaking changes, update the lower bound in all packages
|
||||
- Non-core packages version independently; only raise core bound when using new core APIs
|
||||
- If promoting a package changes a dependent package's published dependency metadata, bump the
|
||||
dependent package's own version in the correct lifecycle pattern for its current stage
|
||||
- Lifecycle version patterns:
|
||||
- `alpha`: `1.0.0a<date>`
|
||||
- `beta`: `1.0.0b<date>`
|
||||
- `rc`: `1.0.0rc<number>`
|
||||
- `released`: `1.0.0`
|
||||
- Keep the `Development Status` classifier in `pyproject.toml` aligned with the lifecycle stage:
|
||||
- `alpha` -> `Development Status :: 3 - Alpha`
|
||||
- `beta` -> `Development Status :: 4 - Beta`
|
||||
- `rc` -> `Development Status :: 4 - Beta`
|
||||
- `released` -> `Development Status :: 5 - Production/Stable`
|
||||
- See the PyPI classifier list for the available classifier values:
|
||||
`https://pypi.org/classifiers/`
|
||||
|
||||
## Installation Options
|
||||
|
||||
@@ -144,6 +217,10 @@ When changing a package, check if its `AGENTS.md` needs updates:
|
||||
- Changing the package's purpose or architecture
|
||||
- Modifying import paths or usage patterns
|
||||
|
||||
Keep `python/PACKAGE_STATUS.md` updated when:
|
||||
- A package is added, removed, renamed, or promoted between lifecycle stages
|
||||
- A package starts or stops exposing individually staged experimental or release-candidate APIs
|
||||
|
||||
When a package adds, removes, or renames environment variables, update the related documentation in the same
|
||||
change:
|
||||
- The package's `README.md` for package-level configuration/env var guidance
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
# Python Package Status
|
||||
|
||||
This file tracks the current lifecycle state of the Python packages in this workspace. Some packages at later stages might have features within them that are not ready yet, these have feature stage decorators on the relevant APIs, and for `experimental` features warnings are raised. See the [Feature-level staged APIs](#feature-level-staged-apis) section below for details on which features are in which stage and where to find them.
|
||||
|
||||
Status is grouped into these buckets:
|
||||
|
||||
- `alpha` - initial release and early development packages that are not yet ready for general use
|
||||
- `beta` - prerelease packages that are not currently release candidates
|
||||
- `rc` - release candidate packages, these are close to ready for release but may still have some breaking changes before the final release
|
||||
- `released` - stable packages without a prerelease suffix, these are stable packages that should not have breaking changes between versions
|
||||
- `deprecated` - removed or deprecated packages that should not be used for new work
|
||||
|
||||
## Current packages
|
||||
|
||||
| Package | Path | State |
|
||||
| --- | --- | --- |
|
||||
| `agent-framework` | `python/` | `released` |
|
||||
| `agent-framework-a2a` | `python/packages/a2a` | `beta` |
|
||||
| `agent-framework-ag-ui` | `python/packages/ag-ui` | `beta` |
|
||||
| `agent-framework-anthropic` | `python/packages/anthropic` | `beta` |
|
||||
| `agent-framework-azure-ai-search` | `python/packages/azure-ai-search` | `beta` |
|
||||
| `agent-framework-azure-cosmos` | `python/packages/azure-cosmos` | `beta` |
|
||||
| `agent-framework-azurefunctions` | `python/packages/azurefunctions` | `beta` |
|
||||
| `agent-framework-bedrock` | `python/packages/bedrock` | `beta` |
|
||||
| `agent-framework-chatkit` | `python/packages/chatkit` | `beta` |
|
||||
| `agent-framework-claude` | `python/packages/claude` | `beta` |
|
||||
| `agent-framework-copilotstudio` | `python/packages/copilotstudio` | `beta` |
|
||||
| `agent-framework-core` | `python/packages/core` | `released` |
|
||||
| `agent-framework-declarative` | `python/packages/declarative` | `beta` |
|
||||
| `agent-framework-devui` | `python/packages/devui` | `beta` |
|
||||
| `agent-framework-durabletask` | `python/packages/durabletask` | `beta` |
|
||||
| `agent-framework-foundry` | `python/packages/foundry` | `released` |
|
||||
| `agent-framework-foundry-local` | `python/packages/foundry_local` | `beta` |
|
||||
| `agent-framework-github-copilot` | `python/packages/github_copilot` | `beta` |
|
||||
| `agent-framework-lab` | `python/packages/lab` | `beta` |
|
||||
| `agent-framework-mem0` | `python/packages/mem0` | `beta` |
|
||||
| `agent-framework-ollama` | `python/packages/ollama` | `beta` |
|
||||
| `agent-framework-openai` | `python/packages/openai` | `released` |
|
||||
| `agent-framework-orchestrations` | `python/packages/orchestrations` | `beta` |
|
||||
| `agent-framework-purview` | `python/packages/purview` | `beta` |
|
||||
| `agent-framework-redis` | `python/packages/redis` | `beta` |
|
||||
|
||||
## Deprecated / removed packages
|
||||
|
||||
| Package | Previous path | State | Notes |
|
||||
| --- | --- | --- | --- |
|
||||
| `agent-framework-azure-ai` | `python/packages/azure-ai` | `deprecated` | The client classes within the `azure-ai` package were renamed, sometimes changed, and moved to `agent-framework-foundry`. |
|
||||
|
||||
## Feature-level staged APIs
|
||||
|
||||
The following feature IDs have explicit feature-stage decorators on public APIs in the packages
|
||||
listed below.
|
||||
|
||||
### Experimental features
|
||||
|
||||
#### `EVALS`
|
||||
|
||||
- `agent-framework-core`: exported evaluation APIs from `agent_framework`, including
|
||||
`LocalEvaluator`, `evaluate_agent`, `evaluate_workflow`, and the related evaluation types and
|
||||
helper checks defined in `agent_framework/_evaluation.py`
|
||||
- `agent-framework-foundry`: `FoundryEvals`, `evaluate_traces`, and `evaluate_foundry_target`
|
||||
|
||||
#### `SKILLS`
|
||||
|
||||
- `agent-framework-core`: exported skills APIs from `agent_framework`, including `Skill`,
|
||||
`SkillResource`, `SkillScript`, `SkillScriptRunner`, and `SkillsProvider` from
|
||||
`agent_framework/_skills.py`
|
||||
|
||||
### Release-candidate features
|
||||
|
||||
There are currently no feature-level `rc` APIs.
|
||||
+7
-7
@@ -9,10 +9,10 @@ We recommend two common installation paths depending on your use case.
|
||||
If you are exploring or developing locally, install the entire framework with all sub-packages:
|
||||
|
||||
```bash
|
||||
pip install agent-framework --pre
|
||||
pip install agent-framework
|
||||
```
|
||||
|
||||
This installs the core and every integration package, making sure that all features are available without additional steps. The `--pre` flag is required while Agent Framework is in preview. This is the simplest way to get started.
|
||||
This installs the core and every integration package, making sure that all features are available without additional steps. This is the simplest way to get started.
|
||||
|
||||
### 2. Selective install
|
||||
|
||||
@@ -22,19 +22,19 @@ If you only need specific integrations, you can install at a more granular level
|
||||
# Core only
|
||||
# includes Azure OpenAI and OpenAI support by default
|
||||
# also includes workflows and orchestrations
|
||||
pip install agent-framework-core --pre
|
||||
pip install agent-framework-core
|
||||
|
||||
# Core + Azure AI Foundry integration
|
||||
pip install agent-framework-foundry --pre
|
||||
pip install agent-framework-foundry
|
||||
|
||||
# Core + Microsoft Copilot Studio integration
|
||||
# Core + Microsoft Copilot Studio integration (preview package)
|
||||
pip install agent-framework-copilotstudio --pre
|
||||
|
||||
# Core + both Microsoft Copilot Studio and Azure AI Foundry integration
|
||||
pip install agent-framework-microsoft agent-framework-foundry --pre
|
||||
pip install --pre agent-framework-copilotstudio agent-framework-foundry
|
||||
```
|
||||
|
||||
This selective approach is useful when you know which integrations you need, and it is the recommended way to set up lightweight environments.
|
||||
This selective approach is useful when you know which integrations you need, and it is the recommended way to set up lightweight environments. Released packages such as `agent-framework`, `agent-framework-core`, and `agent-framework-foundry` no longer require `--pre`, while preview connectors such as `agent-framework-copilotstudio` still do.
|
||||
|
||||
Supported Platforms:
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ description = "A2A integration for Microsoft Agent Framework."
|
||||
authors = [{ name = "Microsoft", email = "af-support@microsoft.com"}]
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
version = "1.0.0b260330"
|
||||
version = "1.0.0b260402"
|
||||
license-files = ["LICENSE"]
|
||||
urls.homepage = "https://aka.ms/agent-framework"
|
||||
urls.source = "https://github.com/microsoft/agent-framework/tree/main/python"
|
||||
@@ -23,7 +23,7 @@ classifiers = [
|
||||
"Typing :: Typed",
|
||||
]
|
||||
dependencies = [
|
||||
"agent-framework-core>=1.0.0rc6",
|
||||
"agent-framework-core>=1.0.0,<2",
|
||||
"a2a-sdk>=0.3.5,<0.3.24",
|
||||
]
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ async def main():
|
||||
metadata = {"thread_id": thread_id} if thread_id else None
|
||||
|
||||
stream = client.get_response(
|
||||
[Message(role="user", text=message)],
|
||||
[Message(role="user", contents=[message])],
|
||||
stream=True,
|
||||
options={"metadata": metadata} if metadata else None,
|
||||
)
|
||||
|
||||
@@ -73,7 +73,7 @@ async def streaming_example(client: AGUIChatClient, thread_id: str | None = None
|
||||
print("Assistant: ", end="", flush=True)
|
||||
|
||||
stream = client.get_response(
|
||||
[Message(role="user", text="Tell me a short joke")],
|
||||
[Message(role="user", contents=["Tell me a short joke"])],
|
||||
stream=True,
|
||||
options={"metadata": metadata} if metadata else None,
|
||||
)
|
||||
@@ -100,7 +100,7 @@ async def non_streaming_example(client: AGUIChatClient, thread_id: str | None =
|
||||
|
||||
print("\nUser: What is 2 + 2?\n")
|
||||
|
||||
response = await client.get_response([Message(role="user", text="What is 2 + 2?")], metadata=metadata)
|
||||
response = await client.get_response([Message(role="user", contents=["What is 2 + 2?"])], metadata=metadata)
|
||||
|
||||
print(f"Assistant: {response.text}")
|
||||
|
||||
@@ -139,7 +139,9 @@ async def tool_example(client: AGUIChatClient, thread_id: str | None = None):
|
||||
print("(Server must be configured with matching tools to execute them)\n")
|
||||
|
||||
response = await client.get_response(
|
||||
[Message(role="user", text="What's the weather in Seattle?")], tools=[get_weather, calculate], metadata=metadata
|
||||
[Message(role="user", contents=["What's the weather in Seattle?"])],
|
||||
tools=[get_weather, calculate],
|
||||
metadata=metadata,
|
||||
)
|
||||
|
||||
print(f"Assistant: {response.text}")
|
||||
@@ -174,7 +176,7 @@ async def conversation_example(client: AGUIChatClient):
|
||||
|
||||
# First turn
|
||||
print("User: My name is Alice\n")
|
||||
response1 = await client.get_response([Message(role="user", text="My name is Alice")])
|
||||
response1 = await client.get_response([Message(role="user", contents=["My name is Alice"])])
|
||||
print(f"Assistant: {response1.text}")
|
||||
thread_id = response1.additional_properties.get("thread_id")
|
||||
print(f"\n[Thread: {thread_id}]")
|
||||
@@ -182,7 +184,7 @@ async def conversation_example(client: AGUIChatClient):
|
||||
# Second turn - using same thread
|
||||
print("\nUser: What's my name?\n")
|
||||
response2 = await client.get_response(
|
||||
[Message(role="user", text="What's my name?")], options={"metadata": {"thread_id": thread_id}}
|
||||
[Message(role="user", contents=["What's my name?"])], options={"metadata": {"thread_id": thread_id}}
|
||||
)
|
||||
print(f"Assistant: {response2.text}")
|
||||
|
||||
@@ -193,7 +195,7 @@ async def conversation_example(client: AGUIChatClient):
|
||||
# Third turn
|
||||
print("\nUser: Can you also tell me what 10 * 5 is?\n")
|
||||
response3 = await client.get_response(
|
||||
[Message(role="user", text="Can you also tell me what 10 * 5 is?")],
|
||||
[Message(role="user", contents=["Can you also tell me what 10 * 5 is?"])],
|
||||
options={"metadata": {"thread_id": thread_id}},
|
||||
tools=[calculate],
|
||||
)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "agent-framework-ag-ui"
|
||||
version = "1.0.0b260330"
|
||||
version = "1.0.0b260402"
|
||||
description = "AG-UI protocol integration for Agent Framework"
|
||||
readme = "README.md"
|
||||
license-files = ["LICENSE"]
|
||||
@@ -22,7 +22,7 @@ classifiers = [
|
||||
"Typing :: Typed",
|
||||
]
|
||||
dependencies = [
|
||||
"agent-framework-core>=1.0.0rc6",
|
||||
"agent-framework-core>=1.0.0,<2",
|
||||
"ag-ui-protocol==0.1.13",
|
||||
"fastapi>=0.115.0,<0.133.1",
|
||||
"uvicorn[standard]>=0.30.0,<0.42.0"
|
||||
|
||||
@@ -67,8 +67,8 @@ class TestAGUIChatClient:
|
||||
"""Test state extraction when no state is present."""
|
||||
client = StubAGUIChatClient(endpoint="http://localhost:8888/")
|
||||
messages = [
|
||||
Message(role="user", text="Hello"),
|
||||
Message(role="assistant", text="Hi there"),
|
||||
Message(role="user", contents=["Hello"]),
|
||||
Message(role="assistant", contents=["Hi there"]),
|
||||
]
|
||||
|
||||
result_messages, state = client.extract_state_from_messages(messages)
|
||||
@@ -87,7 +87,7 @@ class TestAGUIChatClient:
|
||||
state_b64 = base64.b64encode(state_json.encode("utf-8")).decode("utf-8")
|
||||
|
||||
messages = [
|
||||
Message(role="user", text="Hello"),
|
||||
Message(role="user", contents=["Hello"]),
|
||||
Message(
|
||||
role="user",
|
||||
contents=[Content.from_uri(uri=f"data:application/json;base64,{state_b64}")],
|
||||
@@ -125,8 +125,8 @@ class TestAGUIChatClient:
|
||||
"""Test message conversion to AG-UI format."""
|
||||
client = StubAGUIChatClient(endpoint="http://localhost:8888/")
|
||||
messages = [
|
||||
Message(role="user", text="What is the weather?"),
|
||||
Message(role="assistant", text="Let me check.", message_id="msg_123"),
|
||||
Message(role="user", contents=["What is the weather?"]),
|
||||
Message(role="assistant", contents=["Let me check."], message_id="msg_123"),
|
||||
]
|
||||
|
||||
agui_messages = client.convert_messages_to_agui_format(messages)
|
||||
@@ -173,7 +173,7 @@ class TestAGUIChatClient:
|
||||
client = StubAGUIChatClient(endpoint="http://localhost:8888/")
|
||||
monkeypatch.setattr(client.http_service, "post_run", mock_post_run)
|
||||
|
||||
messages = [Message(role="user", text="Test message")]
|
||||
messages = [Message(role="user", contents=["Test message"])]
|
||||
chat_options = ChatOptions()
|
||||
|
||||
updates: list[ChatResponseUpdate] = []
|
||||
@@ -206,7 +206,7 @@ class TestAGUIChatClient:
|
||||
client = StubAGUIChatClient(endpoint="http://localhost:8888/")
|
||||
monkeypatch.setattr(client.http_service, "post_run", mock_post_run)
|
||||
|
||||
messages = [Message(role="user", text="Test message")]
|
||||
messages = [Message(role="user", contents=["Test message"])]
|
||||
chat_options = {}
|
||||
|
||||
response = await client.inner_get_response(messages=messages, options=chat_options)
|
||||
@@ -249,7 +249,7 @@ class TestAGUIChatClient:
|
||||
client = StubAGUIChatClient(endpoint="http://localhost:8888/")
|
||||
monkeypatch.setattr(client.http_service, "post_run", mock_post_run)
|
||||
|
||||
messages = [Message(role="user", text="Test with tools")]
|
||||
messages = [Message(role="user", contents=["Test with tools"])]
|
||||
chat_options = ChatOptions(tools=[test_tool])
|
||||
|
||||
response = await client.inner_get_response(messages=messages, options=chat_options)
|
||||
@@ -273,7 +273,7 @@ class TestAGUIChatClient:
|
||||
client = StubAGUIChatClient(endpoint="http://localhost:8888/")
|
||||
monkeypatch.setattr(client.http_service, "post_run", mock_post_run)
|
||||
|
||||
messages = [Message(role="user", text="Test server tool execution")]
|
||||
messages = [Message(role="user", contents=["Test server tool execution"])]
|
||||
|
||||
updates: list[ChatResponseUpdate] = []
|
||||
async for update in client.get_response(messages, stream=True):
|
||||
@@ -315,7 +315,7 @@ class TestAGUIChatClient:
|
||||
client = StubAGUIChatClient(endpoint="http://localhost:8888/")
|
||||
monkeypatch.setattr(client.http_service, "post_run", mock_post_run)
|
||||
|
||||
messages = [Message(role="user", text="Test server tool execution")]
|
||||
messages = [Message(role="user", contents=["Test server tool execution"])]
|
||||
|
||||
async for _ in client.get_response(
|
||||
messages, stream=True, options={"tool_choice": "auto", "tools": [client_tool]}
|
||||
@@ -331,7 +331,7 @@ class TestAGUIChatClient:
|
||||
state_b64 = base64.b64encode(state_json.encode("utf-8")).decode("utf-8")
|
||||
|
||||
messages = [
|
||||
Message(role="user", text="Hello"),
|
||||
Message(role="user", contents=["Hello"]),
|
||||
Message(
|
||||
role="user",
|
||||
contents=[Content.from_uri(uri=f"data:application/json;base64,{state_b64}")],
|
||||
@@ -388,7 +388,7 @@ class TestAGUIChatClient:
|
||||
client = StubAGUIChatClient(endpoint="http://localhost:8888/")
|
||||
monkeypatch.setattr(client.http_service, "post_run", mock_post_run)
|
||||
|
||||
messages = [Message(role="user", text="Test")]
|
||||
messages = [Message(role="user", contents=["Test"])]
|
||||
response = await client.inner_get_response(messages=messages, options={}, stream=False)
|
||||
|
||||
assert response is not None
|
||||
@@ -416,7 +416,7 @@ class TestAGUIChatClient:
|
||||
client = StubAGUIChatClient(endpoint="http://localhost:8888/")
|
||||
monkeypatch.setattr(client.http_service, "post_run", mock_post_run)
|
||||
|
||||
messages = [Message(role="user", text="Test")]
|
||||
messages = [Message(role="user", contents=["Test"])]
|
||||
updates: list[ChatResponseUpdate] = []
|
||||
async for update in client._inner_get_response(messages=messages, stream=True, options={"tools": [my_tool]}):
|
||||
updates.append(update)
|
||||
@@ -451,7 +451,7 @@ class TestAGUIChatClient:
|
||||
client = StubAGUIChatClient(endpoint="http://localhost:8888/")
|
||||
monkeypatch.setattr(client.http_service, "post_run", mock_post_run)
|
||||
|
||||
messages = [Message(role="user", text="continue")]
|
||||
messages = [Message(role="user", contents=["continue"])]
|
||||
options = {
|
||||
"available_interrupts": available_interrupts,
|
||||
"resume": resume_payload,
|
||||
|
||||
@@ -4,7 +4,7 @@ description = "Anthropic integration for Microsoft Agent Framework."
|
||||
authors = [{ name = "Microsoft", email = "af-support@microsoft.com"}]
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
version = "1.0.0b260330"
|
||||
version = "1.0.0b260402"
|
||||
license-files = ["LICENSE"]
|
||||
urls.homepage = "https://aka.ms/agent-framework"
|
||||
urls.source = "https://github.com/microsoft/agent-framework/tree/main/python"
|
||||
@@ -23,7 +23,7 @@ classifiers = [
|
||||
"Typing :: Typed",
|
||||
]
|
||||
dependencies = [
|
||||
"agent-framework-core>=1.0.0rc6",
|
||||
"agent-framework-core>=1.0.0,<2",
|
||||
"anthropic>=0.80.0,<0.80.1",
|
||||
]
|
||||
|
||||
|
||||
@@ -172,7 +172,7 @@ def test_anthropic_client_service_url(mock_anthropic_client: MagicMock) -> None:
|
||||
def test_prepare_message_for_anthropic_text(mock_anthropic_client: MagicMock) -> None:
|
||||
"""Test converting text message to Anthropic format."""
|
||||
client = create_test_anthropic_client(mock_anthropic_client)
|
||||
message = Message(role="user", text="Hello, world!")
|
||||
message = Message(role="user", contents=["Hello, world!"])
|
||||
|
||||
result = client._prepare_message_for_anthropic(message)
|
||||
|
||||
@@ -491,8 +491,8 @@ def test_prepare_messages_for_anthropic_with_system(
|
||||
"""Test converting messages list with system message."""
|
||||
client = create_test_anthropic_client(mock_anthropic_client)
|
||||
messages = [
|
||||
Message(role="system", text="You are a helpful assistant."),
|
||||
Message(role="user", text="Hello!"),
|
||||
Message(role="system", contents=["You are a helpful assistant."]),
|
||||
Message(role="user", contents=["Hello!"]),
|
||||
]
|
||||
|
||||
result = client._prepare_messages_for_anthropic(messages)
|
||||
@@ -509,8 +509,8 @@ def test_prepare_messages_for_anthropic_without_system(
|
||||
"""Test converting messages list without system message."""
|
||||
client = create_test_anthropic_client(mock_anthropic_client)
|
||||
messages = [
|
||||
Message(role="user", text="Hello!"),
|
||||
Message(role="assistant", text="Hi there!"),
|
||||
Message(role="user", contents=["Hello!"]),
|
||||
Message(role="assistant", contents=["Hi there!"]),
|
||||
]
|
||||
|
||||
result = client._prepare_messages_for_anthropic(messages)
|
||||
@@ -735,7 +735,7 @@ async def test_prepare_options_basic(mock_anthropic_client: MagicMock) -> None:
|
||||
"""Test _prepare_options with basic ChatOptions."""
|
||||
client = create_test_anthropic_client(mock_anthropic_client)
|
||||
|
||||
messages = [Message(role="user", text="Hello")]
|
||||
messages = [Message(role="user", contents=["Hello"])]
|
||||
chat_options = ChatOptions(max_tokens=100, temperature=0.7)
|
||||
|
||||
run_options = client._prepare_options(messages, chat_options)
|
||||
@@ -753,8 +753,8 @@ async def test_prepare_options_with_system_message(
|
||||
client = create_test_anthropic_client(mock_anthropic_client)
|
||||
|
||||
messages = [
|
||||
Message(role="system", text="You are helpful."),
|
||||
Message(role="user", text="Hello"),
|
||||
Message(role="system", contents=["You are helpful."]),
|
||||
Message(role="user", contents=["Hello"]),
|
||||
]
|
||||
chat_options = ChatOptions()
|
||||
|
||||
@@ -807,7 +807,7 @@ async def test_anthropic_shell_tool_is_invoked_in_function_loop(
|
||||
]
|
||||
|
||||
await client.get_response(
|
||||
messages=[Message(role="user", text="Run pwd")],
|
||||
messages=[Message(role="user", contents=["Run pwd"])],
|
||||
options={"tools": [shell_tool_instance], "max_tokens": 64},
|
||||
)
|
||||
|
||||
@@ -833,7 +833,7 @@ async def test_prepare_options_with_tool_choice_auto(
|
||||
"""Test _prepare_options with auto tool choice."""
|
||||
client = create_test_anthropic_client(mock_anthropic_client)
|
||||
|
||||
messages = [Message(role="user", text="Hello")]
|
||||
messages = [Message(role="user", contents=["Hello"])]
|
||||
chat_options = ChatOptions(tool_choice="auto", allow_multiple_tool_calls=False)
|
||||
|
||||
run_options = client._prepare_options(messages, chat_options)
|
||||
@@ -849,7 +849,7 @@ async def test_prepare_options_with_tool_choice_required(
|
||||
"""Test _prepare_options with required tool choice."""
|
||||
client = create_test_anthropic_client(mock_anthropic_client)
|
||||
|
||||
messages = [Message(role="user", text="Hello")]
|
||||
messages = [Message(role="user", contents=["Hello"])]
|
||||
# For required with specific function, need to pass as dict
|
||||
chat_options = ChatOptions(tool_choice={"mode": "required", "required_function_name": "get_weather"})
|
||||
|
||||
@@ -865,7 +865,7 @@ async def test_prepare_options_with_tool_choice_none(
|
||||
"""Test _prepare_options with none tool choice."""
|
||||
client = create_test_anthropic_client(mock_anthropic_client)
|
||||
|
||||
messages = [Message(role="user", text="Hello")]
|
||||
messages = [Message(role="user", contents=["Hello"])]
|
||||
chat_options = ChatOptions(tool_choice="none")
|
||||
|
||||
run_options = client._prepare_options(messages, chat_options)
|
||||
@@ -882,7 +882,7 @@ async def test_prepare_options_with_tools(mock_anthropic_client: MagicMock) -> N
|
||||
"""Get weather for a location."""
|
||||
return f"Weather for {location}"
|
||||
|
||||
messages = [Message(role="user", text="Hello")]
|
||||
messages = [Message(role="user", contents=["Hello"])]
|
||||
chat_options = ChatOptions(tools=[get_weather])
|
||||
|
||||
run_options = client._prepare_options(messages, chat_options)
|
||||
@@ -897,7 +897,7 @@ async def test_prepare_options_with_stop_sequences(
|
||||
"""Test _prepare_options with stop sequences."""
|
||||
client = create_test_anthropic_client(mock_anthropic_client)
|
||||
|
||||
messages = [Message(role="user", text="Hello")]
|
||||
messages = [Message(role="user", contents=["Hello"])]
|
||||
chat_options = ChatOptions(stop=["STOP", "END"])
|
||||
|
||||
run_options = client._prepare_options(messages, chat_options)
|
||||
@@ -909,7 +909,7 @@ async def test_prepare_options_with_top_p(mock_anthropic_client: MagicMock) -> N
|
||||
"""Test _prepare_options with top_p."""
|
||||
client = create_test_anthropic_client(mock_anthropic_client)
|
||||
|
||||
messages = [Message(role="user", text="Hello")]
|
||||
messages = [Message(role="user", contents=["Hello"])]
|
||||
chat_options = ChatOptions(top_p=0.9)
|
||||
|
||||
run_options = client._prepare_options(messages, chat_options)
|
||||
@@ -923,7 +923,7 @@ async def test_prepare_options_excludes_stream_option(
|
||||
"""Test _prepare_options excludes stream when stream is provided in options."""
|
||||
client = create_test_anthropic_client(mock_anthropic_client)
|
||||
|
||||
messages = [Message(role="user", text="Hello")]
|
||||
messages = [Message(role="user", contents=["Hello"])]
|
||||
chat_options: dict[str, Any] = {"stream": True, "max_tokens": 100}
|
||||
|
||||
run_options = client._prepare_options(messages, chat_options)
|
||||
@@ -941,7 +941,7 @@ async def test_prepare_options_filters_internal_kwargs(
|
||||
"""
|
||||
client = create_test_anthropic_client(mock_anthropic_client)
|
||||
|
||||
messages = [Message(role="user", text="Hello")]
|
||||
messages = [Message(role="user", contents=["Hello"])]
|
||||
chat_options: ChatOptions = {}
|
||||
|
||||
# Simulate internal kwargs that get passed through the middleware pipeline
|
||||
@@ -1174,10 +1174,7 @@ def test_parse_contents_server_tool_use_input_json_delta_ignored(
|
||||
delta_content.partial_json = '{"query": "latest news"}'
|
||||
|
||||
result = client._parse_contents_from_anthropic([delta_content])
|
||||
assert result == [], (
|
||||
"input_json_delta after server_tool_use should produce no content, "
|
||||
"but got: %r" % result
|
||||
)
|
||||
assert result == [], "input_json_delta after server_tool_use should produce no content, but got: %r" % result
|
||||
|
||||
# A second delta must also be ignored
|
||||
delta_content_2 = MagicMock()
|
||||
@@ -1186,8 +1183,7 @@ def test_parse_contents_server_tool_use_input_json_delta_ignored(
|
||||
|
||||
result = client._parse_contents_from_anthropic([delta_content_2])
|
||||
assert result == [], (
|
||||
"subsequent input_json_delta after server_tool_use should also be ignored, "
|
||||
"but got: %r" % result
|
||||
"subsequent input_json_delta after server_tool_use should also be ignored, but got: %r" % result
|
||||
)
|
||||
|
||||
|
||||
@@ -1222,7 +1218,7 @@ async def test_inner_get_response(mock_anthropic_client: MagicMock) -> None:
|
||||
|
||||
mock_anthropic_client.beta.messages.create.return_value = mock_message
|
||||
|
||||
messages = [Message(role="user", text="Hi")]
|
||||
messages = [Message(role="user", contents=["Hi"])]
|
||||
chat_options = ChatOptions(max_tokens=10)
|
||||
|
||||
response = await client._inner_get_response( # type: ignore[attr-defined]
|
||||
@@ -1248,7 +1244,7 @@ async def test_inner_get_response_ignores_options_stream_non_streaming(
|
||||
mock_message.stop_reason = "end_turn"
|
||||
mock_anthropic_client.beta.messages.create.return_value = mock_message
|
||||
|
||||
messages = [Message(role="user", text="Hi")]
|
||||
messages = [Message(role="user", contents=["Hi"])]
|
||||
options: dict[str, Any] = {"max_tokens": 10, "stream": True}
|
||||
|
||||
await client._inner_get_response( # type: ignore[attr-defined]
|
||||
@@ -1272,7 +1268,7 @@ async def test_inner_get_response_streaming(mock_anthropic_client: MagicMock) ->
|
||||
|
||||
mock_anthropic_client.beta.messages.create.return_value = mock_stream()
|
||||
|
||||
messages = [Message(role="user", text="Hi")]
|
||||
messages = [Message(role="user", contents=["Hi"])]
|
||||
chat_options = ChatOptions(max_tokens=10)
|
||||
|
||||
chunks: list[ChatResponseUpdate] = []
|
||||
@@ -1299,7 +1295,7 @@ async def test_inner_get_response_ignores_options_stream_streaming(
|
||||
|
||||
mock_anthropic_client.beta.messages.create.return_value = mock_stream()
|
||||
|
||||
messages = [Message(role="user", text="Hi")]
|
||||
messages = [Message(role="user", contents=["Hi"])]
|
||||
options: dict[str, Any] = {"max_tokens": 10, "stream": False}
|
||||
|
||||
async for _ in client._inner_get_response( # type: ignore[attr-defined]
|
||||
@@ -1453,7 +1449,7 @@ async def test_anthropic_client_integration_basic_chat() -> None:
|
||||
"""Integration test for basic chat completion."""
|
||||
client = AnthropicClient()
|
||||
|
||||
messages = [Message(role="user", text="Say 'Hello, World!' and nothing else.")]
|
||||
messages = [Message(role="user", contents=["Say 'Hello, World!' and nothing else."])]
|
||||
|
||||
response = await client.get_response(messages=messages, options={"max_tokens": 50})
|
||||
|
||||
@@ -1471,7 +1467,7 @@ async def test_anthropic_client_integration_streaming_chat() -> None:
|
||||
"""Integration test for streaming chat completion."""
|
||||
client = AnthropicClient()
|
||||
|
||||
messages = [Message(role="user", text="Count from 1 to 5.")]
|
||||
messages = [Message(role="user", contents=["Count from 1 to 5."])]
|
||||
|
||||
chunks = []
|
||||
async for chunk in client.get_response(messages=messages, stream=True, options={"max_tokens": 50}):
|
||||
@@ -1488,7 +1484,7 @@ async def test_anthropic_client_integration_function_calling() -> None:
|
||||
"""Integration test for function calling."""
|
||||
client = AnthropicClient()
|
||||
|
||||
messages = [Message(role="user", text="What's the weather in San Francisco?")]
|
||||
messages = [Message(role="user", contents=["What's the weather in San Francisco?"])]
|
||||
tools = [get_weather]
|
||||
|
||||
response = await client.get_response(
|
||||
@@ -1509,7 +1505,7 @@ async def test_anthropic_client_integration_hosted_tools() -> None:
|
||||
"""Integration test for hosted tools."""
|
||||
client = AnthropicClient()
|
||||
|
||||
messages = [Message(role="user", text="What tools do you have available?")]
|
||||
messages = [Message(role="user", contents=["What tools do you have available?"])]
|
||||
tools = [
|
||||
AnthropicClient.get_web_search_tool(),
|
||||
AnthropicClient.get_code_interpreter_tool(),
|
||||
@@ -1536,8 +1532,8 @@ async def test_anthropic_client_integration_with_system_message() -> None:
|
||||
client = AnthropicClient()
|
||||
|
||||
messages = [
|
||||
Message(role="system", text="You are a pirate. Always respond like a pirate."),
|
||||
Message(role="user", text="Hello!"),
|
||||
Message(role="system", contents=["You are a pirate. Always respond like a pirate."]),
|
||||
Message(role="user", contents=["Hello!"]),
|
||||
]
|
||||
|
||||
response = await client.get_response(messages=messages, options={"max_tokens": 50})
|
||||
@@ -1553,7 +1549,7 @@ async def test_anthropic_client_integration_temperature_control() -> None:
|
||||
"""Integration test with temperature control."""
|
||||
client = AnthropicClient()
|
||||
|
||||
messages = [Message(role="user", text="Say hello.")]
|
||||
messages = [Message(role="user", contents=["Say hello."])]
|
||||
|
||||
response = await client.get_response(
|
||||
messages=messages,
|
||||
@@ -1572,11 +1568,11 @@ async def test_anthropic_client_integration_ordering() -> None:
|
||||
client = AnthropicClient()
|
||||
|
||||
messages = [
|
||||
Message(role="user", text="Say hello."),
|
||||
Message(role="user", text="Then say goodbye."),
|
||||
Message(role="assistant", text="Thank you for chatting!"),
|
||||
Message(role="assistant", text="Let me know if I can help."),
|
||||
Message(role="user", text="Just testing things."),
|
||||
Message(role="user", contents=["Say hello."]),
|
||||
Message(role="user", contents=["Then say goodbye."]),
|
||||
Message(role="assistant", contents=["Thank you for chatting!"]),
|
||||
Message(role="assistant", contents=["Let me know if I can help."]),
|
||||
Message(role="user", contents=["Just testing things."]),
|
||||
]
|
||||
|
||||
response = await client.get_response(messages=messages)
|
||||
@@ -2685,7 +2681,7 @@ async def test_anthropic_client_integration_tool_rich_content_image() -> None:
|
||||
client = AnthropicClient()
|
||||
client.function_invocation_configuration["max_iterations"] = 2
|
||||
|
||||
messages = [Message(role="user", text="Call the get_test_image tool and describe what you see.")]
|
||||
messages = [Message(role="user", contents=["Call the get_test_image tool and describe what you see."])]
|
||||
|
||||
response = await client.get_response(
|
||||
messages=messages,
|
||||
|
||||
+6
-4
@@ -604,7 +604,9 @@ class AzureAISearchContextProvider(ContextProvider):
|
||||
if not result_messages:
|
||||
return
|
||||
|
||||
context.extend_messages(self.source_id, [Message(role="user", text=self.context_prompt), *result_messages])
|
||||
context.extend_messages(
|
||||
self.source_id, [Message(role="user", contents=[self.context_prompt]), *result_messages]
|
||||
)
|
||||
|
||||
def _find_vector_fields(self, index: Any) -> list[str]:
|
||||
"""Find all fields that can store vectors."""
|
||||
@@ -719,7 +721,7 @@ class AzureAISearchContextProvider(ContextProvider):
|
||||
doc_id = doc.get("id") or doc.get("@search.id") # type: ignore[reportUnknownVariableType]
|
||||
doc_text: str = self._extract_document_text(doc, doc_id=doc_id) # type: ignore[reportUnknownArgumentType]
|
||||
if doc_text:
|
||||
result_messages.append(Message(role="user", text=doc_text)) # type: ignore[reportUnknownArgumentType]
|
||||
result_messages.append(Message(role="user", contents=[doc_text])) # type: ignore[reportUnknownArgumentType]
|
||||
return result_messages
|
||||
|
||||
async def _ensure_knowledge_base(self) -> None:
|
||||
@@ -951,7 +953,7 @@ class AzureAISearchContextProvider(ContextProvider):
|
||||
List of Messages, or a single default Message if no results found.
|
||||
"""
|
||||
if not retrieval_result.response:
|
||||
return [Message(role="assistant", text="No results found from Knowledge Base.")]
|
||||
return [Message(role="assistant", contents=["No results found from Knowledge Base."])]
|
||||
|
||||
annotations = AzureAISearchContextProvider._parse_references_to_annotations(retrieval_result.references)
|
||||
|
||||
@@ -972,7 +974,7 @@ class AzureAISearchContextProvider(ContextProvider):
|
||||
result_messages.append(Message(role=kb_msg.role or "assistant", contents=contents))
|
||||
|
||||
if not result_messages:
|
||||
return [Message(role="assistant", text="No results found from Knowledge Base.")]
|
||||
return [Message(role="assistant", contents=["No results found from Knowledge Base."])]
|
||||
return result_messages
|
||||
|
||||
def _extract_document_text(self, doc: dict[str, Any], doc_id: str | None = None) -> str:
|
||||
|
||||
@@ -4,7 +4,7 @@ description = "Azure AI Search integration for Microsoft Agent Framework."
|
||||
authors = [{ name = "Microsoft", email = "af-support@microsoft.com"}]
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
version = "1.0.0b260330"
|
||||
version = "1.0.0b260402"
|
||||
license-files = ["LICENSE"]
|
||||
urls.homepage = "https://aka.ms/agent-framework"
|
||||
urls.source = "https://github.com/microsoft/agent-framework/tree/main/python"
|
||||
@@ -23,7 +23,7 @@ classifiers = [
|
||||
"Typing :: Typed",
|
||||
]
|
||||
dependencies = [
|
||||
"agent-framework-core>=1.0.0rc6",
|
||||
"agent-framework-core>=1.0.0,<2",
|
||||
"azure-search-documents>=11.7.0b2,<11.7.0b3",
|
||||
]
|
||||
|
||||
|
||||
@@ -1370,7 +1370,7 @@ class TestPrepareMessagesForKbSearch:
|
||||
assert len(result) == 0
|
||||
|
||||
def test_fallback_to_msg_text_when_no_contents(self) -> None:
|
||||
msg = Message(role="user", text="fallback text")
|
||||
msg = Message(role="user", contents=["fallback text"])
|
||||
result = AzureAISearchContextProvider._prepare_messages_for_kb_search([msg])
|
||||
assert len(result) == 1
|
||||
assert result[0].content[0].text == "fallback text"
|
||||
|
||||
@@ -4,7 +4,7 @@ description = "Azure Cosmos DB history provider integration for Microsoft Agent
|
||||
authors = [{ name = "Microsoft", email = "af-support@microsoft.com"}]
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
version = "1.0.0b260330"
|
||||
version = "1.0.0b260402"
|
||||
license-files = ["LICENSE"]
|
||||
urls.homepage = "https://aka.ms/agent-framework"
|
||||
urls.source = "https://github.com/microsoft/agent-framework/tree/main/python"
|
||||
@@ -23,7 +23,7 @@ classifiers = [
|
||||
"Typing :: Typed",
|
||||
]
|
||||
dependencies = [
|
||||
"agent-framework-core>=1.0.0rc6",
|
||||
"agent-framework-core>=1.0.0,<2",
|
||||
"azure-cosmos>=4.3.0,<5",
|
||||
]
|
||||
|
||||
|
||||
@@ -240,11 +240,11 @@ def build_agent_executor_response(
|
||||
Returns:
|
||||
AgentExecutorResponse with reconstructed conversation
|
||||
"""
|
||||
final_text = response_text
|
||||
final_text: str = response_text or ""
|
||||
if structured_response:
|
||||
final_text = json.dumps(structured_response)
|
||||
|
||||
assistant_message = Message(role="assistant", text=final_text)
|
||||
assistant_message = Message(role="assistant", contents=[final_text])
|
||||
|
||||
agent_response = AgentResponse(
|
||||
messages=[assistant_message],
|
||||
@@ -255,7 +255,7 @@ def build_agent_executor_response(
|
||||
if isinstance(previous_message, AgentExecutorResponse) and previous_message.full_conversation:
|
||||
full_conversation.extend(previous_message.full_conversation)
|
||||
elif isinstance(previous_message, str):
|
||||
full_conversation.append(Message(role="user", text=previous_message))
|
||||
full_conversation.append(Message(role="user", contents=[previous_message]))
|
||||
|
||||
full_conversation.append(assistant_message)
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ description = "Azure Functions integration for Microsoft Agent Framework."
|
||||
authors = [{ name = "Microsoft", email = "af-support@microsoft.com"}]
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
version = "1.0.0b260330"
|
||||
version = "1.0.0b260402"
|
||||
license-files = ["LICENSE"]
|
||||
urls.homepage = "https://aka.ms/agent-framework"
|
||||
urls.source = "https://github.com/microsoft/agent-framework/tree/main/python"
|
||||
@@ -22,7 +22,7 @@ classifiers = [
|
||||
"Typing :: Typed",
|
||||
]
|
||||
dependencies = [
|
||||
"agent-framework-core>=1.0.0rc6",
|
||||
"agent-framework-core>=1.0.0,<2",
|
||||
"agent-framework-durabletask",
|
||||
"azure-functions>=1.24.0,<2",
|
||||
"azure-functions-durable>=1.3.1,<2",
|
||||
|
||||
@@ -357,7 +357,7 @@ class TestAgentEntityOperations:
|
||||
"""Test that entity can run agent operation."""
|
||||
mock_agent = Mock()
|
||||
mock_agent.run = AsyncMock(
|
||||
return_value=AgentResponse(messages=[Message(role="assistant", text="Test response")])
|
||||
return_value=AgentResponse(messages=[Message(role="assistant", contents=["Test response"])])
|
||||
)
|
||||
|
||||
entity = AgentEntity(mock_agent, state_provider=_InMemoryStateProvider(thread_id="test-conv-123"))
|
||||
@@ -374,7 +374,9 @@ class TestAgentEntityOperations:
|
||||
async def test_entity_stores_conversation_history(self) -> None:
|
||||
"""Test that the entity stores conversation history."""
|
||||
mock_agent = Mock()
|
||||
mock_agent.run = AsyncMock(return_value=AgentResponse(messages=[Message(role="assistant", text="Response 1")]))
|
||||
mock_agent.run = AsyncMock(
|
||||
return_value=AgentResponse(messages=[Message(role="assistant", contents=["Response 1"])])
|
||||
)
|
||||
|
||||
entity = AgentEntity(mock_agent, state_provider=_InMemoryStateProvider(thread_id="conv-1"))
|
||||
|
||||
@@ -406,7 +408,9 @@ class TestAgentEntityOperations:
|
||||
async def test_entity_increments_message_count(self) -> None:
|
||||
"""Test that the entity increments the message count."""
|
||||
mock_agent = Mock()
|
||||
mock_agent.run = AsyncMock(return_value=AgentResponse(messages=[Message(role="assistant", text="Response")]))
|
||||
mock_agent.run = AsyncMock(
|
||||
return_value=AgentResponse(messages=[Message(role="assistant", contents=["Response"])])
|
||||
)
|
||||
|
||||
entity = AgentEntity(mock_agent, state_provider=_InMemoryStateProvider(thread_id="conv-1"))
|
||||
|
||||
@@ -445,7 +449,9 @@ class TestAgentEntityFactory:
|
||||
def test_entity_function_handles_run_operation(self) -> None:
|
||||
"""Test that the entity function handles the run operation."""
|
||||
mock_agent = Mock()
|
||||
mock_agent.run = AsyncMock(return_value=AgentResponse(messages=[Message(role="assistant", text="Response")]))
|
||||
mock_agent.run = AsyncMock(
|
||||
return_value=AgentResponse(messages=[Message(role="assistant", contents=["Response"])])
|
||||
)
|
||||
|
||||
entity_function = create_agent_entity(mock_agent)
|
||||
|
||||
@@ -470,7 +476,9 @@ class TestAgentEntityFactory:
|
||||
def test_entity_function_handles_run_agent_operation(self) -> None:
|
||||
"""Test that the entity function handles the deprecated run_agent operation for backward compatibility."""
|
||||
mock_agent = Mock()
|
||||
mock_agent.run = AsyncMock(return_value=AgentResponse(messages=[Message(role="assistant", text="Response")]))
|
||||
mock_agent.run = AsyncMock(
|
||||
return_value=AgentResponse(messages=[Message(role="assistant", contents=["Response"])])
|
||||
)
|
||||
|
||||
entity_function = create_agent_entity(mock_agent)
|
||||
|
||||
|
||||
@@ -19,7 +19,9 @@ FuncT = TypeVar("FuncT", bound=Callable[..., Any])
|
||||
|
||||
def _agent_response(text: str | None) -> AgentResponse:
|
||||
"""Create an AgentResponse with a single assistant message."""
|
||||
message = Message(role="assistant", text=text) if text is not None else Message(role="assistant", text="")
|
||||
message = (
|
||||
Message(role="assistant", contents=[text]) if text is not None else Message(role="assistant", contents=[""])
|
||||
)
|
||||
return AgentResponse(messages=[message])
|
||||
|
||||
|
||||
|
||||
@@ -206,7 +206,7 @@ class TestSerializationRoundtrip:
|
||||
|
||||
def test_roundtrip_chat_message(self) -> None:
|
||||
"""Test Message survives encode → decode roundtrip."""
|
||||
original = Message(role="user", text="Hello")
|
||||
original = Message(role="user", contents=["Hello"])
|
||||
encoded = serialize_value(original)
|
||||
decoded = deserialize_value(encoded)
|
||||
|
||||
@@ -216,7 +216,7 @@ class TestSerializationRoundtrip:
|
||||
def test_roundtrip_agent_executor_request(self) -> None:
|
||||
"""Test AgentExecutorRequest with nested Messages roundtrips."""
|
||||
original = AgentExecutorRequest(
|
||||
messages=[Message(role="user", text="Hi")],
|
||||
messages=[Message(role="user", contents=["Hi"])],
|
||||
should_respond=True,
|
||||
)
|
||||
encoded = serialize_value(original)
|
||||
@@ -231,8 +231,8 @@ class TestSerializationRoundtrip:
|
||||
"""Test AgentExecutorResponse with nested AgentResponse roundtrips."""
|
||||
original = AgentExecutorResponse(
|
||||
executor_id="test_exec",
|
||||
agent_response=AgentResponse(messages=[Message(role="assistant", text="Reply")]),
|
||||
full_conversation=[Message(role="assistant", text="Reply")],
|
||||
agent_response=AgentResponse(messages=[Message(role="assistant", contents=["Reply"])]),
|
||||
full_conversation=[Message(role="assistant", contents=["Reply"])],
|
||||
)
|
||||
encoded = serialize_value(original)
|
||||
decoded = deserialize_value(encoded)
|
||||
@@ -272,8 +272,8 @@ class TestSerializationRoundtrip:
|
||||
def test_roundtrip_list_of_objects(self) -> None:
|
||||
"""Test list of typed objects roundtrips."""
|
||||
original = [
|
||||
Message(role="user", text="Q"),
|
||||
Message(role="assistant", text="A"),
|
||||
Message(role="user", contents=["Q"]),
|
||||
Message(role="assistant", contents=["A"]),
|
||||
]
|
||||
encoded = serialize_value(original)
|
||||
decoded = deserialize_value(encoded)
|
||||
@@ -284,7 +284,7 @@ class TestSerializationRoundtrip:
|
||||
|
||||
def test_roundtrip_dict_of_objects(self) -> None:
|
||||
"""Test dict with typed values roundtrips (used for shared state)."""
|
||||
original = {"count": 42, "msg": Message(role="user", text="Hi")}
|
||||
original = {"count": 42, "msg": Message(role="user", contents=["Hi"])}
|
||||
encoded = serialize_value(original)
|
||||
decoded = deserialize_value(encoded)
|
||||
|
||||
|
||||
@@ -155,7 +155,7 @@ class TestAgentResponseHelpers:
|
||||
|
||||
# Simulate successful entity task completion
|
||||
entity_task.state = TaskState.SUCCEEDED
|
||||
entity_task.result = AgentResponse(messages=[Message(role="assistant", text="Test response")]).to_dict()
|
||||
entity_task.result = AgentResponse(messages=[Message(role="assistant", contents=["Test response"])]).to_dict()
|
||||
|
||||
# Clear pending_tasks to simulate that parent has processed the child
|
||||
task.pending_tasks.clear()
|
||||
@@ -197,7 +197,9 @@ class TestAgentResponseHelpers:
|
||||
|
||||
# Simulate successful entity task with JSON response
|
||||
entity_task.state = TaskState.SUCCEEDED
|
||||
entity_task.result = AgentResponse(messages=[Message(role="assistant", text='{"answer": "42"}')]).to_dict()
|
||||
entity_task.result = AgentResponse(
|
||||
messages=[Message(role="assistant", contents=['{"answer": "42"}'])]
|
||||
).to_dict()
|
||||
|
||||
# Clear pending_tasks to simulate that parent has processed the child
|
||||
task.pending_tasks.clear()
|
||||
|
||||
@@ -177,10 +177,10 @@ class TestBuildAgentExecutorResponse:
|
||||
# Create a previous response with conversation history
|
||||
previous = AgentExecutorResponse(
|
||||
executor_id="prev",
|
||||
agent_response=AgentResponse(messages=[Message(role="assistant", text="Previous")]),
|
||||
agent_response=AgentResponse(messages=[Message(role="assistant", contents=["Previous"])]),
|
||||
full_conversation=[
|
||||
Message(role="user", text="First"),
|
||||
Message(role="assistant", text="Previous"),
|
||||
Message(role="user", contents=["First"]),
|
||||
Message(role="assistant", contents=["Previous"]),
|
||||
],
|
||||
)
|
||||
|
||||
@@ -211,8 +211,8 @@ class TestExtractMessageContent:
|
||||
"""Test extracting from AgentExecutorResponse with text."""
|
||||
response = AgentExecutorResponse(
|
||||
executor_id="exec",
|
||||
agent_response=AgentResponse(messages=[Message(role="assistant", text="Response text")]),
|
||||
full_conversation=[Message(role="assistant", text="Response text")],
|
||||
agent_response=AgentResponse(messages=[Message(role="assistant", contents=["Response text"])]),
|
||||
full_conversation=[Message(role="assistant", contents=["Response text"])],
|
||||
)
|
||||
|
||||
result = _extract_message_content(response)
|
||||
@@ -225,13 +225,13 @@ class TestExtractMessageContent:
|
||||
executor_id="exec",
|
||||
agent_response=AgentResponse(
|
||||
messages=[
|
||||
Message(role="user", text="First"),
|
||||
Message(role="assistant", text="Last message"),
|
||||
Message(role="user", contents=["First"]),
|
||||
Message(role="assistant", contents=["Last message"]),
|
||||
]
|
||||
),
|
||||
full_conversation=[
|
||||
Message(role="user", text="First"),
|
||||
Message(role="assistant", text="Last message"),
|
||||
Message(role="user", contents=["First"]),
|
||||
Message(role="assistant", contents=["Last message"]),
|
||||
],
|
||||
)
|
||||
|
||||
@@ -244,8 +244,8 @@ class TestExtractMessageContent:
|
||||
"""Test extracting from AgentExecutorRequest."""
|
||||
request = AgentExecutorRequest(
|
||||
messages=[
|
||||
Message(role="user", text="First"),
|
||||
Message(role="user", text="Last request"),
|
||||
Message(role="user", contents=["First"]),
|
||||
Message(role="user", contents=["Last request"]),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ description = "Amazon Bedrock integration for Microsoft Agent Framework."
|
||||
authors = [{ name = "Microsoft", email = "af-support@microsoft.com"}]
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
version = "1.0.0b260330"
|
||||
version = "1.0.0b260402"
|
||||
license-files = ["LICENSE"]
|
||||
urls.homepage = "https://aka.ms/agent-framework"
|
||||
urls.source = "https://github.com/microsoft/agent-framework/tree/main/python"
|
||||
@@ -23,7 +23,7 @@ classifiers = [
|
||||
"Typing :: Typed",
|
||||
]
|
||||
dependencies = [
|
||||
"agent-framework-core>=1.0.0rc6",
|
||||
"agent-framework-core>=1.0.0,<2",
|
||||
"boto3>=1.35.0,<2.0.0",
|
||||
"botocore>=1.35.0,<2.0.0",
|
||||
]
|
||||
|
||||
@@ -102,7 +102,7 @@ class ThreadItemConverter:
|
||||
|
||||
# If only text and no attachments, use text parameter for simplicity
|
||||
if text_content.strip() and not data_contents:
|
||||
user_message = Message(role="user", text=text_content.strip())
|
||||
user_message = Message(role="user", contents=[text_content.strip()])
|
||||
else:
|
||||
# Build contents list with both text and attachments
|
||||
contents: list[Content] = []
|
||||
@@ -116,7 +116,7 @@ class ThreadItemConverter:
|
||||
if item.quoted_text and is_last_message:
|
||||
quoted_context = Message(
|
||||
role="user",
|
||||
text=f"The user is referring to this in particular:\n{item.quoted_text}",
|
||||
contents=[f"The user is referring to this in particular:\n{item.quoted_text}"],
|
||||
)
|
||||
# Prepend quoted context before the main message
|
||||
messages.insert(0, quoted_context)
|
||||
@@ -211,9 +211,9 @@ class ThreadItemConverter:
|
||||
content="User's email: user@example.com",
|
||||
)
|
||||
message = converter.hidden_context_to_input(hidden_item)
|
||||
# Returns: Message(role=SYSTEM, text="<HIDDEN_CONTEXT>User's email: ...</HIDDEN_CONTEXT>")
|
||||
# Returns: Message(role=SYSTEM, contents=["<HIDDEN_CONTEXT>User's email: ...</HIDDEN_CONTEXT>"])
|
||||
"""
|
||||
return Message(role="system", text=f"<HIDDEN_CONTEXT>{item.content}</HIDDEN_CONTEXT>")
|
||||
return Message(role="system", contents=[f"<HIDDEN_CONTEXT>{item.content}</HIDDEN_CONTEXT>"])
|
||||
|
||||
def tag_to_message_content(self, tag: UserMessageTagContent) -> Content:
|
||||
"""Convert a ChatKit tag (@-mention) to Agent Framework content.
|
||||
@@ -292,7 +292,7 @@ class ThreadItemConverter:
|
||||
f"A message was displayed to the user that the following task was performed:\n<Task>\n{task_text}\n</Task>"
|
||||
)
|
||||
|
||||
return Message(role="user", text=text)
|
||||
return Message(role="user", contents=[text])
|
||||
|
||||
def workflow_to_input(self, item: WorkflowItem) -> Message | list[Message] | None:
|
||||
"""Convert a ChatKit WorkflowItem to Agent Framework Message(s).
|
||||
@@ -347,7 +347,7 @@ class ThreadItemConverter:
|
||||
f"<Task>\n{task_text}\n</Task>"
|
||||
)
|
||||
|
||||
messages.append(Message(role="user", text=text))
|
||||
messages.append(Message(role="user", contents=[text]))
|
||||
|
||||
return messages if messages else None
|
||||
|
||||
@@ -389,7 +389,7 @@ class ThreadItemConverter:
|
||||
try:
|
||||
widget_json = item.widget.model_dump_json(exclude_unset=True, exclude_none=True)
|
||||
text = f"The following graphical UI widget (id: {item.id}) was displayed to the user:{widget_json}"
|
||||
return Message(role="user", text=text)
|
||||
return Message(role="user", contents=[text])
|
||||
except Exception:
|
||||
# If JSON serialization fails, skip the widget
|
||||
return None
|
||||
@@ -415,7 +415,7 @@ class ThreadItemConverter:
|
||||
if not text_parts:
|
||||
return None
|
||||
|
||||
return Message(role="assistant", text="".join(text_parts))
|
||||
return Message(role="assistant", contents=["".join(text_parts)])
|
||||
|
||||
async def client_tool_call_to_input(self, item: ClientToolCallItem) -> Message | list[Message] | None:
|
||||
"""Convert a ChatKit ClientToolCallItem to Agent Framework Message(s).
|
||||
|
||||
@@ -4,7 +4,7 @@ description = "OpenAI ChatKit integration for Microsoft Agent Framework."
|
||||
authors = [{ name = "Microsoft", email = "af-support@microsoft.com"}]
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
version = "1.0.0b260330"
|
||||
version = "1.0.0b260402"
|
||||
license-files = ["LICENSE"]
|
||||
urls.homepage = "https://aka.ms/agent-framework"
|
||||
urls.source = "https://github.com/microsoft/agent-framework/tree/main/python"
|
||||
@@ -22,7 +22,7 @@ classifiers = [
|
||||
"Typing :: Typed",
|
||||
]
|
||||
dependencies = [
|
||||
"agent-framework-core>=1.0.0rc6",
|
||||
"agent-framework-core>=1.0.0,<2",
|
||||
"openai-chatkit>=1.4.1,<2.0.0",
|
||||
]
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ description = "Claude Agent SDK integration for Microsoft Agent Framework."
|
||||
authors = [{ name = "Microsoft", email = "af-support@microsoft.com"}]
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
version = "1.0.0b260330"
|
||||
version = "1.0.0b260402"
|
||||
license-files = ["LICENSE"]
|
||||
urls.homepage = "https://aka.ms/agent-framework"
|
||||
urls.source = "https://github.com/microsoft/agent-framework/tree/main/python"
|
||||
@@ -23,7 +23,7 @@ classifiers = [
|
||||
"Typing :: Typed",
|
||||
]
|
||||
dependencies = [
|
||||
"agent-framework-core>=1.0.0rc6",
|
||||
"agent-framework-core>=1.0.0,<2",
|
||||
"claude-agent-sdk>=0.1.36,<0.1.49",
|
||||
]
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ description = "Copilot Studio integration for Microsoft Agent Framework."
|
||||
authors = [{ name = "Microsoft", email = "af-support@microsoft.com"}]
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
version = "1.0.0b260330"
|
||||
version = "1.0.0b260402"
|
||||
license-files = ["LICENSE"]
|
||||
urls.homepage = "https://aka.ms/agent-framework"
|
||||
urls.source = "https://github.com/microsoft/agent-framework/tree/main/python"
|
||||
@@ -23,7 +23,7 @@ classifiers = [
|
||||
"Typing :: Typed",
|
||||
]
|
||||
dependencies = [
|
||||
"agent-framework-core>=1.0.0rc6",
|
||||
"agent-framework-core>=1.0.0,<2",
|
||||
"microsoft-agents-copilotstudio-client>=0.3.1,<0.3.2",
|
||||
]
|
||||
|
||||
|
||||
@@ -136,7 +136,7 @@ from agent_framework import BaseChatClient, ChatResponse, Message
|
||||
class MyClient(BaseChatClient):
|
||||
async def _inner_get_response(self, *, messages, options, **kwargs) -> ChatResponse:
|
||||
# Call your LLM here
|
||||
return ChatResponse(messages=[Message(role="assistant", text="Hi!")])
|
||||
return ChatResponse(messages=[Message(role="assistant", contents=["Hi!"])])
|
||||
|
||||
async def _inner_get_streaming_response(self, *, messages, options, **kwargs):
|
||||
yield ChatResponseUpdate(...)
|
||||
|
||||
@@ -13,11 +13,11 @@ Highlights
|
||||
## Quick Install
|
||||
|
||||
```bash
|
||||
pip install agent-framework-core --pre
|
||||
pip install agent-framework-core
|
||||
# Optional: Add Azure AI Foundry integration
|
||||
pip install agent-framework-foundry --pre
|
||||
pip install agent-framework-foundry
|
||||
# Optional: Add OpenAI integration
|
||||
pip install agent-framework-openai --pre
|
||||
pip install agent-framework-openai
|
||||
```
|
||||
|
||||
Supported Platforms:
|
||||
|
||||
@@ -102,8 +102,6 @@ from ._middleware import (
|
||||
)
|
||||
from ._sessions import (
|
||||
AgentSession,
|
||||
BaseContextProvider, # type: ignore[reportDeprecated]
|
||||
BaseHistoryProvider, # type: ignore[reportDeprecated]
|
||||
ContextProvider,
|
||||
HistoryProvider,
|
||||
InMemoryHistoryProvider,
|
||||
@@ -280,9 +278,7 @@ __all__ = [
|
||||
"Annotation",
|
||||
"BaseAgent",
|
||||
"BaseChatClient",
|
||||
"BaseContextProvider",
|
||||
"BaseEmbeddingClient",
|
||||
"BaseHistoryProvider",
|
||||
"Case",
|
||||
"CharacterEstimatorTokenizer",
|
||||
"ChatAndFunctionMiddlewareTypes",
|
||||
|
||||
@@ -253,7 +253,8 @@ class BaseChatClient(SerializationMixin, ABC, Generic[OptionsCoT]):
|
||||
else:
|
||||
# Non-streaming implementation
|
||||
return ChatResponse(
|
||||
messages=[Message(role="assistant", text="Hello!")], response_id="custom-response"
|
||||
messages=[Message(role="assistant", contents=["Hello!"])],
|
||||
response_id="custom-response",
|
||||
)
|
||||
|
||||
|
||||
@@ -261,9 +262,9 @@ class BaseChatClient(SerializationMixin, ABC, Generic[OptionsCoT]):
|
||||
client = CustomChatClient()
|
||||
|
||||
# Use the client to get responses
|
||||
response = await client.get_response([Message(role="user", text="Hello, how are you?")])
|
||||
response = await client.get_response([Message(role="user", contents=["Hello, how are you?"])])
|
||||
# Or stream responses
|
||||
async for update in client.get_response([Message(role="user", text="Hello!")], stream=True):
|
||||
async for update in client.get_response([Message(role="user", contents=["Hello!"])], stream=True):
|
||||
print(update)
|
||||
"""
|
||||
|
||||
|
||||
@@ -877,7 +877,7 @@ class ToolResultCompactionStrategy:
|
||||
insertion_index = starts.get(group_id, 0)
|
||||
summary_message = Message(
|
||||
role="assistant",
|
||||
text=summary_text,
|
||||
contents=[summary_text],
|
||||
message_id=summary_id,
|
||||
additional_properties={
|
||||
GROUP_ANNOTATION_KEY: summary_annotation,
|
||||
@@ -1015,10 +1015,10 @@ class SummarizationStrategy:
|
||||
try:
|
||||
summary_response: ChatResponse[None] = await self.client.get_response(
|
||||
[
|
||||
Message(role="system", text=self.prompt),
|
||||
Message(role="system", contents=[self.prompt]),
|
||||
Message(
|
||||
role="user",
|
||||
text=_format_messages_for_summary(messages_to_summarize),
|
||||
contents=[_format_messages_for_summary(messages_to_summarize)],
|
||||
),
|
||||
],
|
||||
stream=False,
|
||||
@@ -1044,7 +1044,7 @@ class SummarizationStrategy:
|
||||
|
||||
summary_message = Message(
|
||||
role="assistant",
|
||||
text=summary_text,
|
||||
contents=[summary_text],
|
||||
message_id=summary_id,
|
||||
additional_properties={
|
||||
GROUP_ANNOTATION_KEY: summary_annotation,
|
||||
|
||||
@@ -502,7 +502,7 @@ class ChatMiddleware(ABC):
|
||||
# Add system prompt to messages
|
||||
from agent_framework import Message
|
||||
|
||||
context.messages.insert(0, Message(role="system", text=self.system_prompt))
|
||||
context.messages.insert(0, Message(role="system", contents=[self.system_prompt]))
|
||||
|
||||
# Continue execution
|
||||
await call_next()
|
||||
|
||||
@@ -40,7 +40,7 @@ class SerializationProtocol(Protocol):
|
||||
|
||||
|
||||
# Message implements SerializationProtocol via SerializationMixin
|
||||
user_msg = Message(role="user", text="What's the weather like today?")
|
||||
user_msg = Message(role="user", contents=["What's the weather like today?"])
|
||||
|
||||
# Serialize to dictionary - automatic type identification and nested serialization
|
||||
msg_dict = user_msg.to_dict()
|
||||
|
||||
@@ -13,17 +13,11 @@ This module provides the core types for the context provider pipeline:
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import sys
|
||||
import uuid
|
||||
from abc import abstractmethod
|
||||
from collections.abc import Awaitable, Callable, Mapping, Sequence
|
||||
from typing import TYPE_CHECKING, Any, ClassVar, TypeGuard, cast
|
||||
|
||||
if sys.version_info >= (3, 13):
|
||||
from warnings import deprecated # type: ignore # pragma: no cover
|
||||
else:
|
||||
from typing_extensions import deprecated # type: ignore # pragma: no cover
|
||||
|
||||
from ._middleware import ChatContext, ChatMiddleware
|
||||
from ._types import AgentResponse, ChatResponse, Message, ResponseStream
|
||||
from .exceptions import ChatClientInvalidResponseException
|
||||
@@ -698,30 +692,6 @@ class PerServiceCallHistoryPersistingMiddleware(ChatMiddleware):
|
||||
)
|
||||
|
||||
|
||||
@deprecated(
|
||||
"BaseContextProvider is deprecated. Use ContextProvider instead.",
|
||||
category=DeprecationWarning,
|
||||
)
|
||||
class BaseContextProvider(ContextProvider):
|
||||
"""Deprecated alias for :class:`ContextProvider`.
|
||||
|
||||
.. deprecated::
|
||||
BaseContextProvider is deprecated. Use :class:`ContextProvider` instead.
|
||||
"""
|
||||
|
||||
|
||||
@deprecated(
|
||||
"BaseHistoryProvider is deprecated. Use HistoryProvider instead.",
|
||||
category=DeprecationWarning,
|
||||
)
|
||||
class BaseHistoryProvider(HistoryProvider):
|
||||
"""Deprecated alias for :class:`HistoryProvider`.
|
||||
|
||||
.. deprecated::
|
||||
BaseHistoryProvider is deprecated. Use :class:`HistoryProvider` instead.
|
||||
"""
|
||||
|
||||
|
||||
class AgentSession:
|
||||
"""A conversation session with an agent.
|
||||
|
||||
|
||||
@@ -1669,7 +1669,6 @@ class Message(SerializationMixin):
|
||||
role: RoleLiteral | str,
|
||||
contents: Sequence[Content | str | Mapping[str, Any]] | None = None,
|
||||
*,
|
||||
text: str | None = None,
|
||||
author_name: str | None = None,
|
||||
message_id: str | None = None,
|
||||
additional_properties: MutableMapping[str, Any] | None = None,
|
||||
@@ -1683,21 +1682,14 @@ class Message(SerializationMixin):
|
||||
to TextContent), or dicts (parsed via Content.from_dict). Defaults to empty list.
|
||||
|
||||
Keyword Args:
|
||||
text: Deprecated. Text content of the message. Use contents instead.
|
||||
This parameter is kept for backward compatibility with serialization.
|
||||
author_name: Optional name of the author of the message.
|
||||
message_id: Optional ID of the chat message.
|
||||
additional_properties: Optional additional properties associated with the chat message.
|
||||
Additional properties are used within Agent Framework, they are not sent to services.
|
||||
raw_representation: Optional raw representation of the chat message.
|
||||
"""
|
||||
# Handle contents conversion
|
||||
parsed_contents = [] if contents is None else _parse_content_list(contents)
|
||||
|
||||
# Handle text for backward compatibility (from serialization)
|
||||
if text is not None:
|
||||
parsed_contents.append(Content.from_text(text=text))
|
||||
|
||||
self.role: str = role
|
||||
self.contents = parsed_contents
|
||||
self.author_name = author_name
|
||||
|
||||
@@ -21,7 +21,7 @@ def normalize_messages_input(
|
||||
return []
|
||||
|
||||
if isinstance(messages, str):
|
||||
return [Message(role="user", text=messages)]
|
||||
return [Message(role="user", contents=[messages])]
|
||||
|
||||
if isinstance(messages, Content):
|
||||
return [Message(role="user", contents=[messages])]
|
||||
@@ -31,9 +31,7 @@ def normalize_messages_input(
|
||||
|
||||
normalized: list[Message] = []
|
||||
for item in messages:
|
||||
if isinstance(item, str):
|
||||
normalized.append(Message(role="user", text=item))
|
||||
elif isinstance(item, Content):
|
||||
if isinstance(item, (str, Content)):
|
||||
normalized.append(Message(role="user", contents=[item]))
|
||||
elif isinstance(item, Message):
|
||||
normalized.append(item)
|
||||
|
||||
@@ -4,7 +4,7 @@ description = "Microsoft Agent Framework for building AI Agents with Python. Thi
|
||||
authors = [{ name = "Microsoft", email = "af-support@microsoft.com"}]
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
version = "1.0.0rc6"
|
||||
version = "1.0.0"
|
||||
license-files = ["LICENSE"]
|
||||
urls.homepage = "https://aka.ms/agent-framework"
|
||||
urls.source = "https://github.com/microsoft/agent-framework/tree/main/python"
|
||||
@@ -12,7 +12,7 @@ urls.release_notes = "https://github.com/microsoft/agent-framework/releases?q=ta
|
||||
urls.issues = "https://github.com/microsoft/agent-framework/issues"
|
||||
classifiers = [
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Development Status :: 4 - Beta",
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Intended Audience :: Developers",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
|
||||
@@ -105,7 +105,7 @@ class MockChatClient:
|
||||
self.call_count += 1
|
||||
if self.responses:
|
||||
return self.responses.pop(0)
|
||||
return ChatResponse(messages=Message(role="assistant", text="test response"))
|
||||
return ChatResponse(messages=Message(role="assistant", contents=["test response"]))
|
||||
|
||||
return _get()
|
||||
|
||||
@@ -186,7 +186,7 @@ class MockBaseChatClient(
|
||||
logger.debug(f"Running base chat client inner, with: {messages=}, {options=}, {kwargs=}")
|
||||
self.call_count += 1
|
||||
if not self.run_responses:
|
||||
return ChatResponse(messages=Message(role="assistant", text=f"test response - {messages[-1].text}"))
|
||||
return ChatResponse(messages=Message(role="assistant", contents=[f"test response - {messages[-1].text}"]))
|
||||
|
||||
response = self.run_responses.pop(0)
|
||||
|
||||
@@ -194,7 +194,7 @@ class MockBaseChatClient(
|
||||
return ChatResponse(
|
||||
messages=Message(
|
||||
role="assistant",
|
||||
text="I broke out of the function invocation loop...",
|
||||
contents=["I broke out of the function invocation loop..."],
|
||||
),
|
||||
conversation_id=response.conversation_id,
|
||||
)
|
||||
|
||||
@@ -306,7 +306,7 @@ async def test_chat_client_agent_response_format_dict_from_default_options(
|
||||
) -> None:
|
||||
"""AgentResponse.value should parse JSON dicts from default_options response_format."""
|
||||
json_text = json.dumps({"greeting": "Hello"})
|
||||
client.responses.append(ChatResponse(messages=Message(role="assistant", text=json_text))) # type: ignore[attr-defined]
|
||||
client.responses.append(ChatResponse(messages=Message(role="assistant", contents=[json_text]))) # type: ignore[attr-defined]
|
||||
|
||||
agent = Agent(
|
||||
client=client,
|
||||
@@ -366,13 +366,13 @@ async def test_chat_client_agent_prepare_session_and_messages(
|
||||
from agent_framework._sessions import InMemoryHistoryProvider
|
||||
|
||||
agent = Agent(client=client, context_providers=[InMemoryHistoryProvider()])
|
||||
message = Message(role="user", text="Hello")
|
||||
message = Message(role="user", contents=["Hello"])
|
||||
session = AgentSession()
|
||||
session.state[InMemoryHistoryProvider.DEFAULT_SOURCE_ID] = {"messages": [message]}
|
||||
|
||||
session_context, _ = await agent._prepare_session_and_messages( # type: ignore[reportPrivateUsage]
|
||||
session=session,
|
||||
input_messages=[Message(role="user", text="Test")],
|
||||
input_messages=[Message(role="user", contents=["Test"])],
|
||||
)
|
||||
result_messages = session_context.get_messages(include_input=True)
|
||||
|
||||
@@ -393,7 +393,7 @@ async def test_prepare_session_does_not_mutate_agent_chat_options(
|
||||
|
||||
_, prepared_chat_options = await agent._prepare_session_and_messages( # type: ignore[reportPrivateUsage]
|
||||
session=session,
|
||||
input_messages=[Message(role="user", text="Test")],
|
||||
input_messages=[Message(role="user", contents=["Test"])],
|
||||
)
|
||||
|
||||
assert prepared_chat_options.get("tools") is not None
|
||||
@@ -444,8 +444,8 @@ async def test_chat_agent_persists_history_per_service_call(
|
||||
session = AgentSession()
|
||||
session.state[provider.source_id] = {
|
||||
"messages": [
|
||||
Message(role="user", text="Earlier question"),
|
||||
Message(role="assistant", text="Earlier answer"),
|
||||
Message(role="user", contents=["Earlier question"]),
|
||||
Message(role="assistant", contents=["Earlier answer"]),
|
||||
]
|
||||
}
|
||||
chat_client_base.run_responses = [
|
||||
@@ -462,7 +462,9 @@ async def test_chat_agent_persists_history_per_service_call(
|
||||
),
|
||||
response_id="resp_call_1",
|
||||
),
|
||||
ChatResponse(messages=Message(role="assistant", text="It is sunny in Seattle."), response_id="resp_call_2"),
|
||||
ChatResponse(
|
||||
messages=Message(role="assistant", contents=["It is sunny in Seattle."]), response_id="resp_call_2"
|
||||
),
|
||||
]
|
||||
|
||||
agent = Agent(
|
||||
@@ -498,8 +500,8 @@ async def test_chat_agent_persists_history_per_service_call_streaming(
|
||||
session = AgentSession()
|
||||
session.state[provider.source_id] = {
|
||||
"messages": [
|
||||
Message(role="user", text="Earlier question"),
|
||||
Message(role="assistant", text="Earlier answer"),
|
||||
Message(role="user", contents=["Earlier question"]),
|
||||
Message(role="assistant", contents=["Earlier answer"]),
|
||||
]
|
||||
}
|
||||
chat_client_base.streaming_responses = [
|
||||
@@ -634,7 +636,7 @@ async def test_per_service_call_persistence_uses_real_service_storage_when_clien
|
||||
response_id="resp_call_1",
|
||||
),
|
||||
ChatResponse(
|
||||
messages=Message(role="assistant", text="It is sunny in Seattle."),
|
||||
messages=Message(role="assistant", contents=["It is sunny in Seattle."]),
|
||||
conversation_id="resp_service_managed",
|
||||
response_id="resp_call_2",
|
||||
),
|
||||
@@ -777,7 +779,7 @@ async def test_chat_agent_without_per_service_call_persistence_preserves_respons
|
||||
) -> None:
|
||||
chat_client_base.run_responses = [
|
||||
ChatResponse(
|
||||
messages=Message(role="assistant", text="Hello"),
|
||||
messages=Message(role="assistant", contents=["Hello"]),
|
||||
response_id="resp_call_1",
|
||||
)
|
||||
]
|
||||
@@ -801,7 +803,7 @@ async def test_per_service_call_persistence_rejects_real_service_conversation_id
|
||||
session.state[provider.source_id] = {"messages": []}
|
||||
chat_client_base.run_responses = [
|
||||
ChatResponse(
|
||||
messages=Message(role="assistant", text="Hello"),
|
||||
messages=Message(role="assistant", contents=["Hello"]),
|
||||
conversation_id="resp_service_managed",
|
||||
)
|
||||
]
|
||||
@@ -1138,7 +1140,7 @@ async def test_chat_agent_context_providers_model_before_run(
|
||||
client: SupportsChatGetResponse,
|
||||
) -> None:
|
||||
"""Test that context providers' before_run is called during agent run."""
|
||||
mock_provider = MockContextProvider(messages=[Message(role="system", text="Test context instructions")])
|
||||
mock_provider = MockContextProvider(messages=[Message(role="system", contents=["Test context instructions"])])
|
||||
agent = Agent(client=client, context_providers=[mock_provider])
|
||||
|
||||
await agent.run("Hello")
|
||||
@@ -1185,7 +1187,7 @@ async def test_chat_agent_context_instructions_in_messages(
|
||||
client: SupportsChatGetResponse,
|
||||
) -> None:
|
||||
"""Test that AI context instructions are included in messages."""
|
||||
mock_provider = MockContextProvider(messages=[Message(role="system", text="Context-specific instructions")])
|
||||
mock_provider = MockContextProvider(messages=[Message(role="system", contents=["Context-specific instructions"])])
|
||||
agent = Agent(
|
||||
client=client,
|
||||
instructions="Agent instructions",
|
||||
@@ -1194,7 +1196,7 @@ async def test_chat_agent_context_instructions_in_messages(
|
||||
|
||||
# We need to test the _prepare_session_and_messages method directly
|
||||
session_context, _ = await agent._prepare_session_and_messages( # type: ignore[reportPrivateUsage]
|
||||
session=None, input_messages=[Message(role="user", text="Hello")]
|
||||
session=None, input_messages=[Message(role="user", contents=["Hello"])]
|
||||
)
|
||||
messages = session_context.get_messages(include_input=True)
|
||||
|
||||
@@ -1219,7 +1221,7 @@ async def test_chat_agent_no_context_instructions(
|
||||
)
|
||||
|
||||
session_context, _ = await agent._prepare_session_and_messages( # type: ignore[reportPrivateUsage]
|
||||
session=None, input_messages=[Message(role="user", text="Hello")]
|
||||
session=None, input_messages=[Message(role="user", contents=["Hello"])]
|
||||
)
|
||||
messages = session_context.get_messages(include_input=True)
|
||||
|
||||
@@ -1233,7 +1235,7 @@ async def test_chat_agent_run_stream_context_providers(
|
||||
client: SupportsChatGetResponse,
|
||||
) -> None:
|
||||
"""Test that context providers work with run method."""
|
||||
mock_provider = MockContextProvider(messages=[Message(role="system", text="Stream context instructions")])
|
||||
mock_provider = MockContextProvider(messages=[Message(role="system", contents=["Stream context instructions"])])
|
||||
agent = Agent(client=client, context_providers=[mock_provider])
|
||||
|
||||
# Collect all stream updates and get final response
|
||||
@@ -1727,7 +1729,7 @@ async def test_agent_tool_without_context_does_not_receive_session(chat_client_b
|
||||
],
|
||||
)
|
||||
),
|
||||
ChatResponse(messages=Message(role="assistant", text="done")),
|
||||
ChatResponse(messages=Message(role="assistant", contents=["done"])),
|
||||
]
|
||||
|
||||
agent = Agent(client=chat_client_base, tools=[echo_session_info])
|
||||
@@ -1766,7 +1768,7 @@ async def test_agent_tool_receives_explicit_session_via_function_invocation_cont
|
||||
],
|
||||
)
|
||||
),
|
||||
ChatResponse(messages=Message(role="assistant", text="done")),
|
||||
ChatResponse(messages=Message(role="assistant", contents=["done"])),
|
||||
]
|
||||
|
||||
agent = Agent(client=chat_client_base, tools=[capture_session_context])
|
||||
@@ -1899,8 +1901,8 @@ async def test_chat_agent_compaction_overrides_client_defaults(chat_client_base:
|
||||
)
|
||||
|
||||
await agent.run([
|
||||
Message(role="user", text="Hello"),
|
||||
Message(role="assistant", text="Previous response"),
|
||||
Message(role="user", contents=["Hello"]),
|
||||
Message(role="assistant", contents=["Previous response"]),
|
||||
])
|
||||
|
||||
assert captured_roles == [["user", "assistant"]]
|
||||
@@ -1924,8 +1926,8 @@ async def test_chat_agent_uses_client_compaction_defaults_when_agent_unset(chat_
|
||||
agent = Agent(client=chat_client_base)
|
||||
|
||||
await agent.run([
|
||||
Message(role="user", text="Hello"),
|
||||
Message(role="assistant", text="Previous response"),
|
||||
Message(role="user", contents=["Hello"]),
|
||||
Message(role="assistant", contents=["Previous response"]),
|
||||
])
|
||||
|
||||
assert captured_roles == [["assistant"]]
|
||||
@@ -1957,8 +1959,8 @@ async def test_chat_agent_run_level_compaction_and_tokenizer_override_agent_defa
|
||||
|
||||
await agent.run(
|
||||
[
|
||||
Message(role="user", text="Hello"),
|
||||
Message(role="assistant", text="Previous response"),
|
||||
Message(role="user", contents=["Hello"]),
|
||||
Message(role="assistant", contents=["Previous response"]),
|
||||
],
|
||||
compaction_strategy=TruncationStrategy(max_n=1, compact_to=1),
|
||||
tokenizer=_FixedTokenizer(23),
|
||||
@@ -2352,7 +2354,7 @@ async def test_chat_agent_context_provider_adds_tools_when_agent_has_none(
|
||||
|
||||
# Run the agent and verify context tools are added
|
||||
_, options = await agent._prepare_session_and_messages( # type: ignore[reportPrivateUsage]
|
||||
session=None, input_messages=[Message(role="user", text="Hello")]
|
||||
session=None, input_messages=[Message(role="user", contents=["Hello"])]
|
||||
)
|
||||
|
||||
# The context tools should now be in the options
|
||||
@@ -2381,7 +2383,7 @@ async def test_chat_agent_context_provider_adds_instructions_when_agent_has_none
|
||||
|
||||
# Run the agent and verify context instructions are available
|
||||
_, options = await agent._prepare_session_and_messages( # type: ignore[reportPrivateUsage]
|
||||
session=None, input_messages=[Message(role="user", text="Hello")]
|
||||
session=None, input_messages=[Message(role="user", contents=["Hello"])]
|
||||
)
|
||||
|
||||
# The context instructions should now be in the options
|
||||
@@ -2408,7 +2410,7 @@ async def test_chat_agent_context_provider_adds_middleware_when_agent_has_none(
|
||||
|
||||
session_context, _ = await agent._prepare_session_and_messages( # type: ignore[reportPrivateUsage]
|
||||
session=None,
|
||||
input_messages=[Message(role="user", text="Hello")],
|
||||
input_messages=[Message(role="user", contents=["Hello"])],
|
||||
)
|
||||
|
||||
assert session_context.middleware["middleware-context"] == [context_chat_middleware]
|
||||
|
||||
@@ -40,7 +40,7 @@ class TestAsToolKwargsPropagation:
|
||||
|
||||
# Setup mock response
|
||||
client.responses = [
|
||||
ChatResponse(messages=[Message(role="assistant", text="Response from sub-agent")]),
|
||||
ChatResponse(messages=[Message(role="assistant", contents=["Response from sub-agent"])]),
|
||||
]
|
||||
|
||||
# Create sub-agent with middleware
|
||||
@@ -82,7 +82,7 @@ class TestAsToolKwargsPropagation:
|
||||
|
||||
# Setup mock response
|
||||
client.responses = [
|
||||
ChatResponse(messages=[Message(role="assistant", text="Response from sub-agent")]),
|
||||
ChatResponse(messages=[Message(role="assistant", contents=["Response from sub-agent"])]),
|
||||
]
|
||||
|
||||
sub_agent = Agent(
|
||||
@@ -133,8 +133,8 @@ class TestAsToolKwargsPropagation:
|
||||
)
|
||||
]
|
||||
),
|
||||
ChatResponse(messages=[Message(role="assistant", text="Response from agent_c")]),
|
||||
ChatResponse(messages=[Message(role="assistant", text="Response from agent_b")]),
|
||||
ChatResponse(messages=[Message(role="assistant", contents=["Response from agent_c"])]),
|
||||
ChatResponse(messages=[Message(role="assistant", contents=["Response from agent_b"])]),
|
||||
]
|
||||
|
||||
# Create agent C (bottom level)
|
||||
@@ -219,7 +219,7 @@ class TestAsToolKwargsPropagation:
|
||||
"""Test that as_tool works correctly when no extra kwargs are provided."""
|
||||
# Setup mock response
|
||||
client.responses = [
|
||||
ChatResponse(messages=[Message(role="assistant", text="Response from agent")]),
|
||||
ChatResponse(messages=[Message(role="assistant", contents=["Response from agent"])]),
|
||||
]
|
||||
|
||||
sub_agent = Agent(
|
||||
@@ -248,7 +248,7 @@ class TestAsToolKwargsPropagation:
|
||||
|
||||
# Setup mock response
|
||||
client.responses = [
|
||||
ChatResponse(messages=[Message(role="assistant", text="Response with options")]),
|
||||
ChatResponse(messages=[Message(role="assistant", contents=["Response with options"])]),
|
||||
]
|
||||
|
||||
sub_agent = Agent(
|
||||
@@ -295,8 +295,8 @@ class TestAsToolKwargsPropagation:
|
||||
|
||||
# Setup mock responses for both calls
|
||||
client.responses = [
|
||||
ChatResponse(messages=[Message(role="assistant", text="First response")]),
|
||||
ChatResponse(messages=[Message(role="assistant", text="Second response")]),
|
||||
ChatResponse(messages=[Message(role="assistant", contents=["First response"])]),
|
||||
ChatResponse(messages=[Message(role="assistant", contents=["Second response"])]),
|
||||
]
|
||||
|
||||
sub_agent = Agent(
|
||||
@@ -342,7 +342,7 @@ class TestAsToolKwargsPropagation:
|
||||
|
||||
# Setup mock response
|
||||
client.responses = [
|
||||
ChatResponse(messages=[Message(role="assistant", text="Response from sub-agent")]),
|
||||
ChatResponse(messages=[Message(role="assistant", contents=["Response from sub-agent"])]),
|
||||
]
|
||||
|
||||
sub_agent = Agent(
|
||||
|
||||
@@ -31,13 +31,13 @@ def test_chat_client_type(client: SupportsChatGetResponse):
|
||||
|
||||
|
||||
async def test_chat_client_get_response(client: SupportsChatGetResponse):
|
||||
response = await client.get_response([Message(role="user", text="Hello")])
|
||||
response = await client.get_response([Message(role="user", contents=["Hello"])])
|
||||
assert response.text == "test response"
|
||||
assert response.messages[0].role == "assistant"
|
||||
|
||||
|
||||
async def test_chat_client_get_response_streaming(client: SupportsChatGetResponse):
|
||||
async for update in client.get_response([Message(role="user", text="Hello")], stream=True):
|
||||
async for update in client.get_response([Message(role="user", contents=["Hello"])], stream=True):
|
||||
assert update.text == "test streaming response " or update.text == "another update"
|
||||
assert update.role == "assistant"
|
||||
|
||||
@@ -62,7 +62,7 @@ async def test_base_client_get_response_uses_explicit_client_kwargs(chat_client_
|
||||
async def fake_inner_get_response(**kwargs):
|
||||
assert kwargs["trace_id"] == "trace-123"
|
||||
assert "function_invocation_kwargs" not in kwargs
|
||||
return ChatResponse(messages=[Message(role="assistant", text="ok")])
|
||||
return ChatResponse(messages=[Message(role="assistant", contents=["ok"])])
|
||||
|
||||
with patch.object(
|
||||
chat_client_base,
|
||||
@@ -70,7 +70,7 @@ async def test_base_client_get_response_uses_explicit_client_kwargs(chat_client_
|
||||
side_effect=fake_inner_get_response,
|
||||
) as mock_inner_get_response:
|
||||
await chat_client_base.get_response(
|
||||
[Message(role="user", text="hello")],
|
||||
[Message(role="user", contents=["hello"])],
|
||||
function_invocation_kwargs={"tool_request_id": "tool-123"},
|
||||
client_kwargs={"trace_id": "trace-123"},
|
||||
)
|
||||
@@ -78,13 +78,13 @@ async def test_base_client_get_response_uses_explicit_client_kwargs(chat_client_
|
||||
|
||||
|
||||
async def test_base_client_get_response(chat_client_base: SupportsChatGetResponse):
|
||||
response = await chat_client_base.get_response([Message(role="user", text="Hello")])
|
||||
response = await chat_client_base.get_response([Message(role="user", contents=["Hello"])])
|
||||
assert response.messages[0].role == "assistant"
|
||||
assert response.messages[0].text == "test response - Hello"
|
||||
|
||||
|
||||
async def test_base_client_get_response_streaming(chat_client_base: SupportsChatGetResponse):
|
||||
async for update in chat_client_base.get_response([Message(role="user", text="Hello")], stream=True):
|
||||
async for update in chat_client_base.get_response([Message(role="user", contents=["Hello"])], stream=True):
|
||||
assert update.text == "update - Hello" or update.text == "another update"
|
||||
|
||||
|
||||
@@ -107,8 +107,8 @@ async def test_base_client_applies_compaction_before_non_streaming_inner_call(
|
||||
|
||||
chat_client_base._get_non_streaming_response = _capture # type: ignore[attr-defined,method-assign]
|
||||
await chat_client_base.get_response([
|
||||
Message(role="user", text="Hello"),
|
||||
Message(role="assistant", text="Previous response"),
|
||||
Message(role="user", contents=["Hello"]),
|
||||
Message(role="assistant", contents=["Previous response"]),
|
||||
])
|
||||
assert captured_roles == [["assistant"]]
|
||||
|
||||
@@ -133,8 +133,8 @@ async def test_base_client_applies_compaction_before_streaming_inner_call(
|
||||
chat_client_base._get_streaming_response = _capture # type: ignore[attr-defined,method-assign]
|
||||
async for _ in chat_client_base.get_response(
|
||||
[
|
||||
Message(role="user", text="Hello"),
|
||||
Message(role="assistant", text="Previous response"),
|
||||
Message(role="user", contents=["Hello"]),
|
||||
Message(role="assistant", contents=["Previous response"]),
|
||||
],
|
||||
stream=True,
|
||||
):
|
||||
@@ -161,8 +161,8 @@ async def test_base_client_per_call_compaction_override_applies_before_inner_cal
|
||||
chat_client_base._get_non_streaming_response = _capture # type: ignore[attr-defined,method-assign]
|
||||
await chat_client_base.get_response(
|
||||
[
|
||||
Message(role="user", text="Hello"),
|
||||
Message(role="assistant", text="Previous response"),
|
||||
Message(role="user", contents=["Hello"]),
|
||||
Message(role="assistant", contents=["Previous response"]),
|
||||
],
|
||||
compaction_strategy=TruncationStrategy(max_n=1, compact_to=1),
|
||||
)
|
||||
@@ -191,8 +191,8 @@ async def test_base_client_per_call_tokenizer_override_annotates_messages(
|
||||
chat_client_base._get_non_streaming_response = _capture # type: ignore[attr-defined,method-assign]
|
||||
await chat_client_base.get_response(
|
||||
[
|
||||
Message(role="user", text="Hello"),
|
||||
Message(role="assistant", text="Previous response"),
|
||||
Message(role="user", contents=["Hello"]),
|
||||
Message(role="assistant", contents=["Previous response"]),
|
||||
],
|
||||
compaction_strategy=SlidingWindowStrategy(keep_last_groups=2),
|
||||
tokenizer=_FixedTokenizer(17),
|
||||
@@ -222,8 +222,8 @@ async def test_base_client_per_call_tokenizer_override_without_strategy_annotate
|
||||
chat_client_base._get_non_streaming_response = _capture # type: ignore[attr-defined,method-assign]
|
||||
await chat_client_base.get_response(
|
||||
[
|
||||
Message(role="user", text="Hello"),
|
||||
Message(role="assistant", text="Previous response"),
|
||||
Message(role="user", contents=["Hello"]),
|
||||
Message(role="assistant", contents=["Previous response"]),
|
||||
],
|
||||
tokenizer=_FixedTokenizer(17),
|
||||
)
|
||||
@@ -252,8 +252,8 @@ async def test_base_client_default_tokenizer_without_strategy_annotates_messages
|
||||
|
||||
chat_client_base._get_non_streaming_response = _capture # type: ignore[attr-defined,method-assign]
|
||||
await chat_client_base.get_response([
|
||||
Message(role="user", text="Hello"),
|
||||
Message(role="assistant", text="Previous response"),
|
||||
Message(role="user", contents=["Hello"]),
|
||||
Message(role="assistant", contents=["Previous response"]),
|
||||
])
|
||||
assert captured_token_counts == [[19, 19]]
|
||||
|
||||
@@ -276,7 +276,7 @@ async def test_chat_client_instructions_handling(chat_client_base: SupportsChatG
|
||||
instructions = "You are a helpful assistant."
|
||||
|
||||
async def fake_inner_get_response(**kwargs):
|
||||
return ChatResponse(messages=[Message(role="assistant", text="ok")])
|
||||
return ChatResponse(messages=[Message(role="assistant", contents=["ok"])])
|
||||
|
||||
with patch.object(
|
||||
chat_client_base,
|
||||
@@ -284,7 +284,7 @@ async def test_chat_client_instructions_handling(chat_client_base: SupportsChatG
|
||||
side_effect=fake_inner_get_response,
|
||||
) as mock_inner_get_response:
|
||||
await chat_client_base.get_response(
|
||||
[Message(role="user", text="hello")], options={"instructions": instructions}
|
||||
[Message(role="user", contents=["hello"])], options={"instructions": instructions}
|
||||
)
|
||||
mock_inner_get_response.assert_called_once()
|
||||
_, kwargs = mock_inner_get_response.call_args
|
||||
@@ -296,7 +296,7 @@ async def test_chat_client_instructions_handling(chat_client_base: SupportsChatG
|
||||
from agent_framework._types import prepend_instructions_to_messages
|
||||
|
||||
appended_messages = prepend_instructions_to_messages(
|
||||
[Message(role="user", text="hello")],
|
||||
[Message(role="user", contents=["hello"])],
|
||||
instructions,
|
||||
)
|
||||
assert len(appended_messages) == 2
|
||||
|
||||
@@ -105,10 +105,10 @@ def _group_unknown_value(message: Message, key: str) -> Any:
|
||||
|
||||
def test_group_annotations_keep_tool_call_and_tool_result_atomic() -> None:
|
||||
messages = [
|
||||
Message(role="user", text="hello"),
|
||||
Message(role="user", contents=["hello"]),
|
||||
_assistant_function_call("c1"),
|
||||
_tool_result("c1", "ok"),
|
||||
Message(role="assistant", text="final"),
|
||||
Message(role="assistant", contents=["final"]),
|
||||
]
|
||||
|
||||
annotate_message_groups(messages)
|
||||
@@ -136,11 +136,11 @@ def test_group_annotations_include_reasoning_in_tool_call_group() -> None:
|
||||
|
||||
def test_group_annotations_handle_same_message_reasoning_and_function_calls() -> None:
|
||||
messages = [
|
||||
Message(role="user", text="hello"),
|
||||
Message(role="user", contents=["hello"]),
|
||||
_assistant_reasoning_and_function_calls("c1", "c2"),
|
||||
_tool_result("c1", "ok1"),
|
||||
_tool_result("c2", "ok2"),
|
||||
Message(role="assistant", text="final"),
|
||||
Message(role="assistant", contents=["final"]),
|
||||
]
|
||||
|
||||
annotate_message_groups(messages)
|
||||
@@ -155,8 +155,8 @@ def test_group_annotations_handle_same_message_reasoning_and_function_calls() ->
|
||||
|
||||
def test_annotate_message_groups_with_tokenizer_adds_token_counts() -> None:
|
||||
messages = [
|
||||
Message(role="user", text="hello"),
|
||||
Message(role="assistant", text="world"),
|
||||
Message(role="user", contents=["hello"]),
|
||||
Message(role="assistant", contents=["world"]),
|
||||
]
|
||||
|
||||
annotate_message_groups(
|
||||
@@ -187,9 +187,9 @@ def test_extend_compaction_messages_preserves_existing_annotations_and_tokens()
|
||||
|
||||
|
||||
def test_append_compaction_message_annotates_new_message() -> None:
|
||||
messages = [Message(role="user", text="hello")]
|
||||
messages = [Message(role="user", contents=["hello"])]
|
||||
annotate_message_groups(messages)
|
||||
append_compaction_message(messages, Message(role="assistant", text="world"))
|
||||
append_compaction_message(messages, Message(role="assistant", contents=["world"]))
|
||||
|
||||
assert len(messages) == 2
|
||||
assert isinstance(_group_id(messages[1]), str)
|
||||
@@ -197,11 +197,11 @@ def test_append_compaction_message_annotates_new_message() -> None:
|
||||
|
||||
async def test_truncation_strategy_keeps_system_anchor() -> None:
|
||||
messages = [
|
||||
Message(role="system", text="you are helpful"),
|
||||
Message(role="user", text="u1"),
|
||||
Message(role="assistant", text="a1"),
|
||||
Message(role="user", text="u2"),
|
||||
Message(role="assistant", text="a2"),
|
||||
Message(role="system", contents=["you are helpful"]),
|
||||
Message(role="user", contents=["u1"]),
|
||||
Message(role="assistant", contents=["a1"]),
|
||||
Message(role="user", contents=["u2"]),
|
||||
Message(role="assistant", contents=["a2"]),
|
||||
]
|
||||
strategy = TruncationStrategy(max_n=3, compact_to=3, preserve_system=True)
|
||||
annotate_message_groups(messages)
|
||||
@@ -217,9 +217,9 @@ async def test_truncation_strategy_keeps_system_anchor() -> None:
|
||||
async def test_truncation_strategy_compacts_when_token_limit_exceeded() -> None:
|
||||
tokenizer = CharacterEstimatorTokenizer()
|
||||
messages = [
|
||||
Message(role="system", text="you are helpful"),
|
||||
Message(role="user", text="u1 " * 200),
|
||||
Message(role="assistant", text="a1 " * 200),
|
||||
Message(role="system", contents=["you are helpful"]),
|
||||
Message(role="user", contents=["u1 " * 200]),
|
||||
Message(role="assistant", contents=["a1 " * 200]),
|
||||
]
|
||||
strategy = TruncationStrategy(
|
||||
max_n=80,
|
||||
@@ -248,12 +248,12 @@ def test_truncation_strategy_validates_token_targets() -> None:
|
||||
|
||||
async def test_selective_tool_call_strategy_excludes_older_tool_groups() -> None:
|
||||
messages = [
|
||||
Message(role="user", text="u"),
|
||||
Message(role="user", contents=["u"]),
|
||||
_assistant_function_call("call-1"),
|
||||
_tool_result("call-1", "r1"),
|
||||
_assistant_function_call("call-2"),
|
||||
_tool_result("call-2", "r2"),
|
||||
Message(role="assistant", text="done"),
|
||||
Message(role="assistant", contents=["done"]),
|
||||
]
|
||||
strategy = SelectiveToolCallCompactionStrategy(keep_last_tool_call_groups=1)
|
||||
annotate_message_groups(messages)
|
||||
@@ -269,10 +269,10 @@ async def test_selective_tool_call_strategy_excludes_older_tool_groups() -> None
|
||||
|
||||
async def test_selective_tool_call_strategy_with_zero_removes_assistant_tool_pair() -> None:
|
||||
messages = [
|
||||
Message(role="user", text="u"),
|
||||
Message(role="user", contents=["u"]),
|
||||
_assistant_function_call("call-1"),
|
||||
_tool_result("call-1", "r1"),
|
||||
Message(role="assistant", text="done"),
|
||||
Message(role="assistant", contents=["done"]),
|
||||
]
|
||||
strategy = SelectiveToolCallCompactionStrategy(keep_last_tool_call_groups=0)
|
||||
annotate_message_groups(messages)
|
||||
@@ -304,7 +304,7 @@ class _FakeSummarizer:
|
||||
options: dict[str, Any] | None = None,
|
||||
**kwargs: Any,
|
||||
) -> ChatResponse:
|
||||
return ChatResponse(messages=[Message(role="assistant", text="summarized context")])
|
||||
return ChatResponse(messages=[Message(role="assistant", contents=["summarized context"])])
|
||||
|
||||
|
||||
class _FailingSummarizer:
|
||||
@@ -328,17 +328,17 @@ class _EmptySummarizer:
|
||||
options: dict[str, Any] | None = None,
|
||||
**kwargs: Any,
|
||||
) -> ChatResponse:
|
||||
return ChatResponse(messages=[Message(role="assistant", text=" ")])
|
||||
return ChatResponse(messages=[Message(role="assistant", contents=[" "])])
|
||||
|
||||
|
||||
async def test_summarization_strategy_adds_bidirectional_trace_links() -> None:
|
||||
messages = [
|
||||
Message(role="user", text="u1"),
|
||||
Message(role="assistant", text="a1"),
|
||||
Message(role="user", text="u2"),
|
||||
Message(role="assistant", text="a2"),
|
||||
Message(role="user", text="u3"),
|
||||
Message(role="assistant", text="a3"),
|
||||
Message(role="user", contents=["u1"]),
|
||||
Message(role="assistant", contents=["a1"]),
|
||||
Message(role="user", contents=["u2"]),
|
||||
Message(role="assistant", contents=["a2"]),
|
||||
Message(role="user", contents=["u3"]),
|
||||
Message(role="assistant", contents=["a3"]),
|
||||
]
|
||||
strategy = SummarizationStrategy(client=_FakeSummarizer(), target_count=2, threshold=0)
|
||||
annotate_message_groups(messages)
|
||||
@@ -366,12 +366,12 @@ async def test_summarization_strategy_returns_false_when_summary_generation_fail
|
||||
caplog: Any,
|
||||
) -> None:
|
||||
messages = [
|
||||
Message(role="user", text="u1"),
|
||||
Message(role="assistant", text="a1"),
|
||||
Message(role="user", text="u2"),
|
||||
Message(role="assistant", text="a2"),
|
||||
Message(role="user", text="u3"),
|
||||
Message(role="assistant", text="a3"),
|
||||
Message(role="user", contents=["u1"]),
|
||||
Message(role="assistant", contents=["a1"]),
|
||||
Message(role="user", contents=["u2"]),
|
||||
Message(role="assistant", contents=["a2"]),
|
||||
Message(role="user", contents=["u3"]),
|
||||
Message(role="assistant", contents=["a3"]),
|
||||
]
|
||||
strategy = SummarizationStrategy(client=_FailingSummarizer(), target_count=2, threshold=0)
|
||||
annotate_message_groups(messages)
|
||||
@@ -388,12 +388,12 @@ async def test_summarization_strategy_returns_false_when_summary_is_empty(
|
||||
caplog: Any,
|
||||
) -> None:
|
||||
messages = [
|
||||
Message(role="user", text="u1"),
|
||||
Message(role="assistant", text="a1"),
|
||||
Message(role="user", text="u2"),
|
||||
Message(role="assistant", text="a2"),
|
||||
Message(role="user", text="u3"),
|
||||
Message(role="assistant", text="a3"),
|
||||
Message(role="user", contents=["u1"]),
|
||||
Message(role="assistant", contents=["a1"]),
|
||||
Message(role="user", contents=["u2"]),
|
||||
Message(role="assistant", contents=["a2"]),
|
||||
Message(role="user", contents=["u3"]),
|
||||
Message(role="assistant", contents=["a3"]),
|
||||
]
|
||||
strategy = SummarizationStrategy(client=_EmptySummarizer(), target_count=2, threshold=0)
|
||||
annotate_message_groups(messages)
|
||||
@@ -408,9 +408,9 @@ async def test_summarization_strategy_returns_false_when_summary_is_empty(
|
||||
|
||||
async def test_token_budget_composed_strategy_meets_budget_or_falls_back() -> None:
|
||||
messages = [
|
||||
Message(role="system", text="system"),
|
||||
Message(role="user", text="user " * 200),
|
||||
Message(role="assistant", text="assistant " * 200),
|
||||
Message(role="system", contents=["system"]),
|
||||
Message(role="user", contents=["user " * 200]),
|
||||
Message(role="assistant", contents=["assistant " * 200]),
|
||||
]
|
||||
strategy = TokenBudgetComposedStrategy(
|
||||
token_budget=20,
|
||||
@@ -445,9 +445,9 @@ class _ExcludeOldestNonSystem:
|
||||
|
||||
async def test_apply_compaction_projects_included_messages_only() -> None:
|
||||
messages = [
|
||||
Message(role="system", text="sys"),
|
||||
Message(role="user", text="hello"),
|
||||
Message(role="assistant", text="world"),
|
||||
Message(role="system", contents=["sys"]),
|
||||
Message(role="user", contents=["hello"]),
|
||||
Message(role="assistant", contents=["world"]),
|
||||
]
|
||||
|
||||
projected = await apply_compaction(messages, strategy=_ExcludeOldestNonSystem())
|
||||
@@ -462,12 +462,12 @@ async def test_apply_compaction_projects_included_messages_only() -> None:
|
||||
async def test_tool_result_compaction_collapses_old_groups_into_summary() -> None:
|
||||
"""Old tool-call groups are collapsed into summary messages, newest kept."""
|
||||
messages = [
|
||||
Message(role="user", text="u"),
|
||||
Message(role="user", contents=["u"]),
|
||||
_assistant_function_call("call-1"),
|
||||
_tool_result("call-1", "r1"),
|
||||
_assistant_function_call("call-2"),
|
||||
_tool_result("call-2", "r2"),
|
||||
Message(role="assistant", text="done"),
|
||||
Message(role="assistant", contents=["done"]),
|
||||
]
|
||||
strategy = ToolResultCompactionStrategy(keep_last_tool_call_groups=1)
|
||||
annotate_message_groups(messages)
|
||||
@@ -486,12 +486,12 @@ async def test_tool_result_compaction_collapses_old_groups_into_summary() -> Non
|
||||
async def test_tool_result_compaction_zero_collapses_all() -> None:
|
||||
"""With keep=0, all tool-call groups are collapsed into summaries."""
|
||||
messages = [
|
||||
Message(role="user", text="u"),
|
||||
Message(role="user", contents=["u"]),
|
||||
_assistant_function_call("call-1"),
|
||||
_tool_result("call-1", "r1"),
|
||||
_assistant_function_call("call-2"),
|
||||
_tool_result("call-2", "r2"),
|
||||
Message(role="assistant", text="done"),
|
||||
Message(role="assistant", contents=["done"]),
|
||||
]
|
||||
strategy = ToolResultCompactionStrategy(keep_last_tool_call_groups=0)
|
||||
annotate_message_groups(messages)
|
||||
@@ -508,7 +508,7 @@ async def test_tool_result_compaction_zero_collapses_all() -> None:
|
||||
async def test_tool_result_compaction_no_change_when_within_limit() -> None:
|
||||
"""No compaction when tool groups count does not exceed keep limit."""
|
||||
messages = [
|
||||
Message(role="user", text="u"),
|
||||
Message(role="user", contents=["u"]),
|
||||
_assistant_function_call("call-1"),
|
||||
_tool_result("call-1", "r1"),
|
||||
]
|
||||
@@ -532,7 +532,7 @@ def test_tool_result_compaction_rejects_negative() -> None:
|
||||
async def test_tool_result_compaction_preserves_tool_results_in_summary() -> None:
|
||||
"""Summary text should include the tool results from the collapsed group."""
|
||||
messages = [
|
||||
Message(role="user", text="u"),
|
||||
Message(role="user", contents=["u"]),
|
||||
Message(
|
||||
role="assistant",
|
||||
contents=[
|
||||
@@ -542,7 +542,7 @@ async def test_tool_result_compaction_preserves_tool_results_in_summary() -> Non
|
||||
),
|
||||
_tool_result("c1", "sunny"),
|
||||
_tool_result("c2", "found 3 docs"),
|
||||
Message(role="assistant", text="done"),
|
||||
Message(role="assistant", contents=["done"]),
|
||||
]
|
||||
strategy = ToolResultCompactionStrategy(keep_last_tool_call_groups=0)
|
||||
annotate_message_groups(messages)
|
||||
@@ -559,10 +559,10 @@ async def test_tool_result_compaction_preserves_tool_results_in_summary() -> Non
|
||||
async def test_tool_result_compaction_bidirectional_tracing() -> None:
|
||||
"""Summary and originals should link to each other like SummarizationStrategy does."""
|
||||
messages = [
|
||||
Message(role="user", text="u"),
|
||||
Message(role="user", contents=["u"]),
|
||||
_assistant_function_call("call-1"),
|
||||
_tool_result("call-1", "r1"),
|
||||
Message(role="assistant", text="done"),
|
||||
Message(role="assistant", contents=["done"]),
|
||||
]
|
||||
strategy = ToolResultCompactionStrategy(keep_last_tool_call_groups=0)
|
||||
annotate_message_groups(messages)
|
||||
@@ -594,10 +594,10 @@ async def test_tool_result_compaction_bidirectional_tracing() -> None:
|
||||
async def test_tool_result_compaction_summary_has_full_annotations() -> None:
|
||||
"""Summary messages inserted by ToolResultCompactionStrategy must have all compaction annotations."""
|
||||
messages = [
|
||||
Message(role="user", text="u"),
|
||||
Message(role="user", contents=["u"]),
|
||||
_assistant_function_call("c1"),
|
||||
_tool_result("c1", "r1"),
|
||||
Message(role="assistant", text="done"),
|
||||
Message(role="assistant", contents=["done"]),
|
||||
]
|
||||
strategy = ToolResultCompactionStrategy(keep_last_tool_call_groups=0)
|
||||
annotate_message_groups(messages)
|
||||
@@ -617,12 +617,12 @@ async def test_tool_result_compaction_summary_has_full_annotations() -> None:
|
||||
async def test_summarization_strategy_summary_has_full_annotations() -> None:
|
||||
"""Summary messages inserted by SummarizationStrategy must have all compaction annotations."""
|
||||
messages = [
|
||||
Message(role="user", text="u1"),
|
||||
Message(role="assistant", text="a1"),
|
||||
Message(role="user", text="u2"),
|
||||
Message(role="assistant", text="a2"),
|
||||
Message(role="user", text="u3"),
|
||||
Message(role="assistant", text="a3"),
|
||||
Message(role="user", contents=["u1"]),
|
||||
Message(role="assistant", contents=["a1"]),
|
||||
Message(role="user", contents=["u2"]),
|
||||
Message(role="assistant", contents=["a2"]),
|
||||
Message(role="user", contents=["u3"]),
|
||||
Message(role="assistant", contents=["a3"]),
|
||||
]
|
||||
strategy = SummarizationStrategy(client=_FakeSummarizer(), target_count=2, threshold=0)
|
||||
annotate_message_groups(messages)
|
||||
@@ -647,14 +647,14 @@ async def test_tool_result_compaction_multiple_groups_combined() -> None:
|
||||
separate summary, group 3 stays verbatim.
|
||||
"""
|
||||
messages = [
|
||||
Message(role="user", text="Compare weather in London, Paris, and Tokyo"),
|
||||
Message(role="user", contents=["Compare weather in London, Paris, and Tokyo"]),
|
||||
# Group 1: get_weather for London
|
||||
Message(
|
||||
role="assistant",
|
||||
contents=[Content.from_function_call(call_id="c1", name="get_weather", arguments='{"city":"London"}')],
|
||||
),
|
||||
_tool_result("c1", '{"temp":12,"condition":"cloudy","wind":"NW 15km/h"}'),
|
||||
Message(role="assistant", text="London is cloudy at 12°C."),
|
||||
Message(role="assistant", contents=["London is cloudy at 12°C."]),
|
||||
# Group 2: get_weather for Paris + search_hotels
|
||||
Message(
|
||||
role="assistant",
|
||||
@@ -665,14 +665,14 @@ async def test_tool_result_compaction_multiple_groups_combined() -> None:
|
||||
),
|
||||
_tool_result("c2", '{"temp":18,"condition":"sunny"}'),
|
||||
_tool_result("c3", "Grand Hotel (€120), Le Petit (€85)"),
|
||||
Message(role="assistant", text="Paris is sunny at 18°C. Found 2 hotels."),
|
||||
Message(role="assistant", contents=["Paris is sunny at 18°C. Found 2 hotels."]),
|
||||
# Group 3: get_weather for Tokyo (most recent — should be kept)
|
||||
Message(
|
||||
role="assistant",
|
||||
contents=[Content.from_function_call(call_id="c4", name="get_weather", arguments='{"city":"Tokyo"}')],
|
||||
),
|
||||
_tool_result("c4", '{"temp":22,"condition":"rainy"}'),
|
||||
Message(role="assistant", text="Tokyo is rainy at 22°C."),
|
||||
Message(role="assistant", contents=["Tokyo is rainy at 22°C."]),
|
||||
]
|
||||
strategy = ToolResultCompactionStrategy(keep_last_tool_call_groups=1)
|
||||
annotate_message_groups(messages)
|
||||
@@ -758,13 +758,13 @@ async def test_compaction_provider_compacts_existing_context_messages() -> None:
|
||||
|
||||
context = _MockSessionContext()
|
||||
context.context_messages["history"] = [
|
||||
Message(role="system", text="sys"),
|
||||
Message(role="user", text="u1"),
|
||||
Message(role="assistant", text="a1"),
|
||||
Message(role="user", text="u2"),
|
||||
Message(role="assistant", text="a2"),
|
||||
Message(role="user", text="u3"),
|
||||
Message(role="assistant", text="a3"),
|
||||
Message(role="system", contents=["sys"]),
|
||||
Message(role="user", contents=["u1"]),
|
||||
Message(role="assistant", contents=["a1"]),
|
||||
Message(role="user", contents=["u2"]),
|
||||
Message(role="assistant", contents=["a2"]),
|
||||
Message(role="user", contents=["u3"]),
|
||||
Message(role="assistant", contents=["a3"]),
|
||||
]
|
||||
|
||||
await provider.before_run(agent=None, session=None, context=context, state={})
|
||||
@@ -796,13 +796,13 @@ async def test_compaction_provider_preserves_messages_from_multiple_sources() ->
|
||||
|
||||
context = _MockSessionContext()
|
||||
context.context_messages["history"] = [
|
||||
Message(role="system", text="sys"),
|
||||
Message(role="user", text="old_user"),
|
||||
Message(role="assistant", text="old_assistant"),
|
||||
Message(role="system", contents=["sys"]),
|
||||
Message(role="user", contents=["old_user"]),
|
||||
Message(role="assistant", contents=["old_assistant"]),
|
||||
]
|
||||
context.context_messages["rag"] = [
|
||||
Message(role="user", text="recent_rag_context"),
|
||||
Message(role="assistant", text="recent_rag_answer"),
|
||||
Message(role="user", contents=["recent_rag_context"]),
|
||||
Message(role="assistant", contents=["recent_rag_answer"]),
|
||||
]
|
||||
|
||||
await provider.before_run(agent=None, session=None, context=context, state={})
|
||||
@@ -829,11 +829,11 @@ async def test_compaction_provider_after_run_compacts_stored_history() -> None:
|
||||
session = _MockSession()
|
||||
session.state["in_memory_history"] = {
|
||||
"messages": [
|
||||
Message(role="user", text="old question"),
|
||||
Message(role="assistant", text="old answer"),
|
||||
Message(role="user", contents=["old question"]),
|
||||
Message(role="assistant", contents=["old answer"]),
|
||||
_assistant_function_call("c1"),
|
||||
_tool_result("c1", "result"),
|
||||
Message(role="assistant", text="final answer"),
|
||||
Message(role="assistant", contents=["final answer"]),
|
||||
]
|
||||
}
|
||||
|
||||
@@ -873,11 +873,11 @@ async def test_compaction_provider_both_strategies() -> None:
|
||||
# before_run: compact loaded context
|
||||
context = _MockSessionContext()
|
||||
context.context_messages["history"] = [
|
||||
Message(role="system", text="sys"),
|
||||
Message(role="user", text="u1"),
|
||||
Message(role="assistant", text="a1"),
|
||||
Message(role="user", text="u2"),
|
||||
Message(role="assistant", text="a2"),
|
||||
Message(role="system", contents=["sys"]),
|
||||
Message(role="user", contents=["u1"]),
|
||||
Message(role="assistant", contents=["a1"]),
|
||||
Message(role="user", contents=["u2"]),
|
||||
Message(role="assistant", contents=["a2"]),
|
||||
]
|
||||
await provider.before_run(agent=None, session=None, context=context, state={})
|
||||
assert len(context.get_messages()) == 3
|
||||
@@ -886,10 +886,10 @@ async def test_compaction_provider_both_strategies() -> None:
|
||||
session = _MockSession()
|
||||
session.state["history"] = {
|
||||
"messages": [
|
||||
Message(role="user", text="q"),
|
||||
Message(role="user", contents=["q"]),
|
||||
_assistant_function_call("c1"),
|
||||
_tool_result("c1", "ok"),
|
||||
Message(role="assistant", text="done"),
|
||||
Message(role="assistant", contents=["done"]),
|
||||
]
|
||||
}
|
||||
await provider.after_run(agent=None, session=session, context=_MockSessionContext(), state={})
|
||||
@@ -904,8 +904,8 @@ async def test_compaction_provider_none_strategies_are_noop() -> None:
|
||||
|
||||
context = _MockSessionContext()
|
||||
context.context_messages["history"] = [
|
||||
Message(role="user", text="hello"),
|
||||
Message(role="assistant", text="hi"),
|
||||
Message(role="user", contents=["hello"]),
|
||||
Message(role="assistant", contents=["hi"]),
|
||||
]
|
||||
|
||||
await provider.before_run(agent=None, session=None, context=context, state={})
|
||||
@@ -924,10 +924,10 @@ async def test_in_memory_history_provider_skip_excluded() -> None:
|
||||
provider = _InMemoryHistoryProvider(skip_excluded=True)
|
||||
state: dict[str, Any] = {
|
||||
"messages": [
|
||||
Message(role="user", text="u1"),
|
||||
Message(role="assistant", text="a1", additional_properties={EXCLUDED_KEY: True}),
|
||||
Message(role="user", text="u2"),
|
||||
Message(role="assistant", text="a2"),
|
||||
Message(role="user", contents=["u1"]),
|
||||
Message(role="assistant", contents=["a1"], additional_properties={EXCLUDED_KEY: True}),
|
||||
Message(role="user", contents=["u2"]),
|
||||
Message(role="assistant", contents=["a2"]),
|
||||
]
|
||||
}
|
||||
|
||||
@@ -944,9 +944,9 @@ async def test_in_memory_history_provider_default_loads_all() -> None:
|
||||
provider = _InMemoryHistoryProvider()
|
||||
state: dict[str, Any] = {
|
||||
"messages": [
|
||||
Message(role="user", text="u1"),
|
||||
Message(role="assistant", text="a1", additional_properties={EXCLUDED_KEY: True}),
|
||||
Message(role="user", text="u2"),
|
||||
Message(role="user", contents=["u1"]),
|
||||
Message(role="assistant", contents=["a1"], additional_properties={EXCLUDED_KEY: True}),
|
||||
Message(role="user", contents=["u2"]),
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -55,10 +55,10 @@ async def test_base_client_with_function_calling(chat_client_base: SupportsChatG
|
||||
],
|
||||
)
|
||||
),
|
||||
ChatResponse(messages=Message(role="assistant", text="done")),
|
||||
ChatResponse(messages=Message(role="assistant", contents=["done"])),
|
||||
]
|
||||
response = await chat_client_base.get_response(
|
||||
[Message(role="user", text="hello")], options={"tool_choice": "auto", "tools": [ai_func]}
|
||||
[Message(role="user", contents=["hello"])], options={"tool_choice": "auto", "tools": [ai_func]}
|
||||
)
|
||||
assert exec_counter == 1
|
||||
assert len(response.messages) == 3
|
||||
@@ -93,7 +93,7 @@ async def test_base_client_with_function_calling_string_input(chat_client_base:
|
||||
],
|
||||
)
|
||||
),
|
||||
ChatResponse(messages=Message(role="assistant", text="done")),
|
||||
ChatResponse(messages=Message(role="assistant", contents=["done"])),
|
||||
]
|
||||
|
||||
response = await chat_client_base.get_response("hello", options={"tool_choice": "auto", "tools": [ai_func]})
|
||||
@@ -132,10 +132,10 @@ async def test_base_client_with_function_calling_resets(chat_client_base: Suppor
|
||||
],
|
||||
)
|
||||
),
|
||||
ChatResponse(messages=Message(role="assistant", text="done")),
|
||||
ChatResponse(messages=Message(role="assistant", contents=["done"])),
|
||||
]
|
||||
response = await chat_client_base.get_response(
|
||||
[Message(role="user", text="hello")], options={"tool_choice": "auto", "tools": [ai_func]}
|
||||
[Message(role="user", contents=["hello"])], options={"tool_choice": "auto", "tools": [ai_func]}
|
||||
)
|
||||
assert exec_counter == 2
|
||||
assert len(response.messages) == 5
|
||||
@@ -193,11 +193,11 @@ async def test_function_loop_applies_compaction_projection_each_model_call(chat_
|
||||
],
|
||||
)
|
||||
),
|
||||
ChatResponse(messages=Message(role="assistant", text="done")),
|
||||
ChatResponse(messages=Message(role="assistant", contents=["done"])),
|
||||
]
|
||||
|
||||
await chat_client_base.get_response(
|
||||
[Message(role="user", text="hello")], options={"tool_choice": "auto", "tools": [ai_func]}
|
||||
[Message(role="user", contents=["hello"])], options={"tool_choice": "auto", "tools": [ai_func]}
|
||||
)
|
||||
|
||||
assert len(captured_roles) >= 2
|
||||
@@ -256,11 +256,11 @@ async def test_function_loop_token_budget_strategy_caps_tokens_each_iteration(
|
||||
],
|
||||
)
|
||||
),
|
||||
ChatResponse(messages=Message(role="assistant", text="done")),
|
||||
ChatResponse(messages=Message(role="assistant", contents=["done"])),
|
||||
]
|
||||
|
||||
response = await chat_client_base.get_response(
|
||||
[Message(role="user", text="hello " * 160)],
|
||||
[Message(role="user", contents=["hello " * 160])],
|
||||
options={"tool_choice": "auto", "tools": [ai_func]},
|
||||
)
|
||||
|
||||
@@ -349,13 +349,13 @@ async def test_base_client_executes_function_calls_across_multiple_response_mess
|
||||
conversation_id="conv_after_first_call",
|
||||
),
|
||||
ChatResponse(
|
||||
messages=Message(role="assistant", text="done"),
|
||||
messages=Message(role="assistant", contents=["done"]),
|
||||
conversation_id="conv_after_second_call",
|
||||
),
|
||||
]
|
||||
|
||||
response = await chat_client_base.get_response(
|
||||
[Message(role="user", text="hello")],
|
||||
[Message(role="user", contents=["hello"])],
|
||||
options={"tool_choice": "auto", "tools": [ai_func], "conversation_id": "conv_initial"},
|
||||
)
|
||||
|
||||
@@ -392,7 +392,7 @@ async def test_function_invocation_inside_aiohttp_server(chat_client_base: Suppo
|
||||
],
|
||||
)
|
||||
),
|
||||
ChatResponse(messages=Message(role="assistant", text="done")),
|
||||
ChatResponse(messages=Message(role="assistant", contents=["done"])),
|
||||
]
|
||||
|
||||
agent = Agent(client=chat_client_base, tools=[ai_func])
|
||||
@@ -449,7 +449,7 @@ async def test_function_invocation_in_threaded_aiohttp_app(chat_client_base: Sup
|
||||
],
|
||||
)
|
||||
),
|
||||
ChatResponse(messages=Message(role="assistant", text="done")),
|
||||
ChatResponse(messages=Message(role="assistant", contents=["done"])),
|
||||
]
|
||||
|
||||
agent = Agent(client=chat_client_base, tools=[ai_func])
|
||||
@@ -569,7 +569,7 @@ async def test_function_invocation_scenarios(
|
||||
|
||||
# Single function call content
|
||||
func_call = Content.from_function_call(call_id="1", name=function_name, arguments='{"arg1": "value1"}')
|
||||
completion = Message(role="assistant", text="done")
|
||||
completion = Message(role="assistant", contents=["done"])
|
||||
|
||||
chat_client_base.run_responses = [ChatResponse(messages=Message(role="assistant", contents=[func_call]))] + (
|
||||
[] if approval_required else [ChatResponse(messages=completion)]
|
||||
@@ -618,12 +618,12 @@ async def test_function_invocation_scenarios(
|
||||
options["conversation_id"] = conversation_id
|
||||
|
||||
if not streaming:
|
||||
response = await chat_client_base.get_response([Message(role="user", text="hello")], options=options)
|
||||
response = await chat_client_base.get_response([Message(role="user", contents=["hello"])], options=options)
|
||||
messages = response.messages
|
||||
else:
|
||||
updates = []
|
||||
async for update in chat_client_base.get_response(
|
||||
[Message(role="user", text="hello")], options=options, stream=True
|
||||
[Message(role="user", contents=["hello"])], options=options, stream=True
|
||||
):
|
||||
updates.append(update)
|
||||
messages = updates
|
||||
@@ -729,7 +729,7 @@ async def test_rejected_approval(chat_client_base: SupportsChatGetResponse):
|
||||
],
|
||||
)
|
||||
),
|
||||
ChatResponse(messages=Message(role="assistant", text="done")),
|
||||
ChatResponse(messages=Message(role="assistant", contents=["done"])),
|
||||
]
|
||||
|
||||
# Get the response with approval requests
|
||||
@@ -850,7 +850,7 @@ async def test_persisted_approval_messages_replay_correctly(chat_client_base: Su
|
||||
],
|
||||
)
|
||||
),
|
||||
ChatResponse(messages=Message(role="assistant", text="done")),
|
||||
ChatResponse(messages=Message(role="assistant", contents=["done"])),
|
||||
]
|
||||
|
||||
# Get approval request
|
||||
@@ -860,7 +860,7 @@ async def test_persisted_approval_messages_replay_correctly(chat_client_base: Su
|
||||
|
||||
# Store messages (like a thread would)
|
||||
persisted_messages = [
|
||||
Message(role="user", text="hello"),
|
||||
Message(role="user", contents=["hello"]),
|
||||
*response1.messages,
|
||||
]
|
||||
|
||||
@@ -899,7 +899,7 @@ async def test_no_duplicate_function_calls_after_approval_processing(chat_client
|
||||
],
|
||||
)
|
||||
),
|
||||
ChatResponse(messages=Message(role="assistant", text="done")),
|
||||
ChatResponse(messages=Message(role="assistant", contents=["done"])),
|
||||
]
|
||||
|
||||
response1 = await chat_client_base.get_response(
|
||||
@@ -943,7 +943,7 @@ async def test_rejection_result_uses_function_call_id(chat_client_base: Supports
|
||||
],
|
||||
)
|
||||
),
|
||||
ChatResponse(messages=Message(role="assistant", text="done")),
|
||||
ChatResponse(messages=Message(role="assistant", contents=["done"])),
|
||||
]
|
||||
|
||||
response1 = await chat_client_base.get_response(
|
||||
@@ -1000,14 +1000,14 @@ async def test_max_iterations_limit(chat_client_base: SupportsChatGetResponse):
|
||||
)
|
||||
),
|
||||
# Failsafe response when tool_choice is set to "none"
|
||||
ChatResponse(messages=Message(role="assistant", text="giving up on tools")),
|
||||
ChatResponse(messages=Message(role="assistant", contents=["giving up on tools"])),
|
||||
]
|
||||
|
||||
# Set max_iterations to 1 in additional_properties
|
||||
chat_client_base.function_invocation_configuration["max_iterations"] = 1
|
||||
|
||||
response = await chat_client_base.get_response(
|
||||
[Message(role="user", text="hello")], options={"tool_choice": "auto", "tools": [ai_func]}
|
||||
[Message(role="user", contents=["hello"])], options={"tool_choice": "auto", "tools": [ai_func]}
|
||||
)
|
||||
|
||||
# With max_iterations=1, we should:
|
||||
@@ -1061,7 +1061,7 @@ async def test_max_iterations_no_orphaned_function_calls(chat_client_base: Suppo
|
||||
chat_client_base.function_invocation_configuration["max_iterations"] = 2
|
||||
|
||||
response = await chat_client_base.get_response(
|
||||
[Message(role="user", text="hello")],
|
||||
[Message(role="user", contents=["hello"])],
|
||||
options={"tool_choice": "auto", "tools": [ai_func]},
|
||||
)
|
||||
|
||||
@@ -1111,13 +1111,13 @@ async def test_max_iterations_makes_final_toolchoice_none_call(chat_client_base:
|
||||
)
|
||||
),
|
||||
# This response should be reached via failsafe (tool_choice="none")
|
||||
ChatResponse(messages=Message(role="assistant", text="Final answer after giving up on tools.")),
|
||||
ChatResponse(messages=Message(role="assistant", contents=["Final answer after giving up on tools."])),
|
||||
]
|
||||
|
||||
chat_client_base.function_invocation_configuration["max_iterations"] = 1
|
||||
|
||||
response = await chat_client_base.get_response(
|
||||
[Message(role="user", text="hello")],
|
||||
[Message(role="user", contents=["hello"])],
|
||||
options={"tool_choice": "auto", "tools": [ai_func]},
|
||||
)
|
||||
|
||||
@@ -1170,13 +1170,13 @@ async def test_max_iterations_preserves_all_fcc_messages(chat_client_base: Suppo
|
||||
],
|
||||
)
|
||||
),
|
||||
ChatResponse(messages=Message(role="assistant", text="Done")),
|
||||
ChatResponse(messages=Message(role="assistant", contents=["Done"])),
|
||||
]
|
||||
|
||||
chat_client_base.function_invocation_configuration["max_iterations"] = 2
|
||||
|
||||
response = await chat_client_base.get_response(
|
||||
[Message(role="user", text="hello")],
|
||||
[Message(role="user", contents=["hello"])],
|
||||
options={"tool_choice": "auto", "tools": [ai_func]},
|
||||
)
|
||||
|
||||
@@ -1301,14 +1301,14 @@ async def test_max_function_calls_limits_parallel_invocations(chat_client_base:
|
||||
)
|
||||
),
|
||||
# Final response after tool_choice="none" is forced
|
||||
ChatResponse(messages=Message(role="assistant", text="done")),
|
||||
ChatResponse(messages=Message(role="assistant", contents=["done"])),
|
||||
]
|
||||
|
||||
# Allow many iterations but cap total function calls at 5
|
||||
chat_client_base.function_invocation_configuration["max_function_calls"] = 5
|
||||
|
||||
response = await chat_client_base.get_response(
|
||||
[Message(role="user", text="search")], options={"tool_choice": "auto", "tools": [search_func]}
|
||||
[Message(role="user", contents=["search"])], options={"tool_choice": "auto", "tools": [search_func]}
|
||||
)
|
||||
|
||||
# First iteration executes 3 calls (total=3, under limit).
|
||||
@@ -1355,13 +1355,13 @@ async def test_max_function_calls_single_calls_per_iteration(chat_client_base: S
|
||||
)
|
||||
),
|
||||
# After limit is reached
|
||||
ChatResponse(messages=Message(role="assistant", text="all done")),
|
||||
ChatResponse(messages=Message(role="assistant", contents=["all done"])),
|
||||
]
|
||||
|
||||
chat_client_base.function_invocation_configuration["max_function_calls"] = 2
|
||||
|
||||
response = await chat_client_base.get_response(
|
||||
[Message(role="user", text="look up keys")], options={"tool_choice": "auto", "tools": [lookup_func]}
|
||||
[Message(role="user", contents=["look up keys"])], options={"tool_choice": "auto", "tools": [lookup_func]}
|
||||
)
|
||||
|
||||
# 2 single calls executed, then limit reached, tool_choice="none" forced
|
||||
@@ -1390,13 +1390,13 @@ async def test_max_function_calls_none_means_unlimited(chat_client_base: Support
|
||||
)
|
||||
)
|
||||
for i in range(5)
|
||||
] + [ChatResponse(messages=Message(role="assistant", text="finished"))]
|
||||
] + [ChatResponse(messages=Message(role="assistant", contents=["finished"]))]
|
||||
|
||||
# Explicitly set to None (default) — should not limit
|
||||
chat_client_base.function_invocation_configuration["max_function_calls"] = None
|
||||
|
||||
response = await chat_client_base.get_response(
|
||||
[Message(role="user", text="do things")], options={"tool_choice": "auto", "tools": [do_thing_func]}
|
||||
[Message(role="user", contents=["do things"])], options={"tool_choice": "auto", "tools": [do_thing_func]}
|
||||
)
|
||||
|
||||
assert exec_counter == 5
|
||||
@@ -1414,14 +1414,14 @@ async def test_function_invocation_config_enabled_false(chat_client_base: Suppor
|
||||
return f"Processed {arg1}"
|
||||
|
||||
chat_client_base.run_responses = [
|
||||
ChatResponse(messages=Message(role="assistant", text="response without function calling")),
|
||||
ChatResponse(messages=Message(role="assistant", contents=["response without function calling"])),
|
||||
]
|
||||
|
||||
# Disable function invocation
|
||||
chat_client_base.function_invocation_configuration["enabled"] = False
|
||||
|
||||
response = await chat_client_base.get_response(
|
||||
[Message(role="user", text="hello")], options={"tool_choice": "auto", "tools": [ai_func]}
|
||||
[Message(role="user", contents=["hello"])], options={"tool_choice": "auto", "tools": [ai_func]}
|
||||
)
|
||||
|
||||
# Function should not be executed - when enabled=False, the loop doesn't run
|
||||
@@ -1447,12 +1447,12 @@ async def test_function_invocation_config_enabled_false_preserves_invocation_kwa
|
||||
|
||||
chat_client_base.chat_middleware = [capture_middleware]
|
||||
chat_client_base.run_responses = [
|
||||
ChatResponse(messages=Message(role="assistant", text="response without function calling")),
|
||||
ChatResponse(messages=Message(role="assistant", contents=["response without function calling"])),
|
||||
]
|
||||
chat_client_base.function_invocation_configuration["enabled"] = False
|
||||
|
||||
await chat_client_base.get_response(
|
||||
[Message(role="user", text="hello")],
|
||||
[Message(role="user", contents=["hello"])],
|
||||
options={"tool_choice": "auto", "tools": [ai_func]},
|
||||
function_invocation_kwargs={"tool_request_id": "tool-123"},
|
||||
)
|
||||
@@ -1502,14 +1502,14 @@ async def test_function_invocation_config_max_consecutive_errors(chat_client_bas
|
||||
],
|
||||
)
|
||||
),
|
||||
ChatResponse(messages=Message(role="assistant", text="final response")),
|
||||
ChatResponse(messages=Message(role="assistant", contents=["final response"])),
|
||||
]
|
||||
|
||||
# Set max_consecutive_errors to 2
|
||||
chat_client_base.function_invocation_configuration["max_consecutive_errors_per_request"] = 2
|
||||
|
||||
response = await chat_client_base.get_response(
|
||||
[Message(role="user", text="hello")], options={"tool_choice": "auto", "tools": [error_func]}
|
||||
[Message(role="user", contents=["hello"])], options={"tool_choice": "auto", "tools": [error_func]}
|
||||
)
|
||||
|
||||
# Should stop after 2 consecutive errors and force a non-tool response
|
||||
@@ -1552,7 +1552,7 @@ async def test_function_invocation_stop_clears_conversation_id_non_stream(chat_c
|
||||
session_stub = type("SessionStub", (), {"service_session_id": "resp_seed"})()
|
||||
|
||||
response = await chat_client_base.get_response(
|
||||
[Message(role="user", text="hello")],
|
||||
[Message(role="user", contents=["hello"])],
|
||||
options={"tool_choice": "auto", "tools": [error_func]},
|
||||
client_kwargs={"session": session_stub},
|
||||
)
|
||||
@@ -1579,14 +1579,14 @@ async def test_function_invocation_config_terminate_on_unknown_calls_false(chat_
|
||||
],
|
||||
)
|
||||
),
|
||||
ChatResponse(messages=Message(role="assistant", text="done")),
|
||||
ChatResponse(messages=Message(role="assistant", contents=["done"])),
|
||||
]
|
||||
|
||||
# Set terminate_on_unknown_calls to False (default)
|
||||
chat_client_base.function_invocation_configuration["terminate_on_unknown_calls"] = False
|
||||
|
||||
response = await chat_client_base.get_response(
|
||||
[Message(role="user", text="hello")], options={"tool_choice": "auto", "tools": [known_func]}
|
||||
[Message(role="user", contents=["hello"])], options={"tool_choice": "auto", "tools": [known_func]}
|
||||
)
|
||||
|
||||
# Should have a result message indicating the tool wasn't found
|
||||
@@ -1624,7 +1624,7 @@ async def test_function_invocation_config_terminate_on_unknown_calls_true(chat_c
|
||||
# Should raise an exception when encountering an unknown function
|
||||
with pytest.raises(KeyError, match='Error: Requested function "unknown_function" not found'):
|
||||
await chat_client_base.get_response(
|
||||
[Message(role="user", text="hello")], options={"tool_choice": "auto", "tools": [known_func]}
|
||||
[Message(role="user", contents=["hello"])], options={"tool_choice": "auto", "tools": [known_func]}
|
||||
)
|
||||
|
||||
assert exec_counter == 0
|
||||
@@ -1656,7 +1656,7 @@ async def test_function_invocation_config_additional_tools(chat_client_base: Sup
|
||||
],
|
||||
)
|
||||
),
|
||||
ChatResponse(messages=Message(role="assistant", text="done")),
|
||||
ChatResponse(messages=Message(role="assistant", contents=["done"])),
|
||||
]
|
||||
|
||||
# Add hidden_func to additional_tools
|
||||
@@ -1664,7 +1664,7 @@ async def test_function_invocation_config_additional_tools(chat_client_base: Sup
|
||||
|
||||
# Only pass visible_func in the tools parameter
|
||||
response = await chat_client_base.get_response(
|
||||
[Message(role="user", text="hello")], options={"tool_choice": "auto", "tools": [visible_func]}
|
||||
[Message(role="user", contents=["hello"])], options={"tool_choice": "auto", "tools": [visible_func]}
|
||||
)
|
||||
|
||||
# Additional tools are treated as declaration_only, so not executed
|
||||
@@ -1697,14 +1697,14 @@ async def test_function_invocation_config_include_detailed_errors_false(chat_cli
|
||||
],
|
||||
)
|
||||
),
|
||||
ChatResponse(messages=Message(role="assistant", text="done")),
|
||||
ChatResponse(messages=Message(role="assistant", contents=["done"])),
|
||||
]
|
||||
|
||||
# Set include_detailed_errors to False (default)
|
||||
chat_client_base.function_invocation_configuration["include_detailed_errors"] = False
|
||||
|
||||
response = await chat_client_base.get_response(
|
||||
[Message(role="user", text="hello")], options={"tool_choice": "auto", "tools": [error_func]}
|
||||
[Message(role="user", contents=["hello"])], options={"tool_choice": "auto", "tools": [error_func]}
|
||||
)
|
||||
|
||||
# Should have a generic error message
|
||||
@@ -1733,14 +1733,14 @@ async def test_function_invocation_config_include_detailed_errors_true(chat_clie
|
||||
],
|
||||
)
|
||||
),
|
||||
ChatResponse(messages=Message(role="assistant", text="done")),
|
||||
ChatResponse(messages=Message(role="assistant", contents=["done"])),
|
||||
]
|
||||
|
||||
# Set include_detailed_errors to True
|
||||
chat_client_base.function_invocation_configuration["include_detailed_errors"] = True
|
||||
|
||||
response = await chat_client_base.get_response(
|
||||
[Message(role="user", text="hello")], options={"tool_choice": "auto", "tools": [error_func]}
|
||||
[Message(role="user", contents=["hello"])], options={"tool_choice": "auto", "tools": [error_func]}
|
||||
)
|
||||
|
||||
# Should have detailed error message
|
||||
@@ -1832,14 +1832,14 @@ async def test_argument_validation_error_with_detailed_errors(chat_client_base:
|
||||
],
|
||||
)
|
||||
),
|
||||
ChatResponse(messages=Message(role="assistant", text="done")),
|
||||
ChatResponse(messages=Message(role="assistant", contents=["done"])),
|
||||
]
|
||||
|
||||
# Set include_detailed_errors to True
|
||||
chat_client_base.function_invocation_configuration["include_detailed_errors"] = True
|
||||
|
||||
response = await chat_client_base.get_response(
|
||||
[Message(role="user", text="hello")], options={"tool_choice": "auto", "tools": [typed_func]}
|
||||
[Message(role="user", contents=["hello"])], options={"tool_choice": "auto", "tools": [typed_func]}
|
||||
)
|
||||
|
||||
# Should have detailed validation error
|
||||
@@ -1868,14 +1868,14 @@ async def test_argument_validation_error_without_detailed_errors(chat_client_bas
|
||||
],
|
||||
)
|
||||
),
|
||||
ChatResponse(messages=Message(role="assistant", text="done")),
|
||||
ChatResponse(messages=Message(role="assistant", contents=["done"])),
|
||||
]
|
||||
|
||||
# Set include_detailed_errors to False (default)
|
||||
chat_client_base.function_invocation_configuration["include_detailed_errors"] = False
|
||||
|
||||
response = await chat_client_base.get_response(
|
||||
[Message(role="user", text="hello")], options={"tool_choice": "auto", "tools": [typed_func]}
|
||||
[Message(role="user", contents=["hello"])], options={"tool_choice": "auto", "tools": [typed_func]}
|
||||
)
|
||||
|
||||
# Should have generic validation error
|
||||
@@ -1906,7 +1906,7 @@ async def test_hosted_tool_approval_response(chat_client_base: SupportsChatGetRe
|
||||
)
|
||||
|
||||
chat_client_base.run_responses = [
|
||||
ChatResponse(messages=Message(role="assistant", text="done")),
|
||||
ChatResponse(messages=Message(role="assistant", contents=["done"])),
|
||||
]
|
||||
|
||||
# Send the approval response
|
||||
@@ -1947,13 +1947,13 @@ async def test_hosted_mcp_approval_response_passthrough(chat_client_base: Suppor
|
||||
|
||||
# The second call (after approval) should return a final response
|
||||
chat_client_base.run_responses = [
|
||||
ChatResponse(messages=Message(role="assistant", text="Here are the docs results.")),
|
||||
ChatResponse(messages=Message(role="assistant", contents=["Here are the docs results."])),
|
||||
]
|
||||
|
||||
# Build message list mimicking handle_approvals_without_session:
|
||||
# [original query, assistant with approval_request, user with approval_response]
|
||||
messages = [
|
||||
Message(role="user", text="Search docs for azure storage"),
|
||||
Message(role="user", contents=["Search docs for azure storage"]),
|
||||
Message(role="assistant", contents=[mcp_approval_request]),
|
||||
Message(role="user", contents=[mcp_approval_response]),
|
||||
]
|
||||
@@ -2034,7 +2034,7 @@ async def test_mixed_local_and_hosted_approval_flow(chat_client_base: SupportsCh
|
||||
chat_client_base.run_responses = [
|
||||
ChatResponse(messages=Message(role="assistant", contents=[local_fc])),
|
||||
# After local approval + hosted approval, the final response
|
||||
ChatResponse(messages=Message(role="assistant", text="Done with both tools.")),
|
||||
ChatResponse(messages=Message(role="assistant", contents=["Done with both tools."])),
|
||||
]
|
||||
|
||||
# User approves the local function call
|
||||
@@ -2045,7 +2045,7 @@ async def test_mixed_local_and_hosted_approval_flow(chat_client_base: SupportsCh
|
||||
mcp_approval_response = mcp_approval_request.to_function_approval_response(approved=True)
|
||||
|
||||
messages = [
|
||||
Message(role="user", text="Search docs and run local"),
|
||||
Message(role="user", contents=["Search docs and run local"]),
|
||||
Message(role="assistant", contents=[local_fc, mcp_approval_request]),
|
||||
Message(role="user", contents=[local_approval_response]),
|
||||
Message(role="user", contents=[mcp_approval_response]),
|
||||
@@ -2080,12 +2080,12 @@ async def test_unapproved_tool_execution_raises_exception(chat_client_base: Supp
|
||||
],
|
||||
)
|
||||
),
|
||||
ChatResponse(messages=Message(role="assistant", text="done")),
|
||||
ChatResponse(messages=Message(role="assistant", contents=["done"])),
|
||||
]
|
||||
|
||||
# Get approval request
|
||||
response1 = await chat_client_base.get_response(
|
||||
[Message(role="user", text="hello")], options={"tool_choice": "auto", "tools": [test_func]}
|
||||
[Message(role="user", contents=["hello"])], options={"tool_choice": "auto", "tools": [test_func]}
|
||||
)
|
||||
|
||||
approval_req = [c for c in response1.messages[0].contents if c.type == "function_approval_request"][0]
|
||||
@@ -2137,7 +2137,7 @@ async def test_approved_function_call_with_error_without_detailed_errors(chat_cl
|
||||
contents=[Content.from_function_call(call_id="1", name="error_func", arguments='{"arg1": "value1"}')],
|
||||
)
|
||||
),
|
||||
ChatResponse(messages=Message(role="assistant", text="done")),
|
||||
ChatResponse(messages=Message(role="assistant", contents=["done"])),
|
||||
]
|
||||
|
||||
# Set include_detailed_errors to False (default)
|
||||
@@ -2145,7 +2145,7 @@ async def test_approved_function_call_with_error_without_detailed_errors(chat_cl
|
||||
|
||||
# Get approval request
|
||||
response1 = await chat_client_base.get_response(
|
||||
[Message(role="user", text="hello")], options={"tool_choice": "auto", "tools": [error_func]}
|
||||
[Message(role="user", contents=["hello"])], options={"tool_choice": "auto", "tools": [error_func]}
|
||||
)
|
||||
|
||||
approval_req = [c for c in response1.messages[0].contents if c.type == "function_approval_request"][0]
|
||||
@@ -2202,7 +2202,7 @@ async def test_approved_function_call_with_error_with_detailed_errors(chat_clien
|
||||
contents=[Content.from_function_call(call_id="1", name="error_func", arguments='{"arg1": "value1"}')],
|
||||
)
|
||||
),
|
||||
ChatResponse(messages=Message(role="assistant", text="done")),
|
||||
ChatResponse(messages=Message(role="assistant", contents=["done"])),
|
||||
]
|
||||
|
||||
# Set include_detailed_errors to True
|
||||
@@ -2210,7 +2210,7 @@ async def test_approved_function_call_with_error_with_detailed_errors(chat_clien
|
||||
|
||||
# Get approval request
|
||||
response1 = await chat_client_base.get_response(
|
||||
[Message(role="user", text="hello")], options={"tool_choice": "auto", "tools": [error_func]}
|
||||
[Message(role="user", contents=["hello"])], options={"tool_choice": "auto", "tools": [error_func]}
|
||||
)
|
||||
|
||||
approval_req = [c for c in response1.messages[0].contents if c.type == "function_approval_request"][0]
|
||||
@@ -2267,7 +2267,7 @@ async def test_approved_function_call_with_validation_error(chat_client_base: Su
|
||||
],
|
||||
)
|
||||
),
|
||||
ChatResponse(messages=Message(role="assistant", text="done")),
|
||||
ChatResponse(messages=Message(role="assistant", contents=["done"])),
|
||||
]
|
||||
|
||||
# Set include_detailed_errors to True to see validation details
|
||||
@@ -2275,7 +2275,7 @@ async def test_approved_function_call_with_validation_error(chat_client_base: Su
|
||||
|
||||
# Get approval request
|
||||
response1 = await chat_client_base.get_response(
|
||||
[Message(role="user", text="hello")], options={"tool_choice": "auto", "tools": [typed_func]}
|
||||
[Message(role="user", contents=["hello"])], options={"tool_choice": "auto", "tools": [typed_func]}
|
||||
)
|
||||
|
||||
approval_req = [c for c in response1.messages[0].contents if c.type == "function_approval_request"][0]
|
||||
@@ -2328,12 +2328,12 @@ async def test_approved_function_call_successful_execution(chat_client_base: Sup
|
||||
contents=[Content.from_function_call(call_id="1", name="success_func", arguments='{"arg1": "value1"}')],
|
||||
)
|
||||
),
|
||||
ChatResponse(messages=Message(role="assistant", text="done")),
|
||||
ChatResponse(messages=Message(role="assistant", contents=["done"])),
|
||||
]
|
||||
|
||||
# Get approval request
|
||||
response1 = await chat_client_base.get_response(
|
||||
[Message(role="user", text="hello")], options={"tool_choice": "auto", "tools": [success_func]}
|
||||
[Message(role="user", contents=["hello"])], options={"tool_choice": "auto", "tools": [success_func]}
|
||||
)
|
||||
|
||||
approval_req = [c for c in response1.messages[0].contents if c.type == "function_approval_request"][0]
|
||||
@@ -2391,7 +2391,7 @@ async def test_declaration_only_tool(chat_client_base: SupportsChatGetResponse):
|
||||
],
|
||||
)
|
||||
),
|
||||
ChatResponse(messages=Message(role="assistant", text="done")),
|
||||
ChatResponse(messages=Message(role="assistant", contents=["done"])),
|
||||
]
|
||||
|
||||
response = await chat_client_base.get_response(
|
||||
@@ -2447,11 +2447,11 @@ async def test_multiple_function_calls_parallel_execution(chat_client_base: Supp
|
||||
],
|
||||
)
|
||||
),
|
||||
ChatResponse(messages=Message(role="assistant", text="done")),
|
||||
ChatResponse(messages=Message(role="assistant", contents=["done"])),
|
||||
]
|
||||
|
||||
response = await chat_client_base.get_response(
|
||||
[Message(role="user", text="hello")], options={"tool_choice": "auto", "tools": [func1, func2]}
|
||||
[Message(role="user", contents=["hello"])], options={"tool_choice": "auto", "tools": [func1, func2]}
|
||||
)
|
||||
|
||||
# Both functions should have been executed
|
||||
@@ -2485,12 +2485,12 @@ async def test_callable_function_converted_to_tool(chat_client_base: SupportsCha
|
||||
],
|
||||
)
|
||||
),
|
||||
ChatResponse(messages=Message(role="assistant", text="done")),
|
||||
ChatResponse(messages=Message(role="assistant", contents=["done"])),
|
||||
]
|
||||
|
||||
# Pass plain function (will be auto-converted)
|
||||
response = await chat_client_base.get_response(
|
||||
[Message(role="user", text="hello")], options={"tool_choice": "auto", "tools": [plain_function]}
|
||||
[Message(role="user", contents=["hello"])], options={"tool_choice": "auto", "tools": [plain_function]}
|
||||
)
|
||||
|
||||
# Function should be executed
|
||||
@@ -2518,13 +2518,13 @@ async def test_conversation_id_handling(chat_client_base: SupportsChatGetRespons
|
||||
conversation_id="conv_123", # Simulate service-side thread
|
||||
),
|
||||
ChatResponse(
|
||||
messages=Message(role="assistant", text="done"),
|
||||
messages=Message(role="assistant", contents=["done"]),
|
||||
conversation_id="conv_123",
|
||||
),
|
||||
]
|
||||
|
||||
response = await chat_client_base.get_response(
|
||||
[Message(role="user", text="hello")], options={"tool_choice": "auto", "tools": [test_func]}
|
||||
[Message(role="user", contents=["hello"])], options={"tool_choice": "auto", "tools": [test_func]}
|
||||
)
|
||||
|
||||
# Should have executed the function
|
||||
@@ -2549,11 +2549,11 @@ async def test_function_result_appended_to_existing_assistant_message(chat_clien
|
||||
],
|
||||
)
|
||||
),
|
||||
ChatResponse(messages=Message(role="assistant", text="done")),
|
||||
ChatResponse(messages=Message(role="assistant", contents=["done"])),
|
||||
]
|
||||
|
||||
response = await chat_client_base.get_response(
|
||||
[Message(role="user", text="hello")], options={"tool_choice": "auto", "tools": [test_func]}
|
||||
[Message(role="user", contents=["hello"])], options={"tool_choice": "auto", "tools": [test_func]}
|
||||
)
|
||||
|
||||
# Should have messages with both function call and function result
|
||||
@@ -2596,11 +2596,11 @@ async def test_error_recovery_resets_counter(chat_client_base: SupportsChatGetRe
|
||||
],
|
||||
)
|
||||
),
|
||||
ChatResponse(messages=Message(role="assistant", text="done")),
|
||||
ChatResponse(messages=Message(role="assistant", contents=["done"])),
|
||||
]
|
||||
|
||||
response = await chat_client_base.get_response(
|
||||
[Message(role="user", text="hello")], options={"tool_choice": "auto", "tools": [sometimes_fails]}
|
||||
[Message(role="user", contents=["hello"])], options={"tool_choice": "auto", "tools": [sometimes_fails]}
|
||||
)
|
||||
|
||||
# Should have both an error and a success
|
||||
@@ -2912,7 +2912,7 @@ async def test_streaming_function_invocation_config_terminate_on_unknown_calls_t
|
||||
# Should raise an exception when encountering an unknown function
|
||||
with pytest.raises(KeyError, match='Error: Requested function "unknown_function" not found'):
|
||||
async for _ in chat_client_base.get_response(
|
||||
[Message(role="user", text="hello")], options={"tool_choice": "auto", "tools": [known_func]}
|
||||
[Message(role="user", contents=["hello"])], options={"tool_choice": "auto", "tools": [known_func]}
|
||||
):
|
||||
pass
|
||||
|
||||
@@ -3248,7 +3248,7 @@ async def test_terminate_loop_single_function_call(chat_client_base: SupportsCha
|
||||
],
|
||||
)
|
||||
),
|
||||
ChatResponse(messages=Message(role="assistant", text="done")),
|
||||
ChatResponse(messages=Message(role="assistant", contents=["done"])),
|
||||
]
|
||||
|
||||
response = await chat_client_base.get_response(
|
||||
@@ -3314,7 +3314,7 @@ async def test_terminate_loop_multiple_function_calls_one_terminates(chat_client
|
||||
],
|
||||
)
|
||||
),
|
||||
ChatResponse(messages=Message(role="assistant", text="done")),
|
||||
ChatResponse(messages=Message(role="assistant", contents=["done"])),
|
||||
]
|
||||
|
||||
response = await chat_client_base.get_response(
|
||||
@@ -3444,7 +3444,7 @@ async def test_conversation_id_updated_in_options_between_tool_iterations():
|
||||
async def _get() -> ChatResponse:
|
||||
self.call_count += 1
|
||||
if not self.run_responses:
|
||||
return ChatResponse(messages=Message(role="assistant", text="done"))
|
||||
return ChatResponse(messages=Message(role="assistant", contents=["done"]))
|
||||
return self.run_responses.pop(0)
|
||||
|
||||
return _get()
|
||||
@@ -3491,7 +3491,7 @@ async def test_conversation_id_updated_in_options_between_tool_iterations():
|
||||
conversation_id="conv_after_first_call",
|
||||
),
|
||||
ChatResponse(
|
||||
messages=Message(role="assistant", text="done"),
|
||||
messages=Message(role="assistant", contents=["done"]),
|
||||
conversation_id="conv_after_second_call",
|
||||
),
|
||||
]
|
||||
@@ -3706,7 +3706,7 @@ async def test_user_input_request_propagates_through_as_tool(chat_client_base: S
|
||||
]
|
||||
|
||||
response = await chat_client_base.get_response(
|
||||
[Message(role="user", text="delegate this")],
|
||||
[Message(role="user", contents=["delegate this"])],
|
||||
options={"tool_choice": "auto", "tools": [delegate_tool]},
|
||||
)
|
||||
|
||||
@@ -3755,7 +3755,7 @@ async def test_user_input_request_multiple_contents_propagate(chat_client_base:
|
||||
]
|
||||
|
||||
response = await chat_client_base.get_response(
|
||||
[Message(role="user", text="do something")],
|
||||
[Message(role="user", contents=["do something"])],
|
||||
options={"tool_choice": "auto", "tools": [multi_request]},
|
||||
)
|
||||
|
||||
@@ -3792,11 +3792,11 @@ async def test_user_input_request_empty_contents_returns_fallback(chat_client_ba
|
||||
],
|
||||
)
|
||||
),
|
||||
ChatResponse(messages=Message(role="assistant", text="handled")),
|
||||
ChatResponse(messages=Message(role="assistant", contents=["handled"])),
|
||||
]
|
||||
|
||||
response = await chat_client_base.get_response(
|
||||
[Message(role="user", text="do something")],
|
||||
[Message(role="user", contents=["do something"])],
|
||||
options={"tool_choice": "auto", "tools": [empty_request]},
|
||||
)
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ class TestAgentContext:
|
||||
|
||||
def test_init_with_defaults(self, mock_agent: SupportsAgentRun) -> None:
|
||||
"""Test AgentContext initialization with default values."""
|
||||
messages = [Message(role="user", text="test")]
|
||||
messages = [Message(role="user", contents=["test"])]
|
||||
context = AgentContext(agent=mock_agent, messages=messages)
|
||||
|
||||
assert context.agent is mock_agent
|
||||
@@ -48,7 +48,7 @@ class TestAgentContext:
|
||||
|
||||
def test_init_with_custom_values(self, mock_agent: SupportsAgentRun) -> None:
|
||||
"""Test AgentContext initialization with custom values."""
|
||||
messages = [Message(role="user", text="test")]
|
||||
messages = [Message(role="user", contents=["test"])]
|
||||
metadata = {"key": "value"}
|
||||
context = AgentContext(agent=mock_agent, messages=messages, stream=True, metadata=metadata)
|
||||
|
||||
@@ -61,7 +61,7 @@ class TestAgentContext:
|
||||
"""Test AgentContext initialization with session parameter."""
|
||||
from agent_framework import AgentSession
|
||||
|
||||
messages = [Message(role="user", text="test")]
|
||||
messages = [Message(role="user", contents=["test"])]
|
||||
session = AgentSession()
|
||||
context = AgentContext(agent=mock_agent, messages=messages, session=session)
|
||||
|
||||
@@ -100,7 +100,7 @@ class TestChatContext:
|
||||
|
||||
def test_init_with_defaults(self, mock_chat_client: Any) -> None:
|
||||
"""Test ChatContext initialization with default values."""
|
||||
messages = [Message(role="user", text="test")]
|
||||
messages = [Message(role="user", contents=["test"])]
|
||||
chat_options: dict[str, Any] = {}
|
||||
context = ChatContext(client=mock_chat_client, messages=messages, options=chat_options)
|
||||
|
||||
@@ -113,7 +113,7 @@ class TestChatContext:
|
||||
|
||||
def test_init_with_custom_values(self, mock_chat_client: Any) -> None:
|
||||
"""Test ChatContext initialization with custom values."""
|
||||
messages = [Message(role="user", text="test")]
|
||||
messages = [Message(role="user", contents=["test"])]
|
||||
chat_options: dict[str, Any] = {"temperature": 0.5}
|
||||
metadata = {"key": "value"}
|
||||
|
||||
@@ -167,10 +167,10 @@ class TestAgentMiddlewarePipeline:
|
||||
async def test_execute_no_middleware(self, mock_agent: SupportsAgentRun) -> None:
|
||||
"""Test pipeline execution with no middleware."""
|
||||
pipeline = AgentMiddlewarePipeline()
|
||||
messages = [Message(role="user", text="test")]
|
||||
messages = [Message(role="user", contents=["test"])]
|
||||
context = AgentContext(agent=mock_agent, messages=messages)
|
||||
|
||||
expected_response = AgentResponse(messages=[Message(role="assistant", text="response")])
|
||||
expected_response = AgentResponse(messages=[Message(role="assistant", contents=["response"])])
|
||||
|
||||
async def final_handler(ctx: AgentContext) -> AgentResponse:
|
||||
return expected_response
|
||||
@@ -193,10 +193,10 @@ class TestAgentMiddlewarePipeline:
|
||||
|
||||
middleware = OrderTrackingMiddleware("test")
|
||||
pipeline = AgentMiddlewarePipeline(middleware)
|
||||
messages = [Message(role="user", text="test")]
|
||||
messages = [Message(role="user", contents=["test"])]
|
||||
context = AgentContext(agent=mock_agent, messages=messages)
|
||||
|
||||
expected_response = AgentResponse(messages=[Message(role="assistant", text="response")])
|
||||
expected_response = AgentResponse(messages=[Message(role="assistant", contents=["response"])])
|
||||
|
||||
async def final_handler(ctx: AgentContext) -> AgentResponse:
|
||||
execution_order.append("handler")
|
||||
@@ -209,7 +209,7 @@ class TestAgentMiddlewarePipeline:
|
||||
async def test_execute_stream_no_middleware(self, mock_agent: SupportsAgentRun) -> None:
|
||||
"""Test pipeline streaming execution with no middleware."""
|
||||
pipeline = AgentMiddlewarePipeline()
|
||||
messages = [Message(role="user", text="test")]
|
||||
messages = [Message(role="user", contents=["test"])]
|
||||
context = AgentContext(agent=mock_agent, messages=messages, stream=True)
|
||||
|
||||
async def final_handler(ctx: AgentContext) -> ResponseStream[AgentResponseUpdate, AgentResponse]:
|
||||
@@ -244,7 +244,7 @@ class TestAgentMiddlewarePipeline:
|
||||
|
||||
middleware = StreamOrderTrackingMiddleware("test")
|
||||
pipeline = AgentMiddlewarePipeline(middleware)
|
||||
messages = [Message(role="user", text="test")]
|
||||
messages = [Message(role="user", contents=["test"])]
|
||||
context = AgentContext(agent=mock_agent, messages=messages, stream=True)
|
||||
|
||||
async def final_handler(ctx: AgentContext) -> ResponseStream[AgentResponseUpdate, AgentResponse]:
|
||||
@@ -270,14 +270,14 @@ class TestAgentMiddlewarePipeline:
|
||||
"""Test pipeline execution with termination before next()."""
|
||||
middleware = self.PreNextTerminateMiddleware()
|
||||
pipeline = AgentMiddlewarePipeline(middleware)
|
||||
messages = [Message(role="user", text="test")]
|
||||
messages = [Message(role="user", contents=["test"])]
|
||||
context = AgentContext(agent=mock_agent, messages=messages)
|
||||
execution_order: list[str] = []
|
||||
|
||||
async def final_handler(ctx: AgentContext) -> AgentResponse:
|
||||
# Handler should not be executed when terminated before next()
|
||||
execution_order.append("handler")
|
||||
return AgentResponse(messages=[Message(role="assistant", text="response")])
|
||||
return AgentResponse(messages=[Message(role="assistant", contents=["response"])])
|
||||
|
||||
response = await pipeline.execute(context, final_handler)
|
||||
assert response is None
|
||||
@@ -288,13 +288,13 @@ class TestAgentMiddlewarePipeline:
|
||||
"""Test pipeline execution with termination after next()."""
|
||||
middleware = self.PostNextTerminateMiddleware()
|
||||
pipeline = AgentMiddlewarePipeline(middleware)
|
||||
messages = [Message(role="user", text="test")]
|
||||
messages = [Message(role="user", contents=["test"])]
|
||||
context = AgentContext(agent=mock_agent, messages=messages)
|
||||
execution_order: list[str] = []
|
||||
|
||||
async def final_handler(ctx: AgentContext) -> AgentResponse:
|
||||
execution_order.append("handler")
|
||||
return AgentResponse(messages=[Message(role="assistant", text="response")])
|
||||
return AgentResponse(messages=[Message(role="assistant", contents=["response"])])
|
||||
|
||||
response = await pipeline.execute(context, final_handler)
|
||||
assert response is not None
|
||||
@@ -306,7 +306,7 @@ class TestAgentMiddlewarePipeline:
|
||||
"""Test pipeline streaming execution with termination before next()."""
|
||||
middleware = self.PreNextTerminateMiddleware()
|
||||
pipeline = AgentMiddlewarePipeline(middleware)
|
||||
messages = [Message(role="user", text="test")]
|
||||
messages = [Message(role="user", contents=["test"])]
|
||||
context = AgentContext(agent=mock_agent, messages=messages, stream=True)
|
||||
execution_order: list[str] = []
|
||||
|
||||
@@ -334,7 +334,7 @@ class TestAgentMiddlewarePipeline:
|
||||
"""Test pipeline streaming execution with termination after next()."""
|
||||
middleware = self.PostNextTerminateMiddleware()
|
||||
pipeline = AgentMiddlewarePipeline(middleware)
|
||||
messages = [Message(role="user", text="test")]
|
||||
messages = [Message(role="user", contents=["test"])]
|
||||
context = AgentContext(agent=mock_agent, messages=messages, stream=True)
|
||||
execution_order: list[str] = []
|
||||
|
||||
@@ -371,11 +371,11 @@ class TestAgentMiddlewarePipeline:
|
||||
|
||||
middleware = SessionCapturingMiddleware()
|
||||
pipeline = AgentMiddlewarePipeline(middleware)
|
||||
messages = [Message(role="user", text="test")]
|
||||
messages = [Message(role="user", contents=["test"])]
|
||||
session = AgentSession()
|
||||
context = AgentContext(agent=mock_agent, messages=messages, session=session)
|
||||
|
||||
expected_response = AgentResponse(messages=[Message(role="assistant", text="response")])
|
||||
expected_response = AgentResponse(messages=[Message(role="assistant", contents=["response"])])
|
||||
|
||||
async def final_handler(ctx: AgentContext) -> AgentResponse:
|
||||
return expected_response
|
||||
@@ -396,10 +396,10 @@ class TestAgentMiddlewarePipeline:
|
||||
|
||||
middleware = SessionCapturingMiddleware()
|
||||
pipeline = AgentMiddlewarePipeline(middleware)
|
||||
messages = [Message(role="user", text="test")]
|
||||
messages = [Message(role="user", contents=["test"])]
|
||||
context = AgentContext(agent=mock_agent, messages=messages, session=None)
|
||||
|
||||
expected_response = AgentResponse(messages=[Message(role="assistant", text="response")])
|
||||
expected_response = AgentResponse(messages=[Message(role="assistant", contents=["response"])])
|
||||
|
||||
async def final_handler(ctx: AgentContext) -> AgentResponse:
|
||||
return expected_response
|
||||
@@ -563,11 +563,11 @@ class TestChatMiddlewarePipeline:
|
||||
async def test_execute_no_middleware(self, mock_chat_client: Any) -> None:
|
||||
"""Test pipeline execution with no middleware."""
|
||||
pipeline = ChatMiddlewarePipeline()
|
||||
messages = [Message(role="user", text="test")]
|
||||
messages = [Message(role="user", contents=["test"])]
|
||||
chat_options: dict[str, Any] = {}
|
||||
context = ChatContext(client=mock_chat_client, messages=messages, options=chat_options)
|
||||
|
||||
expected_response = ChatResponse(messages=[Message(role="assistant", text="response")])
|
||||
expected_response = ChatResponse(messages=[Message(role="assistant", contents=["response"])])
|
||||
|
||||
async def final_handler(ctx: ChatContext) -> ChatResponse:
|
||||
return expected_response
|
||||
@@ -590,11 +590,11 @@ class TestChatMiddlewarePipeline:
|
||||
|
||||
middleware = OrderTrackingChatMiddleware("test")
|
||||
pipeline = ChatMiddlewarePipeline(middleware)
|
||||
messages = [Message(role="user", text="test")]
|
||||
messages = [Message(role="user", contents=["test"])]
|
||||
chat_options: dict[str, Any] = {}
|
||||
context = ChatContext(client=mock_chat_client, messages=messages, options=chat_options)
|
||||
|
||||
expected_response = ChatResponse(messages=[Message(role="assistant", text="response")])
|
||||
expected_response = ChatResponse(messages=[Message(role="assistant", contents=["response"])])
|
||||
|
||||
async def final_handler(ctx: ChatContext) -> ChatResponse:
|
||||
execution_order.append("handler")
|
||||
@@ -607,7 +607,7 @@ class TestChatMiddlewarePipeline:
|
||||
async def test_execute_stream_no_middleware(self, mock_chat_client: Any) -> None:
|
||||
"""Test pipeline streaming execution with no middleware."""
|
||||
pipeline = ChatMiddlewarePipeline()
|
||||
messages = [Message(role="user", text="test")]
|
||||
messages = [Message(role="user", contents=["test"])]
|
||||
chat_options: dict[str, Any] = {}
|
||||
context = ChatContext(client=mock_chat_client, messages=messages, options=chat_options, stream=True)
|
||||
|
||||
@@ -642,7 +642,7 @@ class TestChatMiddlewarePipeline:
|
||||
|
||||
middleware = StreamOrderTrackingChatMiddleware("test")
|
||||
pipeline = ChatMiddlewarePipeline(middleware)
|
||||
messages = [Message(role="user", text="test")]
|
||||
messages = [Message(role="user", contents=["test"])]
|
||||
chat_options: dict[str, Any] = {}
|
||||
context = ChatContext(client=mock_chat_client, messages=messages, options=chat_options, stream=True)
|
||||
|
||||
@@ -669,7 +669,7 @@ class TestChatMiddlewarePipeline:
|
||||
"""Test pipeline execution with termination before next()."""
|
||||
middleware = self.PreNextTerminateChatMiddleware()
|
||||
pipeline = ChatMiddlewarePipeline(middleware)
|
||||
messages = [Message(role="user", text="test")]
|
||||
messages = [Message(role="user", contents=["test"])]
|
||||
chat_options: dict[str, Any] = {}
|
||||
context = ChatContext(client=mock_chat_client, messages=messages, options=chat_options)
|
||||
execution_order: list[str] = []
|
||||
@@ -677,7 +677,7 @@ class TestChatMiddlewarePipeline:
|
||||
async def final_handler(ctx: ChatContext) -> ChatResponse:
|
||||
# Handler should not be executed when terminated before next()
|
||||
execution_order.append("handler")
|
||||
return ChatResponse(messages=[Message(role="assistant", text="response")])
|
||||
return ChatResponse(messages=[Message(role="assistant", contents=["response"])])
|
||||
|
||||
response = await pipeline.execute(context, final_handler)
|
||||
assert response is None
|
||||
@@ -688,14 +688,14 @@ class TestChatMiddlewarePipeline:
|
||||
"""Test pipeline execution with termination after next()."""
|
||||
middleware = self.PostNextTerminateChatMiddleware()
|
||||
pipeline = ChatMiddlewarePipeline(middleware)
|
||||
messages = [Message(role="user", text="test")]
|
||||
messages = [Message(role="user", contents=["test"])]
|
||||
chat_options: dict[str, Any] = {}
|
||||
context = ChatContext(client=mock_chat_client, messages=messages, options=chat_options)
|
||||
execution_order: list[str] = []
|
||||
|
||||
async def final_handler(ctx: ChatContext) -> ChatResponse:
|
||||
execution_order.append("handler")
|
||||
return ChatResponse(messages=[Message(role="assistant", text="response")])
|
||||
return ChatResponse(messages=[Message(role="assistant", contents=["response"])])
|
||||
|
||||
response = await pipeline.execute(context, final_handler)
|
||||
assert response is not None
|
||||
@@ -707,7 +707,7 @@ class TestChatMiddlewarePipeline:
|
||||
"""Test pipeline streaming execution with termination before next()."""
|
||||
middleware = self.PreNextTerminateChatMiddleware()
|
||||
pipeline = ChatMiddlewarePipeline(middleware)
|
||||
messages = [Message(role="user", text="test")]
|
||||
messages = [Message(role="user", contents=["test"])]
|
||||
chat_options: dict[str, Any] = {}
|
||||
context = ChatContext(client=mock_chat_client, messages=messages, options=chat_options, stream=True)
|
||||
execution_order: list[str] = []
|
||||
@@ -732,7 +732,7 @@ class TestChatMiddlewarePipeline:
|
||||
"""Test pipeline streaming execution with termination after next()."""
|
||||
middleware = self.PostNextTerminateChatMiddleware()
|
||||
pipeline = ChatMiddlewarePipeline(middleware)
|
||||
messages = [Message(role="user", text="test")]
|
||||
messages = [Message(role="user", contents=["test"])]
|
||||
chat_options: dict[str, Any] = {}
|
||||
context = ChatContext(client=mock_chat_client, messages=messages, options=chat_options, stream=True)
|
||||
execution_order: list[str] = []
|
||||
@@ -774,12 +774,12 @@ class TestClassBasedMiddleware:
|
||||
|
||||
middleware = MetadataAgentMiddleware()
|
||||
pipeline = AgentMiddlewarePipeline(middleware)
|
||||
messages = [Message(role="user", text="test")]
|
||||
messages = [Message(role="user", contents=["test"])]
|
||||
context = AgentContext(agent=mock_agent, messages=messages)
|
||||
|
||||
async def final_handler(ctx: AgentContext) -> AgentResponse:
|
||||
metadata_updates.append("handler")
|
||||
return AgentResponse(messages=[Message(role="assistant", text="response")])
|
||||
return AgentResponse(messages=[Message(role="assistant", contents=["response"])])
|
||||
|
||||
result = await pipeline.execute(context, final_handler)
|
||||
|
||||
@@ -835,12 +835,12 @@ class TestFunctionBasedMiddleware:
|
||||
execution_order.append("function_after")
|
||||
|
||||
pipeline = AgentMiddlewarePipeline(test_agent_middleware)
|
||||
messages = [Message(role="user", text="test")]
|
||||
messages = [Message(role="user", contents=["test"])]
|
||||
context = AgentContext(agent=mock_agent, messages=messages)
|
||||
|
||||
async def final_handler(ctx: AgentContext) -> AgentResponse:
|
||||
execution_order.append("handler")
|
||||
return AgentResponse(messages=[Message(role="assistant", text="response")])
|
||||
return AgentResponse(messages=[Message(role="assistant", contents=["response"])])
|
||||
|
||||
result = await pipeline.execute(context, final_handler)
|
||||
|
||||
@@ -894,12 +894,12 @@ class TestMixedMiddleware:
|
||||
execution_order.append("function_after")
|
||||
|
||||
pipeline = AgentMiddlewarePipeline(ClassMiddleware(), function_middleware)
|
||||
messages = [Message(role="user", text="test")]
|
||||
messages = [Message(role="user", contents=["test"])]
|
||||
context = AgentContext(agent=mock_agent, messages=messages)
|
||||
|
||||
async def final_handler(ctx: AgentContext) -> AgentResponse:
|
||||
execution_order.append("handler")
|
||||
return AgentResponse(messages=[Message(role="assistant", text="response")])
|
||||
return AgentResponse(messages=[Message(role="assistant", contents=["response"])])
|
||||
|
||||
result = await pipeline.execute(context, final_handler)
|
||||
|
||||
@@ -956,13 +956,13 @@ class TestMixedMiddleware:
|
||||
execution_order.append("function_after")
|
||||
|
||||
pipeline = ChatMiddlewarePipeline(ClassChatMiddleware(), function_chat_middleware)
|
||||
messages = [Message(role="user", text="test")]
|
||||
messages = [Message(role="user", contents=["test"])]
|
||||
chat_options: dict[str, Any] = {}
|
||||
context = ChatContext(client=mock_chat_client, messages=messages, options=chat_options)
|
||||
|
||||
async def final_handler(ctx: ChatContext) -> ChatResponse:
|
||||
execution_order.append("handler")
|
||||
return ChatResponse(messages=[Message(role="assistant", text="response")])
|
||||
return ChatResponse(messages=[Message(role="assistant", contents=["response"])])
|
||||
|
||||
result = await pipeline.execute(context, final_handler)
|
||||
|
||||
@@ -997,12 +997,12 @@ class TestMultipleMiddlewareOrdering:
|
||||
|
||||
middleware = [FirstMiddleware(), SecondMiddleware(), ThirdMiddleware()]
|
||||
pipeline = AgentMiddlewarePipeline(*middleware)
|
||||
messages = [Message(role="user", text="test")]
|
||||
messages = [Message(role="user", contents=["test"])]
|
||||
context = AgentContext(agent=mock_agent, messages=messages)
|
||||
|
||||
async def final_handler(ctx: AgentContext) -> AgentResponse:
|
||||
execution_order.append("handler")
|
||||
return AgentResponse(messages=[Message(role="assistant", text="response")])
|
||||
return AgentResponse(messages=[Message(role="assistant", contents=["response"])])
|
||||
|
||||
result = await pipeline.execute(context, final_handler)
|
||||
|
||||
@@ -1081,13 +1081,13 @@ class TestMultipleMiddlewareOrdering:
|
||||
|
||||
middleware = [FirstChatMiddleware(), SecondChatMiddleware(), ThirdChatMiddleware()]
|
||||
pipeline = ChatMiddlewarePipeline(*middleware)
|
||||
messages = [Message(role="user", text="test")]
|
||||
messages = [Message(role="user", contents=["test"])]
|
||||
chat_options: dict[str, Any] = {}
|
||||
context = ChatContext(client=mock_chat_client, messages=messages, options=chat_options)
|
||||
|
||||
async def final_handler(ctx: ChatContext) -> ChatResponse:
|
||||
execution_order.append("handler")
|
||||
return ChatResponse(messages=[Message(role="assistant", text="response")])
|
||||
return ChatResponse(messages=[Message(role="assistant", contents=["response"])])
|
||||
|
||||
result = await pipeline.execute(context, final_handler)
|
||||
|
||||
@@ -1133,13 +1133,13 @@ class TestContextContentValidation:
|
||||
|
||||
middleware = ContextValidationMiddleware()
|
||||
pipeline = AgentMiddlewarePipeline(middleware)
|
||||
messages = [Message(role="user", text="test")]
|
||||
messages = [Message(role="user", contents=["test"])]
|
||||
context = AgentContext(agent=mock_agent, messages=messages)
|
||||
|
||||
async def final_handler(ctx: AgentContext) -> AgentResponse:
|
||||
# Verify metadata was set by middleware
|
||||
assert ctx.metadata.get("validated") is True
|
||||
return AgentResponse(messages=[Message(role="assistant", text="response")])
|
||||
return AgentResponse(messages=[Message(role="assistant", contents=["response"])])
|
||||
|
||||
result = await pipeline.execute(context, final_handler)
|
||||
assert result is not None
|
||||
@@ -1212,14 +1212,14 @@ class TestContextContentValidation:
|
||||
|
||||
middleware = ChatContextValidationMiddleware()
|
||||
pipeline = ChatMiddlewarePipeline(middleware)
|
||||
messages = [Message(role="user", text="test")]
|
||||
messages = [Message(role="user", contents=["test"])]
|
||||
chat_options: dict[str, Any] = {"temperature": 0.5}
|
||||
context = ChatContext(client=mock_chat_client, messages=messages, options=chat_options)
|
||||
|
||||
async def final_handler(ctx: ChatContext) -> ChatResponse:
|
||||
# Verify metadata was set by middleware
|
||||
assert ctx.metadata.get("validated") is True
|
||||
return ChatResponse(messages=[Message(role="assistant", text="response")])
|
||||
return ChatResponse(messages=[Message(role="assistant", contents=["response"])])
|
||||
|
||||
result = await pipeline.execute(context, final_handler)
|
||||
assert result is not None
|
||||
@@ -1239,14 +1239,14 @@ class TestStreamingScenarios:
|
||||
|
||||
middleware = StreamingFlagMiddleware()
|
||||
pipeline = AgentMiddlewarePipeline(middleware)
|
||||
messages = [Message(role="user", text="test")]
|
||||
messages = [Message(role="user", contents=["test"])]
|
||||
|
||||
# Test non-streaming
|
||||
context = AgentContext(agent=mock_agent, messages=messages)
|
||||
|
||||
async def final_handler(ctx: AgentContext) -> AgentResponse:
|
||||
streaming_flags.append(ctx.stream)
|
||||
return AgentResponse(messages=[Message(role="assistant", text="response")])
|
||||
return AgentResponse(messages=[Message(role="assistant", contents=["response"])])
|
||||
|
||||
await pipeline.execute(context, final_handler)
|
||||
|
||||
@@ -1280,7 +1280,7 @@ class TestStreamingScenarios:
|
||||
|
||||
middleware = StreamProcessingMiddleware()
|
||||
pipeline = AgentMiddlewarePipeline(middleware)
|
||||
messages = [Message(role="user", text="test")]
|
||||
messages = [Message(role="user", contents=["test"])]
|
||||
context = AgentContext(agent=mock_agent, messages=messages, stream=True)
|
||||
|
||||
async def final_stream_handler(ctx: AgentContext) -> ResponseStream[AgentResponseUpdate, AgentResponse]:
|
||||
@@ -1320,7 +1320,7 @@ class TestStreamingScenarios:
|
||||
|
||||
middleware = ChatStreamingFlagMiddleware()
|
||||
pipeline = ChatMiddlewarePipeline(middleware)
|
||||
messages = [Message(role="user", text="test")]
|
||||
messages = [Message(role="user", contents=["test"])]
|
||||
chat_options: dict[str, Any] = {}
|
||||
|
||||
# Test non-streaming
|
||||
@@ -1328,7 +1328,7 @@ class TestStreamingScenarios:
|
||||
|
||||
async def final_handler(ctx: ChatContext) -> ChatResponse:
|
||||
streaming_flags.append(ctx.stream)
|
||||
return ChatResponse(messages=[Message(role="assistant", text="response")])
|
||||
return ChatResponse(messages=[Message(role="assistant", contents=["response"])])
|
||||
|
||||
await pipeline.execute(context, final_handler)
|
||||
|
||||
@@ -1362,7 +1362,7 @@ class TestStreamingScenarios:
|
||||
|
||||
middleware = ChatStreamProcessingMiddleware()
|
||||
pipeline = ChatMiddlewarePipeline(middleware)
|
||||
messages = [Message(role="user", text="test")]
|
||||
messages = [Message(role="user", contents=["test"])]
|
||||
chat_options: dict[str, Any] = {}
|
||||
context = ChatContext(client=mock_chat_client, messages=messages, options=chat_options, stream=True)
|
||||
|
||||
@@ -1442,7 +1442,7 @@ class TestMiddlewareExecutionControl:
|
||||
|
||||
middleware = NoNextMiddleware()
|
||||
pipeline = AgentMiddlewarePipeline(middleware)
|
||||
messages = [Message(role="user", text="test")]
|
||||
messages = [Message(role="user", contents=["test"])]
|
||||
context = AgentContext(agent=mock_agent, messages=messages)
|
||||
|
||||
handler_called = False
|
||||
@@ -1450,7 +1450,7 @@ class TestMiddlewareExecutionControl:
|
||||
async def final_handler(ctx: AgentContext) -> AgentResponse:
|
||||
nonlocal handler_called
|
||||
handler_called = True
|
||||
return AgentResponse(messages=[Message(role="assistant", text="should not execute")])
|
||||
return AgentResponse(messages=[Message(role="assistant", contents=["should not execute"])])
|
||||
|
||||
result = await pipeline.execute(context, final_handler)
|
||||
|
||||
@@ -1469,7 +1469,7 @@ class TestMiddlewareExecutionControl:
|
||||
|
||||
middleware = NoNextStreamingMiddleware()
|
||||
pipeline = AgentMiddlewarePipeline(middleware)
|
||||
messages = [Message(role="user", text="test")]
|
||||
messages = [Message(role="user", contents=["test"])]
|
||||
context = AgentContext(agent=mock_agent, messages=messages, stream=True)
|
||||
|
||||
handler_called = False
|
||||
@@ -1539,7 +1539,7 @@ class TestMiddlewareExecutionControl:
|
||||
await call_next()
|
||||
|
||||
pipeline = AgentMiddlewarePipeline(FirstMiddleware(), SecondMiddleware())
|
||||
messages = [Message(role="user", text="test")]
|
||||
messages = [Message(role="user", contents=["test"])]
|
||||
context = AgentContext(agent=mock_agent, messages=messages)
|
||||
|
||||
handler_called = False
|
||||
@@ -1547,7 +1547,7 @@ class TestMiddlewareExecutionControl:
|
||||
async def final_handler(ctx: AgentContext) -> AgentResponse:
|
||||
nonlocal handler_called
|
||||
handler_called = True
|
||||
return AgentResponse(messages=[Message(role="assistant", text="should not execute")])
|
||||
return AgentResponse(messages=[Message(role="assistant", contents=["should not execute"])])
|
||||
|
||||
result = await pipeline.execute(context, final_handler)
|
||||
|
||||
@@ -1566,7 +1566,7 @@ class TestMiddlewareExecutionControl:
|
||||
|
||||
middleware = NoNextChatMiddleware()
|
||||
pipeline = ChatMiddlewarePipeline(middleware)
|
||||
messages = [Message(role="user", text="test")]
|
||||
messages = [Message(role="user", contents=["test"])]
|
||||
chat_options: dict[str, Any] = {}
|
||||
context = ChatContext(client=mock_chat_client, messages=messages, options=chat_options)
|
||||
|
||||
@@ -1575,7 +1575,7 @@ class TestMiddlewareExecutionControl:
|
||||
async def final_handler(ctx: ChatContext) -> ChatResponse:
|
||||
nonlocal handler_called
|
||||
handler_called = True
|
||||
return ChatResponse(messages=[Message(role="assistant", text="should not execute")])
|
||||
return ChatResponse(messages=[Message(role="assistant", contents=["should not execute"])])
|
||||
|
||||
result = await pipeline.execute(context, final_handler)
|
||||
|
||||
@@ -1594,7 +1594,7 @@ class TestMiddlewareExecutionControl:
|
||||
|
||||
middleware = NoNextStreamingChatMiddleware()
|
||||
pipeline = ChatMiddlewarePipeline(middleware)
|
||||
messages = [Message(role="user", text="test")]
|
||||
messages = [Message(role="user", contents=["test"])]
|
||||
chat_options: dict[str, Any] = {}
|
||||
context = ChatContext(client=mock_chat_client, messages=messages, options=chat_options, stream=True)
|
||||
|
||||
@@ -1639,7 +1639,7 @@ class TestMiddlewareExecutionControl:
|
||||
await call_next()
|
||||
|
||||
pipeline = ChatMiddlewarePipeline(FirstChatMiddleware(), SecondChatMiddleware())
|
||||
messages = [Message(role="user", text="test")]
|
||||
messages = [Message(role="user", contents=["test"])]
|
||||
chat_options: dict[str, Any] = {}
|
||||
context = ChatContext(client=mock_chat_client, messages=messages, options=chat_options)
|
||||
|
||||
@@ -1648,7 +1648,7 @@ class TestMiddlewareExecutionControl:
|
||||
async def final_handler(ctx: ChatContext) -> ChatResponse:
|
||||
nonlocal handler_called
|
||||
handler_called = True
|
||||
return ChatResponse(messages=[Message(role="assistant", text="should not execute")])
|
||||
return ChatResponse(messages=[Message(role="assistant", contents=["should not execute"])])
|
||||
|
||||
result = await pipeline.execute(context, final_handler)
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ class TestResultOverrideMiddleware:
|
||||
|
||||
async def test_agent_middleware_response_override_non_streaming(self, mock_agent: SupportsAgentRun) -> None:
|
||||
"""Test that agent middleware can override response for non-streaming execution."""
|
||||
override_response = AgentResponse(messages=[Message(role="assistant", text="overridden response")])
|
||||
override_response = AgentResponse(messages=[Message(role="assistant", contents=["overridden response"])])
|
||||
|
||||
class ResponseOverrideMiddleware(AgentMiddleware):
|
||||
async def process(self, context: AgentContext, call_next: Callable[[], Awaitable[None]]) -> None:
|
||||
@@ -49,7 +49,7 @@ class TestResultOverrideMiddleware:
|
||||
|
||||
middleware = ResponseOverrideMiddleware()
|
||||
pipeline = AgentMiddlewarePipeline(middleware)
|
||||
messages = [Message(role="user", text="test")]
|
||||
messages = [Message(role="user", contents=["test"])]
|
||||
context = AgentContext(agent=mock_agent, messages=messages)
|
||||
|
||||
handler_called = False
|
||||
@@ -57,7 +57,7 @@ class TestResultOverrideMiddleware:
|
||||
async def final_handler(ctx: AgentContext) -> AgentResponse:
|
||||
nonlocal handler_called
|
||||
handler_called = True
|
||||
return AgentResponse(messages=[Message(role="assistant", text="original response")])
|
||||
return AgentResponse(messages=[Message(role="assistant", contents=["original response"])])
|
||||
|
||||
result = await pipeline.execute(context, final_handler)
|
||||
|
||||
@@ -83,7 +83,7 @@ class TestResultOverrideMiddleware:
|
||||
|
||||
middleware = StreamResponseOverrideMiddleware()
|
||||
pipeline = AgentMiddlewarePipeline(middleware)
|
||||
messages = [Message(role="user", text="test")]
|
||||
messages = [Message(role="user", contents=["test"])]
|
||||
context = AgentContext(agent=mock_agent, messages=messages, stream=True)
|
||||
|
||||
async def final_handler(ctx: AgentContext) -> ResponseStream[AgentResponseUpdate, AgentResponse]:
|
||||
@@ -146,7 +146,7 @@ class TestResultOverrideMiddleware:
|
||||
# Then conditionally override based on content
|
||||
if any("special" in msg.text for msg in context.messages if msg.text):
|
||||
context.result = AgentResponse(
|
||||
messages=[Message(role="assistant", text="Special response from middleware!")]
|
||||
messages=[Message(role="assistant", contents=["Special response from middleware!"])]
|
||||
)
|
||||
|
||||
# Create Agent with override middleware
|
||||
@@ -154,14 +154,14 @@ class TestResultOverrideMiddleware:
|
||||
agent = Agent(client=mock_chat_client, middleware=[middleware])
|
||||
|
||||
# Test override case
|
||||
override_messages = [Message(role="user", text="Give me a special response")]
|
||||
override_messages = [Message(role="user", contents=["Give me a special response"])]
|
||||
override_response = await agent.run(override_messages)
|
||||
assert override_response.messages[0].text == "Special response from middleware!"
|
||||
# Verify chat client was called since middleware called next()
|
||||
assert mock_chat_client.call_count == 1
|
||||
|
||||
# Test normal case
|
||||
normal_messages = [Message(role="user", text="Normal request")]
|
||||
normal_messages = [Message(role="user", contents=["Normal request"])]
|
||||
normal_response = await agent.run(normal_messages)
|
||||
assert normal_response.messages[0].text == "test response"
|
||||
# Verify chat client was called for normal case
|
||||
@@ -190,7 +190,7 @@ class TestResultOverrideMiddleware:
|
||||
agent = Agent(client=mock_chat_client, middleware=[middleware])
|
||||
|
||||
# Test streaming override case
|
||||
override_messages = [Message(role="user", text="Give me a custom stream")]
|
||||
override_messages = [Message(role="user", contents=["Give me a custom stream"])]
|
||||
override_updates: list[AgentResponseUpdate] = []
|
||||
async for update in agent.run(override_messages, stream=True):
|
||||
override_updates.append(update)
|
||||
@@ -201,7 +201,7 @@ class TestResultOverrideMiddleware:
|
||||
assert override_updates[2].text == " response!"
|
||||
|
||||
# Test normal streaming case
|
||||
normal_messages = [Message(role="user", text="Normal streaming request")]
|
||||
normal_messages = [Message(role="user", contents=["Normal streaming request"])]
|
||||
normal_updates: list[AgentResponseUpdate] = []
|
||||
async for update in agent.run(normal_messages, stream=True):
|
||||
normal_updates.append(update)
|
||||
@@ -228,10 +228,10 @@ class TestResultOverrideMiddleware:
|
||||
async def final_handler(ctx: AgentContext) -> AgentResponse:
|
||||
nonlocal handler_called
|
||||
handler_called = True
|
||||
return AgentResponse(messages=[Message(role="assistant", text="executed response")])
|
||||
return AgentResponse(messages=[Message(role="assistant", contents=["executed response"])])
|
||||
|
||||
# Test case where next() is NOT called
|
||||
no_execute_messages = [Message(role="user", text="Don't run this")]
|
||||
no_execute_messages = [Message(role="user", contents=["Don't run this"])]
|
||||
no_execute_context = AgentContext(agent=mock_agent, messages=no_execute_messages, stream=False)
|
||||
no_execute_result = await pipeline.execute(no_execute_context, final_handler)
|
||||
|
||||
@@ -243,7 +243,7 @@ class TestResultOverrideMiddleware:
|
||||
handler_called = False
|
||||
|
||||
# Test case where next() IS called
|
||||
execute_messages = [Message(role="user", text="Please execute this")]
|
||||
execute_messages = [Message(role="user", contents=["Please execute this"])]
|
||||
execute_context = AgentContext(agent=mock_agent, messages=execute_messages, stream=False)
|
||||
execute_result = await pipeline.execute(execute_context, final_handler)
|
||||
|
||||
@@ -321,11 +321,11 @@ class TestResultObservability:
|
||||
|
||||
middleware = ObservabilityMiddleware()
|
||||
pipeline = AgentMiddlewarePipeline(middleware)
|
||||
messages = [Message(role="user", text="test")]
|
||||
messages = [Message(role="user", contents=["test"])]
|
||||
context = AgentContext(agent=mock_agent, messages=messages, stream=False)
|
||||
|
||||
async def final_handler(ctx: AgentContext) -> AgentResponse:
|
||||
return AgentResponse(messages=[Message(role="assistant", text="executed response")])
|
||||
return AgentResponse(messages=[Message(role="assistant", contents=["executed response"])])
|
||||
|
||||
result = await pipeline.execute(context, final_handler)
|
||||
|
||||
@@ -384,16 +384,16 @@ class TestResultObservability:
|
||||
if "modify" in context.result.messages[0].text:
|
||||
# Override after observing
|
||||
context.result = AgentResponse(
|
||||
messages=[Message(role="assistant", text="modified after execution")]
|
||||
messages=[Message(role="assistant", contents=["modified after execution"])]
|
||||
)
|
||||
|
||||
middleware = PostExecutionOverrideMiddleware()
|
||||
pipeline = AgentMiddlewarePipeline(middleware)
|
||||
messages = [Message(role="user", text="test")]
|
||||
messages = [Message(role="user", contents=["test"])]
|
||||
context = AgentContext(agent=mock_agent, messages=messages, stream=False)
|
||||
|
||||
async def final_handler(ctx: AgentContext) -> AgentResponse:
|
||||
return AgentResponse(messages=[Message(role="assistant", text="response to modify")])
|
||||
return AgentResponse(messages=[Message(role="assistant", contents=["response to modify"])])
|
||||
|
||||
result = await pipeline.execute(context, final_handler)
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ class TestChatAgentClassBasedMiddleware:
|
||||
agent = Agent(client=client, middleware=[middleware])
|
||||
|
||||
# Execute the agent
|
||||
messages = [Message(role="user", text="test message")]
|
||||
messages = [Message(role="user", contents=["test message"])]
|
||||
response = await agent.run(messages)
|
||||
|
||||
# Verify response
|
||||
@@ -105,7 +105,7 @@ class TestChatAgentClassBasedMiddleware:
|
||||
middleware = TrackingFunctionMiddleware("function_middleware")
|
||||
agent = Agent(client=chat_client_base, middleware=[middleware])
|
||||
|
||||
messages = [Message(role="user", text="test message")]
|
||||
messages = [Message(role="user", contents=["test message"])]
|
||||
response = await agent.run(messages)
|
||||
|
||||
assert response is not None
|
||||
@@ -135,8 +135,8 @@ class TestChatAgentFunctionBasedMiddleware:
|
||||
|
||||
# Execute the agent with multiple messages
|
||||
messages = [
|
||||
Message(role="user", text="message1"),
|
||||
Message(role="user", text="message2"), # This should not be processed due to termination
|
||||
Message(role="user", contents=["message1"]),
|
||||
Message(role="user", contents=["message2"]), # This should not be processed due to termination
|
||||
]
|
||||
response = await agent.run(messages)
|
||||
|
||||
@@ -163,8 +163,8 @@ class TestChatAgentFunctionBasedMiddleware:
|
||||
|
||||
# Execute the agent with multiple messages
|
||||
messages = [
|
||||
Message(role="user", text="message1"),
|
||||
Message(role="user", text="message2"),
|
||||
Message(role="user", contents=["message1"]),
|
||||
Message(role="user", contents=["message2"]),
|
||||
]
|
||||
response = await agent.run(messages)
|
||||
|
||||
@@ -229,7 +229,7 @@ class TestChatAgentFunctionBasedMiddleware:
|
||||
agent = Agent(client=client, middleware=[tracking_agent_middleware])
|
||||
|
||||
# Execute the agent
|
||||
messages = [Message(role="user", text="test message")]
|
||||
messages = [Message(role="user", contents=["test message"])]
|
||||
response = await agent.run(messages)
|
||||
|
||||
# Verify response
|
||||
@@ -266,7 +266,7 @@ class TestChatAgentFunctionBasedMiddleware:
|
||||
execution_order.append("function_function_after")
|
||||
|
||||
agent = Agent(client=chat_client_base, middleware=[tracking_function_middleware])
|
||||
messages = [Message(role="user", text="test message")]
|
||||
messages = [Message(role="user", contents=["test message"])]
|
||||
response = await agent.run(messages)
|
||||
|
||||
assert response is not None
|
||||
@@ -303,7 +303,7 @@ class TestChatAgentStreamingMiddleware:
|
||||
]
|
||||
|
||||
# Execute streaming
|
||||
messages = [Message(role="user", text="test message")]
|
||||
messages = [Message(role="user", contents=["test message"])]
|
||||
updates: list[AgentResponseUpdate] = []
|
||||
async for update in agent.run(messages, stream=True):
|
||||
updates.append(update)
|
||||
@@ -333,7 +333,7 @@ class TestChatAgentStreamingMiddleware:
|
||||
# Create Agent with middleware
|
||||
middleware = FlagTrackingMiddleware()
|
||||
agent = Agent(client=client, middleware=[middleware])
|
||||
messages = [Message(role="user", text="test message")]
|
||||
messages = [Message(role="user", contents=["test message"])]
|
||||
|
||||
# Test non-streaming execution
|
||||
response = await agent.run(messages)
|
||||
@@ -372,7 +372,7 @@ class TestChatAgentMultipleMiddlewareOrdering:
|
||||
agent = Agent(client=client, middleware=[middleware1, middleware2, middleware3])
|
||||
|
||||
# Execute the agent
|
||||
messages = [Message(role="user", text="test message")]
|
||||
messages = [Message(role="user", contents=["test message"])]
|
||||
response = await agent.run(messages)
|
||||
|
||||
# Verify response
|
||||
@@ -424,7 +424,7 @@ class TestChatAgentMultipleMiddlewareOrdering:
|
||||
function_function_middleware,
|
||||
],
|
||||
)
|
||||
await agent.run([Message(role="user", text="test")])
|
||||
await agent.run([Message(role="user", contents=["test"])])
|
||||
|
||||
async def test_mixed_middleware_types_with_supported_client(self, chat_client_base: "MockBaseChatClient") -> None:
|
||||
"""Test mixed class and function-based middleware with a full chat client."""
|
||||
@@ -457,7 +457,7 @@ class TestChatAgentMultipleMiddlewareOrdering:
|
||||
],
|
||||
)
|
||||
|
||||
messages = [Message(role="user", text="test message")]
|
||||
messages = [Message(role="user", contents=["test message"])]
|
||||
response = await agent.run(messages)
|
||||
|
||||
assert response is not None
|
||||
@@ -488,7 +488,7 @@ class TestChatAgentMultipleMiddlewareOrdering:
|
||||
MiddlewareException,
|
||||
match="Context providers may only add chat or function middleware",
|
||||
):
|
||||
await agent.run([Message(role="user", text="test message")])
|
||||
await agent.run([Message(role="user", contents=["test message"])])
|
||||
|
||||
|
||||
# region Tool Functions for Testing
|
||||
@@ -547,7 +547,7 @@ class TestChatAgentFunctionMiddlewareWithTools:
|
||||
)
|
||||
]
|
||||
)
|
||||
final_response = ChatResponse(messages=[Message(role="assistant", text="Final response")])
|
||||
final_response = ChatResponse(messages=[Message(role="assistant", contents=["Final response"])])
|
||||
|
||||
chat_client_base.run_responses = [function_call_response, final_response]
|
||||
|
||||
@@ -560,7 +560,7 @@ class TestChatAgentFunctionMiddlewareWithTools:
|
||||
)
|
||||
|
||||
# Execute the agent
|
||||
messages = [Message(role="user", text="Get weather for Seattle")]
|
||||
messages = [Message(role="user", contents=["Get weather for Seattle"])]
|
||||
response = await agent.run(messages)
|
||||
|
||||
# Verify response
|
||||
@@ -609,7 +609,7 @@ class TestChatAgentFunctionMiddlewareWithTools:
|
||||
)
|
||||
]
|
||||
)
|
||||
final_response = ChatResponse(messages=[Message(role="assistant", text="Final response")])
|
||||
final_response = ChatResponse(messages=[Message(role="assistant", contents=["Final response"])])
|
||||
|
||||
chat_client_base.run_responses = [function_call_response, final_response]
|
||||
|
||||
@@ -621,7 +621,7 @@ class TestChatAgentFunctionMiddlewareWithTools:
|
||||
)
|
||||
|
||||
# Execute the agent
|
||||
messages = [Message(role="user", text="Get weather for San Francisco")]
|
||||
messages = [Message(role="user", contents=["Get weather for San Francisco"])]
|
||||
response = await agent.run(messages)
|
||||
|
||||
# Verify response
|
||||
@@ -683,7 +683,7 @@ class TestChatAgentFunctionMiddlewareWithTools:
|
||||
)
|
||||
]
|
||||
)
|
||||
final_response = ChatResponse(messages=[Message(role="assistant", text="Final response")])
|
||||
final_response = ChatResponse(messages=[Message(role="assistant", contents=["Final response"])])
|
||||
|
||||
chat_client_base.run_responses = [function_call_response, final_response]
|
||||
|
||||
@@ -695,7 +695,7 @@ class TestChatAgentFunctionMiddlewareWithTools:
|
||||
)
|
||||
|
||||
# Execute the agent
|
||||
messages = [Message(role="user", text="Get weather for New York")]
|
||||
messages = [Message(role="user", contents=["Get weather for New York"])]
|
||||
response = await agent.run(messages)
|
||||
|
||||
# Verify response
|
||||
@@ -795,7 +795,7 @@ class TestChatAgentFunctionMiddlewareWithTools:
|
||||
agent = Agent(client=chat_client_base, middleware=[kwargs_middleware], tools=[sample_tool_function])
|
||||
|
||||
# Execute the agent with custom parameters passed as kwargs
|
||||
messages = [Message(role="user", text="test message")]
|
||||
messages = [Message(role="user", contents=["test message"])]
|
||||
response = await agent.run(messages, options={"additional_function_arguments": {"custom_param": "test_value"}})
|
||||
|
||||
# Verify response
|
||||
@@ -841,14 +841,14 @@ class TestChatAgentFunctionMiddlewareWithTools:
|
||||
)
|
||||
]
|
||||
),
|
||||
ChatResponse(messages=[Message(role="assistant", text="Done!")]),
|
||||
ChatResponse(messages=[Message(role="assistant", contents=["Done!"])]),
|
||||
]
|
||||
|
||||
agent = Agent(client=chat_client_base, middleware=[capture_middleware], tools=[sample_tool_function])
|
||||
|
||||
session_metadata = {"tenant": "acme-corp", "region": "us-west"}
|
||||
await agent.run(
|
||||
[Message(role="user", text="Get weather")],
|
||||
[Message(role="user", contents=["Get weather"])],
|
||||
function_invocation_kwargs={
|
||||
"user_id": "user-456",
|
||||
"session_metadata": session_metadata,
|
||||
@@ -885,13 +885,13 @@ class TestChatAgentFunctionMiddlewareWithTools:
|
||||
)
|
||||
]
|
||||
),
|
||||
ChatResponse(messages=[Message(role="assistant", text="Done!")]),
|
||||
ChatResponse(messages=[Message(role="assistant", contents=["Done!"])]),
|
||||
]
|
||||
|
||||
agent = Agent(client=chat_client_base, middleware=[capture_middleware], tools=[sample_tool_function])
|
||||
|
||||
await agent.run(
|
||||
[Message(role="user", text="Get weather")],
|
||||
[Message(role="user", contents=["Get weather"])],
|
||||
function_invocation_kwargs={
|
||||
"user_id": "from-kwargs",
|
||||
"tenant_id": "from-kwargs",
|
||||
@@ -940,13 +940,13 @@ class TestChatAgentFunctionMiddlewareWithTools:
|
||||
)
|
||||
]
|
||||
),
|
||||
ChatResponse(messages=[Message(role="assistant", text="Done!")]),
|
||||
ChatResponse(messages=[Message(role="assistant", contents=["Done!"])]),
|
||||
]
|
||||
|
||||
agent = Agent(client=chat_client_base, middleware=[capture_middleware], tools=[sample_tool_function])
|
||||
|
||||
await agent.run(
|
||||
[Message(role="user", text="Get weather for both cities")],
|
||||
[Message(role="user", contents=["Get weather for both cities"])],
|
||||
function_invocation_kwargs={
|
||||
"user_id": "user-456",
|
||||
"request_id": "req-001",
|
||||
@@ -984,12 +984,12 @@ class TestChatAgentFunctionMiddlewareWithTools:
|
||||
)
|
||||
]
|
||||
),
|
||||
ChatResponse(messages=[Message(role="assistant", text="Done!")]),
|
||||
ChatResponse(messages=[Message(role="assistant", contents=["Done!"])]),
|
||||
]
|
||||
|
||||
agent = Agent(client=chat_client_base, middleware=[capture_middleware], tools=[sample_tool_function])
|
||||
|
||||
await agent.run([Message(role="user", text="Get weather")])
|
||||
await agent.run([Message(role="user", contents=["Get weather"])])
|
||||
|
||||
# No runtime kwargs should be present
|
||||
assert "user_id" not in captured_kwargs
|
||||
@@ -1355,7 +1355,7 @@ class TestRunLevelMiddleware:
|
||||
)
|
||||
]
|
||||
)
|
||||
final_response = ChatResponse(messages=[Message(role="assistant", text="Final response")])
|
||||
final_response = ChatResponse(messages=[Message(role="assistant", contents=["Final response"])])
|
||||
chat_client_base.run_responses = [function_call_response, final_response]
|
||||
|
||||
# Create agent with agent-level middleware
|
||||
@@ -1446,7 +1446,7 @@ class TestMiddlewareDecoratorLogic:
|
||||
)
|
||||
]
|
||||
)
|
||||
final_response = ChatResponse(messages=[Message(role="assistant", text="Final response")])
|
||||
final_response = ChatResponse(messages=[Message(role="assistant", contents=["Final response"])])
|
||||
chat_client_base.responses = [function_call_response, final_response]
|
||||
|
||||
# Should work without errors
|
||||
@@ -1456,7 +1456,7 @@ class TestMiddlewareDecoratorLogic:
|
||||
tools=[custom_tool_wrapped],
|
||||
)
|
||||
|
||||
response = await agent.run([Message(role="user", text="test")])
|
||||
response = await agent.run([Message(role="user", contents=["test"])])
|
||||
|
||||
assert response is not None
|
||||
assert "decorator_type_match_agent" in execution_order
|
||||
@@ -1477,7 +1477,7 @@ class TestMiddlewareDecoratorLogic:
|
||||
await call_next()
|
||||
|
||||
agent = Agent(client=client, middleware=[mismatched_middleware])
|
||||
await agent.run([Message(role="user", text="test")])
|
||||
await agent.run([Message(role="user", contents=["test"])])
|
||||
|
||||
async def test_only_decorator_specified(self, chat_client_base: "MockBaseChatClient") -> None:
|
||||
"""Only decorator specified - rely on decorator."""
|
||||
@@ -1517,7 +1517,7 @@ class TestMiddlewareDecoratorLogic:
|
||||
)
|
||||
]
|
||||
)
|
||||
final_response = ChatResponse(messages=[Message(role="assistant", text="Final response")])
|
||||
final_response = ChatResponse(messages=[Message(role="assistant", contents=["Final response"])])
|
||||
chat_client_base.responses = [function_call_response, final_response]
|
||||
|
||||
# Should work - relies on decorator
|
||||
@@ -1527,7 +1527,7 @@ class TestMiddlewareDecoratorLogic:
|
||||
tools=[custom_tool_wrapped],
|
||||
)
|
||||
|
||||
response = await agent.run([Message(role="user", text="test")])
|
||||
response = await agent.run([Message(role="user", contents=["test"])])
|
||||
|
||||
assert response is not None
|
||||
assert "decorator_only_agent" in execution_order
|
||||
@@ -1573,7 +1573,7 @@ class TestMiddlewareDecoratorLogic:
|
||||
)
|
||||
]
|
||||
)
|
||||
final_response = ChatResponse(messages=[Message(role="assistant", text="Final response")])
|
||||
final_response = ChatResponse(messages=[Message(role="assistant", contents=["Final response"])])
|
||||
chat_client_base.responses = [function_call_response, final_response]
|
||||
|
||||
# Should work - relies on type annotations
|
||||
@@ -1581,7 +1581,7 @@ class TestMiddlewareDecoratorLogic:
|
||||
client=chat_client_base, middleware=[type_only_agent, type_only_function], tools=[custom_tool_wrapped]
|
||||
)
|
||||
|
||||
response = await agent.run([Message(role="user", text="test")])
|
||||
response = await agent.run([Message(role="user", contents=["test"])])
|
||||
|
||||
assert response is not None
|
||||
assert "type_only_agent" in execution_order
|
||||
@@ -1596,7 +1596,7 @@ class TestMiddlewareDecoratorLogic:
|
||||
# Should raise MiddlewareException
|
||||
with pytest.raises(MiddlewareException, match="Cannot determine middleware type"):
|
||||
agent = Agent(client=client, middleware=[no_info_middleware])
|
||||
await agent.run([Message(role="user", text="test")])
|
||||
await agent.run([Message(role="user", contents=["test"])])
|
||||
|
||||
async def test_insufficient_parameters_error(self, client: Any) -> None:
|
||||
"""Test that middleware with insufficient parameters raises an error."""
|
||||
@@ -1610,7 +1610,7 @@ class TestMiddlewareDecoratorLogic:
|
||||
pass
|
||||
|
||||
agent = Agent(client=client, middleware=[insufficient_params_middleware])
|
||||
await agent.run([Message(role="user", text="test")])
|
||||
await agent.run([Message(role="user", contents=["test"])])
|
||||
|
||||
async def test_decorator_markers_preserved(self) -> None:
|
||||
"""Test that decorator markers are properly set on functions."""
|
||||
@@ -1682,7 +1682,7 @@ class TestChatAgentSessionBehavior:
|
||||
session = agent.create_session()
|
||||
|
||||
# First run
|
||||
first_messages = [Message(role="user", text="first message")]
|
||||
first_messages = [Message(role="user", contents=["first message"])]
|
||||
first_response = await agent.run(first_messages, session=session)
|
||||
|
||||
# Verify first response
|
||||
@@ -1690,7 +1690,7 @@ class TestChatAgentSessionBehavior:
|
||||
assert len(first_response.messages) > 0
|
||||
|
||||
# Second run - use the same thread
|
||||
second_messages = [Message(role="user", text="second message")]
|
||||
second_messages = [Message(role="user", contents=["second message"])]
|
||||
second_response = await agent.run(second_messages, session=session)
|
||||
|
||||
# Verify second response
|
||||
@@ -1762,7 +1762,7 @@ class TestChatAgentChatMiddleware:
|
||||
agent = Agent(client=client, middleware=[middleware])
|
||||
|
||||
# Execute the agent
|
||||
messages = [Message(role="user", text="test message")]
|
||||
messages = [Message(role="user", contents=["test message"])]
|
||||
response = await agent.run(messages)
|
||||
|
||||
# Verify response
|
||||
@@ -1789,7 +1789,7 @@ class TestChatAgentChatMiddleware:
|
||||
agent = Agent(client=client, middleware=[tracking_chat_middleware])
|
||||
|
||||
# Execute the agent
|
||||
messages = [Message(role="user", text="test message")]
|
||||
messages = [Message(role="user", contents=["test message"])]
|
||||
response = await agent.run(messages)
|
||||
|
||||
# Verify response
|
||||
@@ -1813,7 +1813,7 @@ class TestChatAgentChatMiddleware:
|
||||
if msg.role == "system":
|
||||
continue
|
||||
original_text = msg.text or ""
|
||||
context.messages[idx] = Message(role=msg.role, text=f"MODIFIED: {original_text}")
|
||||
context.messages[idx] = Message(role=msg.role, contents=[f"MODIFIED: {original_text}"])
|
||||
break
|
||||
await call_next()
|
||||
|
||||
@@ -1822,7 +1822,7 @@ class TestChatAgentChatMiddleware:
|
||||
agent = Agent(client=client, middleware=[message_modifier_middleware])
|
||||
|
||||
# Execute the agent
|
||||
messages = [Message(role="user", text="test message")]
|
||||
messages = [Message(role="user", contents=["test message"])]
|
||||
response = await agent.run(messages)
|
||||
|
||||
# Verify that the message was modified (MockBaseChatClient echoes back the input)
|
||||
@@ -1836,7 +1836,7 @@ class TestChatAgentChatMiddleware:
|
||||
async def response_override_middleware(context: ChatContext, call_next: Callable[[], Awaitable[None]]) -> None:
|
||||
# Override the response without calling next()
|
||||
context.result = ChatResponse(
|
||||
messages=[Message(role="assistant", text="MiddlewareTypes overridden response")],
|
||||
messages=[Message(role="assistant", contents=["MiddlewareTypes overridden response"])],
|
||||
response_id="middleware-response-123",
|
||||
)
|
||||
context.terminate = True
|
||||
@@ -1846,7 +1846,7 @@ class TestChatAgentChatMiddleware:
|
||||
agent = Agent(client=client, middleware=[response_override_middleware])
|
||||
|
||||
# Execute the agent
|
||||
messages = [Message(role="user", text="test message")]
|
||||
messages = [Message(role="user", contents=["test message"])]
|
||||
response = await agent.run(messages)
|
||||
|
||||
# Verify that the response was overridden
|
||||
@@ -1876,7 +1876,7 @@ class TestChatAgentChatMiddleware:
|
||||
agent = Agent(client=client, middleware=[first_middleware, second_middleware])
|
||||
|
||||
# Execute the agent
|
||||
messages = [Message(role="user", text="test message")]
|
||||
messages = [Message(role="user", contents=["test message"])]
|
||||
response = await agent.run(messages)
|
||||
|
||||
# Verify response
|
||||
@@ -1914,7 +1914,7 @@ class TestChatAgentChatMiddleware:
|
||||
]
|
||||
|
||||
# Execute streaming
|
||||
messages = [Message(role="user", text="test message")]
|
||||
messages = [Message(role="user", contents=["test message"])]
|
||||
updates: list[AgentResponseUpdate] = []
|
||||
async for update in agent.run(messages, stream=True):
|
||||
updates.append(update)
|
||||
@@ -1937,7 +1937,9 @@ class TestChatAgentChatMiddleware:
|
||||
async def process(self, context: ChatContext, call_next: Callable[[], Awaitable[None]]) -> None:
|
||||
execution_order.append("middleware_before")
|
||||
# Set a custom response since we're terminating
|
||||
context.result = ChatResponse(messages=[Message(role="assistant", text="Terminated by middleware")])
|
||||
context.result = ChatResponse(
|
||||
messages=[Message(role="assistant", contents=["Terminated by middleware"])]
|
||||
)
|
||||
raise MiddlewareTermination
|
||||
# We call next() but since terminate=True, execution should stop
|
||||
await call_next()
|
||||
@@ -1948,7 +1950,7 @@ class TestChatAgentChatMiddleware:
|
||||
agent = Agent(client=client, middleware=[PreTerminationChatMiddleware()])
|
||||
|
||||
# Execute the agent
|
||||
messages = [Message(role="user", text="test message")]
|
||||
messages = [Message(role="user", contents=["test message"])]
|
||||
response = await agent.run(messages)
|
||||
|
||||
# Verify response was from middleware
|
||||
@@ -1973,7 +1975,7 @@ class TestChatAgentChatMiddleware:
|
||||
agent = Agent(client=client, middleware=[PostTerminationChatMiddleware()])
|
||||
|
||||
# Execute the agent
|
||||
messages = [Message(role="user", text="test message")]
|
||||
messages = [Message(role="user", contents=["test message"])]
|
||||
response = await agent.run(messages)
|
||||
|
||||
# Verify response is from actual execution
|
||||
@@ -2012,7 +2014,7 @@ class TestChatAgentChatMiddleware:
|
||||
middleware=[chat_middleware, function_middleware, agent_middleware],
|
||||
tools=[sample_tool_function],
|
||||
)
|
||||
await agent.run([Message(role="user", text="test")])
|
||||
await agent.run([Message(role="user", contents=["test"])])
|
||||
|
||||
assert execution_order == [
|
||||
"agent_middleware_before",
|
||||
@@ -2041,7 +2043,7 @@ class TestChatAgentChatMiddleware:
|
||||
)
|
||||
]
|
||||
),
|
||||
ChatResponse(messages=[Message(role="assistant", text="Final response")]),
|
||||
ChatResponse(messages=[Message(role="assistant", contents=["Final response"])]),
|
||||
]
|
||||
|
||||
async def tracking_agent_middleware(
|
||||
@@ -2076,7 +2078,7 @@ class TestChatAgentChatMiddleware:
|
||||
tools=[sample_tool_function],
|
||||
)
|
||||
|
||||
response = await agent.run([Message(role="user", text="test")])
|
||||
response = await agent.run([Message(role="user", contents=["test"])])
|
||||
|
||||
assert response is not None
|
||||
assert client.call_count == 2
|
||||
@@ -2168,7 +2170,7 @@ class TestChatAgentChatMiddleware:
|
||||
)
|
||||
]
|
||||
),
|
||||
ChatResponse(messages=[Message(role="assistant", text="Final response")]),
|
||||
ChatResponse(messages=[Message(role="assistant", contents=["Final response"])]),
|
||||
]
|
||||
|
||||
agent = Agent(
|
||||
@@ -2179,7 +2181,7 @@ class TestChatAgentChatMiddleware:
|
||||
)
|
||||
|
||||
response = await agent.run(
|
||||
[Message(role="user", text="Get weather for Seattle")],
|
||||
[Message(role="user", contents=["Get weather for Seattle"])],
|
||||
middleware=[run_chat_middleware, run_function_middleware],
|
||||
)
|
||||
|
||||
@@ -2230,7 +2232,7 @@ class TestChatAgentChatMiddleware:
|
||||
agent = Agent(client=client, middleware=[kwargs_middleware])
|
||||
|
||||
# Execute the agent with runtime options
|
||||
messages = [Message(role="user", text="test message")]
|
||||
messages = [Message(role="user", contents=["test message"])]
|
||||
response = await agent.run(
|
||||
messages,
|
||||
options={"temperature": 0.7, "max_tokens": 100, "custom_param": "test_value"},
|
||||
@@ -2288,7 +2290,7 @@ class TestChatAgentChatMiddleware:
|
||||
# yield AgentResponseUpdate()
|
||||
|
||||
# return _stream()
|
||||
# return AgentResponse(messages=[Message(role="assistant", text="response")])
|
||||
# return AgentResponse(messages=[Message(role="assistant", contents=["response"])])
|
||||
|
||||
# def get_new_thread(self, **kwargs):
|
||||
# return None
|
||||
|
||||
@@ -43,7 +43,7 @@ class TestChatMiddleware:
|
||||
chat_client_base.chat_middleware = [LoggingChatMiddleware()]
|
||||
|
||||
# Execute chat client directly
|
||||
messages = [Message(role="user", text="test message")]
|
||||
messages = [Message(role="user", contents=["test message"])]
|
||||
response = await chat_client_base.get_response(messages)
|
||||
|
||||
# Verify response
|
||||
@@ -68,7 +68,7 @@ class TestChatMiddleware:
|
||||
chat_client_base.chat_middleware = [logging_chat_middleware]
|
||||
|
||||
# Execute chat client directly
|
||||
messages = [Message(role="user", text="test message")]
|
||||
messages = [Message(role="user", contents=["test message"])]
|
||||
response = await chat_client_base.get_response(messages)
|
||||
|
||||
# Verify response
|
||||
@@ -87,14 +87,14 @@ class TestChatMiddleware:
|
||||
# Modify the first message by adding a prefix
|
||||
if context.messages and len(context.messages) > 0:
|
||||
original_text = context.messages[0].text or ""
|
||||
context.messages[0] = Message(role=context.messages[0].role, text=f"MODIFIED: {original_text}")
|
||||
context.messages[0] = Message(role=context.messages[0].role, contents=[f"MODIFIED: {original_text}"])
|
||||
await call_next()
|
||||
|
||||
# Add middleware to chat client
|
||||
chat_client_base.chat_middleware = [message_modifier_middleware]
|
||||
|
||||
# Execute chat client
|
||||
messages = [Message(role="user", text="test message")]
|
||||
messages = [Message(role="user", contents=["test message"])]
|
||||
response = await chat_client_base.get_response(messages)
|
||||
|
||||
# Verify that the message was modified (MockChatClient echoes back the input)
|
||||
@@ -110,7 +110,7 @@ class TestChatMiddleware:
|
||||
async def response_override_middleware(context: ChatContext, call_next: Callable[[], Awaitable[None]]) -> None:
|
||||
# Override the response without calling next()
|
||||
context.result = ChatResponse(
|
||||
messages=[Message(role="assistant", text="MiddlewareTypes overridden response")],
|
||||
messages=[Message(role="assistant", contents=["MiddlewareTypes overridden response"])],
|
||||
response_id="middleware-response-123",
|
||||
)
|
||||
context.terminate = True
|
||||
@@ -119,7 +119,7 @@ class TestChatMiddleware:
|
||||
chat_client_base.chat_middleware = [response_override_middleware]
|
||||
|
||||
# Execute chat client
|
||||
messages = [Message(role="user", text="test message")]
|
||||
messages = [Message(role="user", contents=["test message"])]
|
||||
response = await chat_client_base.get_response(messages)
|
||||
|
||||
# Verify that the response was overridden
|
||||
@@ -148,7 +148,7 @@ class TestChatMiddleware:
|
||||
chat_client_base.chat_middleware = [first_middleware, second_middleware]
|
||||
|
||||
# Execute chat client
|
||||
messages = [Message(role="user", text="test message")]
|
||||
messages = [Message(role="user", contents=["test message"])]
|
||||
response = await chat_client_base.get_response(messages)
|
||||
|
||||
# Verify response
|
||||
@@ -179,7 +179,7 @@ class TestChatMiddleware:
|
||||
agent = Agent(client=client, middleware=[agent_level_chat_middleware])
|
||||
|
||||
# Execute the agent
|
||||
messages = [Message(role="user", text="test message")]
|
||||
messages = [Message(role="user", contents=["test message"])]
|
||||
response = await agent.run(messages)
|
||||
|
||||
# Verify response
|
||||
@@ -213,7 +213,7 @@ class TestChatMiddleware:
|
||||
agent = Agent(client=chat_client_base, middleware=[first_middleware, second_middleware])
|
||||
|
||||
# Execute the agent
|
||||
messages = [Message(role="user", text="test message")]
|
||||
messages = [Message(role="user", contents=["test message"])]
|
||||
response = await agent.run(messages)
|
||||
|
||||
# Verify response
|
||||
@@ -252,7 +252,7 @@ class TestChatMiddleware:
|
||||
chat_client_base.chat_middleware = [streaming_middleware]
|
||||
|
||||
# Execute streaming response
|
||||
messages = [Message(role="user", text="test message")]
|
||||
messages = [Message(role="user", contents=["test message"])]
|
||||
updates: list[object] = []
|
||||
async for update in chat_client_base.get_response(messages, stream=True):
|
||||
updates.append(update)
|
||||
@@ -274,7 +274,7 @@ class TestChatMiddleware:
|
||||
await call_next()
|
||||
|
||||
# First call with run-level middleware
|
||||
messages = [Message(role="user", text="first message")]
|
||||
messages = [Message(role="user", contents=["first message"])]
|
||||
response1 = await chat_client_base.get_response(
|
||||
messages,
|
||||
client_kwargs={"middleware": [counting_middleware]},
|
||||
@@ -283,13 +283,13 @@ class TestChatMiddleware:
|
||||
assert execution_count["count"] == 1
|
||||
|
||||
# Second call WITHOUT run-level middleware - should not execute the middleware
|
||||
messages = [Message(role="user", text="second message")]
|
||||
messages = [Message(role="user", contents=["second message"])]
|
||||
response2 = await chat_client_base.get_response(messages)
|
||||
assert response2 is not None
|
||||
assert execution_count["count"] == 1 # Should still be 1, not 2
|
||||
|
||||
# Third call with run-level middleware again - should execute
|
||||
messages = [Message(role="user", text="third message")]
|
||||
messages = [Message(role="user", contents=["third message"])]
|
||||
response3 = await chat_client_base.get_response(
|
||||
messages,
|
||||
client_kwargs={"middleware": [counting_middleware]},
|
||||
@@ -310,7 +310,7 @@ class TestChatMiddleware:
|
||||
|
||||
async def fake_inner_get_response(**kwargs: Any) -> ChatResponse:
|
||||
assert "middleware" not in kwargs
|
||||
return ChatResponse(messages=[Message(role="assistant", text="ok")])
|
||||
return ChatResponse(messages=[Message(role="assistant", contents=["ok"])])
|
||||
|
||||
with patch.object(
|
||||
chat_client_base,
|
||||
@@ -318,7 +318,7 @@ class TestChatMiddleware:
|
||||
side_effect=fake_inner_get_response,
|
||||
) as mock_inner_get_response:
|
||||
response = await chat_client_base.get_response(
|
||||
[Message(role="user", text="hello")],
|
||||
[Message(role="user", contents=["hello"])],
|
||||
client_kwargs={"middleware": [inspecting_middleware], "trace_id": "trace-123"},
|
||||
)
|
||||
|
||||
@@ -350,7 +350,7 @@ class TestChatMiddleware:
|
||||
chat_client_base.chat_middleware = [kwargs_middleware]
|
||||
|
||||
# Execute chat client with runtime options
|
||||
messages = [Message(role="user", text="test message")]
|
||||
messages = [Message(role="user", contents=["test message"])]
|
||||
response = await chat_client_base.get_response(
|
||||
messages,
|
||||
options={"temperature": 0.7, "max_tokens": 100, "custom_param": "test_value"},
|
||||
@@ -493,12 +493,12 @@ class TestChatMiddleware:
|
||||
]
|
||||
)
|
||||
final_response = ChatResponse(
|
||||
messages=[Message(role="assistant", text="Based on the weather data, it's sunny!")]
|
||||
messages=[Message(role="assistant", contents=["Based on the weather data, it's sunny!"])]
|
||||
)
|
||||
|
||||
client.run_responses = [function_call_response, final_response]
|
||||
# Execute the chat client directly with tools - this should trigger function invocation and middleware
|
||||
messages = [Message(role="user", text="What's the weather in San Francisco?")]
|
||||
messages = [Message(role="user", contents=["What's the weather in San Francisco?"])]
|
||||
response = await client.get_response(messages, options={"tools": [sample_tool_wrapped]})
|
||||
|
||||
# Verify response
|
||||
@@ -557,7 +557,7 @@ class TestChatMiddleware:
|
||||
client.run_responses = [function_call_response]
|
||||
|
||||
# Execute the chat client directly with run-level middleware and tools
|
||||
messages = [Message(role="user", text="What's the weather in New York?")]
|
||||
messages = [Message(role="user", contents=["What's the weather in New York?"])]
|
||||
response = await client.get_response(
|
||||
messages,
|
||||
options={"tools": [sample_tool_wrapped]},
|
||||
@@ -627,11 +627,11 @@ class TestChatMiddleware:
|
||||
)
|
||||
]
|
||||
),
|
||||
ChatResponse(messages=[Message(role="assistant", text="Based on the weather data, it's sunny!")]),
|
||||
ChatResponse(messages=[Message(role="assistant", contents=["Based on the weather data, it's sunny!"])]),
|
||||
]
|
||||
|
||||
response = await client.get_response(
|
||||
[Message(role="user", text="What's the weather in Seattle?")],
|
||||
[Message(role="user", contents=["What's the weather in Seattle?"])],
|
||||
options={"tools": [sample_tool_wrapped]},
|
||||
client_kwargs={"middleware": [run_level_chat_middleware, run_level_function_middleware]},
|
||||
)
|
||||
@@ -710,7 +710,7 @@ class TestChatMiddleware:
|
||||
|
||||
updates: list[ChatResponseUpdate] = []
|
||||
async for update in client.get_response(
|
||||
[Message(role="user", text="What's the weather in Seattle?")],
|
||||
[Message(role="user", contents=["What's the weather in Seattle?"])],
|
||||
options={"tools": [sample_tool_wrapped]},
|
||||
client_kwargs={"middleware": [run_level_chat_middleware, run_level_function_middleware]},
|
||||
stream=True,
|
||||
|
||||
@@ -203,7 +203,7 @@ async def test_chat_client_observability(mock_chat_client, span_exporter: InMemo
|
||||
"""Test that when diagnostics are enabled, telemetry is applied."""
|
||||
client = mock_chat_client()
|
||||
|
||||
messages = [Message(role="user", text="Test message")]
|
||||
messages = [Message(role="user", contents=["Test message"])]
|
||||
span_exporter.clear()
|
||||
response = await client.get_response(messages=messages, options={"model": "Test"})
|
||||
assert response is not None
|
||||
@@ -227,7 +227,7 @@ async def test_chat_client_observability_accepts_model_option(
|
||||
"""Test that telemetry also captures the modern model option."""
|
||||
client = mock_chat_client()
|
||||
|
||||
messages = [Message(role="user", text="Test message")]
|
||||
messages = [Message(role="user", contents=["Test message"])]
|
||||
span_exporter.clear()
|
||||
response = await client.get_response(messages=messages, options={"model": "Test"})
|
||||
assert response is not None
|
||||
@@ -243,7 +243,7 @@ async def test_chat_client_streaming_observability(
|
||||
):
|
||||
"""Test streaming telemetry through the chat telemetry mixin."""
|
||||
client = mock_chat_client()
|
||||
messages = [Message(role="user", text="Test")]
|
||||
messages = [Message(role="user", contents=["Test"])]
|
||||
span_exporter.clear()
|
||||
# Collect all yielded updates
|
||||
updates = []
|
||||
@@ -274,7 +274,7 @@ async def test_chat_client_observability_with_instructions(
|
||||
|
||||
client = mock_chat_client()
|
||||
|
||||
messages = [Message(role="user", text="Test message")]
|
||||
messages = [Message(role="user", contents=["Test message"])]
|
||||
options = {"model": "Test", "instructions": "You are a helpful assistant."}
|
||||
span_exporter.clear()
|
||||
response = await client.get_response(messages=messages, options=options)
|
||||
@@ -303,7 +303,7 @@ async def test_chat_client_streaming_observability_with_instructions(
|
||||
import json
|
||||
|
||||
client = mock_chat_client()
|
||||
messages = [Message(role="user", text="Test")]
|
||||
messages = [Message(role="user", contents=["Test"])]
|
||||
options = {"model": "Test", "instructions": "You are a helpful assistant."}
|
||||
span_exporter.clear()
|
||||
|
||||
@@ -332,7 +332,7 @@ async def test_chat_client_observability_without_instructions(
|
||||
"""Test that system_instructions attribute is not set when instructions are not provided."""
|
||||
client = mock_chat_client()
|
||||
|
||||
messages = [Message(role="user", text="Test message")]
|
||||
messages = [Message(role="user", contents=["Test message"])]
|
||||
options = {"model": "Test"} # No instructions
|
||||
span_exporter.clear()
|
||||
response = await client.get_response(messages=messages, options=options)
|
||||
@@ -353,7 +353,7 @@ async def test_chat_client_observability_with_empty_instructions(
|
||||
"""Test that system_instructions attribute is not set when instructions is an empty string."""
|
||||
client = mock_chat_client()
|
||||
|
||||
messages = [Message(role="user", text="Test message")]
|
||||
messages = [Message(role="user", contents=["Test message"])]
|
||||
options = {"model": "Test", "instructions": ""} # Empty string
|
||||
span_exporter.clear()
|
||||
response = await client.get_response(messages=messages, options=options)
|
||||
@@ -376,7 +376,7 @@ async def test_chat_client_observability_with_list_instructions(
|
||||
|
||||
client = mock_chat_client()
|
||||
|
||||
messages = [Message(role="user", text="Test message")]
|
||||
messages = [Message(role="user", contents=["Test message"])]
|
||||
options = {"model": "Test", "instructions": ["Instruction 1", "Instruction 2"]}
|
||||
span_exporter.clear()
|
||||
response = await client.get_response(messages=messages, options=options)
|
||||
@@ -397,7 +397,7 @@ async def test_chat_client_observability_with_list_instructions(
|
||||
async def test_chat_client_without_model_observability(mock_chat_client, span_exporter: InMemorySpanExporter):
|
||||
"""Test telemetry shouldn't fail when the model is not provided for unknown reason."""
|
||||
client = mock_chat_client()
|
||||
messages = [Message(role="user", text="Test")]
|
||||
messages = [Message(role="user", contents=["Test"])]
|
||||
span_exporter.clear()
|
||||
response = await client.get_response(messages=messages)
|
||||
|
||||
@@ -414,7 +414,7 @@ async def test_chat_client_without_model_observability(mock_chat_client, span_ex
|
||||
async def test_chat_client_streaming_without_model_observability(mock_chat_client, span_exporter: InMemorySpanExporter):
|
||||
"""Test streaming telemetry shouldn't fail when the model is not provided for unknown reason."""
|
||||
client = mock_chat_client()
|
||||
messages = [Message(role="user", text="Test")]
|
||||
messages = [Message(role="user", contents=["Test"])]
|
||||
span_exporter.clear()
|
||||
# Collect all yielded updates
|
||||
updates = []
|
||||
@@ -1549,7 +1549,7 @@ async def test_chat_client_observability_exception(mock_chat_client, span_export
|
||||
raise ValueError("Test error")
|
||||
|
||||
client = FailingChatClient()
|
||||
messages = [Message(role="user", text="Test")]
|
||||
messages = [Message(role="user", contents=["Test"])]
|
||||
|
||||
span_exporter.clear()
|
||||
with pytest.raises(ValueError, match="Test error"):
|
||||
@@ -1579,7 +1579,7 @@ async def test_chat_client_streaming_observability_exception(mock_chat_client, s
|
||||
return ResponseStream(_stream(), finalizer=ChatResponse.from_updates)
|
||||
|
||||
client = FailingStreamingChatClient()
|
||||
messages = [Message(role="user", text="Test")]
|
||||
messages = [Message(role="user", contents=["Test"])]
|
||||
|
||||
span_exporter.clear()
|
||||
with pytest.raises(ValueError, match="Streaming error"):
|
||||
@@ -2079,13 +2079,13 @@ async def test_capture_messages_with_finish_reason(mock_chat_client, span_export
|
||||
class ClientWithFinishReason(mock_chat_client):
|
||||
async def _inner_get_response(self, *, messages, options, **kwargs):
|
||||
return ChatResponse(
|
||||
messages=[Message(role="assistant", text="Done")],
|
||||
messages=[Message(role="assistant", contents=["Done"])],
|
||||
usage_details=UsageDetails(input_token_count=5, output_token_count=10),
|
||||
finish_reason="stop",
|
||||
)
|
||||
|
||||
client = ClientWithFinishReason()
|
||||
messages = [Message(role="user", text="Test")]
|
||||
messages = [Message(role="user", contents=["Test"])]
|
||||
|
||||
span_exporter.clear()
|
||||
response = await client.get_response(messages=messages, options={"model": "Test"})
|
||||
@@ -2175,7 +2175,7 @@ async def test_agent_streaming_exception(span_exporter: InMemorySpanExporter, en
|
||||
async def test_chat_client_when_disabled(mock_chat_client, span_exporter: InMemorySpanExporter):
|
||||
"""Test that no spans are created when instrumentation is disabled."""
|
||||
client = mock_chat_client()
|
||||
messages = [Message(role="user", text="Test")]
|
||||
messages = [Message(role="user", contents=["Test"])]
|
||||
|
||||
span_exporter.clear()
|
||||
response = await client.get_response(messages=messages, options={"model": "Test"})
|
||||
@@ -2190,7 +2190,7 @@ async def test_chat_client_when_disabled(mock_chat_client, span_exporter: InMemo
|
||||
async def test_chat_client_streaming_when_disabled(mock_chat_client, span_exporter: InMemorySpanExporter):
|
||||
"""Test streaming creates no spans when instrumentation is disabled."""
|
||||
client = mock_chat_client()
|
||||
messages = [Message(role="user", text="Test")]
|
||||
messages = [Message(role="user", contents=["Test"])]
|
||||
|
||||
span_exporter.clear()
|
||||
updates = []
|
||||
@@ -2540,7 +2540,7 @@ async def test_layer_ordering_span_sequence_with_function_calling(span_exporter:
|
||||
],
|
||||
)
|
||||
return ChatResponse(
|
||||
messages=[Message(role="assistant", text="The weather in Seattle is sunny!")],
|
||||
messages=[Message(role="assistant", contents=["The weather in Seattle is sunny!"])],
|
||||
)
|
||||
|
||||
return _get()
|
||||
@@ -2549,7 +2549,7 @@ async def test_layer_ordering_span_sequence_with_function_calling(span_exporter:
|
||||
span_exporter.clear()
|
||||
|
||||
response = await client.get_response(
|
||||
messages=[Message(role="user", text="What's the weather in Seattle?")],
|
||||
messages=[Message(role="user", contents=["What's the weather in Seattle?"])],
|
||||
options={"tools": [get_weather], "tool_choice": "auto"},
|
||||
)
|
||||
|
||||
@@ -2598,7 +2598,7 @@ async def test_agent_and_chat_spans_do_not_duplicate_response_telemetry(
|
||||
|
||||
def _finalize(updates: Sequence[ChatResponseUpdate]) -> ChatResponse:
|
||||
return ChatResponse(
|
||||
messages=[Message(role="assistant", text="Nested response")],
|
||||
messages=[Message(role="assistant", contents=["Nested response"])],
|
||||
response_id="nested_resp_123",
|
||||
usage_details=UsageDetails(input_token_count=11, output_token_count=22),
|
||||
finish_reason="stop",
|
||||
@@ -2608,7 +2608,7 @@ async def test_agent_and_chat_spans_do_not_duplicate_response_telemetry(
|
||||
|
||||
async def _get() -> ChatResponse:
|
||||
return ChatResponse(
|
||||
messages=[Message(role="assistant", text="Nested response")],
|
||||
messages=[Message(role="assistant", contents=["Nested response"])],
|
||||
response_id="nested_resp_123",
|
||||
usage_details=UsageDetails(input_token_count=11, output_token_count=22),
|
||||
finish_reason="stop",
|
||||
@@ -2666,12 +2666,12 @@ async def test_capture_messages_preserves_non_ascii_characters(mock_chat_client,
|
||||
class ClientWithJapanese(mock_chat_client):
|
||||
async def _inner_get_response(self, *, messages, options, **kwargs):
|
||||
return ChatResponse(
|
||||
messages=[Message(role="assistant", text=japanese_text)],
|
||||
messages=[Message(role="assistant", contents=[japanese_text])],
|
||||
usage_details=UsageDetails(input_token_count=5, output_token_count=10),
|
||||
)
|
||||
|
||||
client = ClientWithJapanese()
|
||||
messages = [Message(role="user", text=japanese_text)]
|
||||
messages = [Message(role="user", contents=[japanese_text])]
|
||||
|
||||
span_exporter.clear()
|
||||
response = await client.get_response(messages=messages, options={"model": "Test"})
|
||||
@@ -2715,7 +2715,7 @@ async def test_system_instructions_preserves_non_ascii_characters(span_exporter:
|
||||
_capture_messages(
|
||||
span=span,
|
||||
provider_name="test_provider",
|
||||
messages=[Message(role="user", text="Test")],
|
||||
messages=[Message(role="user", contents=["Test"])],
|
||||
system_instructions=chinese_text,
|
||||
)
|
||||
|
||||
@@ -2840,7 +2840,7 @@ async def test_agent_instructions_from_default_options(
|
||||
agent = mock_chat_agent()
|
||||
agent.default_options = {"model": "TestModel", "instructions": "Default system instructions."}
|
||||
|
||||
messages = [Message(role="user", text="Test message")]
|
||||
messages = [Message(role="user", contents=["Test message"])]
|
||||
span_exporter.clear()
|
||||
response = await agent.run(messages)
|
||||
|
||||
@@ -2866,7 +2866,7 @@ async def test_agent_instructions_from_options_override(
|
||||
agent = mock_chat_agent()
|
||||
agent.default_options = {"model": "TestModel"} # No default instructions
|
||||
|
||||
messages = [Message(role="user", text="Test message")]
|
||||
messages = [Message(role="user", contents=["Test message"])]
|
||||
span_exporter.clear()
|
||||
response = await agent.run(messages, options={"instructions": "Override instructions."})
|
||||
|
||||
@@ -2891,7 +2891,7 @@ async def test_agent_instructions_merged_from_default_and_options(
|
||||
agent = mock_chat_agent()
|
||||
agent.default_options = {"model": "TestModel", "instructions": "Default instructions."}
|
||||
|
||||
messages = [Message(role="user", text="Test message")]
|
||||
messages = [Message(role="user", contents=["Test message"])]
|
||||
span_exporter.clear()
|
||||
response = await agent.run(messages, options={"instructions": "Additional instructions."})
|
||||
|
||||
@@ -2918,7 +2918,7 @@ async def test_agent_streaming_instructions_from_default_options(
|
||||
agent = mock_chat_agent()
|
||||
agent.default_options = {"model": "TestModel", "instructions": "Default streaming instructions."}
|
||||
|
||||
messages = [Message(role="user", text="Test message")]
|
||||
messages = [Message(role="user", contents=["Test message"])]
|
||||
span_exporter.clear()
|
||||
updates = []
|
||||
stream = agent.run(messages, stream=True)
|
||||
@@ -2947,7 +2947,7 @@ async def test_agent_streaming_instructions_merged_from_default_and_options(
|
||||
agent = mock_chat_agent()
|
||||
agent.default_options = {"model": "TestModel", "instructions": "Default instructions."}
|
||||
|
||||
messages = [Message(role="user", text="Test message")]
|
||||
messages = [Message(role="user", contents=["Test message"])]
|
||||
span_exporter.clear()
|
||||
updates = []
|
||||
stream = agent.run(messages, stream=True, options={"instructions": "Stream override."})
|
||||
@@ -2975,7 +2975,7 @@ async def test_agent_no_instructions_in_default_or_options(
|
||||
agent = mock_chat_agent()
|
||||
agent.default_options = {"model": "TestModel"} # No instructions
|
||||
|
||||
messages = [Message(role="user", text="Test message")]
|
||||
messages = [Message(role="user", contents=["Test message"])]
|
||||
span_exporter.clear()
|
||||
response = await agent.run(messages)
|
||||
|
||||
@@ -3204,7 +3204,7 @@ async def test_agent_invoke_span_aggregates_usage_across_tool_calls(span_exporte
|
||||
usage_details=UsageDetails(input_token_count=2239, output_token_count=192),
|
||||
),
|
||||
ChatResponse(
|
||||
messages=Message(role="assistant", text="The weather in Seattle is sunny."),
|
||||
messages=Message(role="assistant", contents=["The weather in Seattle is sunny."]),
|
||||
usage_details=UsageDetails(input_token_count=2569, output_token_count=99),
|
||||
),
|
||||
]
|
||||
@@ -3248,7 +3248,7 @@ async def test_agent_invoke_span_usage_single_call(span_exporter: InMemorySpanEx
|
||||
client = MockBaseChatClient()
|
||||
client.run_responses = [
|
||||
ChatResponse(
|
||||
messages=Message(role="assistant", text="Hello!"),
|
||||
messages=Message(role="assistant", contents=["Hello!"]),
|
||||
usage_details=UsageDetails(input_token_count=100, output_token_count=50),
|
||||
),
|
||||
]
|
||||
@@ -3291,7 +3291,7 @@ async def test_agent_invoke_span_aggregates_usage_on_max_iterations_exhaustion(s
|
||||
),
|
||||
# Exhaustion path: consumed by tool_choice="none" final call (mock ignores usage)
|
||||
ChatResponse(
|
||||
messages=Message(role="assistant", text="placeholder"),
|
||||
messages=Message(role="assistant", contents=["placeholder"]),
|
||||
usage_details=UsageDetails(input_token_count=300, output_token_count=60),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -8,8 +8,6 @@ import pytest
|
||||
from agent_framework import (
|
||||
AgentContext,
|
||||
AgentSession,
|
||||
BaseContextProvider,
|
||||
BaseHistoryProvider,
|
||||
ChatContext,
|
||||
ContextProvider,
|
||||
HistoryProvider,
|
||||
@@ -237,23 +235,6 @@ class TestContextProvider:
|
||||
await provider.after_run(agent=None, session=session, context=ctx, state={}) # type: ignore[arg-type]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Deprecated provider alias tests
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestDeprecatedProviderAliases:
|
||||
def test_base_context_provider_warns_and_is_compatible(self) -> None:
|
||||
with pytest.warns(DeprecationWarning, match="BaseContextProvider is deprecated. Use ContextProvider instead."):
|
||||
provider = BaseContextProvider(source_id="test")
|
||||
|
||||
assert isinstance(provider, ContextProvider)
|
||||
|
||||
def test_base_provider_aliases_preserve_subtyping(self) -> None:
|
||||
assert issubclass(BaseContextProvider, ContextProvider)
|
||||
assert issubclass(BaseHistoryProvider, HistoryProvider)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# HistoryProvider tests
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@@ -699,7 +699,7 @@ def test_ai_content_serialization(args: dict):
|
||||
def test_chat_message_text():
|
||||
"""Test the Message class to ensure it initializes correctly with text content."""
|
||||
# Create a Message with a role and text content
|
||||
message = Message(role="user", text="Hello, how are you?")
|
||||
message = Message(role="user", contents=["Hello, how are you?"])
|
||||
|
||||
# Check the type and content
|
||||
assert message.role == "user"
|
||||
@@ -730,7 +730,7 @@ def test_chat_message_contents():
|
||||
|
||||
|
||||
def test_chat_message_with_chatrole_instance():
|
||||
m = Message(role="user", text="hi")
|
||||
m = Message(role="user", contents=["hi"])
|
||||
assert m.role == "user"
|
||||
assert m.text == "hi"
|
||||
|
||||
@@ -741,7 +741,7 @@ def test_chat_message_with_chatrole_instance():
|
||||
def test_chat_response():
|
||||
"""Test the ChatResponse class to ensure it initializes correctly with a message."""
|
||||
# Create a Message
|
||||
message = Message(role="assistant", text="I'm doing well, thank you!")
|
||||
message = Message(role="assistant", contents=["I'm doing well, thank you!"])
|
||||
|
||||
# Create a ChatResponse with the message
|
||||
response = ChatResponse(messages=message)
|
||||
@@ -756,7 +756,7 @@ def test_chat_response():
|
||||
|
||||
def test_chat_response_accepts_model_alias() -> None:
|
||||
"""Test ChatResponse accepts model and exposes it through model alias."""
|
||||
response = ChatResponse(messages=Message(role="assistant", text="Hello"), model="claude-test")
|
||||
response = ChatResponse(messages=Message(role="assistant", contents=["Hello"]), model="claude-test")
|
||||
|
||||
assert response.model == "claude-test"
|
||||
assert response.model == "claude-test"
|
||||
@@ -769,7 +769,7 @@ class OutputModel(BaseModel):
|
||||
def test_chat_response_with_format():
|
||||
"""Test the ChatResponse class to ensure it initializes correctly with a message."""
|
||||
# Create a Message
|
||||
message = Message(role="assistant", text='{"response": "Hello"}')
|
||||
message = Message(role="assistant", contents=['{"response": "Hello"}'])
|
||||
|
||||
# Create a ChatResponse with the message
|
||||
response = ChatResponse(messages=message, response_format=OutputModel)
|
||||
@@ -786,7 +786,7 @@ def test_chat_response_with_format():
|
||||
def test_chat_response_with_format_init():
|
||||
"""Test the ChatResponse class to ensure it initializes correctly with a message."""
|
||||
# Create a Message
|
||||
message = Message(role="assistant", text='{"response": "Hello"}')
|
||||
message = Message(role="assistant", contents=['{"response": "Hello"}'])
|
||||
|
||||
# Create a ChatResponse with the message
|
||||
response = ChatResponse(messages=message, response_format=OutputModel)
|
||||
@@ -802,7 +802,7 @@ def test_chat_response_with_format_init():
|
||||
|
||||
def test_chat_response_with_mapping_response_format() -> None:
|
||||
"""ChatResponse.value should parse JSON when response_format is a mapping."""
|
||||
message = Message(role="assistant", text='{"response": "Hello"}')
|
||||
message = Message(role="assistant", contents=['{"response": "Hello"}'])
|
||||
response = ChatResponse(
|
||||
messages=message,
|
||||
response_format={"type": "object", "properties": {"response": {"type": "string"}}},
|
||||
@@ -821,7 +821,7 @@ def test_chat_response_value_raises_on_invalid_schema():
|
||||
name: str = Field(min_length=10)
|
||||
score: int = Field(gt=0, le=100)
|
||||
|
||||
message = Message(role="assistant", text='{"id": 1, "name": "test", "score": -5}')
|
||||
message = Message(role="assistant", contents=['{"id": 1, "name": "test", "score": -5}'])
|
||||
response = ChatResponse(messages=message, response_format=StrictSchema)
|
||||
|
||||
with raises(ValidationError) as exc_info:
|
||||
@@ -842,7 +842,7 @@ def test_agent_response_value_raises_on_invalid_schema():
|
||||
name: str = Field(min_length=10)
|
||||
score: int = Field(gt=0, le=100)
|
||||
|
||||
message = Message(role="assistant", text='{"id": 1, "name": "test", "score": -5}')
|
||||
message = Message(role="assistant", contents=['{"id": 1, "name": "test", "score": -5}'])
|
||||
response = AgentResponse(messages=message, response_format=StrictSchema)
|
||||
|
||||
with raises(ValidationError) as exc_info:
|
||||
@@ -1185,7 +1185,7 @@ def test_chat_options_and_tool_choice_required_specific_function() -> None:
|
||||
|
||||
@fixture
|
||||
def chat_message() -> Message:
|
||||
return Message(role="user", text="Hello")
|
||||
return Message(role="user", contents=["Hello"])
|
||||
|
||||
|
||||
@fixture
|
||||
@@ -1302,7 +1302,7 @@ def test_agent_run_response_created_at() -> None:
|
||||
# Test with a properly formatted UTC timestamp
|
||||
utc_timestamp = "2024-12-01T00:31:30.000000Z"
|
||||
response = AgentResponse(
|
||||
messages=[Message(role="assistant", text="Hello")],
|
||||
messages=[Message(role="assistant", contents=["Hello"])],
|
||||
created_at=utc_timestamp,
|
||||
)
|
||||
assert response.created_at == utc_timestamp
|
||||
@@ -1312,7 +1312,7 @@ def test_agent_run_response_created_at() -> None:
|
||||
now_utc = datetime.now(tz=timezone.utc)
|
||||
formatted_utc = now_utc.strftime("%Y-%m-%dT%H:%M:%S.%fZ")
|
||||
response_with_now = AgentResponse(
|
||||
messages=[Message(role="assistant", text="Hello")],
|
||||
messages=[Message(role="assistant", contents=["Hello"])],
|
||||
created_at=formatted_utc,
|
||||
)
|
||||
assert response_with_now.created_at == formatted_utc
|
||||
@@ -1466,7 +1466,7 @@ def test_chat_tool_mode_eq_with_string():
|
||||
|
||||
@fixture
|
||||
def agent_run_response_async() -> AgentResponse:
|
||||
return AgentResponse(messages=[Message(role="user", text="Hello")])
|
||||
return AgentResponse(messages=[Message(role="user", contents=["Hello"])])
|
||||
|
||||
|
||||
async def test_agent_run_response_from_async_generator():
|
||||
|
||||
@@ -158,8 +158,8 @@ async def test_agent_executor_checkpoint_stores_and_restores_state() -> None:
|
||||
|
||||
# Add some initial messages to the session state to verify session state persistence
|
||||
initial_messages = [
|
||||
Message(role="user", text="Initial message 1"),
|
||||
Message(role="assistant", text="Initial response 1"),
|
||||
Message(role="user", contents=["Initial message 1"]),
|
||||
Message(role="assistant", contents=["Initial response 1"]),
|
||||
]
|
||||
initial_session.state["history"] = {"messages": initial_messages}
|
||||
|
||||
@@ -256,9 +256,9 @@ async def test_agent_executor_save_and_restore_state_directly() -> None:
|
||||
|
||||
# Add messages to session state
|
||||
session_messages = [
|
||||
Message(role="user", text="Message in session 1"),
|
||||
Message(role="assistant", text="Session response 1"),
|
||||
Message(role="user", text="Message in session 2"),
|
||||
Message(role="user", contents=["Message in session 1"]),
|
||||
Message(role="assistant", contents=["Session response 1"]),
|
||||
Message(role="user", contents=["Message in session 2"]),
|
||||
]
|
||||
session.state["history"] = {"messages": session_messages}
|
||||
|
||||
@@ -266,8 +266,8 @@ async def test_agent_executor_save_and_restore_state_directly() -> None:
|
||||
|
||||
# Add messages to executor cache
|
||||
cache_messages = [
|
||||
Message(role="user", text="Cached user message"),
|
||||
Message(role="assistant", text="Cached assistant response"),
|
||||
Message(role="user", contents=["Cached user message"]),
|
||||
Message(role="assistant", contents=["Cached assistant response"]),
|
||||
]
|
||||
executor._cache = list(cache_messages) # type: ignore[reportPrivateUsage]
|
||||
|
||||
@@ -562,7 +562,7 @@ async def test_checkpoint_restore_works_without_context_mode_in_state() -> None:
|
||||
|
||||
# Simulate a checkpoint state without context_mode (as saved by the new code)
|
||||
state: dict[str, Any] = {
|
||||
"cache": [Message(role="user", text="cached msg")],
|
||||
"cache": [Message(role="user", contents=["cached msg"])],
|
||||
"full_conversation": [],
|
||||
"agent_session": AgentSession().to_dict(),
|
||||
"pending_agent_requests": {},
|
||||
|
||||
@@ -8,7 +8,7 @@ from agent_framework._workflows._events import WorkflowEvent
|
||||
|
||||
def test_workflow_event_with_agent_response_data_type() -> None:
|
||||
"""Verify WorkflowEvent[AgentResponse].data is typed as AgentResponse."""
|
||||
response = AgentResponse(messages=[Message(role="assistant", text="Hello")])
|
||||
response = AgentResponse(messages=[Message(role="assistant", contents=["Hello"])])
|
||||
event: WorkflowEvent[AgentResponse] = WorkflowEvent.emit(executor_id="test", data=response)
|
||||
|
||||
# This assignment should pass type checking without a cast
|
||||
@@ -29,7 +29,7 @@ def test_workflow_event_with_agent_response_update_data_type() -> None:
|
||||
|
||||
def test_workflow_event_repr() -> None:
|
||||
"""Verify WorkflowEvent.__repr__ uses consistent format."""
|
||||
response = AgentResponse(messages=[Message(role="assistant", text="Hello")])
|
||||
response = AgentResponse(messages=[Message(role="assistant", contents=["Hello"])])
|
||||
event: WorkflowEvent[AgentResponse] = WorkflowEvent.emit(executor_id="test", data=response)
|
||||
|
||||
repr_str = repr(event)
|
||||
|
||||
@@ -540,7 +540,7 @@ async def test_executor_invoked_event_data_not_mutated_by_handler():
|
||||
async def mutator(messages: list[Message], ctx: WorkflowContext[list[Message]]) -> None:
|
||||
# The handler mutates the input list by appending new messages
|
||||
original_len = len(messages)
|
||||
messages.append(Message(role="assistant", text="Added by executor"))
|
||||
messages.append(Message(role="assistant", contents=["Added by executor"]))
|
||||
await ctx.send_message(messages)
|
||||
# Verify mutation happened
|
||||
assert len(messages) == original_len + 1
|
||||
@@ -548,7 +548,7 @@ async def test_executor_invoked_event_data_not_mutated_by_handler():
|
||||
workflow = WorkflowBuilder(start_executor=mutator).build()
|
||||
|
||||
# Run with a single user message
|
||||
input_messages = [Message(role="user", text="hello")]
|
||||
input_messages = [Message(role="user", contents=["hello"])]
|
||||
events = await workflow.run(input_messages)
|
||||
|
||||
# Find the invoked event for the Mutator executor
|
||||
|
||||
@@ -322,7 +322,7 @@ class _RoundTripCoordinator(Executor):
|
||||
assert response.full_conversation is not None
|
||||
await ctx.send_message(
|
||||
AgentExecutorRequest(
|
||||
messages=list(response.full_conversation) + [Message(role="user", text="apply feedback")],
|
||||
messages=list(response.full_conversation) + [Message(role="user", contents=["apply feedback"])],
|
||||
should_respond=True,
|
||||
),
|
||||
target_id=self._target_agent_id,
|
||||
@@ -418,7 +418,7 @@ class _FullHistoryReplayCoordinator(Executor):
|
||||
ctx: WorkflowContext[AgentExecutorRequest, Any],
|
||||
) -> None:
|
||||
full_conv = list(response.full_conversation or response.agent_response.messages)
|
||||
full_conv.append(Message(role="user", text="follow-up"))
|
||||
full_conv.append(Message(role="user", contents=["follow-up"]))
|
||||
# Simulate a prior run: the target executor has a stored previous_response_id.
|
||||
self._target_exec._session.service_session_id = "resp_PREVIOUS_RUN" # pyright: ignore[reportPrivateUsage]
|
||||
await ctx.send_message(
|
||||
|
||||
@@ -344,7 +344,7 @@ class TestWorkflowAgent:
|
||||
workflow = WorkflowBuilder(start_executor=yielding_executor).build()
|
||||
|
||||
# Run directly - should return output event (type='output') in result
|
||||
direct_result = await workflow.run([Message(role="user", text="hello")])
|
||||
direct_result = await workflow.run([Message(role="user", contents=["hello"])])
|
||||
direct_outputs = direct_result.get_outputs()
|
||||
assert len(direct_outputs) == 1
|
||||
assert direct_outputs[0] == "processed: hello"
|
||||
@@ -479,8 +479,8 @@ class TestWorkflowAgent:
|
||||
async def list_yielding_executor(messages: list[Message], ctx: WorkflowContext[Never, list[Message]]) -> None:
|
||||
# Yield a list of Messages (as SequentialBuilder does)
|
||||
msg_list = [
|
||||
Message(role="user", text="first message"),
|
||||
Message(role="assistant", text="second message"),
|
||||
Message(role="user", contents=["first message"]),
|
||||
Message(role="assistant", contents=["second message"]),
|
||||
Message(
|
||||
role="assistant",
|
||||
contents=[Content.from_text(text="third"), Content.from_text(text="fourth")],
|
||||
|
||||
@@ -65,7 +65,7 @@ class DummyAgent(BaseAgent):
|
||||
if isinstance(m, Message):
|
||||
norm.append(m)
|
||||
elif isinstance(m, str):
|
||||
norm.append(Message(role="user", text=m))
|
||||
norm.append(Message(role="user", contents=[m]))
|
||||
return AgentResponse(messages=norm)
|
||||
|
||||
async def _run_stream_impl(self) -> AsyncIterator[AgentResponseUpdate]:
|
||||
|
||||
@@ -469,10 +469,10 @@ async def test_magentic_kwargs_flow_to_agents() -> None:
|
||||
self.task_ledger = None
|
||||
|
||||
async def plan(self, magentic_context: MagenticContext) -> Message:
|
||||
return Message(role="assistant", text="Plan: Test task", author_name="manager")
|
||||
return Message(role="assistant", contents=["Plan: Test task"], author_name="manager")
|
||||
|
||||
async def replan(self, magentic_context: MagenticContext) -> Message:
|
||||
return Message(role="assistant", text="Replan: Test task", author_name="manager")
|
||||
return Message(role="assistant", contents=["Replan: Test task"], author_name="manager")
|
||||
|
||||
async def create_progress_ledger(self, magentic_context: MagenticContext) -> MagenticProgressLedger:
|
||||
# Return completed on first call
|
||||
@@ -485,7 +485,7 @@ async def test_magentic_kwargs_flow_to_agents() -> None:
|
||||
)
|
||||
|
||||
async def prepare_final_answer(self, magentic_context: MagenticContext) -> Message:
|
||||
return Message(role="assistant", text="Final answer", author_name="manager")
|
||||
return Message(role="assistant", contents=["Final answer"], author_name="manager")
|
||||
|
||||
agent = _KwargsCapturingAgent(name="agent1")
|
||||
manager = _MockManager()
|
||||
@@ -520,10 +520,10 @@ async def test_magentic_kwargs_stored_in_state() -> None:
|
||||
self.task_ledger = None
|
||||
|
||||
async def plan(self, magentic_context: MagenticContext) -> Message:
|
||||
return Message(role="assistant", text="Plan", author_name="manager")
|
||||
return Message(role="assistant", contents=["Plan"], author_name="manager")
|
||||
|
||||
async def replan(self, magentic_context: MagenticContext) -> Message:
|
||||
return Message(role="assistant", text="Replan", author_name="manager")
|
||||
return Message(role="assistant", contents=["Replan"], author_name="manager")
|
||||
|
||||
async def create_progress_ledger(self, magentic_context: MagenticContext) -> MagenticProgressLedger:
|
||||
return MagenticProgressLedger(
|
||||
@@ -535,7 +535,7 @@ async def test_magentic_kwargs_stored_in_state() -> None:
|
||||
)
|
||||
|
||||
async def prepare_final_answer(self, magentic_context: MagenticContext) -> Message:
|
||||
return Message(role="assistant", text="Final", author_name="manager")
|
||||
return Message(role="assistant", contents=["Final"], author_name="manager")
|
||||
|
||||
agent = _KwargsCapturingAgent(name="agent1")
|
||||
manager = _MockManager()
|
||||
|
||||
+2
-2
@@ -640,7 +640,7 @@ class InvokeAzureAgentExecutor(DeclarativeActionExecutor):
|
||||
|
||||
# Add user input to conversation history first (via state.append only)
|
||||
if input_text:
|
||||
user_message = Message(role="user", text=input_text)
|
||||
user_message = Message(role="user", contents=[input_text])
|
||||
state.append(messages_path, user_message)
|
||||
|
||||
# Get conversation history from state AFTER adding user message
|
||||
@@ -717,7 +717,7 @@ class InvokeAzureAgentExecutor(DeclarativeActionExecutor):
|
||||
"Agent '%s': No messages in response, creating simple assistant message",
|
||||
agent_name,
|
||||
)
|
||||
assistant_message = Message(role="assistant", text=accumulated_response)
|
||||
assistant_message = Message(role="assistant", contents=[accumulated_response])
|
||||
state.append(messages_path, assistant_message)
|
||||
|
||||
# Store results in state - support both schema formats:
|
||||
|
||||
+3
-1
@@ -588,7 +588,9 @@ class BaseToolExecutor(DeclarativeActionExecutor):
|
||||
messages=[
|
||||
Message(
|
||||
role="assistant",
|
||||
text=f"Function '{function_name}' was rejected: {response.reason or 'No reason provided'}",
|
||||
contents=[
|
||||
f"Function '{function_name}' was rejected: {response.reason or 'No reason provided'}"
|
||||
],
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
@@ -4,7 +4,7 @@ description = "Declarative specification support for Microsoft Agent Framework."
|
||||
authors = [{ name = "Microsoft", email = "af-support@microsoft.com"}]
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
version = "1.0.0b260330"
|
||||
version = "1.0.0b260402"
|
||||
license-files = ["LICENSE"]
|
||||
urls.homepage = "https://aka.ms/agent-framework"
|
||||
urls.source = "https://github.com/microsoft/agent-framework/tree/main/python"
|
||||
@@ -22,7 +22,7 @@ classifiers = [
|
||||
"Typing :: Typed",
|
||||
]
|
||||
dependencies = [
|
||||
"agent-framework-core>=1.0.0rc6",
|
||||
"agent-framework-core>=1.0.0,<2",
|
||||
"powerfx>=0.0.32,<0.0.35; python_version < '3.14'",
|
||||
"pyyaml>=6.0,<7.0",
|
||||
]
|
||||
|
||||
@@ -314,7 +314,7 @@ class InMemoryConversationStore(ConversationStore):
|
||||
text_obj = first_content.get("text", "")
|
||||
text = text_obj if isinstance(text_obj, str) else str(text_obj)
|
||||
|
||||
chat_msg = Message(role=role, text=text) # type: ignore[arg-type]
|
||||
chat_msg = Message(role=role, contents=[text]) # type: ignore[arg-type]
|
||||
chat_messages.append(chat_msg)
|
||||
|
||||
# Add messages to internal storage
|
||||
|
||||
@@ -484,6 +484,43 @@ def parse_input_for_type(input_data: Any, target_type: type) -> Any:
|
||||
return input_data
|
||||
|
||||
|
||||
def _build_message_from_legacy_payload(input_data: str | dict[str, Any]) -> Message:
|
||||
"""Convert raw DevUI input into a framework Message.
|
||||
|
||||
This preserves DevUI compatibility for older payloads that still send
|
||||
``{"role": "...", "text": "..."}`` instead of the framework-native
|
||||
``{"role": "...", "contents": [...]}`` shape.
|
||||
"""
|
||||
if isinstance(input_data, str):
|
||||
return Message(role="user", contents=[input_data])
|
||||
|
||||
role = input_data.get("role", "user")
|
||||
role = role if isinstance(role, str) else str(role)
|
||||
|
||||
if "contents" in input_data:
|
||||
contents = input_data["contents"]
|
||||
else:
|
||||
contents = None
|
||||
for field in ("text", "message", "content", "input", "data"):
|
||||
if field in input_data:
|
||||
contents = input_data[field]
|
||||
break
|
||||
|
||||
if contents is None:
|
||||
contents_list: list[Any] = []
|
||||
elif isinstance(contents, list):
|
||||
contents_list = contents # type: ignore[reportUnknownVariableType]
|
||||
else:
|
||||
contents_list = [contents]
|
||||
|
||||
kwargs: dict[str, Any] = {}
|
||||
for field in ("author_name", "message_id", "additional_properties", "raw_representation"):
|
||||
if field in input_data:
|
||||
kwargs[field] = input_data[field]
|
||||
|
||||
return Message(role=role, contents=contents_list, **kwargs)
|
||||
|
||||
|
||||
def _parse_string_input(input_str: str, target_type: type) -> Any:
|
||||
"""Parse string input to target type.
|
||||
|
||||
@@ -531,6 +568,14 @@ def _parse_string_input(input_str: str, target_type: type) -> Any:
|
||||
# SerializationMixin (like Message)
|
||||
if is_serialization_mixin(target_type):
|
||||
try:
|
||||
if target_type is Message:
|
||||
if input_str.strip().startswith("{"):
|
||||
data = json.loads(input_str)
|
||||
parsed_dict = _string_key_dict(data)
|
||||
if parsed_dict is not None:
|
||||
return _build_message_from_legacy_payload(parsed_dict)
|
||||
return _build_message_from_legacy_payload(input_str)
|
||||
|
||||
# Try parsing as JSON dict first
|
||||
if input_str.strip().startswith("{"):
|
||||
data = json.loads(input_str)
|
||||
@@ -538,20 +583,10 @@ def _parse_string_input(input_str: str, target_type: type) -> Any:
|
||||
return target_type.from_dict(data) # type: ignore
|
||||
return target_type(**data) # type: ignore
|
||||
|
||||
# For Message specifically: create from text
|
||||
# Try common field patterns
|
||||
# Try other common fields
|
||||
common_fields = ["text", "message", "content"]
|
||||
sig = inspect.signature(target_type)
|
||||
params = list(sig.parameters.keys())
|
||||
|
||||
# If it has 'text' param, use it
|
||||
if "text" in params:
|
||||
try:
|
||||
return target_type(role="user", text=input_str) # type: ignore
|
||||
except Exception as e:
|
||||
logger.debug(f"Failed to create SerializationMixin with text field: {e}")
|
||||
|
||||
# Try other common fields
|
||||
for field in common_fields:
|
||||
if field in params:
|
||||
try:
|
||||
@@ -631,6 +666,8 @@ def _parse_dict_input(input_dict: dict[str, Any], target_type: type) -> Any:
|
||||
# SerializationMixin
|
||||
if is_serialization_mixin(target_type):
|
||||
try:
|
||||
if target_type is Message:
|
||||
return _build_message_from_legacy_payload(input_dict)
|
||||
if hasattr(target_type, "from_dict"):
|
||||
return target_type.from_dict(input_dict) # type: ignore
|
||||
return target_type(**input_dict) # type: ignore
|
||||
|
||||
@@ -4,7 +4,7 @@ description = "Debug UI for Microsoft Agent Framework with OpenAI-compatible API
|
||||
authors = [{ name = "Microsoft", email = "af-support@microsoft.com"}]
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
version = "1.0.0b260330"
|
||||
version = "1.0.0b260402"
|
||||
license-files = ["LICENSE"]
|
||||
urls.homepage = "https://github.com/microsoft/agent-framework"
|
||||
urls.source = "https://github.com/microsoft/agent-framework/tree/main/python"
|
||||
@@ -23,7 +23,7 @@ classifiers = [
|
||||
"Typing :: Typed",
|
||||
]
|
||||
dependencies = [
|
||||
"agent-framework-core>=1.0.0rc6",
|
||||
"agent-framework-core>=1.0.0,<2",
|
||||
"openai>=1.99.0,<3",
|
||||
"opentelemetry-sdk>=1.39.0,<2",
|
||||
"fastapi>=0.115.0,<0.133.1",
|
||||
@@ -34,7 +34,7 @@ classifiers = [
|
||||
dev = [
|
||||
"pytest==9.0.2",
|
||||
"watchdog==6.0.0",
|
||||
"agent-framework-orchestrations==1.0.0b260304",
|
||||
"agent-framework-orchestrations==1.0.0b260402",
|
||||
]
|
||||
all = [
|
||||
"pytest==9.0.2",
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
# DevUI Samples - Moved
|
||||
|
||||
**The DevUI samples have been relocated to the main samples folder for better consistency and discoverability.**
|
||||
|
||||
## New Location
|
||||
|
||||
All DevUI samples are now located at:
|
||||
|
||||
```
|
||||
python/samples/02-agents/devui/
|
||||
```
|
||||
|
||||
## Available Samples
|
||||
|
||||
- **weather_agent** - Basic OpenAI weather agent
|
||||
- **weather_agent_azure** - Azure OpenAI weather agent
|
||||
- **foundry_agent** - Azure AI Foundry weather agent
|
||||
- **spam_workflow** - Email spam detection workflow
|
||||
- **fanout_workflow** - Complex fan-in/fan-out data processing workflow
|
||||
- **in_memory_mode.py** - In-memory entity registration example
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
cd ../../samples/02-agents/devui
|
||||
python in_memory_mode.py
|
||||
```
|
||||
|
||||
Or for directory discovery:
|
||||
|
||||
```bash
|
||||
cd ../../samples/02-agents/devui
|
||||
devui
|
||||
```
|
||||
|
||||
## Learn More
|
||||
|
||||
See the [DevUI samples README](../../../samples/02-agents/devui/README.md) for detailed documentation.
|
||||
@@ -1,3 +0,0 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
"""Examples package for Agent Framework DevUI."""
|
||||
@@ -4,7 +4,7 @@ description = "Durable Task integration for Microsoft Agent Framework."
|
||||
authors = [{ name = "Microsoft", email = "af-support@microsoft.com"}]
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
version = "1.0.0b260330"
|
||||
version = "1.0.0b260402"
|
||||
license-files = ["LICENSE"]
|
||||
urls.homepage = "https://aka.ms/agent-framework"
|
||||
urls.source = "https://github.com/microsoft/agent-framework/tree/main/python"
|
||||
@@ -22,7 +22,7 @@ classifiers = [
|
||||
"Typing :: Typed",
|
||||
]
|
||||
dependencies = [
|
||||
"agent-framework-core>=1.0.0rc6",
|
||||
"agent-framework-core>=1.0.0,<2",
|
||||
"durabletask>=1.3.0,<2",
|
||||
"durabletask-azuremanaged>=1.3.0,<2",
|
||||
"python-dateutil>=2.8.0,<3",
|
||||
@@ -30,7 +30,7 @@ dependencies = [
|
||||
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"types-python-dateutil==2.9.0.20260305",
|
||||
"types-python-dateutil==2.9.0.20260402",
|
||||
]
|
||||
|
||||
[tool.uv]
|
||||
|
||||
@@ -83,7 +83,9 @@ def _role_value(chat_message: DurableAgentStateMessage) -> str:
|
||||
|
||||
def _agent_response(text: str | None) -> AgentResponse:
|
||||
"""Create an AgentResponse with a single assistant message."""
|
||||
message = Message(role="assistant", text=text) if text is not None else Message(role="assistant", text="")
|
||||
message = (
|
||||
Message(role="assistant", contents=[text]) if text is not None else Message(role="assistant", contents=[""])
|
||||
)
|
||||
return AgentResponse(messages=[message], created_at="2024-01-01T00:00:00Z")
|
||||
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ class TestDurableAIAgentMessageNormalization:
|
||||
|
||||
def test_run_accepts_chat_message(self, test_agent: DurableAIAgent[Any], mock_executor: Mock) -> None:
|
||||
"""Verify run accepts and normalizes Message objects."""
|
||||
chat_msg = Message(role="user", text="Test message")
|
||||
chat_msg = Message(role="user", contents=["Test message"])
|
||||
test_agent.run(chat_msg)
|
||||
|
||||
mock_executor.run_durable_agent.assert_called_once()
|
||||
@@ -95,8 +95,8 @@ class TestDurableAIAgentMessageNormalization:
|
||||
def test_run_accepts_list_of_chat_messages(self, test_agent: DurableAIAgent[Any], mock_executor: Mock) -> None:
|
||||
"""Verify run accepts and joins list of Message objects."""
|
||||
messages = [
|
||||
Message(role="user", text="Message 1"),
|
||||
Message(role="assistant", text="Message 2"),
|
||||
Message(role="user", contents=["Message 1"]),
|
||||
Message(role="assistant", contents=["Message 2"]),
|
||||
]
|
||||
test_agent.run(messages)
|
||||
|
||||
|
||||
@@ -219,7 +219,7 @@ class FoundryMemoryProvider(ContextProvider):
|
||||
if line_separated_memories:
|
||||
context.extend_messages(
|
||||
self.source_id,
|
||||
[Message(role="user", text=f"{self.context_prompt}\n{line_separated_memories}")],
|
||||
[Message(role="user", contents=[f"{self.context_prompt}\n{line_separated_memories}"])],
|
||||
)
|
||||
except Exception as e:
|
||||
# Log but don't fail - memory retrieval is non-critical
|
||||
|
||||
@@ -4,7 +4,7 @@ description = "Microsoft Foundry integrations for Microsoft Agent Framework."
|
||||
authors = [{ name = "Microsoft", email = "af-support@microsoft.com"}]
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
version = "1.0.0rc6"
|
||||
version = "1.0.0"
|
||||
license-files = ["LICENSE"]
|
||||
urls.homepage = "https://aka.ms/agent-framework"
|
||||
urls.source = "https://github.com/microsoft/agent-framework/tree/main/python"
|
||||
@@ -12,7 +12,7 @@ urls.release_notes = "https://github.com/microsoft/agent-framework/releases?q=ta
|
||||
urls.issues = "https://github.com/microsoft/agent-framework/issues"
|
||||
classifiers = [
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Development Status :: 4 - Beta",
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Intended Audience :: Developers",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
@@ -23,8 +23,8 @@ classifiers = [
|
||||
"Typing :: Typed",
|
||||
]
|
||||
dependencies = [
|
||||
"agent-framework-core>=1.0.0rc6",
|
||||
"agent-framework-openai>=1.0.0rc6",
|
||||
"agent-framework-core>=1.0.0,<2",
|
||||
"agent-framework-openai>=1.0.0,<2",
|
||||
"azure-ai-inference>=1.0.0b9,<1.0.0b10",
|
||||
"azure-ai-projects>=2.0.0,<3.0",
|
||||
]
|
||||
|
||||
@@ -359,7 +359,7 @@ async def test_web_search_tool_with_location() -> None:
|
||||
assert web_search_tool.user_location.city == "Seattle"
|
||||
assert web_search_tool.user_location.country == "US"
|
||||
_, run_options, _ = await client._prepare_request(
|
||||
messages=[Message(role="user", text="What's the weather?")],
|
||||
messages=[Message(role="user", contents=["What's the weather?"])],
|
||||
options={"tools": [web_search_tool], "tool_choice": "auto"},
|
||||
)
|
||||
|
||||
@@ -387,7 +387,7 @@ async def test_code_interpreter_tool_variations() -> None:
|
||||
assert code_tool_with_files.container.file_ids == ["file1", "file2"]
|
||||
|
||||
_, run_options, _ = await client._prepare_request(
|
||||
messages=[Message(role="user", text="Process these files")],
|
||||
messages=[Message(role="user", contents=["Process these files"])],
|
||||
options={"tools": [code_tool_with_files]},
|
||||
)
|
||||
|
||||
@@ -428,7 +428,7 @@ async def test_chat_message_parsing_with_function_calls() -> None:
|
||||
)
|
||||
function_result = Content.from_function_result(call_id="test-call-id", result="Function executed successfully")
|
||||
messages = [
|
||||
Message(role="user", text="Call a function"),
|
||||
Message(role="user", contents=["Call a function"]),
|
||||
Message(role="assistant", contents=[function_call]),
|
||||
Message(role="tool", contents=[function_result]),
|
||||
]
|
||||
@@ -471,7 +471,7 @@ async def test_content_filter_exception() -> None:
|
||||
client.client.responses.create.side_effect = mock_error
|
||||
|
||||
with pytest.raises(OpenAIContentFilterException) as exc_info:
|
||||
await client.get_response(messages=[Message(role="user", text="Test message")])
|
||||
await client.get_response(messages=[Message(role="user", contents=["Test message"])])
|
||||
|
||||
assert "content error" in str(exc_info.value)
|
||||
|
||||
@@ -495,7 +495,7 @@ async def test_response_format_parse_path() -> None:
|
||||
client.client.responses.parse = AsyncMock(return_value=mock_parsed_response)
|
||||
|
||||
response = await client.get_response(
|
||||
messages=[Message(role="user", text="Test message")],
|
||||
messages=[Message(role="user", contents=["Test message"])],
|
||||
options={"response_format": OutputStruct, "store": True},
|
||||
)
|
||||
assert response.response_id == "parsed_response_123"
|
||||
@@ -523,7 +523,7 @@ async def test_response_format_parse_path_with_conversation_id() -> None:
|
||||
client.client.responses.parse = AsyncMock(return_value=mock_parsed_response)
|
||||
|
||||
response = await client.get_response(
|
||||
messages=[Message(role="user", text="Test message")],
|
||||
messages=[Message(role="user", contents=["Test message"])],
|
||||
options={"response_format": OutputStruct, "store": True},
|
||||
)
|
||||
assert response.response_id == "parsed_response_123"
|
||||
@@ -563,7 +563,7 @@ async def test_response_format_dict_parse_path() -> None:
|
||||
client.client.responses.create = AsyncMock(return_value=mock_response)
|
||||
|
||||
response = await client.get_response(
|
||||
messages=[Message(role="user", text="Test message")],
|
||||
messages=[Message(role="user", contents=["Test message"])],
|
||||
options={"response_format": response_format},
|
||||
)
|
||||
|
||||
@@ -589,7 +589,7 @@ async def test_bad_request_error_non_content_filter() -> None:
|
||||
|
||||
with pytest.raises(ChatClientException) as exc_info:
|
||||
await client.get_response(
|
||||
messages=[Message(role="user", text="Test message")],
|
||||
messages=[Message(role="user", contents=["Test message"])],
|
||||
options={"response_format": OutputStruct},
|
||||
)
|
||||
|
||||
@@ -656,12 +656,12 @@ async def test_integration_options(
|
||||
client.function_invocation_configuration["max_iterations"] = 2
|
||||
|
||||
if option_name.startswith("tools") or option_name.startswith("tool_choice"):
|
||||
messages = [Message(role="user", text="What is the weather in Seattle?")]
|
||||
messages = [Message(role="user", contents=["What is the weather in Seattle?"])]
|
||||
elif option_name.startswith("response_format"):
|
||||
messages = [Message(role="user", text="The weather in Seattle is sunny")]
|
||||
messages.append(Message(role="user", text="What is the weather in Seattle?"))
|
||||
messages = [Message(role="user", contents=["The weather in Seattle is sunny"])]
|
||||
messages.append(Message(role="user", contents=["What is the weather in Seattle?"]))
|
||||
else:
|
||||
messages = [Message(role="user", text="Say 'Hello World' briefly.")]
|
||||
messages = [Message(role="user", contents=["Say 'Hello World' briefly."])]
|
||||
|
||||
options: dict[str, Any] = {option_name: option_value}
|
||||
if option_name.startswith("tool_choice"):
|
||||
@@ -700,7 +700,7 @@ async def test_integration_web_search() -> None:
|
||||
"messages": [
|
||||
Message(
|
||||
role="user",
|
||||
text="Who are the main characters of Kpop Demon Hunters? Do a web search to find the answer.",
|
||||
contents=["Who are the main characters of Kpop Demon Hunters? Do a web search to find the answer."],
|
||||
)
|
||||
],
|
||||
"options": {"tool_choice": "auto", "tools": [web_search_tool]},
|
||||
@@ -728,7 +728,7 @@ async def test_integration_tool_rich_content_image() -> None:
|
||||
client = FoundryChatClient(credential=AzureCliCredential())
|
||||
client.function_invocation_configuration["max_iterations"] = 2
|
||||
|
||||
messages = [Message(role="user", text="Call the get_test_image tool and describe what you see.")]
|
||||
messages = [Message(role="user", contents=["Call the get_test_image tool and describe what you see."])]
|
||||
options: dict[str, Any] = {"tools": [get_test_image], "tool_choice": "auto"}
|
||||
|
||||
response = await client.get_response(messages=messages, options=options, stream=True).get_final_response()
|
||||
|
||||
@@ -156,7 +156,7 @@ async def test_retrieves_static_memories_on_first_run(mock_project_client: Async
|
||||
scope="user_123",
|
||||
)
|
||||
session = AgentSession(session_id="test-session")
|
||||
ctx = SessionContext(input_messages=[Message(role="user", text="Hello")], session_id="s1")
|
||||
ctx = SessionContext(input_messages=[Message(role="user", contents=["Hello"])], session_id="s1")
|
||||
|
||||
await provider.before_run( # type: ignore[arg-type]
|
||||
agent=None, session=session, context=ctx, state=session.state.setdefault(provider.source_id, {})
|
||||
@@ -191,7 +191,7 @@ async def test_contextual_memories_added_to_context(mock_project_client: AsyncMo
|
||||
scope="user_123",
|
||||
)
|
||||
session = AgentSession(session_id="test-session")
|
||||
ctx = SessionContext(input_messages=[Message(role="user", text="Hello")], session_id="s1")
|
||||
ctx = SessionContext(input_messages=[Message(role="user", contents=["Hello"])], session_id="s1")
|
||||
|
||||
await provider.before_run( # type: ignore[arg-type]
|
||||
agent=None, session=session, context=ctx, state=session.state.setdefault(provider.source_id, {})
|
||||
@@ -218,7 +218,7 @@ async def test_empty_input_skips_contextual_search(mock_project_client: AsyncMoc
|
||||
scope="user_123",
|
||||
)
|
||||
session = AgentSession(session_id="test-session")
|
||||
ctx = SessionContext(input_messages=[Message(role="user", text="")], session_id="s1")
|
||||
ctx = SessionContext(input_messages=[Message(role="user", contents=[""])], session_id="s1")
|
||||
|
||||
await provider.before_run( # type: ignore[arg-type]
|
||||
agent=None, session=session, context=ctx, state=session.state.setdefault(provider.source_id, {})
|
||||
@@ -240,7 +240,7 @@ async def test_empty_search_results_no_messages(mock_project_client: AsyncMock)
|
||||
scope="user_123",
|
||||
)
|
||||
session = AgentSession(session_id="test-session")
|
||||
ctx = SessionContext(input_messages=[Message(role="user", text="test")], session_id="s1")
|
||||
ctx = SessionContext(input_messages=[Message(role="user", contents=["test"])], session_id="s1")
|
||||
|
||||
await provider.before_run( # type: ignore[arg-type]
|
||||
agent=None, session=session, context=ctx, state=session.state.setdefault(provider.source_id, {})
|
||||
@@ -265,7 +265,7 @@ async def test_static_memories_only_retrieved_once(mock_project_client: AsyncMoc
|
||||
scope="user_123",
|
||||
)
|
||||
session = AgentSession(session_id="test-session")
|
||||
ctx = SessionContext(input_messages=[Message(role="user", text="Hello")], session_id="s1")
|
||||
ctx = SessionContext(input_messages=[Message(role="user", contents=["Hello"])], session_id="s1")
|
||||
|
||||
# First call
|
||||
await provider.before_run( # type: ignore[arg-type]
|
||||
@@ -280,7 +280,7 @@ async def test_static_memories_only_retrieved_once(mock_project_client: AsyncMoc
|
||||
mock_project_client.beta.memory_stores.search_memories.return_value = contextual_result2
|
||||
|
||||
# Second call - should only search contextual, not static
|
||||
ctx2 = SessionContext(input_messages=[Message(role="user", text="World")], session_id="s1")
|
||||
ctx2 = SessionContext(input_messages=[Message(role="user", contents=["World"])], session_id="s1")
|
||||
await provider.before_run( # type: ignore[arg-type]
|
||||
agent=None, session=session, context=ctx2, state=session.state.setdefault(provider.source_id, {})
|
||||
)
|
||||
@@ -296,7 +296,7 @@ async def test_handles_search_exception_gracefully(mock_project_client: AsyncMoc
|
||||
scope="user_123",
|
||||
)
|
||||
session = AgentSession(session_id="test-session")
|
||||
ctx = SessionContext(input_messages=[Message(role="user", text="Hello")], session_id="s1")
|
||||
ctx = SessionContext(input_messages=[Message(role="user", contents=["Hello"])], session_id="s1")
|
||||
|
||||
# Should not raise exception
|
||||
await provider.before_run( # type: ignore[arg-type]
|
||||
@@ -321,8 +321,8 @@ async def test_stores_input_and_response(mock_project_client: AsyncMock) -> None
|
||||
scope="user_123",
|
||||
)
|
||||
session = AgentSession(session_id="test-session")
|
||||
ctx = SessionContext(input_messages=[Message(role="user", text="question")], session_id="s1")
|
||||
ctx._response = AgentResponse(messages=[Message(role="assistant", text="answer")])
|
||||
ctx = SessionContext(input_messages=[Message(role="user", contents=["question"])], session_id="s1")
|
||||
ctx._response = AgentResponse(messages=[Message(role="assistant", contents=["answer"])])
|
||||
|
||||
await provider.after_run( # type: ignore[arg-type]
|
||||
agent=None, session=session, context=ctx, state=session.state.setdefault(provider.source_id, {})
|
||||
@@ -350,12 +350,12 @@ async def test_only_stores_user_assistant_system(mock_project_client: AsyncMock)
|
||||
session = AgentSession(session_id="test-session")
|
||||
ctx = SessionContext(
|
||||
input_messages=[
|
||||
Message(role="user", text="hello"),
|
||||
Message(role="tool", text="tool output"),
|
||||
Message(role="user", contents=["hello"]),
|
||||
Message(role="tool", contents=["tool output"]),
|
||||
],
|
||||
session_id="s1",
|
||||
)
|
||||
ctx._response = AgentResponse(messages=[Message(role="assistant", text="reply")])
|
||||
ctx._response = AgentResponse(messages=[Message(role="assistant", contents=["reply"])])
|
||||
|
||||
await provider.after_run( # type: ignore[arg-type]
|
||||
agent=None, session=session, context=ctx, state=session.state.setdefault(provider.source_id, {})
|
||||
@@ -377,8 +377,8 @@ async def test_skips_empty_messages(mock_project_client: AsyncMock) -> None:
|
||||
session = AgentSession(session_id="test-session")
|
||||
ctx = SessionContext(
|
||||
input_messages=[
|
||||
Message(role="user", text=""),
|
||||
Message(role="user", text=" "),
|
||||
Message(role="user", contents=[""]),
|
||||
Message(role="user", contents=[" "]),
|
||||
],
|
||||
session_id="s1",
|
||||
)
|
||||
@@ -402,8 +402,8 @@ async def test_uses_configured_update_delay(mock_project_client: AsyncMock) -> N
|
||||
update_delay=60,
|
||||
)
|
||||
session = AgentSession(session_id="test-session")
|
||||
ctx = SessionContext(input_messages=[Message(role="user", text="hi")], session_id="s1")
|
||||
ctx._response = AgentResponse(messages=[Message(role="assistant", text="hey")])
|
||||
ctx = SessionContext(input_messages=[Message(role="user", contents=["hi"])], session_id="s1")
|
||||
ctx._response = AgentResponse(messages=[Message(role="assistant", contents=["hey"])])
|
||||
|
||||
await provider.after_run( # type: ignore[arg-type]
|
||||
agent=None, session=session, context=ctx, state=session.state.setdefault(provider.source_id, {})
|
||||
@@ -427,8 +427,8 @@ async def test_uses_previous_update_id_for_incremental_updates(mock_project_clie
|
||||
scope="user_123",
|
||||
)
|
||||
session = AgentSession(session_id="test-session")
|
||||
ctx1 = SessionContext(input_messages=[Message(role="user", text="first")], session_id="s1")
|
||||
ctx1._response = AgentResponse(messages=[Message(role="assistant", text="response1")])
|
||||
ctx1 = SessionContext(input_messages=[Message(role="user", contents=["first"])], session_id="s1")
|
||||
ctx1._response = AgentResponse(messages=[Message(role="assistant", contents=["response1"])])
|
||||
|
||||
# First update
|
||||
await provider.after_run( # type: ignore[arg-type]
|
||||
@@ -437,8 +437,8 @@ async def test_uses_previous_update_id_for_incremental_updates(mock_project_clie
|
||||
assert session.state[provider.source_id]["previous_update_id"] == "update-1"
|
||||
|
||||
# Second update should use previous_update_id
|
||||
ctx2 = SessionContext(input_messages=[Message(role="user", text="second")], session_id="s1")
|
||||
ctx2._response = AgentResponse(messages=[Message(role="assistant", text="response2")])
|
||||
ctx2 = SessionContext(input_messages=[Message(role="user", contents=["second"])], session_id="s1")
|
||||
ctx2._response = AgentResponse(messages=[Message(role="assistant", contents=["response2"])])
|
||||
|
||||
await provider.after_run( # type: ignore[arg-type]
|
||||
agent=None, session=session, context=ctx2, state=session.state.setdefault(provider.source_id, {})
|
||||
@@ -458,8 +458,8 @@ async def test_handles_update_exception_gracefully(mock_project_client: AsyncMoc
|
||||
scope="user_123",
|
||||
)
|
||||
session = AgentSession(session_id="test-session")
|
||||
ctx = SessionContext(input_messages=[Message(role="user", text="hi")], session_id="s1")
|
||||
ctx._response = AgentResponse(messages=[Message(role="assistant", text="hey")])
|
||||
ctx = SessionContext(input_messages=[Message(role="user", contents=["hi"])], session_id="s1")
|
||||
ctx._response = AgentResponse(messages=[Message(role="assistant", contents=["hey"])])
|
||||
|
||||
# Should not raise exception
|
||||
await provider.after_run( # type: ignore[arg-type]
|
||||
|
||||
@@ -4,7 +4,7 @@ description = "Foundry Local integration for Microsoft Agent Framework."
|
||||
authors = [{ name = "Microsoft", email = "af-support@microsoft.com"}]
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
version = "1.0.0b260330"
|
||||
version = "1.0.0b260402"
|
||||
license-files = ["LICENSE"]
|
||||
urls.homepage = "https://aka.ms/agent-framework"
|
||||
urls.source = "https://github.com/microsoft/agent-framework/tree/main/python"
|
||||
@@ -23,8 +23,8 @@ classifiers = [
|
||||
"Typing :: Typed",
|
||||
]
|
||||
dependencies = [
|
||||
"agent-framework-core>=1.0.0rc6",
|
||||
"agent-framework-openai>=1.0.0rc6",
|
||||
"agent-framework-core>=1.0.0,<2",
|
||||
"agent-framework-openai>=1.0.0,<2",
|
||||
"foundry-local-sdk>=0.5.1,<0.5.2",
|
||||
]
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ description = "GitHub Copilot integration for Microsoft Agent Framework."
|
||||
authors = [{ name = "Microsoft", email = "af-support@microsoft.com"}]
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
version = "1.0.0b260330"
|
||||
version = "1.0.0b260402"
|
||||
license-files = ["LICENSE"]
|
||||
urls.homepage = "https://aka.ms/agent-framework"
|
||||
urls.source = "https://github.com/microsoft/agent-framework/tree/main/python"
|
||||
@@ -23,7 +23,7 @@ classifiers = [
|
||||
"Typing :: Typed",
|
||||
]
|
||||
dependencies = [
|
||||
"agent-framework-core>=1.0.0rc6",
|
||||
"agent-framework-core>=1.0.0,<2",
|
||||
"github-copilot-sdk>=0.1.31,<0.1.33; python_version >= '3.11'",
|
||||
]
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ description = "Experimental modules for Microsoft Agent Framework"
|
||||
authors = [{ name = "Microsoft", email = "af-support@microsoft.com"}]
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
version = "1.0.0b260330"
|
||||
version = "1.0.0b260402"
|
||||
license-files = ["LICENSE"]
|
||||
urls.homepage = "https://aka.ms/agent-framework"
|
||||
urls.source = "https://github.com/microsoft/agent-framework/tree/main/python"
|
||||
@@ -22,18 +22,18 @@ classifiers = [
|
||||
"Programming Language :: Python :: 3.14",
|
||||
]
|
||||
dependencies = [
|
||||
"agent-framework-core>=1.0.0rc6",
|
||||
"agent-framework-core>=1.0.0,<2",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
# GAIA benchmark module dependencies
|
||||
gaia = [
|
||||
"pydantic>=2.0.0",
|
||||
"opentelemetry-api>=1.39.0",
|
||||
"opentelemetry-sdk>=1.39.0,<2",
|
||||
"tqdm>=4.60.0",
|
||||
"huggingface-hub>=0.20.0",
|
||||
"orjson>=3.10.7,<4",
|
||||
gaia = [
|
||||
"pydantic>=2.0.0",
|
||||
"opentelemetry-api>=1.39.0",
|
||||
"opentelemetry-sdk>=1.39.0,<2",
|
||||
"tqdm>=4.60.0",
|
||||
"huggingface-hub>=0.20.0",
|
||||
"orjson>=3.10.7,<4",
|
||||
"pyarrow>=18.0.0", # For reading parquet files
|
||||
]
|
||||
|
||||
@@ -57,19 +57,19 @@ math = [
|
||||
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"uv==0.10.9",
|
||||
"ruff==0.15.5",
|
||||
"uv==0.11.3",
|
||||
"ruff==0.15.8",
|
||||
"pytest==9.0.2",
|
||||
"mypy==1.19.1",
|
||||
"mypy==1.20.0",
|
||||
"pyright==1.1.408",
|
||||
#tasks
|
||||
"poethepoet==0.42.1",
|
||||
"rich==13.7.1",
|
||||
"tomli==2.4.0",
|
||||
"rich>=13.7.1,<15.0.0",
|
||||
"tomli==2.4.1",
|
||||
"tomli-w==1.2.0",
|
||||
# tau2 from source (not available on PyPI)
|
||||
"tau2@ git+https://github.com/sierra-research/tau2-bench@5ba9e3e56db57c5e4114bf7f901291f09b2c5619",
|
||||
"prek==0.3.4",
|
||||
"prek==0.3.8",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
|
||||
@@ -353,11 +353,11 @@ class TaskRunner:
|
||||
# Matches tau2's expected conversation start pattern
|
||||
logger.info(f"Starting workflow with hardcoded greeting: '{DEFAULT_FIRST_AGENT_MESSAGE}'")
|
||||
|
||||
first_message = Message(role="assistant", text=DEFAULT_FIRST_AGENT_MESSAGE)
|
||||
first_message = Message(role="assistant", contents=[DEFAULT_FIRST_AGENT_MESSAGE])
|
||||
initial_greeting = AgentExecutorResponse(
|
||||
executor_id=ASSISTANT_AGENT_ID,
|
||||
agent_response=AgentResponse(messages=[first_message]),
|
||||
full_conversation=[Message(role="assistant", text=DEFAULT_FIRST_AGENT_MESSAGE)],
|
||||
full_conversation=[Message(role="assistant", contents=[DEFAULT_FIRST_AGENT_MESSAGE])],
|
||||
)
|
||||
|
||||
# STEP 4: Execute the workflow and collect results
|
||||
|
||||
@@ -131,7 +131,7 @@ class Mem0ContextProvider(ContextProvider):
|
||||
if line_separated_memories:
|
||||
context.extend_messages(
|
||||
self.source_id,
|
||||
[Message(role="user", text=f"{self.context_prompt}\n{line_separated_memories}")],
|
||||
[Message(role="user", contents=[f"{self.context_prompt}\n{line_separated_memories}"])],
|
||||
)
|
||||
|
||||
async def after_run(
|
||||
|
||||
@@ -4,7 +4,7 @@ description = "Mem0 integration for Microsoft Agent Framework."
|
||||
authors = [{ name = "Microsoft", email = "af-support@microsoft.com"}]
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
version = "1.0.0b260330"
|
||||
version = "1.0.0b260402"
|
||||
license-files = ["LICENSE"]
|
||||
urls.homepage = "https://aka.ms/agent-framework"
|
||||
urls.source = "https://github.com/microsoft/agent-framework/tree/main/python"
|
||||
@@ -23,7 +23,7 @@ classifiers = [
|
||||
"Typing :: Typed",
|
||||
]
|
||||
dependencies = [
|
||||
"agent-framework-core>=1.0.0rc6",
|
||||
"agent-framework-core>=1.0.0,<2",
|
||||
"mem0ai>=1.0.0,<2",
|
||||
]
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ class TestBeforeRun:
|
||||
]
|
||||
provider = Mem0ContextProvider(source_id="mem0", mem0_client=mock_mem0_client, user_id="u1")
|
||||
session = AgentSession(session_id="test-session")
|
||||
ctx = SessionContext(input_messages=[Message(role="user", text="Hello")], session_id="s1")
|
||||
ctx = SessionContext(input_messages=[Message(role="user", contents=["Hello"])], session_id="s1")
|
||||
|
||||
await provider.before_run(
|
||||
agent=None, session=session, context=ctx, state=session.state.setdefault(provider.source_id, {})
|
||||
@@ -112,7 +112,7 @@ class TestBeforeRun:
|
||||
"""Empty input messages → no search performed."""
|
||||
provider = Mem0ContextProvider(source_id="mem0", mem0_client=mock_mem0_client, user_id="u1")
|
||||
session = AgentSession(session_id="test-session")
|
||||
ctx = SessionContext(input_messages=[Message(role="user", text="")], session_id="s1")
|
||||
ctx = SessionContext(input_messages=[Message(role="user", contents=[""])], session_id="s1")
|
||||
|
||||
await provider.before_run(
|
||||
agent=None, session=session, context=ctx, state=session.state.setdefault(provider.source_id, {})
|
||||
@@ -126,7 +126,7 @@ class TestBeforeRun:
|
||||
mock_mem0_client.search.return_value = []
|
||||
provider = Mem0ContextProvider(source_id="mem0", mem0_client=mock_mem0_client, user_id="u1")
|
||||
session = AgentSession(session_id="test-session")
|
||||
ctx = SessionContext(input_messages=[Message(role="user", text="test")], session_id="s1")
|
||||
ctx = SessionContext(input_messages=[Message(role="user", contents=["test"])], session_id="s1")
|
||||
|
||||
await provider.before_run(
|
||||
agent=None, session=session, context=ctx, state=session.state.setdefault(provider.source_id, {})
|
||||
@@ -138,7 +138,7 @@ class TestBeforeRun:
|
||||
"""Raises ValueError when no filters."""
|
||||
provider = Mem0ContextProvider(source_id="mem0", mem0_client=mock_mem0_client)
|
||||
session = AgentSession(session_id="test-session")
|
||||
ctx = SessionContext(input_messages=[Message(role="user", text="test")], session_id="s1")
|
||||
ctx = SessionContext(input_messages=[Message(role="user", contents=["test"])], session_id="s1")
|
||||
|
||||
with pytest.raises(ValueError, match="At least one of the filters"):
|
||||
await provider.before_run(agent=None, session=session, context=ctx, state=session.state) # type: ignore[arg-type]
|
||||
@@ -148,7 +148,7 @@ class TestBeforeRun:
|
||||
mock_mem0_client.search.return_value = {"results": [{"memory": "remembered fact"}]}
|
||||
provider = Mem0ContextProvider(source_id="mem0", mem0_client=mock_mem0_client, user_id="u1")
|
||||
session = AgentSession(session_id="test-session")
|
||||
ctx = SessionContext(input_messages=[Message(role="user", text="test")], session_id="s1")
|
||||
ctx = SessionContext(input_messages=[Message(role="user", contents=["test"])], session_id="s1")
|
||||
|
||||
await provider.before_run(
|
||||
agent=None, session=session, context=ctx, state=session.state.setdefault(provider.source_id, {})
|
||||
@@ -164,8 +164,8 @@ class TestBeforeRun:
|
||||
session = AgentSession(session_id="test-session")
|
||||
ctx = SessionContext(
|
||||
input_messages=[
|
||||
Message(role="user", text="Hello"),
|
||||
Message(role="user", text="World"),
|
||||
Message(role="user", contents=["Hello"]),
|
||||
Message(role="user", contents=["World"]),
|
||||
],
|
||||
session_id="s1",
|
||||
)
|
||||
@@ -182,7 +182,7 @@ class TestBeforeRun:
|
||||
mock_oss_mem0_client.search.return_value = [{"memory": "User likes Python"}]
|
||||
provider = Mem0ContextProvider(source_id="mem0", mem0_client=mock_oss_mem0_client, user_id="u1")
|
||||
session = AgentSession(session_id="test-session")
|
||||
ctx = SessionContext(input_messages=[Message(role="user", text="Hello")], session_id="s1")
|
||||
ctx = SessionContext(input_messages=[Message(role="user", contents=["Hello"])], session_id="s1")
|
||||
|
||||
await provider.before_run(
|
||||
agent=None, session=session, context=ctx, state=session.state.setdefault(provider.source_id, {})
|
||||
@@ -200,7 +200,7 @@ class TestBeforeRun:
|
||||
source_id="mem0", mem0_client=mock_oss_mem0_client, user_id="u1", agent_id="a1", application_id="app1"
|
||||
)
|
||||
session = AgentSession(session_id="test-session")
|
||||
ctx = SessionContext(input_messages=[Message(role="user", text="Hello")], session_id="s1")
|
||||
ctx = SessionContext(input_messages=[Message(role="user", contents=["Hello"])], session_id="s1")
|
||||
|
||||
await provider.before_run(
|
||||
agent=None, session=session, context=ctx, state=session.state.setdefault(provider.source_id, {})
|
||||
@@ -216,7 +216,7 @@ class TestBeforeRun:
|
||||
mock_mem0_client.search.return_value = []
|
||||
provider = Mem0ContextProvider(source_id="mem0", mem0_client=mock_mem0_client, user_id="u1")
|
||||
session = AgentSession(session_id="test-session")
|
||||
ctx = SessionContext(input_messages=[Message(role="user", text="Hello")], session_id="s1")
|
||||
ctx = SessionContext(input_messages=[Message(role="user", contents=["Hello"])], session_id="s1")
|
||||
|
||||
await provider.before_run(
|
||||
agent=None, session=session, context=ctx, state=session.state.setdefault(provider.source_id, {})
|
||||
@@ -238,8 +238,8 @@ class TestAfterRun:
|
||||
"""Stores input+response messages to mem0 via client.add."""
|
||||
provider = Mem0ContextProvider(source_id="mem0", mem0_client=mock_mem0_client, user_id="u1")
|
||||
session = AgentSession(session_id="test-session")
|
||||
ctx = SessionContext(input_messages=[Message(role="user", text="question")], session_id="s1")
|
||||
ctx._response = AgentResponse(messages=[Message(role="assistant", text="answer")])
|
||||
ctx = SessionContext(input_messages=[Message(role="user", contents=["question"])], session_id="s1")
|
||||
ctx._response = AgentResponse(messages=[Message(role="assistant", contents=["answer"])])
|
||||
|
||||
await provider.after_run(
|
||||
agent=None, session=session, context=ctx, state=session.state.setdefault(provider.source_id, {})
|
||||
@@ -260,12 +260,12 @@ class TestAfterRun:
|
||||
session = AgentSession(session_id="test-session")
|
||||
ctx = SessionContext(
|
||||
input_messages=[
|
||||
Message(role="user", text="hello"),
|
||||
Message(role="tool", text="tool output"),
|
||||
Message(role="user", contents=["hello"]),
|
||||
Message(role="tool", contents=["tool output"]),
|
||||
],
|
||||
session_id="s1",
|
||||
)
|
||||
ctx._response = AgentResponse(messages=[Message(role="assistant", text="reply")])
|
||||
ctx._response = AgentResponse(messages=[Message(role="assistant", contents=["reply"])])
|
||||
|
||||
await provider.after_run(
|
||||
agent=None, session=session, context=ctx, state=session.state.setdefault(provider.source_id, {})
|
||||
@@ -282,8 +282,8 @@ class TestAfterRun:
|
||||
session = AgentSession(session_id="test-session")
|
||||
ctx = SessionContext(
|
||||
input_messages=[
|
||||
Message(role="user", text=""),
|
||||
Message(role="user", text=" "),
|
||||
Message(role="user", contents=[""]),
|
||||
Message(role="user", contents=[" "]),
|
||||
],
|
||||
session_id="s1",
|
||||
)
|
||||
@@ -299,8 +299,8 @@ class TestAfterRun:
|
||||
"""run_id is not passed to mem0 add, so memories are not scoped to sessions."""
|
||||
provider = Mem0ContextProvider(source_id="mem0", mem0_client=mock_mem0_client, user_id="u1")
|
||||
session = AgentSession(session_id="test-session")
|
||||
ctx = SessionContext(input_messages=[Message(role="user", text="hi")], session_id="my-session")
|
||||
ctx._response = AgentResponse(messages=[Message(role="assistant", text="hey")])
|
||||
ctx = SessionContext(input_messages=[Message(role="user", contents=["hi"])], session_id="my-session")
|
||||
ctx._response = AgentResponse(messages=[Message(role="assistant", contents=["hey"])])
|
||||
|
||||
await provider.after_run(
|
||||
agent=None, session=session, context=ctx, state=session.state.setdefault(provider.source_id, {})
|
||||
@@ -312,8 +312,8 @@ class TestAfterRun:
|
||||
"""Raises ValueError when no filters."""
|
||||
provider = Mem0ContextProvider(source_id="mem0", mem0_client=mock_mem0_client)
|
||||
session = AgentSession(session_id="test-session")
|
||||
ctx = SessionContext(input_messages=[Message(role="user", text="hi")], session_id="s1")
|
||||
ctx._response = AgentResponse(messages=[Message(role="assistant", text="hey")])
|
||||
ctx = SessionContext(input_messages=[Message(role="user", contents=["hi"])], session_id="s1")
|
||||
ctx._response = AgentResponse(messages=[Message(role="assistant", contents=["hey"])])
|
||||
|
||||
with pytest.raises(ValueError, match="At least one of the filters"):
|
||||
await provider.after_run(agent=None, session=session, context=ctx, state=session.state) # type: ignore[arg-type]
|
||||
@@ -324,7 +324,7 @@ class TestAfterRun:
|
||||
source_id="mem0", mem0_client=mock_mem0_client, user_id="u1", application_id="app1"
|
||||
)
|
||||
session = AgentSession(session_id="test-session")
|
||||
ctx = SessionContext(input_messages=[Message(role="user", text="hi")], session_id="s1")
|
||||
ctx = SessionContext(input_messages=[Message(role="user", contents=["hi"])], session_id="s1")
|
||||
ctx._response = AgentResponse(messages=[])
|
||||
|
||||
await provider.after_run(
|
||||
|
||||
@@ -4,7 +4,7 @@ description = "Ollama integration for Microsoft Agent Framework."
|
||||
authors = [{ name = "Microsoft", email = "af-support@microsoft.com"}]
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
version = "1.0.0b260330"
|
||||
version = "1.0.0b260402"
|
||||
license-files = ["LICENSE"]
|
||||
urls.homepage = "https://learn.microsoft.com/en-us/agent-framework/"
|
||||
urls.source = "https://github.com/microsoft/agent-framework/tree/main/python"
|
||||
@@ -23,7 +23,7 @@ classifiers = [
|
||||
"Typing :: Typed",
|
||||
]
|
||||
dependencies = [
|
||||
"agent-framework-core>=1.0.0rc6",
|
||||
"agent-framework-core>=1.0.0,<2",
|
||||
"ollama>=0.5.3,<0.5.4",
|
||||
]
|
||||
|
||||
|
||||
@@ -239,8 +239,8 @@ async def test_cmc(
|
||||
mock_chat_completion_response: AsyncStream[OllamaChatResponse],
|
||||
) -> None:
|
||||
mock_chat.return_value = mock_chat_completion_response
|
||||
chat_history.append(Message(text="hello world", role="system"))
|
||||
chat_history.append(Message(text="hello world", role="user"))
|
||||
chat_history.append(Message(contents=["hello world"], role="system"))
|
||||
chat_history.append(Message(contents=["hello world"], role="user"))
|
||||
|
||||
ollama_client = OllamaChatClient()
|
||||
result = await ollama_client.get_response(messages=chat_history)
|
||||
@@ -261,8 +261,8 @@ async def test_cmc_response_format_dict(
|
||||
prompt_eval_count=1,
|
||||
created_at="2024-01-01T00:00:00Z",
|
||||
)
|
||||
chat_history.append(Message(text="hello world", role="system"))
|
||||
chat_history.append(Message(text="hello world", role="user"))
|
||||
chat_history.append(Message(contents=["hello world"], role="system"))
|
||||
chat_history.append(Message(contents=["hello world"], role="user"))
|
||||
|
||||
ollama_client = OllamaChatClient()
|
||||
result = await ollama_client.get_response(
|
||||
@@ -283,7 +283,7 @@ async def test_cmc_reasoning(
|
||||
mock_chat_completion_response_reasoning: AsyncStream[OllamaChatResponse],
|
||||
) -> None:
|
||||
mock_chat.return_value = mock_chat_completion_response_reasoning
|
||||
chat_history.append(Message(text="hello world", role="user"))
|
||||
chat_history.append(Message(contents=["hello world"], role="user"))
|
||||
|
||||
ollama_client = OllamaChatClient()
|
||||
result = await ollama_client.get_response(messages=chat_history)
|
||||
@@ -300,7 +300,7 @@ async def test_cmc_chat_failure(
|
||||
) -> None:
|
||||
# Simulate a failure in the Ollama client
|
||||
mock_chat.side_effect = Exception("Connection error")
|
||||
chat_history.append(Message(text="hello world", role="user"))
|
||||
chat_history.append(Message(contents=["hello world"], role="user"))
|
||||
|
||||
ollama_client = OllamaChatClient()
|
||||
|
||||
@@ -319,8 +319,8 @@ async def test_cmc_streaming(
|
||||
mock_streaming_chat_completion_response: AsyncStream[OllamaChatResponse],
|
||||
) -> None:
|
||||
mock_chat.return_value = mock_streaming_chat_completion_response
|
||||
chat_history.append(Message(text="hello world", role="system"))
|
||||
chat_history.append(Message(text="hello world", role="user"))
|
||||
chat_history.append(Message(contents=["hello world"], role="system"))
|
||||
chat_history.append(Message(contents=["hello world"], role="user"))
|
||||
|
||||
ollama_client = OllamaChatClient()
|
||||
result = ollama_client.get_response(messages=chat_history, stream=True)
|
||||
@@ -337,7 +337,7 @@ async def test_cmc_streaming_reasoning(
|
||||
mock_streaming_chat_completion_response_reasoning: AsyncStream[OllamaChatResponse],
|
||||
) -> None:
|
||||
mock_chat.return_value = mock_streaming_chat_completion_response_reasoning
|
||||
chat_history.append(Message(text="hello world", role="user"))
|
||||
chat_history.append(Message(contents=["hello world"], role="user"))
|
||||
|
||||
ollama_client = OllamaChatClient()
|
||||
result = ollama_client.get_response(messages=chat_history, stream=True)
|
||||
@@ -355,7 +355,7 @@ async def test_cmc_streaming_chat_failure(
|
||||
) -> None:
|
||||
# Simulate a failure in the Ollama client for streaming
|
||||
mock_chat.side_effect = Exception("Streaming connection error")
|
||||
chat_history.append(Message(text="hello world", role="user"))
|
||||
chat_history.append(Message(contents=["hello world"], role="user"))
|
||||
|
||||
ollama_client = OllamaChatClient()
|
||||
|
||||
@@ -380,7 +380,7 @@ async def test_cmc_streaming_with_tool_call(
|
||||
mock_streaming_chat_completion_response,
|
||||
]
|
||||
|
||||
chat_history.append(Message(text="hello world", role="user"))
|
||||
chat_history.append(Message(contents=["hello world"], role="user"))
|
||||
|
||||
ollama_client = OllamaChatClient()
|
||||
result = ollama_client.get_response(messages=chat_history, stream=True, options={"tools": [hello_world]})
|
||||
@@ -411,7 +411,7 @@ async def test_cmc_with_dict_tool_passthrough(
|
||||
) -> None:
|
||||
"""Test that dict-based tools are passed through to Ollama."""
|
||||
mock_chat.return_value = mock_chat_completion_response
|
||||
chat_history.append(Message(text="hello world", role="user"))
|
||||
chat_history.append(Message(contents=["hello world"], role="user"))
|
||||
|
||||
ollama_client = OllamaChatClient()
|
||||
await ollama_client.get_response(
|
||||
@@ -500,7 +500,7 @@ async def test_cmc_with_invalid_content_type(
|
||||
async def test_cmc_integration_with_tool_call(
|
||||
chat_history: list[Message],
|
||||
) -> None:
|
||||
chat_history.append(Message(text="Call the hello world function and repeat what it says", role="user"))
|
||||
chat_history.append(Message(contents=["Call the hello world function and repeat what it says"], role="user"))
|
||||
|
||||
ollama_client = OllamaChatClient()
|
||||
result = await ollama_client.get_response(messages=chat_history, options={"tools": [hello_world]})
|
||||
@@ -517,7 +517,7 @@ async def test_cmc_integration_with_tool_call(
|
||||
async def test_cmc_integration_with_chat_completion(
|
||||
chat_history: list[Message],
|
||||
) -> None:
|
||||
chat_history.append(Message(text="Say Hello World", role="user"))
|
||||
chat_history.append(Message(contents=["Say Hello World"], role="user"))
|
||||
|
||||
ollama_client = OllamaChatClient()
|
||||
result = await ollama_client.get_response(messages=chat_history)
|
||||
@@ -531,7 +531,7 @@ async def test_cmc_integration_with_chat_completion(
|
||||
async def test_cmc_streaming_integration_with_tool_call(
|
||||
chat_history: list[Message],
|
||||
) -> None:
|
||||
chat_history.append(Message(text="Call the hello world function and repeat what it says", role="user"))
|
||||
chat_history.append(Message(contents=["Call the hello world function and repeat what it says"], role="user"))
|
||||
|
||||
ollama_client = OllamaChatClient()
|
||||
result: AsyncIterable[ChatResponseUpdate] = ollama_client.get_response(
|
||||
@@ -558,7 +558,7 @@ async def test_cmc_streaming_integration_with_tool_call(
|
||||
async def test_cmc_streaming_integration_with_chat_completion(
|
||||
chat_history: list[Message],
|
||||
) -> None:
|
||||
chat_history.append(Message(text="Say Hello World", role="user"))
|
||||
chat_history.append(Message(contents=["Say Hello World"], role="user"))
|
||||
|
||||
ollama_client = OllamaChatClient()
|
||||
result: AsyncIterable[ChatResponseUpdate] = ollama_client.get_response(messages=chat_history, stream=True)
|
||||
|
||||
@@ -11,7 +11,7 @@ This package provides:
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install agent-framework-openai --pre
|
||||
pip install agent-framework-openai
|
||||
```
|
||||
|
||||
## Which chat client should I use?
|
||||
|
||||
@@ -4,7 +4,7 @@ description = "OpenAI integrations for Microsoft Agent Framework."
|
||||
authors = [{ name = "Microsoft", email = "af-support@microsoft.com"}]
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
version = "1.0.0rc6"
|
||||
version = "1.0.0"
|
||||
license-files = ["LICENSE"]
|
||||
urls.homepage = "https://aka.ms/agent-framework"
|
||||
urls.source = "https://github.com/microsoft/agent-framework/tree/main/python"
|
||||
@@ -12,7 +12,7 @@ urls.release_notes = "https://github.com/microsoft/agent-framework/releases?q=ta
|
||||
urls.issues = "https://github.com/microsoft/agent-framework/issues"
|
||||
classifiers = [
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Development Status :: 4 - Beta",
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Intended Audience :: Developers",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
@@ -23,7 +23,7 @@ classifiers = [
|
||||
"Typing :: Typed",
|
||||
]
|
||||
dependencies = [
|
||||
"agent-framework-core>=1.0.0rc6",
|
||||
"agent-framework-core>=1.0.0,<2",
|
||||
"openai>=1.99.0,<3",
|
||||
]
|
||||
|
||||
|
||||
@@ -255,7 +255,7 @@ async def test_get_response_with_all_parameters() -> None:
|
||||
"""Test request preparation with a comprehensive parameter set."""
|
||||
client = OpenAIChatClient(model="test-model", api_key="test-key")
|
||||
_, run_options, _ = await client._prepare_request(
|
||||
messages=[Message(role="user", text="Test message")],
|
||||
messages=[Message(role="user", contents=["Test message"])],
|
||||
options={
|
||||
"include": ["message.output_text.logprobs"],
|
||||
"instructions": "You are a helpful assistant",
|
||||
@@ -320,7 +320,7 @@ async def test_web_search_tool_with_location() -> None:
|
||||
)
|
||||
|
||||
_, run_options, _ = await client._prepare_request(
|
||||
messages=[Message(role="user", text="What's the weather?")],
|
||||
messages=[Message(role="user", contents=["What's the weather?"])],
|
||||
options={"tools": [web_search_tool], "tool_choice": "auto"},
|
||||
)
|
||||
|
||||
@@ -346,7 +346,7 @@ async def test_code_interpreter_tool_variations() -> None:
|
||||
code_tool_with_files = OpenAIChatClient.get_code_interpreter_tool(file_ids=["file1", "file2"])
|
||||
|
||||
_, run_options, _ = await client._prepare_request(
|
||||
messages=[Message(role="user", text="Process these files")],
|
||||
messages=[Message(role="user", contents=["Process these files"])],
|
||||
options={"tools": [code_tool_with_files]},
|
||||
)
|
||||
|
||||
@@ -367,7 +367,7 @@ async def test_content_filter_exception() -> None:
|
||||
|
||||
with patch.object(client.client.responses, "create", side_effect=mock_error):
|
||||
with pytest.raises(OpenAIContentFilterException) as exc_info:
|
||||
await client.get_response(messages=[Message(role="user", text="Test message")])
|
||||
await client.get_response(messages=[Message(role="user", contents=["Test message"])])
|
||||
|
||||
assert "content error" in str(exc_info.value)
|
||||
|
||||
@@ -404,7 +404,7 @@ async def test_chat_message_parsing_with_function_calls() -> None:
|
||||
function_result = Content.from_function_result(call_id="test-call-id", result="Function executed successfully")
|
||||
|
||||
messages = [
|
||||
Message(role="user", text="Call a function"),
|
||||
Message(role="user", contents=["Call a function"]),
|
||||
Message(role="assistant", contents=[function_call]),
|
||||
Message(role="tool", contents=[function_result]),
|
||||
]
|
||||
@@ -450,7 +450,7 @@ async def test_response_format_parse_path() -> None:
|
||||
|
||||
with patch.object(client.client.responses, "parse", return_value=mock_parsed_response):
|
||||
response = await client.get_response(
|
||||
messages=[Message(role="user", text="Test message")],
|
||||
messages=[Message(role="user", contents=["Test message"])],
|
||||
options={"response_format": OutputStruct, "store": True},
|
||||
)
|
||||
assert response.response_id == "parsed_response_123"
|
||||
@@ -477,7 +477,7 @@ async def test_response_format_parse_path_with_conversation_id() -> None:
|
||||
|
||||
with patch.object(client.client.responses, "parse", return_value=mock_parsed_response):
|
||||
response = await client.get_response(
|
||||
messages=[Message(role="user", text="Test message")],
|
||||
messages=[Message(role="user", contents=["Test message"])],
|
||||
options={"response_format": OutputStruct, "store": True},
|
||||
)
|
||||
assert response.response_id == "parsed_response_123"
|
||||
@@ -515,7 +515,7 @@ async def test_response_format_dict_parse_path() -> None:
|
||||
|
||||
with patch.object(client.client.responses, "create", return_value=mock_response):
|
||||
response = await client.get_response(
|
||||
messages=[Message(role="user", text="Test message")],
|
||||
messages=[Message(role="user", contents=["Test message"])],
|
||||
options={"response_format": response_format},
|
||||
)
|
||||
|
||||
@@ -540,7 +540,7 @@ async def test_bad_request_error_non_content_filter() -> None:
|
||||
with patch.object(client.client.responses, "parse", side_effect=mock_error):
|
||||
with pytest.raises(ChatClientException) as exc_info:
|
||||
await client.get_response(
|
||||
messages=[Message(role="user", text="Test message")],
|
||||
messages=[Message(role="user", contents=["Test message"])],
|
||||
options={"response_format": OutputStruct},
|
||||
)
|
||||
|
||||
@@ -561,7 +561,7 @@ async def test_streaming_content_filter_exception_handling() -> None:
|
||||
mock_create.side_effect.code = "content_filter"
|
||||
|
||||
with pytest.raises(OpenAIContentFilterException, match="service encountered a content error"):
|
||||
response_stream = client.get_response(stream=True, messages=[Message(role="user", text="Test")])
|
||||
response_stream = client.get_response(stream=True, messages=[Message(role="user", contents=["Test"])])
|
||||
async for _ in response_stream:
|
||||
break
|
||||
|
||||
@@ -926,7 +926,7 @@ async def test_local_shell_tool_is_invoked_in_function_loop() -> None:
|
||||
|
||||
with patch.object(client.client.responses, "create", side_effect=[mock_response1, mock_response2]) as mock_create:
|
||||
await client.get_response(
|
||||
messages=[Message(role="user", text="What Python version is available?")],
|
||||
messages=[Message(role="user", contents=["What Python version is available?"])],
|
||||
options={"tools": [local_shell_tool]},
|
||||
)
|
||||
|
||||
@@ -999,7 +999,7 @@ async def test_shell_call_is_invoked_as_local_shell_function_loop() -> None:
|
||||
|
||||
with patch.object(client.client.responses, "create", side_effect=[mock_response1, mock_response2]) as mock_create:
|
||||
await client.get_response(
|
||||
messages=[Message(role="user", text="What Python version is available?")],
|
||||
messages=[Message(role="user", contents=["What Python version is available?"])],
|
||||
options={"tools": [local_shell_tool]},
|
||||
)
|
||||
|
||||
@@ -1264,8 +1264,8 @@ def test_prepare_messages_for_openai_assistant_history_uses_output_text_with_ann
|
||||
client = OpenAIChatClient(model="test-model", api_key="test-key")
|
||||
|
||||
messages = [
|
||||
Message(role="user", text="What is async/await?"),
|
||||
Message(role="assistant", text="Async/await enables non-blocking concurrency."),
|
||||
Message(role="user", contents=["What is async/await?"]),
|
||||
Message(role="assistant", contents=["Async/await enables non-blocking concurrency."]),
|
||||
]
|
||||
|
||||
prepared = client._prepare_messages_for_openai(messages)
|
||||
@@ -2263,7 +2263,7 @@ async def test_end_to_end_mcp_approval_flow(span_exporter) -> None:
|
||||
# Patch the create call to return the two mocked responses in sequence
|
||||
with patch.object(client.client.responses, "create", side_effect=[mock_response1, mock_response2]) as mock_create:
|
||||
# First call: get the approval request
|
||||
response = await client.get_response(messages=[Message(role="user", text="Trigger approval")])
|
||||
response = await client.get_response(messages=[Message(role="user", contents=["Trigger approval"])])
|
||||
assert response.messages[0].contents[0].type == "function_approval_request"
|
||||
req = response.messages[0].contents[0]
|
||||
assert req.id == "approval-1"
|
||||
@@ -2515,7 +2515,7 @@ def test_streaming_annotation_added_with_unknown_type() -> None:
|
||||
async def test_service_response_exception_includes_original_error_details() -> None:
|
||||
"""Test that ChatClientException messages include original error details in the new format."""
|
||||
client = OpenAIChatClient(model="test-model", api_key="test-key")
|
||||
messages = [Message(role="user", text="test message")]
|
||||
messages = [Message(role="user", contents=["test message"])]
|
||||
|
||||
mock_response = MagicMock()
|
||||
original_error_message = "Request rate limit exceeded"
|
||||
@@ -2540,7 +2540,7 @@ async def test_service_response_exception_includes_original_error_details() -> N
|
||||
async def test_get_response_streaming_with_response_format() -> None:
|
||||
"""Test get_response streaming with response_format."""
|
||||
client = OpenAIChatClient(model="test-model", api_key="test-key")
|
||||
messages = [Message(role="user", text="Test streaming with format")]
|
||||
messages = [Message(role="user", contents=["Test streaming with format"])]
|
||||
|
||||
# It will fail due to invalid API key, but exercises the code path
|
||||
with pytest.raises(ChatClientException):
|
||||
@@ -3090,7 +3090,7 @@ def test_parse_response_from_openai_image_generation_fallback():
|
||||
|
||||
async def test_prepare_options_store_parameter_handling() -> None:
|
||||
client = OpenAIChatClient(model="test-model", api_key="test-key")
|
||||
messages = [Message(role="user", text="Test message")]
|
||||
messages = [Message(role="user", contents=["Test message"])]
|
||||
|
||||
test_conversation_id = "test-conversation-123"
|
||||
chat_options = ChatOptions(store=True, conversation_id=test_conversation_id)
|
||||
@@ -3142,7 +3142,7 @@ async def test_instructions_sent_first_turn_then_skipped_for_continuation() -> N
|
||||
|
||||
with patch.object(client.client.responses, "create", return_value=mock_response) as mock_create:
|
||||
await client.get_response(
|
||||
messages=[Message(role="user", text="Hello")],
|
||||
messages=[Message(role="user", contents=["Hello"])],
|
||||
options={"instructions": "Reply in uppercase."},
|
||||
)
|
||||
|
||||
@@ -3153,7 +3153,7 @@ async def test_instructions_sent_first_turn_then_skipped_for_continuation() -> N
|
||||
assert first_input_messages[1]["role"] == "user"
|
||||
|
||||
await client.get_response(
|
||||
messages=[Message(role="user", text="Tell me a joke")],
|
||||
messages=[Message(role="user", contents=["Tell me a joke"])],
|
||||
options={
|
||||
"instructions": "Reply in uppercase.",
|
||||
"conversation_id": "resp_123",
|
||||
@@ -3175,7 +3175,7 @@ async def test_instructions_not_repeated_for_continuation_ids(
|
||||
|
||||
with patch.object(client.client.responses, "create", return_value=mock_response) as mock_create:
|
||||
await client.get_response(
|
||||
messages=[Message(role="user", text="Continue conversation")],
|
||||
messages=[Message(role="user", contents=["Continue conversation"])],
|
||||
options={"instructions": "Be helpful.", "conversation_id": conversation_id},
|
||||
)
|
||||
|
||||
@@ -3191,7 +3191,7 @@ async def test_instructions_included_without_conversation_id() -> None:
|
||||
|
||||
with patch.object(client.client.responses, "create", return_value=mock_response) as mock_create:
|
||||
await client.get_response(
|
||||
messages=[Message(role="user", text="Hello")],
|
||||
messages=[Message(role="user", contents=["Hello"])],
|
||||
options={"instructions": "You are a helpful assistant."},
|
||||
)
|
||||
|
||||
@@ -3300,14 +3300,14 @@ async def test_integration_options(
|
||||
# Prepare test message
|
||||
if option_name.startswith("tools") or option_name.startswith("tool_choice"):
|
||||
# Use weather-related prompt for tool tests
|
||||
messages = [Message(role="user", text="What is the weather in Seattle?")]
|
||||
messages = [Message(role="user", contents=["What is the weather in Seattle?"])]
|
||||
elif option_name.startswith("response_format"):
|
||||
# Use prompt that works well with structured output
|
||||
messages = [Message(role="user", text="The weather in Seattle is sunny")]
|
||||
messages.append(Message(role="user", text="What is the weather in Seattle?"))
|
||||
messages = [Message(role="user", contents=["The weather in Seattle is sunny"])]
|
||||
messages.append(Message(role="user", contents=["What is the weather in Seattle?"]))
|
||||
else:
|
||||
# Generic prompt for simple options
|
||||
messages = [Message(role="user", text="Say 'Hello World' briefly.")]
|
||||
messages = [Message(role="user", contents=["Say 'Hello World' briefly."])]
|
||||
|
||||
# Build options dict
|
||||
options: dict[str, Any] = {option_name: option_value}
|
||||
@@ -3358,7 +3358,7 @@ async def test_integration_web_search() -> None:
|
||||
"messages": [
|
||||
Message(
|
||||
role="user",
|
||||
text="What is the current weather? Do not ask for my current location.",
|
||||
contents=["What is the current weather? Do not ask for my current location."],
|
||||
)
|
||||
],
|
||||
"options": {
|
||||
@@ -3390,7 +3390,7 @@ async def test_integration_file_search() -> None:
|
||||
messages=[
|
||||
Message(
|
||||
role="user",
|
||||
text="What is the weather today? Do a file search to find the answer.",
|
||||
contents=["What is the weather today? Do a file search to find the answer."],
|
||||
)
|
||||
],
|
||||
options={
|
||||
@@ -3424,7 +3424,7 @@ async def test_integration_streaming_file_search() -> None:
|
||||
messages=[
|
||||
Message(
|
||||
role="user",
|
||||
text="What is the weather today? Do a file search to find the answer.",
|
||||
contents=["What is the weather today? Do a file search to find the answer."],
|
||||
)
|
||||
],
|
||||
options={
|
||||
@@ -3468,7 +3468,7 @@ async def test_integration_tool_rich_content_image() -> None:
|
||||
messages = [
|
||||
Message(
|
||||
role="user",
|
||||
text="Call the get_test_image tool and describe what you see.",
|
||||
contents=["Call the get_test_image tool and describe what you see."],
|
||||
)
|
||||
]
|
||||
options: dict[str, Any] = {"tools": [get_test_image], "tool_choice": "auto"}
|
||||
|
||||
@@ -285,14 +285,14 @@ async def test_integration_options(
|
||||
|
||||
for streaming in [False, True]:
|
||||
if option_name in {"tools", "tool_choice"}:
|
||||
messages = [Message(role="user", text="What is the weather in Seattle?")]
|
||||
messages = [Message(role="user", contents=["What is the weather in Seattle?"])]
|
||||
elif option_name == "response_format":
|
||||
messages = [
|
||||
Message(role="user", text="The weather in Seattle is sunny"),
|
||||
Message(role="user", text="What is the weather in Seattle?"),
|
||||
Message(role="user", contents=["The weather in Seattle is sunny"]),
|
||||
Message(role="user", contents=["What is the weather in Seattle?"]),
|
||||
]
|
||||
else:
|
||||
messages = [Message(role="user", text="Say 'Hello World' briefly.")]
|
||||
messages = [Message(role="user", contents=["Say 'Hello World' briefly."])]
|
||||
|
||||
options: dict[str, Any] = {option_name: option_value}
|
||||
if option_name == "tool_choice":
|
||||
@@ -339,7 +339,7 @@ async def test_integration_web_search() -> None:
|
||||
messages=[
|
||||
Message(
|
||||
role="user",
|
||||
text="What is the current weather? Do not ask for my current location.",
|
||||
contents=["What is the current weather? Do not ask for my current location."],
|
||||
)
|
||||
],
|
||||
options={
|
||||
@@ -361,7 +361,9 @@ async def test_integration_client_file_search() -> None:
|
||||
file_id, vector_store = await create_vector_store(client)
|
||||
try:
|
||||
response = await client.get_response(
|
||||
messages=[Message(role="user", text="What is the weather today? Do a file search to find the answer.")],
|
||||
messages=[
|
||||
Message(role="user", contents=["What is the weather today? Do a file search to find the answer."])
|
||||
],
|
||||
options={
|
||||
"tools": [OpenAIChatClient.get_file_search_tool(vector_store_ids=[vector_store.vector_store_id])],
|
||||
"tool_choice": "auto",
|
||||
@@ -384,7 +386,9 @@ async def test_integration_client_file_search_streaming() -> None:
|
||||
file_id, vector_store = await create_vector_store(client)
|
||||
try:
|
||||
response_stream = client.get_response(
|
||||
messages=[Message(role="user", text="What is the weather today? Do a file search to find the answer.")],
|
||||
messages=[
|
||||
Message(role="user", contents=["What is the weather today? Do a file search to find the answer."])
|
||||
],
|
||||
stream=True,
|
||||
options={
|
||||
"tools": [OpenAIChatClient.get_file_search_tool(vector_store_ids=[vector_store.vector_store_id])],
|
||||
@@ -407,7 +411,7 @@ async def test_integration_client_agent_hosted_mcp_tool() -> None:
|
||||
async with AzureCliCredential() as credential:
|
||||
client = OpenAIChatClient(credential=credential)
|
||||
response = await client.get_response(
|
||||
messages=[Message(role="user", text="How to create an Azure storage account using az cli?")],
|
||||
messages=[Message(role="user", contents=["How to create an Azure storage account using az cli?"])],
|
||||
options={
|
||||
"max_tokens": 5000,
|
||||
"tools": OpenAIChatClient.get_mcp_tool(
|
||||
@@ -432,7 +436,7 @@ async def test_integration_client_agent_hosted_code_interpreter_tool() -> None:
|
||||
client = OpenAIChatClient(credential=credential)
|
||||
|
||||
response = await client.get_response(
|
||||
messages=[Message(role="user", text="Calculate the sum of numbers from 1 to 10 using Python code.")],
|
||||
messages=[Message(role="user", contents=["Calculate the sum of numbers from 1 to 10 using Python code."])],
|
||||
options={"tools": [OpenAIChatClient.get_code_interpreter_tool()]},
|
||||
)
|
||||
|
||||
@@ -496,7 +500,7 @@ async def test_azure_openai_chat_client_tool_rich_content_image() -> None:
|
||||
client.function_invocation_configuration["max_iterations"] = 2
|
||||
|
||||
for streaming in [False, True]:
|
||||
messages = [Message(role="user", text="Call the get_test_image tool and describe what you see.")]
|
||||
messages = [Message(role="user", contents=["Call the get_test_image tool and describe what you see."])]
|
||||
options: dict[str, Any] = {"tools": [get_test_image], "tool_choice": "auto"}
|
||||
|
||||
if streaming:
|
||||
|
||||
@@ -196,7 +196,7 @@ async def test_content_filter_exception_handling(
|
||||
) -> None:
|
||||
"""Test that content filter errors are properly handled."""
|
||||
client = OpenAIChatCompletionClient()
|
||||
messages = [Message(role="user", text="test message")]
|
||||
messages = [Message(role="user", contents=["test message"])]
|
||||
|
||||
# Create a mock BadRequestError with content_filter code
|
||||
mock_response = MagicMock()
|
||||
@@ -271,7 +271,7 @@ async def test_mcp_tool_dict_causes_api_rejection(openai_unit_test_env: dict[str
|
||||
rather than a silent no-op.
|
||||
"""
|
||||
client = OpenAIChatCompletionClient()
|
||||
messages = [Message(role="user", text="test message")]
|
||||
messages = [Message(role="user", contents=["test message"])]
|
||||
|
||||
mcp_tool = {
|
||||
"type": "mcp",
|
||||
@@ -331,7 +331,7 @@ def get_weather(location: str) -> str:
|
||||
async def test_exception_message_includes_original_error_details() -> None:
|
||||
"""Test that exception messages include original error details in the new format."""
|
||||
client = OpenAIChatCompletionClient(model="test-model", api_key="test-key")
|
||||
messages = [Message(role="user", text="test message")]
|
||||
messages = [Message(role="user", contents=["test message"])]
|
||||
|
||||
mock_response = MagicMock()
|
||||
original_error_message = "Invalid API request format"
|
||||
@@ -1183,7 +1183,7 @@ def test_prepare_options_without_model(openai_unit_test_env: dict[str, str]) ->
|
||||
client = OpenAIChatCompletionClient()
|
||||
client.model = None # Remove model
|
||||
|
||||
messages = [Message(role="user", text="test")]
|
||||
messages = [Message(role="user", contents=["test"])]
|
||||
|
||||
with pytest.raises(ValueError, match="model must be a non-empty string"):
|
||||
client._prepare_options(messages, {})
|
||||
@@ -1221,7 +1221,7 @@ def test_prepare_options_with_instructions(
|
||||
"""Test that instructions are prepended as system message."""
|
||||
client = OpenAIChatCompletionClient()
|
||||
|
||||
messages = [Message(role="user", text="Hello")]
|
||||
messages = [Message(role="user", contents=["Hello"])]
|
||||
options = {"instructions": "You are a helpful assistant."}
|
||||
|
||||
prepared_options = client._prepare_options(messages, options)
|
||||
@@ -1244,8 +1244,8 @@ def test_prepare_options_with_instructions_no_duplicate(
|
||||
|
||||
# Simulate messages that already contain the system instruction
|
||||
messages = [
|
||||
Message(role="system", text="You are a helpful assistant."),
|
||||
Message(role="user", text="Hello"),
|
||||
Message(role="system", contents=["You are a helpful assistant."]),
|
||||
Message(role="user", contents=["Hello"]),
|
||||
]
|
||||
options = {"instructions": "You are a helpful assistant."}
|
||||
|
||||
@@ -1416,7 +1416,7 @@ def test_tool_choice_required_with_function_name(
|
||||
"""Test that tool_choice with required mode and function name is correctly prepared."""
|
||||
client = OpenAIChatCompletionClient()
|
||||
|
||||
messages = [Message(role="user", text="test")]
|
||||
messages = [Message(role="user", contents=["test"])]
|
||||
options = {
|
||||
"tools": [get_weather],
|
||||
"tool_choice": {"mode": "required", "required_function_name": "get_weather"},
|
||||
@@ -1434,7 +1434,7 @@ def test_response_format_dict_passthrough(openai_unit_test_env: dict[str, str])
|
||||
"""Test that response_format as dict is passed through directly."""
|
||||
client = OpenAIChatCompletionClient()
|
||||
|
||||
messages = [Message(role="user", text="test")]
|
||||
messages = [Message(role="user", contents=["test"])]
|
||||
custom_format = {
|
||||
"type": "json_schema",
|
||||
"json_schema": {"name": "Test", "schema": {"type": "object"}},
|
||||
@@ -1503,7 +1503,7 @@ def test_prepare_options_removes_parallel_tool_calls_when_no_tools(
|
||||
"""Test that parallel_tool_calls is removed when no tools are present."""
|
||||
client = OpenAIChatCompletionClient()
|
||||
|
||||
messages = [Message(role="user", text="test")]
|
||||
messages = [Message(role="user", contents=["test"])]
|
||||
options = {"allow_multiple_tool_calls": True}
|
||||
|
||||
prepared_options = client._prepare_options(messages, options)
|
||||
@@ -1516,7 +1516,7 @@ def test_prepare_options_excludes_conversation_id(openai_unit_test_env: dict[str
|
||||
"""Test that conversation_id is excluded from prepared options for chat completions."""
|
||||
client = OpenAIChatCompletionClient()
|
||||
|
||||
messages = [Message(role="user", text="test")]
|
||||
messages = [Message(role="user", contents=["test"])]
|
||||
options = {"conversation_id": "12345", "temperature": 0.7}
|
||||
|
||||
prepared_options = client._prepare_options(messages, options)
|
||||
@@ -1532,7 +1532,7 @@ async def test_streaming_exception_handling(
|
||||
) -> None:
|
||||
"""Test that streaming errors are properly handled."""
|
||||
client = OpenAIChatCompletionClient()
|
||||
messages = [Message(role="user", text="test")]
|
||||
messages = [Message(role="user", contents=["test"])]
|
||||
|
||||
# Create a mock error during streaming
|
||||
mock_error = Exception("Streaming error")
|
||||
@@ -1640,14 +1640,14 @@ async def test_integration_options(
|
||||
# Prepare test message
|
||||
if option_name.startswith("tools") or option_name.startswith("tool_choice"):
|
||||
# Use weather-related prompt for tool tests
|
||||
messages = [Message(role="user", text="What is the weather in Seattle?")]
|
||||
messages = [Message(role="user", contents=["What is the weather in Seattle?"])]
|
||||
elif option_name.startswith("response_format"):
|
||||
# Use prompt that works well with structured output
|
||||
messages = [Message(role="user", text="The weather in Seattle is sunny")]
|
||||
messages.append(Message(role="user", text="What is the weather in Seattle?"))
|
||||
messages = [Message(role="user", contents=["The weather in Seattle is sunny"])]
|
||||
messages.append(Message(role="user", contents=["What is the weather in Seattle?"]))
|
||||
else:
|
||||
# Generic prompt for simple options
|
||||
messages = [Message(role="user", text="Say 'Hello World' briefly.")]
|
||||
messages = [Message(role="user", contents=["Say 'Hello World' briefly."])]
|
||||
|
||||
# Build options dict
|
||||
options: dict[str, Any] = {option_name: option_value}
|
||||
@@ -1705,7 +1705,7 @@ async def test_integration_web_search() -> None:
|
||||
"messages": [
|
||||
Message(
|
||||
role="user",
|
||||
text="Who are the main characters of Kpop Demon Hunters? Do a web search to find the answer.",
|
||||
contents=["Who are the main characters of Kpop Demon Hunters? Do a web search to find the answer."],
|
||||
)
|
||||
],
|
||||
"options": {
|
||||
@@ -1737,7 +1737,7 @@ async def test_integration_web_search() -> None:
|
||||
"messages": [
|
||||
Message(
|
||||
role="user",
|
||||
text="What is the current weather? Do not ask for my current location.",
|
||||
contents=["What is the current weather? Do not ask for my current location."],
|
||||
)
|
||||
],
|
||||
"options": {
|
||||
|
||||
@@ -195,14 +195,16 @@ async def test_azure_openai_chat_completion_client_response() -> None:
|
||||
messages = [
|
||||
Message(
|
||||
role="user",
|
||||
text=(
|
||||
"Emily and David, two passionate scientists, met during a research expedition to Antarctica. "
|
||||
"Bonded by their love for the natural world and shared curiosity, they uncovered a "
|
||||
"groundbreaking phenomenon in glaciology that could potentially reshape our understanding "
|
||||
"of climate change."
|
||||
),
|
||||
contents=[
|
||||
(
|
||||
"Emily and David, two passionate scientists, met during a research expedition to Antarctica. "
|
||||
"Bonded by their love for the natural world and shared curiosity, they uncovered a "
|
||||
"groundbreaking phenomenon in glaciology that could potentially reshape our understanding "
|
||||
"of climate change."
|
||||
)
|
||||
],
|
||||
),
|
||||
Message(role="user", text="who are Emily and David?"),
|
||||
Message(role="user", contents=["who are Emily and David?"]),
|
||||
]
|
||||
|
||||
response = await client.get_response(messages=messages)
|
||||
@@ -223,7 +225,7 @@ async def test_azure_openai_chat_completion_client_response_tools() -> None:
|
||||
client = OpenAIChatCompletionClient(credential=credential)
|
||||
|
||||
response = await client.get_response(
|
||||
messages=[Message(role="user", text="who are Emily and David?")],
|
||||
messages=[Message(role="user", contents=["who are Emily and David?"])],
|
||||
options={"tools": [get_story_text], "tool_choice": "auto"},
|
||||
)
|
||||
|
||||
@@ -244,14 +246,16 @@ async def test_azure_openai_chat_completion_client_streaming() -> None:
|
||||
messages=[
|
||||
Message(
|
||||
role="user",
|
||||
text=(
|
||||
"Emily and David, two passionate scientists, met during a research expedition to Antarctica. "
|
||||
"Bonded by their love for the natural world and shared curiosity, they uncovered a "
|
||||
"groundbreaking phenomenon in glaciology that could potentially reshape our understanding "
|
||||
"of climate change."
|
||||
),
|
||||
contents=[
|
||||
(
|
||||
"Emily and David, two passionate scientists, met during a research expedition to "
|
||||
"Antarctica. Bonded by their love for the natural world and shared curiosity, they "
|
||||
"uncovered a groundbreaking phenomenon in glaciology that could potentially reshape our "
|
||||
"understanding of climate change."
|
||||
)
|
||||
],
|
||||
),
|
||||
Message(role="user", text="who are Emily and David?"),
|
||||
Message(role="user", contents=["who are Emily and David?"]),
|
||||
],
|
||||
stream=True,
|
||||
)
|
||||
@@ -277,7 +281,7 @@ async def test_azure_openai_chat_completion_client_streaming_tools() -> None:
|
||||
client = OpenAIChatCompletionClient(credential=credential)
|
||||
|
||||
response = client.get_response(
|
||||
messages=[Message(role="user", text="who are Emily and David?")],
|
||||
messages=[Message(role="user", contents=["who are Emily and David?"])],
|
||||
stream=True,
|
||||
options={"tools": [get_story_text], "tool_choice": "auto"},
|
||||
)
|
||||
|
||||
@@ -67,7 +67,7 @@ async def test_cmc(
|
||||
openai_unit_test_env: dict[str, str],
|
||||
):
|
||||
mock_create.return_value = mock_chat_completion_response
|
||||
chat_history.append(Message(role="user", text="hello world"))
|
||||
chat_history.append(Message(role="user", contents=["hello world"]))
|
||||
|
||||
openai_chat_completion = OpenAIChatCompletionClient()
|
||||
await openai_chat_completion.get_response(messages=chat_history)
|
||||
@@ -86,7 +86,7 @@ async def test_cmc_chat_options(
|
||||
openai_unit_test_env: dict[str, str],
|
||||
):
|
||||
mock_create.return_value = mock_chat_completion_response
|
||||
chat_history.append(Message(role="user", text="hello world"))
|
||||
chat_history.append(Message(role="user", contents=["hello world"]))
|
||||
|
||||
openai_chat_completion = OpenAIChatCompletionClient()
|
||||
await openai_chat_completion.get_response(
|
||||
@@ -107,7 +107,7 @@ async def test_cmc_no_fcc_in_response(
|
||||
openai_unit_test_env: dict[str, str],
|
||||
):
|
||||
mock_create.return_value = mock_chat_completion_response
|
||||
chat_history.append(Message(role="user", text="hello world"))
|
||||
chat_history.append(Message(role="user", contents=["hello world"]))
|
||||
orig_chat_history = deepcopy(chat_history)
|
||||
|
||||
openai_chat_completion = OpenAIChatCompletionClient()
|
||||
@@ -129,7 +129,7 @@ async def test_cmc_structured_output_no_fcc(
|
||||
openai_unit_test_env: dict[str, str],
|
||||
):
|
||||
mock_create.return_value = mock_chat_completion_response
|
||||
chat_history.append(Message(role="user", text="hello world"))
|
||||
chat_history.append(Message(role="user", contents=["hello world"]))
|
||||
|
||||
# Define a mock response format
|
||||
class Test(BaseModel):
|
||||
@@ -151,7 +151,7 @@ async def test_scmc_chat_options(
|
||||
openai_unit_test_env: dict[str, str],
|
||||
):
|
||||
mock_create.return_value = mock_streaming_chat_completion_response
|
||||
chat_history.append(Message(role="user", text="hello world"))
|
||||
chat_history.append(Message(role="user", contents=["hello world"]))
|
||||
|
||||
openai_chat_completion = OpenAIChatCompletionClient()
|
||||
async for msg in openai_chat_completion.get_response(
|
||||
@@ -177,7 +177,7 @@ async def test_cmc_general_exception(
|
||||
openai_unit_test_env: dict[str, str],
|
||||
):
|
||||
mock_create.return_value = mock_chat_completion_response
|
||||
chat_history.append(Message(role="user", text="hello world"))
|
||||
chat_history.append(Message(role="user", contents=["hello world"]))
|
||||
|
||||
openai_chat_completion = OpenAIChatCompletionClient()
|
||||
with pytest.raises(ChatClientException):
|
||||
@@ -194,7 +194,7 @@ async def test_cmc_additional_properties(
|
||||
openai_unit_test_env: dict[str, str],
|
||||
):
|
||||
mock_create.return_value = mock_chat_completion_response
|
||||
chat_history.append(Message(role="user", text="hello world"))
|
||||
chat_history.append(Message(role="user", contents=["hello world"]))
|
||||
|
||||
openai_chat_completion = OpenAIChatCompletionClient()
|
||||
await openai_chat_completion.get_response(messages=chat_history, options={"reasoning_effort": "low"})
|
||||
@@ -232,7 +232,7 @@ async def test_get_streaming(
|
||||
stream = MagicMock(spec=AsyncStream)
|
||||
stream.__aiter__.return_value = [content1, content2]
|
||||
mock_create.return_value = stream
|
||||
chat_history.append(Message(role="user", text="hello world"))
|
||||
chat_history.append(Message(role="user", contents=["hello world"]))
|
||||
orig_chat_history = deepcopy(chat_history)
|
||||
|
||||
openai_chat_completion = OpenAIChatCompletionClient()
|
||||
@@ -272,7 +272,7 @@ async def test_get_streaming_singular(
|
||||
stream = MagicMock(spec=AsyncStream)
|
||||
stream.__aiter__.return_value = [content1, content2]
|
||||
mock_create.return_value = stream
|
||||
chat_history.append(Message(role="user", text="hello world"))
|
||||
chat_history.append(Message(role="user", contents=["hello world"]))
|
||||
orig_chat_history = deepcopy(chat_history)
|
||||
|
||||
openai_chat_completion = OpenAIChatCompletionClient()
|
||||
@@ -312,7 +312,7 @@ async def test_get_streaming_structured_output_no_fcc(
|
||||
stream = MagicMock(spec=AsyncStream)
|
||||
stream.__aiter__.return_value = [content1, content2]
|
||||
mock_create.return_value = stream
|
||||
chat_history.append(Message(role="user", text="hello world"))
|
||||
chat_history.append(Message(role="user", contents=["hello world"]))
|
||||
|
||||
# Define a mock response format
|
||||
class Test(BaseModel):
|
||||
@@ -336,7 +336,7 @@ async def test_get_streaming_no_fcc_in_response(
|
||||
openai_unit_test_env: dict[str, str],
|
||||
):
|
||||
mock_create.return_value = mock_streaming_chat_completion_response
|
||||
chat_history.append(Message(role="user", text="hello world"))
|
||||
chat_history.append(Message(role="user", contents=["hello world"]))
|
||||
orig_chat_history = deepcopy(chat_history)
|
||||
|
||||
openai_chat_completion = OpenAIChatCompletionClient()
|
||||
|
||||
+6
-6
@@ -188,7 +188,7 @@ class BaseGroupChatOrchestrator(Executor, ABC):
|
||||
Usage:
|
||||
workflow.run("Write a blog post about AI agents")
|
||||
"""
|
||||
await self._handle_messages([Message(role="user", text=task)], ctx)
|
||||
await self._handle_messages([Message(role="user", contents=[task])], ctx)
|
||||
|
||||
@handler
|
||||
async def handle_message(
|
||||
@@ -205,7 +205,7 @@ class BaseGroupChatOrchestrator(Executor, ABC):
|
||||
ctx: Workflow context
|
||||
|
||||
Usage:
|
||||
workflow.run(Message(role="user", text="Write a blog post about AI agents"))
|
||||
workflow.run(Message(role="user", contents=["Write a blog post about AI agents"]))
|
||||
"""
|
||||
await self._handle_messages([task], ctx)
|
||||
|
||||
@@ -224,8 +224,8 @@ class BaseGroupChatOrchestrator(Executor, ABC):
|
||||
ctx: Workflow context
|
||||
Usage:
|
||||
workflow.run([
|
||||
Message(role="user", text="Write a blog post about AI agents"),
|
||||
Message(role="user", text="Make it engaging and informative.")
|
||||
Message(role="user", contents=["Write a blog post about AI agents"]),
|
||||
Message(role="user", contents=["Make it engaging and informative."])
|
||||
])
|
||||
"""
|
||||
if not task:
|
||||
@@ -377,7 +377,7 @@ class BaseGroupChatOrchestrator(Executor, ABC):
|
||||
Returns:
|
||||
Message with completion content
|
||||
"""
|
||||
return Message(role="assistant", text=message, author_name=self._name)
|
||||
return Message(role="assistant", contents=[message], author_name=self._name)
|
||||
|
||||
# Participant routing (shared across all patterns)
|
||||
|
||||
@@ -441,7 +441,7 @@ class BaseGroupChatOrchestrator(Executor, ABC):
|
||||
# AgentExecutors receive simple message list
|
||||
messages: list[Message] = []
|
||||
if additional_instruction:
|
||||
messages.append(Message(role="user", text=additional_instruction))
|
||||
messages.append(Message(role="user", contents=[additional_instruction]))
|
||||
request = AgentExecutorRequest(messages=messages, should_respond=True)
|
||||
await ctx.send_message(request, target_id=target)
|
||||
await ctx.add_event(
|
||||
|
||||
@@ -499,7 +499,7 @@ class AgentBasedGroupChatOrchestrator(BaseGroupChatOrchestrator):
|
||||
])
|
||||
)
|
||||
# Prepend instruction as system message
|
||||
current_conversation.append(Message(role="user", text=instruction))
|
||||
current_conversation.append(Message(role="user", contents=[instruction]))
|
||||
|
||||
retry_attempts = self._retry_attempts
|
||||
while True:
|
||||
@@ -515,7 +515,7 @@ class AgentBasedGroupChatOrchestrator(BaseGroupChatOrchestrator):
|
||||
current_conversation = [
|
||||
Message(
|
||||
role="user",
|
||||
text=f"Your input could not be parsed due to an error: {ex}. Please try again.",
|
||||
contents=[f"Your input could not be parsed due to an error: {ex}. Please try again."],
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
@@ -161,7 +161,7 @@ class HandoffAgentUserRequest:
|
||||
"""Create a HandoffAgentUserRequest from a simple text response."""
|
||||
messages: list[Message] = []
|
||||
if isinstance(response, str):
|
||||
messages.append(Message(role="user", text=response))
|
||||
messages.append(Message(role="user", contents=[response]))
|
||||
elif isinstance(response, Message):
|
||||
messages.append(response)
|
||||
elif isinstance(response, list):
|
||||
@@ -169,7 +169,7 @@ class HandoffAgentUserRequest:
|
||||
if isinstance(item, Message):
|
||||
messages.append(item)
|
||||
elif isinstance(item, str):
|
||||
messages.append(Message(role="user", text=item))
|
||||
messages.append(Message(role="user", contents=[item]))
|
||||
else:
|
||||
raise TypeError("List items must be either str or Message instances")
|
||||
else:
|
||||
@@ -535,7 +535,7 @@ class HandoffAgentExecutor(AgentExecutor):
|
||||
# or a termination condition is met.
|
||||
# This allows the agent to perform long-running tasks without returning control
|
||||
# to the coordinator or user prematurely.
|
||||
self._cache.extend([Message(role="user", text=self._autonomous_mode_prompt)])
|
||||
self._cache.extend([Message(role="user", contents=[self._autonomous_mode_prompt])])
|
||||
self._autonomous_mode_turns += 1
|
||||
await self._run_agent_and_emit(ctx)
|
||||
else:
|
||||
|
||||
@@ -604,14 +604,14 @@ class StandardMagenticManager(MagenticManagerBase):
|
||||
# Gather facts
|
||||
facts_user = Message(
|
||||
role="user",
|
||||
text=self.task_ledger_facts_prompt.format(task=magentic_context.task),
|
||||
contents=[self.task_ledger_facts_prompt.format(task=magentic_context.task)],
|
||||
)
|
||||
facts_msg = await self._complete([*magentic_context.chat_history, facts_user])
|
||||
|
||||
# Create plan
|
||||
plan_user = Message(
|
||||
role="user",
|
||||
text=self.task_ledger_plan_prompt.format(team=team_text),
|
||||
contents=[self.task_ledger_plan_prompt.format(team=team_text)],
|
||||
)
|
||||
plan_msg = await self._complete([*magentic_context.chat_history, facts_user, facts_msg, plan_user])
|
||||
|
||||
@@ -628,7 +628,7 @@ class StandardMagenticManager(MagenticManagerBase):
|
||||
facts=facts_msg.text,
|
||||
plan=plan_msg.text,
|
||||
)
|
||||
return Message(role="assistant", text=combined, author_name=MAGENTIC_MANAGER_NAME)
|
||||
return Message(role="assistant", contents=[combined], author_name=MAGENTIC_MANAGER_NAME)
|
||||
|
||||
async def replan(self, magentic_context: MagenticContext) -> Message:
|
||||
"""Update facts and plan when stalling or looping has been detected."""
|
||||
@@ -640,16 +640,18 @@ class StandardMagenticManager(MagenticManagerBase):
|
||||
# Update facts
|
||||
facts_update_user = Message(
|
||||
role="user",
|
||||
text=self.task_ledger_facts_update_prompt.format(
|
||||
task=magentic_context.task, old_facts=self.task_ledger.facts.text
|
||||
),
|
||||
contents=[
|
||||
self.task_ledger_facts_update_prompt.format(
|
||||
task=magentic_context.task, old_facts=self.task_ledger.facts.text
|
||||
)
|
||||
],
|
||||
)
|
||||
updated_facts = await self._complete([*magentic_context.chat_history, facts_update_user])
|
||||
|
||||
# Update plan
|
||||
plan_update_user = Message(
|
||||
role="user",
|
||||
text=self.task_ledger_plan_update_prompt.format(team=team_text),
|
||||
contents=[self.task_ledger_plan_update_prompt.format(team=team_text)],
|
||||
)
|
||||
updated_plan = await self._complete([
|
||||
*magentic_context.chat_history,
|
||||
@@ -671,7 +673,7 @@ class StandardMagenticManager(MagenticManagerBase):
|
||||
facts=updated_facts.text,
|
||||
plan=updated_plan.text,
|
||||
)
|
||||
return Message(role="assistant", text=combined, author_name=MAGENTIC_MANAGER_NAME)
|
||||
return Message(role="assistant", contents=[combined], author_name=MAGENTIC_MANAGER_NAME)
|
||||
|
||||
async def create_progress_ledger(self, magentic_context: MagenticContext) -> MagenticProgressLedger:
|
||||
"""Use the model to produce a JSON progress ledger based on the conversation so far.
|
||||
@@ -691,7 +693,7 @@ class StandardMagenticManager(MagenticManagerBase):
|
||||
team=team_text,
|
||||
names=names_csv,
|
||||
)
|
||||
user_message = Message(role="user", text=prompt)
|
||||
user_message = Message(role="user", contents=[prompt])
|
||||
|
||||
# Include full context to help the model decide current stage, with small retry loop
|
||||
attempts = 0
|
||||
@@ -718,12 +720,12 @@ class StandardMagenticManager(MagenticManagerBase):
|
||||
async def prepare_final_answer(self, magentic_context: MagenticContext) -> Message:
|
||||
"""Ask the model to produce the final answer addressed to the user."""
|
||||
prompt = self.final_answer_prompt.format(task=magentic_context.task)
|
||||
user_message = Message(role="user", text=prompt)
|
||||
user_message = Message(role="user", contents=[prompt])
|
||||
response = await self._complete([*magentic_context.chat_history, user_message])
|
||||
# Ensure role is assistant
|
||||
return Message(
|
||||
role="assistant",
|
||||
text=response.text,
|
||||
contents=[response.text],
|
||||
author_name=response.author_name or MAGENTIC_MANAGER_NAME,
|
||||
)
|
||||
|
||||
@@ -806,11 +808,11 @@ class MagenticPlanReviewResponse:
|
||||
def revise(feedback: str | list[str] | Message | list[Message]) -> "MagenticPlanReviewResponse":
|
||||
"""Create a revision response with feedback."""
|
||||
if isinstance(feedback, str):
|
||||
feedback = [Message(role="user", text=feedback)]
|
||||
feedback = [Message(role="user", contents=[feedback])]
|
||||
elif isinstance(feedback, Message):
|
||||
feedback = [feedback]
|
||||
elif isinstance(feedback, list):
|
||||
feedback = [Message(role="user", text=item) if isinstance(item, str) else item for item in feedback]
|
||||
feedback = [Message(role="user", contents=[item]) if isinstance(item, str) else item for item in feedback]
|
||||
|
||||
return MagenticPlanReviewResponse(review=feedback)
|
||||
|
||||
@@ -1120,7 +1122,7 @@ class MagenticOrchestrator(BaseGroupChatOrchestrator):
|
||||
# Add instruction to conversation (assistant guidance)
|
||||
instruction_msg = Message(
|
||||
role="assistant",
|
||||
text=str(instruction),
|
||||
contents=[str(instruction)],
|
||||
author_name=MAGENTIC_MANAGER_NAME,
|
||||
)
|
||||
self._magentic_context.chat_history.append(instruction_msg)
|
||||
@@ -1232,7 +1234,7 @@ class MagenticOrchestrator(BaseGroupChatOrchestrator):
|
||||
*self._magentic_context.chat_history,
|
||||
Message(
|
||||
role="assistant",
|
||||
text=f"Workflow terminated due to reaching maximum {limit_type} count.",
|
||||
contents=[f"Workflow terminated due to reaching maximum {limit_type} count."],
|
||||
author_name=MAGENTIC_MANAGER_NAME,
|
||||
),
|
||||
])
|
||||
|
||||
+1
-1
@@ -73,7 +73,7 @@ class AgentRequestInfoResponse:
|
||||
Returns:
|
||||
AgentRequestInfoResponse instance.
|
||||
"""
|
||||
return AgentRequestInfoResponse(messages=[Message(role="user", text=text) for text in texts])
|
||||
return AgentRequestInfoResponse(messages=[Message(role="user", contents=[text]) for text in texts])
|
||||
|
||||
@staticmethod
|
||||
def approve() -> "AgentRequestInfoResponse":
|
||||
|
||||
+2
-2
@@ -36,7 +36,7 @@ def clean_conversation_for_handoff(conversation: list[Message]) -> list[Message]
|
||||
|
||||
msg_copy = Message(
|
||||
role=msg.role,
|
||||
text=" ".join(text_parts),
|
||||
contents=[" ".join(text_parts)],
|
||||
author_name=msg.author_name,
|
||||
additional_properties=dict(msg.additional_properties) if msg.additional_properties else None,
|
||||
)
|
||||
@@ -66,6 +66,6 @@ def create_completion_message(
|
||||
message_text = text or f"Conversation {reason}."
|
||||
return Message(
|
||||
role="assistant",
|
||||
text=message_text,
|
||||
contents=[message_text],
|
||||
author_name=author_name,
|
||||
)
|
||||
|
||||
@@ -4,7 +4,7 @@ description = "Orchestration patterns for Microsoft Agent Framework. Includes Se
|
||||
authors = [{ name = "Microsoft", email = "af-support@microsoft.com"}]
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
version = "1.0.0b260330"
|
||||
version = "1.0.0b260402"
|
||||
license-files = ["LICENSE"]
|
||||
urls.homepage = "https://aka.ms/agent-framework"
|
||||
urls.source = "https://github.com/microsoft/agent-framework/tree/main/python"
|
||||
@@ -23,7 +23,7 @@ classifiers = [
|
||||
"Typing :: Typed",
|
||||
]
|
||||
dependencies = [
|
||||
"agent-framework-core>=1.0.0rc6",
|
||||
"agent-framework-core>=1.0.0,<2",
|
||||
]
|
||||
|
||||
[tool.uv]
|
||||
|
||||
@@ -32,7 +32,7 @@ class _FakeAgentExec(Executor):
|
||||
|
||||
@handler
|
||||
async def run(self, request: AgentExecutorRequest, ctx: WorkflowContext[AgentExecutorResponse]) -> None:
|
||||
response = AgentResponse(messages=Message(role="assistant", text=self._reply_text))
|
||||
response = AgentResponse(messages=Message(role="assistant", contents=[self._reply_text]))
|
||||
full_conversation = list(request.messages) + list(response.messages)
|
||||
await ctx.send_message(AgentExecutorResponse(self.id, response, full_conversation=full_conversation))
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ class StubAgent(BaseAgent):
|
||||
return self._run_impl()
|
||||
|
||||
async def _run_impl(self) -> AgentResponse:
|
||||
response = Message(role="assistant", text=self._reply_text, author_name=self.name)
|
||||
response = Message(role="assistant", contents=[self._reply_text], author_name=self.name)
|
||||
return AgentResponse(messages=[response])
|
||||
|
||||
async def _run_stream_impl(self) -> AsyncIterable[AgentResponseUpdate]:
|
||||
@@ -89,10 +89,12 @@ class StubManagerAgent(Agent):
|
||||
messages=[
|
||||
Message(
|
||||
role="assistant",
|
||||
text=(
|
||||
'{"terminate": false, "reason": "Selecting agent", '
|
||||
'"next_speaker": "agent", "final_message": null}'
|
||||
),
|
||||
contents=[
|
||||
(
|
||||
'{"terminate": false, "reason": "Selecting agent", '
|
||||
'"next_speaker": "agent", "final_message": null}'
|
||||
)
|
||||
],
|
||||
author_name=self.name,
|
||||
)
|
||||
],
|
||||
@@ -110,10 +112,12 @@ class StubManagerAgent(Agent):
|
||||
messages=[
|
||||
Message(
|
||||
role="assistant",
|
||||
text=(
|
||||
'{"terminate": true, "reason": "Task complete", '
|
||||
'"next_speaker": null, "final_message": "agent manager final"}'
|
||||
),
|
||||
contents=[
|
||||
(
|
||||
'{"terminate": true, "reason": "Task complete", '
|
||||
'"next_speaker": null, "final_message": "agent manager final"}'
|
||||
)
|
||||
],
|
||||
author_name=self.name,
|
||||
)
|
||||
],
|
||||
@@ -141,12 +145,14 @@ class ConcatenatedJsonManagerAgent(Agent):
|
||||
messages=[
|
||||
Message(
|
||||
role="assistant",
|
||||
text=(
|
||||
'{"terminate": false, "reason": "invalid candidate", '
|
||||
'"next_speaker": "unknown", "final_message": null} '
|
||||
'{"terminate": false, "reason": "pick known participant", '
|
||||
'"next_speaker": "agent", "final_message": null}'
|
||||
),
|
||||
contents=[
|
||||
(
|
||||
'{"terminate": false, "reason": "invalid candidate", '
|
||||
'"next_speaker": "unknown", "final_message": null} '
|
||||
'{"terminate": false, "reason": "pick known participant", '
|
||||
'"next_speaker": "agent", "final_message": null}'
|
||||
)
|
||||
],
|
||||
author_name=self.name,
|
||||
)
|
||||
]
|
||||
@@ -156,10 +162,12 @@ class ConcatenatedJsonManagerAgent(Agent):
|
||||
messages=[
|
||||
Message(
|
||||
role="assistant",
|
||||
text=(
|
||||
'{"terminate": true, "reason": "Task complete", '
|
||||
'"next_speaker": null, "final_message": "concatenated manager final"}'
|
||||
),
|
||||
contents=[
|
||||
(
|
||||
'{"terminate": true, "reason": "Task complete", '
|
||||
'"next_speaker": null, "final_message": "concatenated manager final"}'
|
||||
)
|
||||
],
|
||||
author_name=self.name,
|
||||
)
|
||||
]
|
||||
@@ -189,7 +197,7 @@ class StubMagenticManager(MagenticManagerBase):
|
||||
self._round = 0
|
||||
|
||||
async def plan(self, magentic_context: MagenticContext) -> Message:
|
||||
return Message(role="assistant", text="plan", author_name="magentic_manager")
|
||||
return Message(role="assistant", contents=["plan"], author_name="magentic_manager")
|
||||
|
||||
async def replan(self, magentic_context: MagenticContext) -> Message:
|
||||
return await self.plan(magentic_context)
|
||||
@@ -215,7 +223,7 @@ class StubMagenticManager(MagenticManagerBase):
|
||||
)
|
||||
|
||||
async def prepare_final_answer(self, magentic_context: MagenticContext) -> Message:
|
||||
return Message(role="assistant", text="final", author_name="magentic_manager")
|
||||
return Message(role="assistant", contents=["final"], author_name="magentic_manager")
|
||||
|
||||
|
||||
async def test_group_chat_builder_basic_flow() -> None:
|
||||
@@ -258,8 +266,8 @@ async def test_group_chat_as_agent_accepts_conversation() -> None:
|
||||
|
||||
agent = workflow.as_agent(name="group-chat-agent")
|
||||
conversation = [
|
||||
Message(role="user", text="kickoff", author_name="user"),
|
||||
Message(role="assistant", text="noted", author_name="alpha"),
|
||||
Message(role="user", contents=["kickoff"], author_name="user"),
|
||||
Message(role="assistant", contents=["noted"], author_name="alpha"),
|
||||
]
|
||||
response = await agent.run(conversation)
|
||||
|
||||
@@ -549,7 +557,7 @@ class TestConversationHandling:
|
||||
|
||||
async def test_handle_chat_message_input(self) -> None:
|
||||
"""Test handling Message input directly."""
|
||||
task_message = Message(role="user", text="test message")
|
||||
task_message = Message(role="user", contents=["test message"])
|
||||
|
||||
def selector(state: GroupChatState) -> str:
|
||||
# Verify the task message was preserved in conversation
|
||||
@@ -573,8 +581,8 @@ class TestConversationHandling:
|
||||
async def test_handle_conversation_list_input(self) -> None:
|
||||
"""Test handling conversation list preserves context."""
|
||||
conversation = [
|
||||
Message(role="system", text="system message"),
|
||||
Message(role="user", text="user message"),
|
||||
Message(role="system", contents=["system message"]),
|
||||
Message(role="user", contents=["user message"]),
|
||||
]
|
||||
|
||||
def selector(state: GroupChatState) -> str:
|
||||
@@ -913,10 +921,12 @@ async def test_group_chat_with_orchestrator_factory_returning_chat_agent():
|
||||
messages=[
|
||||
Message(
|
||||
role="assistant",
|
||||
text=(
|
||||
'{"terminate": false, "reason": "Selecting alpha", '
|
||||
'"next_speaker": "alpha", "final_message": null}'
|
||||
),
|
||||
contents=[
|
||||
(
|
||||
'{"terminate": false, "reason": "Selecting alpha", '
|
||||
'"next_speaker": "alpha", "final_message": null}'
|
||||
)
|
||||
],
|
||||
author_name=self.name,
|
||||
)
|
||||
],
|
||||
@@ -933,10 +943,12 @@ async def test_group_chat_with_orchestrator_factory_returning_chat_agent():
|
||||
messages=[
|
||||
Message(
|
||||
role="assistant",
|
||||
text=(
|
||||
'{"terminate": true, "reason": "Task complete", '
|
||||
'"next_speaker": null, "final_message": "dynamic manager final"}'
|
||||
),
|
||||
contents=[
|
||||
(
|
||||
'{"terminate": true, "reason": "Task complete", '
|
||||
'"next_speaker": null, "final_message": "dynamic manager final"}'
|
||||
)
|
||||
],
|
||||
author_name=self.name,
|
||||
)
|
||||
],
|
||||
|
||||
@@ -269,7 +269,7 @@ async def test_resume_keeps_prior_user_context_for_same_agent() -> None:
|
||||
second_events = await _drain(
|
||||
workflow.run(
|
||||
stream=True,
|
||||
responses={first_request.request_id: [Message(role="user", text="Order 2939393")]},
|
||||
responses={first_request.request_id: [Message(role="user", contents=["Order 2939393"])]},
|
||||
)
|
||||
)
|
||||
second_request = _latest_request_info_event(second_events)
|
||||
@@ -280,7 +280,7 @@ async def test_resume_keeps_prior_user_context_for_same_agent() -> None:
|
||||
third_events = await _drain(
|
||||
workflow.run(
|
||||
stream=True,
|
||||
responses={second_request.request_id: [Message(role="user", text="It arrived broken and unusable.")]},
|
||||
responses={second_request.request_id: [Message(role="user", contents=["It arrived broken and unusable."])]},
|
||||
)
|
||||
)
|
||||
third_request = _latest_request_info_event(third_events)
|
||||
@@ -370,7 +370,7 @@ async def test_tool_approval_responses_are_not_replayed_from_history() -> None:
|
||||
await _drain(
|
||||
workflow.run(
|
||||
stream=True,
|
||||
responses={second_request.request_id: [Message(role="user", text="Thanks, what's next?")]},
|
||||
responses={second_request.request_id: [Message(role="user", contents=["Thanks, what's next?"])]},
|
||||
)
|
||||
)
|
||||
|
||||
@@ -679,7 +679,7 @@ async def test_handoff_resume_preserves_approved_tool_output_for_stateless_runs(
|
||||
await _drain(
|
||||
workflow.run(
|
||||
stream=True,
|
||||
responses={order_request.request_id: [Message(role="user", text="Please continue with refund.")]},
|
||||
responses={order_request.request_id: [Message(role="user", contents=["Please continue with refund."])]},
|
||||
)
|
||||
)
|
||||
|
||||
@@ -767,7 +767,7 @@ def test_clean_conversation_for_handoff_keeps_text_only_history() -> None:
|
||||
)
|
||||
|
||||
conversation = [
|
||||
Message(role="user", text="My order arrived damaged."),
|
||||
Message(role="user", contents=["My order arrived damaged."]),
|
||||
Message(
|
||||
role="assistant",
|
||||
contents=[
|
||||
@@ -933,7 +933,7 @@ async def test_handoff_async_termination_condition() -> None:
|
||||
|
||||
events = await _drain(
|
||||
workflow.run(
|
||||
stream=True, responses={requests[-1].request_id: [Message(role="user", text="Second user message")]}
|
||||
stream=True, responses={requests[-1].request_id: [Message(role="user", contents=["Second user message"])]}
|
||||
)
|
||||
)
|
||||
outputs = [ev for ev in events if ev.type == "output"]
|
||||
@@ -1011,7 +1011,7 @@ async def test_tool_choice_preserved_from_agent_config():
|
||||
if options:
|
||||
recorded_tool_choices.append(options.get("tool_choice"))
|
||||
return ChatResponse(
|
||||
messages=[Message(role="assistant", text="Response")],
|
||||
messages=[Message(role="assistant", contents=["Response"])],
|
||||
response_id="test_response",
|
||||
)
|
||||
|
||||
|
||||
@@ -585,7 +585,7 @@ async def _collect_agent_responses_setup(participant: SupportsAgentRun) -> list[
|
||||
captured.append(
|
||||
Message(
|
||||
role=ev.data.role or "assistant",
|
||||
text=ev.data.text or "",
|
||||
contents=[ev.data.text or ""],
|
||||
author_name=ev.data.author_name,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -72,7 +72,7 @@ class TestAgentRequestInfoResponse:
|
||||
|
||||
def test_create_response_with_messages(self):
|
||||
"""Test creating an AgentRequestInfoResponse with messages."""
|
||||
messages = [Message(role="user", text="Additional info")]
|
||||
messages = [Message(role="user", contents=["Additional info"])]
|
||||
response = AgentRequestInfoResponse(messages=messages)
|
||||
|
||||
assert response.messages == messages
|
||||
@@ -80,8 +80,8 @@ class TestAgentRequestInfoResponse:
|
||||
def test_from_messages_factory(self):
|
||||
"""Test creating response from Message list."""
|
||||
messages = [
|
||||
Message(role="user", text="Message 1"),
|
||||
Message(role="user", text="Message 2"),
|
||||
Message(role="user", contents=["Message 1"]),
|
||||
Message(role="user", contents=["Message 2"]),
|
||||
]
|
||||
response = AgentRequestInfoResponse.from_messages(messages)
|
||||
|
||||
@@ -113,7 +113,7 @@ class TestAgentRequestInfoExecutor:
|
||||
"""Test that request_info handler calls ctx.request_info."""
|
||||
executor = AgentRequestInfoExecutor(id="test_executor")
|
||||
|
||||
agent_response = AgentResponse(messages=[Message(role="assistant", text="Agent response")])
|
||||
agent_response = AgentResponse(messages=[Message(role="assistant", contents=["Agent response"])])
|
||||
agent_response = AgentExecutorResponse(
|
||||
executor_id="test_agent",
|
||||
agent_response=agent_response,
|
||||
@@ -132,7 +132,7 @@ class TestAgentRequestInfoExecutor:
|
||||
"""Test response handler when user provides additional messages."""
|
||||
executor = AgentRequestInfoExecutor(id="test_executor")
|
||||
|
||||
agent_response = AgentResponse(messages=[Message(role="assistant", text="Original")])
|
||||
agent_response = AgentResponse(messages=[Message(role="assistant", contents=["Original"])])
|
||||
original_request = AgentExecutorResponse(
|
||||
executor_id="test_agent",
|
||||
agent_response=agent_response,
|
||||
@@ -159,7 +159,7 @@ class TestAgentRequestInfoExecutor:
|
||||
"""Test response handler when user approves (no additional messages)."""
|
||||
executor = AgentRequestInfoExecutor(id="test_executor")
|
||||
|
||||
agent_response = AgentResponse(messages=[Message(role="assistant", text="Original")])
|
||||
agent_response = AgentResponse(messages=[Message(role="assistant", contents=["Original"])])
|
||||
original_request = AgentExecutorResponse(
|
||||
executor_id="test_agent",
|
||||
agent_response=agent_response,
|
||||
@@ -212,10 +212,10 @@ class _TestAgent:
|
||||
"""Dummy run method."""
|
||||
if stream:
|
||||
return self._run_stream_impl()
|
||||
return AgentResponse(messages=[Message(role="assistant", text="Test response")])
|
||||
return AgentResponse(messages=[Message(role="assistant", contents=["Test response"])])
|
||||
|
||||
async def _run_stream_impl(self) -> AsyncIterable[AgentResponseUpdate]:
|
||||
yield AgentResponseUpdate(messages=[Message(role="assistant", text="Test response stream")])
|
||||
yield AgentResponseUpdate(messages=[Message(role="assistant", contents=["Test response stream"])])
|
||||
|
||||
def create_session(self, **kwargs: Any) -> AgentSession:
|
||||
"""Creates a new conversation session for the agent."""
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user