mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
b000a2cf51
* Adding AgentFileStore and FileAccessProvider to support file ased operations for agents. * Address PR review feedback on FileAccessProvider - Probe symlinks on the unresolved candidate path so in-root symlinks cannot silently pass and out-of-root symlinks surface the correct error message. - Validate matching_lines elements in FileSearchResult.from_dict and raise a clean ValueError for non-mapping entries. - Cap search regex pattern length (256 chars) via a new _compile_search_regex helper to mitigate ReDoS, and surface the cap in the file_access_search_files tool description. - Skip non-UTF-8 files during filesystem search instead of aborting the entire directory walk. - Replace the module-scope trailing string in the data-processing sample with comments to avoid Ruff B018. - Remove the checked-in working/region_totals.md sample artifact so the save flow works from a clean checkout. - Expand the Windows stdout reconfiguration comment in task_runner.py for clarity. - Add tests for invalid/oversize regex, non-UTF-8 file search, and in-root symlink rejection. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix mypy redundant-cast in FileSearchResult.from_dict Use cast(list[object], ...) instead of cast(list[Any], ...) so the cast represents a real type change (lists are invariant) and is no longer flagged by mypy as redundant, while still satisfying pyright's reportUnknownVariableType. Matches the existing pattern in _memory.py. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Tighten path normalization and directory resolution in FileAccess - _normalize_relative_path now strips surrounding whitespace up front so leading/trailing spaces never leak into file segments, and rejects trailing path separators for file paths so 'foo/' is no longer silently coerced to 'foo'. - FileSystemAgentFileStore._resolve_safe_directory_path normalizes with is_directory=True and maps an empty normalized result to the root. This matches InMemoryAgentFileStore so whitespace-only directory inputs resolve to the root instead of raising. - Added tests for whitespace stripping, trailing-separator rejection, and whitespace-only directory listing on the filesystem store. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Harden FileAccess search and atomic save in store API - Add wall-clock timeout (10s) around regex scans so a pathological pattern (e.g. `(a+)+`) below the length cap cannot stall the event loop. - Offload the InMemoryAgentFileStore regex scan to a worker thread, matching the filesystem store. - Fail closed when `Path.is_symlink` raises during the safe-path probe so a permission error cannot silently bypass the symlink/reparse-point rejection. - Add `overwrite: bool = True` to `AgentFileStore.write_file`; the in-memory store performs the check under the existing lock and the filesystem store uses `open(mode='x')` so concurrent callers cannot race past `overwrite=False`. - `file_access_save_file` now relies on the atomic store call instead of a separate `file_exists` round-trip. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix Python 3.10 timeout handling and add directory arg to list/search tools - Catch asyncio.TimeoutError in _run_search_with_timeout. In Python 3.10 asyncio.wait_for raises asyncio.exceptions.TimeoutError, which is distinct from the builtin TimeoutError (the two were unified in 3.11). Catching the asyncio alias works on every supported version. - Add an optional directory parameter to file_access_list_files and file_access_search_files so agents can enumerate / scope searches to nested folders, not just the store root. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address FileAccess review feedback: case, errors, signal, TOCTOU - InMemoryAgentFileStore now stores (display_name, content) so list_files and search_files return the original-case names callers wrote, matching the behaviour of FileSystemAgentFileStore on case-preserving filesystems and removing the silent in-memory vs. on-disk contract divergence. - FileSystemAgentFileStore.read_file raises ValueError instead of letting UnicodeDecodeError bubble for binary / non-UTF-8 input, restoring symmetry with search_files (which still skips) and giving the tool layer a recoverable type to translate. - Tool wrappers now catch ValueError and OSError around every operation and surface them as readable strings, so 'you used ..' and 'the file already exists' are both reported to the model the same way instead of the former crashing out as an unhandled exception. - _search_files_sync logs per skipped non-UTF-8 file at WARNING and an aggregate INFO summary so operators can distinguish 'no matches' from 'half the corpus was unreadable'. - FileSystemAgentFileStore softens its docstrings to acknowledge the inherent probe-then-open TOCTOU window. On POSIX both read and write now pass O_NOFOLLOW so the kernel refuses if the leaf segment becomes a symlink between the probe and the open. Windows has no equivalent flag; the limitation is documented. - Tests cover: case preservation on list/search, ValueError on non-UTF-8 read at the store and tool layer, tool-layer string responses for path-traversal and oversized-regex inputs, search-skip log output, symlink rejection on delete/search/list, and symlinked intermediate directory rejection. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address FileAccess nit comments: docstrings, enumerate, opt-in delete approval - Expand FileSearchMatch/FileSearchResult.to_dict docstrings to explain why the override is needed (__slots__ defeats the mixin's __dict__ iteration) and why exclude/exclude_none are accepted-but-ignored (mixin signature compatibility for callers like to_json). - Use enumerate(lines, start=1) in _search_file_content so the +1 below is no longer needed; rename loop variable to line_number for clarity. - Add opt-in require_delete_approval: bool = False on FileAccessProvider. When True, file_access_delete_file is registered with approval_mode 'always_require' so the host must approve every delete. Default False preserves current behaviour and matches the .NET reference, but deployments that want a safer-by-default posture can enable it. - Add tests covering both delete approval modes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * FileAccess: require delete approval by default Flip the default for FileAccessProvider(require_delete_approval=...) from False to True so destructive deletes are gated by host approval out of the box. Callers that want the previous autonomous behaviour (which matches the .NET reference) can pass require_delete_approval=False. Tests updated accordingly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fixing linkinspector by installing Chrome for puppeteer first. --------- Co-authored-by: Ben Thomas <25218250+alliscode@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
8.6 KiB
8.6 KiB
Core Package (agent-framework-core)
The foundation package containing all core abstractions, types, and built-in OpenAI/Azure OpenAI support.
Module Structure
agent_framework/
├── __init__.py # Public API exports
├── security.py # Public security primitives, middleware, and tools
├── _agents.py # Agent implementations
├── _clients.py # Chat client base classes and protocols
├── _types.py # Core types (Message, ChatResponse, Content, etc.)
├── _tools.py # Tool definitions and function invocation
├── _middleware.py # Middleware system for request/response interception
├── _sessions.py # AgentSession and context provider abstractions
├── _skills.py # Agent Skills system (models, executors, provider)
├── _mcp.py # Model Context Protocol support
├── _workflows/ # Workflow orchestration (sequential, concurrent, handoff, etc.)
├── openai/ # Built-in OpenAI client
├── azure/ # Lazy-loading entry point for Azure integrations
└── <provider>/ # Other lazy-loading provider folders
Core Classes
Agents (_agents.py)
SupportsAgentRun- Protocol defining the agent interfaceBaseAgent- Abstract base class for agentsAgent- Main agent class wrapping a chat client with tools, instructions, and middleware
Chat Clients (_clients.py)
SupportsChatGetResponse- Protocol for chat client implementationsBaseChatClient- Abstract base class with middleware support; subclasses implement_inner_get_response()and_inner_get_streaming_response()
Types (_types.py)
Message- Represents a chat message with role, content, and metadataChatResponse- Response from a chat client containing messages and usageChatResponseUpdate- Streaming response updateAgentResponse/AgentResponseUpdate- Agent-level response wrappersContent- Base class for message content (text, function calls, images, etc.)ChatOptions- TypedDict for chat request options
Tools (_tools.py)
ToolProtocol- Protocol for tool definitionsFunctionTool- Wraps Python functions as tools with JSON schema generation@tooldecorator - Converts functions to toolsuse_function_invocation()- Decorator to add automatic function calling to chat clients
Middleware (_middleware.py)
AgentMiddleware- Intercepts agentrun()callsChatMiddleware- Intercepts chat clientget_response()callsFunctionMiddleware- Intercepts function/tool invocationsAgentContext/ChatContext/FunctionInvocationContext- Context objects passed through middleware
Sessions (_sessions.py)
AgentSession- Manages conversation state and session metadataSessionContext- Context object for session-scoped data during agent runsContextProvider- Base class for context providers (RAG, memory systems)HistoryProvider- Base class for conversation history storageInMemoryHistoryProvider- Built-in session-state history provider for local runsFileHistoryProvider- JSON Lines file-backed history provider storing one file per session with one message record per line
Skills (_skills.py)
Skill- Abstract base for a skill definition bundling instructions (content) with frontmatter metadata, resources, and scripts. Concrete subclasses (InlineSkill,FileSkill,ClassSkill) accept afrontmatter=SkillFrontmatter(...)argument carrying the spec fields. Adding new spec fields is done in one place — onSkillFrontmatter— keeping the subclass constructors stable.SkillFrontmatter- L1 discovery metadata for a skill (name,description,license,compatibility,allowed_tools,metadata). All fields are mutable plain attributes; the constructor validatesname,description, andcompatibilityagainst the spec but post-construction assignments are not re-validated. Spec fields are reachable on every skill viaskill.frontmatter.SkillResource- Named supplementary content attached to a skill; holds either staticcontentor a dynamicfunction(sync or async). Exactly one must be provided.SkillScript- An executable script attached to a skill; holds either an inlinefunction(code-defined, runs in-process) or apathto a file on disk (file-based, delegated to a runner). Exactly one must be provided.SkillScriptRunner- Protocol for file-based script execution. Any callable matching(skill, script, args) -> Anysatisfies it. Code-defined scripts do not use a runner.SkillsProvider- Context provider (extendsContextProvider) that discovers file-based skills fromSKILL.mdfiles and/or accepts code-definedSkillinstances. Follows progressive disclosure: advertise → load → read resources / run scripts.
File Access Harness (_harness/_file_access.py)
AgentFileStore- Abstract async store backing the file-access harness. Implementations exposewrite_file,read_file,delete_file,list_files,file_exists,search_files, andcreate_directoryover forward-slash relative paths.InMemoryAgentFileStore- Dict-backed store suitable for tests and lightweight scenarios.FileSystemAgentFileStore- Disk-backed store rooted under a configurable directory. Enforces relative-path normalization, root containment, and rejects symlink/reparse-point segments to prevent escape.FileSearchResult/FileSearchMatch-SerializationMixinDTOs returned bysearch_files, carrying the matching file name, a context snippet, and the matching lines with 1-based line numbers.FileAccessProvider-ContextProviderthat adds shared file-access tools (file_access_save_file,file_access_read_file,file_access_delete_file,file_access_list_files,file_access_search_files) plus default usage instructions to each invocation. UnlikeMemoryContextProvider, the store is intentionally shared across sessions and agents.
Workflows (_workflows/)
Workflow- Graph-based workflow definitionWorkflowBuilder- Fluent API for building workflows, including explicitoutput_from/intermediate_output_fromselection for caller-facing emissions.output_fromis an allow-list for Workflow Output; unselected executor payloads are hidden unlessintermediate_output_fromselects them as Intermediate Output. Useoutput_from="all"for explicit all-output behavior andintermediate_output_from="all_other"for visible progress from every output-capable executor not selected byoutput_from.WorkflowRunResult- Non-streaming workflow result with Workflow Outputget_outputs()and Intermediate Outputget_intermediate_outputs()accessors- Orchestrators:
SequentialOrchestrator,ConcurrentOrchestrator,GroupChatOrchestrator,MagenticOrchestrator,HandoffOrchestrator
Built-in Providers
OpenAI (openai/)
OpenAIChatClient- Chat client for the OpenAI Responses APIOpenAIChatCompletionClient- Chat client for the OpenAI Chat Completions API
Foundry (foundry/)
FoundryChatClient- Chat client for Azure AI Foundry project endpoints
Key Patterns
Creating an Agent
from agent_framework import Agent
from agent_framework.openai import OpenAIChatClient
agent = Agent(
client=OpenAIChatClient(),
instructions="You are helpful.",
tools=[my_function],
)
response = await agent.run("Hello")
Using as_agent() Shorthand
agent = OpenAIChatClient().as_agent(
name="Assistant",
instructions="You are helpful.",
)
Middleware Pipeline
from agent_framework import Agent, AgentMiddleware, AgentContext
class LoggingMiddleware(AgentMiddleware):
async def process(self, context: AgentContext, call_next) -> None:
print(f"Input: {context.messages}")
await call_next()
print(f"Output: {context.result}")
agent = Agent(..., middleware=[LoggingMiddleware()])
Custom Chat Client
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", contents=["Hi!"])])
async def _inner_get_streaming_response(self, *, messages, options, **kwargs):
yield ChatResponseUpdate(...)