forked from microsoft/agent-framework
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfile_history_provider.py
More file actions
157 lines (128 loc) · 5.66 KB
/
file_history_provider.py
File metadata and controls
157 lines (128 loc) · 5.66 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# 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>
"""