Files
agent-framework/python/samples/02-agents/conversations/file_history_provider.py
T
Evan Mattson ea3320d39f Python: Fix OpenAI Responses streaming to propagate created_at from final response.completed event (#5382)
* Fix streaming response losing created_at from response.completed event (#5347)

The streaming path in _parse_chunk_from_openai did not extract created_at
from the response.completed event, unlike the non-streaming path in
_parse_responses_response. This caused durabletask persistence warnings
when created_at was None.

Extract created_at in the response.completed case and pass it to the
returned ChatResponseUpdate.

Also fix pre-existing pyright errors for optional orjson import in sample
files.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix orjson import suppression to use pyright instead of mypy (#5347)

Replace `# type: ignore[import-not-found]` with
`# pyright: ignore[reportMissingImports]` on optional orjson imports
in conversation sample files, matching the repo's Pyright strict
configuration.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-22 06:19:31 +00:00

158 lines
5.7 KiB
Python

# Copyright (c) Microsoft. All rights reserved.
from __future__ import annotations
import asyncio
import os
import tempfile
from collections.abc import Iterator
from contextlib import contextmanager
from pathlib import Path
from typing import Annotated
# Uncomment this filter to suppress the experimental FileHistoryProvider warning
# before running the sample.
# import warnings # isort: skip
# warnings.filterwarnings("ignore", message=r"\[FILE_HISTORY\].*", category=FutureWarning)
from agent_framework import Agent, FileHistoryProvider, tool
from agent_framework.foundry import FoundryChatClient
from azure.identity import AzureCliCredential
from dotenv import load_dotenv
from pydantic import Field
try:
import orjson # pyright: ignore[reportMissingImports]
except ImportError:
orjson = None
# Load environment variables from .env file.
load_dotenv()
"""
File History Provider
This sample demonstrates how to use the experimental `FileHistoryProvider` with
`FoundryChatClient` and a function tool so the persisted JSON Lines file shows
the tool-calling loop as well as the regular chat turns.
Environment variables:
FOUNDRY_PROJECT_ENDPOINT: Azure AI Foundry project endpoint.
FOUNDRY_MODEL: Foundry model deployment name.
Key components:
- `FileHistoryProvider`: Stores one message JSON object per line in a local
`.jsonl` file for each session.
- `lookup_weather`: A function tool that makes the persisted file show the
assistant function call and tool result lines.
- `json.dumps(..., indent=2)`: Pretty-prints selected records in the sample
output while keeping the on-disk JSONL file compact and valid.
- `USE_TEMP_DIRECTORY`: Toggle between a temporary directory and a persistent
`sessions/` folder next to this sample file.
Security posture:
- The history files are plaintext JSONL on disk, so use a trusted storage
directory and treat the files as conversation logs, not as secure secret
storage.
- Path safety checks protect the filename derived from the session id, but they
do not redact message contents or encrypt the file.
"""
USE_TEMP_DIRECTORY = False
"""When True, store JSONL files in a temporary directory for this run only."""
LOCAL_SESSIONS_DIRECTORY_NAME = "sessions"
"""Folder name used when persisting history next to this sample file."""
@tool(approval_mode="never_require")
def lookup_weather(
location: Annotated[str, Field(description="The city to look up weather for.")],
) -> str:
"""Return a deterministic weather report for a city."""
weather_reports = {
"Seattle": "Seattle is rainy with a high of 13C.",
"Amsterdam": "Amsterdam is cloudy with a high of 16C.",
}
return weather_reports.get(location, f"{location} is sunny with a high of 20C.")
@contextmanager
def _resolve_storage_directory() -> Iterator[Path]:
"""Yield the configured storage directory for the sample run."""
if USE_TEMP_DIRECTORY:
with tempfile.TemporaryDirectory(prefix="af-file-history-") as temp_directory:
yield Path(temp_directory)
return
storage_directory = Path(__file__).resolve().parent / LOCAL_SESSIONS_DIRECTORY_NAME
storage_directory.mkdir(parents=True, exist_ok=True)
yield storage_directory
async def main() -> None:
"""Run the file history provider sample."""
with _resolve_storage_directory() as storage_directory:
print(f"Using temporary directory: {USE_TEMP_DIRECTORY}")
print(f"Storage directory: {storage_directory}\n")
# 2. Create the agent with a tool so the JSONL file includes tool-calling messages.
agent = Agent(
client=FoundryChatClient(
project_endpoint=os.getenv("FOUNDRY_PROJECT_ENDPOINT"),
model=os.getenv("FOUNDRY_MODEL"),
credential=AzureCliCredential(),
),
name="FileHistoryAgent",
instructions=(
"You are a helpful assistant, use the lookup_weather tool for weather questions and "
"answer with the tool result in one sentence."
),
tools=[lookup_weather],
# if orjson is available, use it for faster JSON serialization in the FileHistoryProvider,
# otherwise fall back to the default json module.
context_providers=[
FileHistoryProvider(
storage_directory,
dumps=orjson.dumps if orjson else None,
loads=orjson.loads if orjson else None,
)
],
default_options={"store": False},
)
# 3. Let Agent create the default UUID session id for this conversation.
session = agent.create_session()
# 4. Ask a question that triggers the weather tool.
print("=== Run with tool calling ===")
query = "Use the lookup_weather tool for Seattle and tell me the weather."
response = await agent.run(query, session=session)
print(f"User: {query}")
print(f"Assistant: {response.text}\n")
# 5. Ask a follow-up question that triggers the weather tool as well
print("=== Follow-up question ===")
query = "And what about Amsterdam?"
response = await agent.run(query, session=session)
print(f"User: {query}")
print(f"Assistant: {response.text}\n")
if __name__ == "__main__":
asyncio.run(main())
"""
Sample output:
Using temporary directory: False
Storage directory: /path/to/samples/02-agents/conversations/sessions
=== Run with tool calling ===
User: Use the lookup_weather tool for Seattle and tell me the weather.
Assistant: <model response varies>
=== Follow-up question ===
User: And what about Amsterdam?
Assistant: <model response varies>
"""