mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
bf4ad48cf2
* MCP long-running task support in Python * Fix pyupgrade and AGENTS.md reconnect description - pyupgrade: drop forward-reference string annotations in _mcp.py (Python 3.10+ resolves them natively now that MCPTaskOptions is defined before use). - AGENTS.md: align reconnect description with current behavior. Phase 1 (initial tools/call) does NOT retry on connection loss; raises 'connection lost; task state unknown' instead, so a server that accepted the request but lost the response cannot start the operation twice. Phase 2 (tasks/get / tasks/result) still reconnects once against the same task_id. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix bandit nosec marker for CI pipeline * Address PR feedbacks * Clarifiied comments and addressed more PR feedbacks. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
12 KiB
12 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. A tool can declare aFunctionInvocationContextparameter to receive it;context.toolsis the live, mutable tools list for the run, andcontext.add_tools(...)/context.remove_tools(...)enable progressive tool exposure (changes apply on the next function-calling iteration).
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.
Model Context Protocol (_mcp.py)
MCPTool- Base wrapper that owns the MCPClientSessionand exposes the remote server's tools asFunctionTools.MCPStdioTool/MCPStreamableHTTPTool/MCPWebsocketTool- Transport-specific subclasses.MCPTaskOptions(experimental,MCP_LONG_RUNNING_TASKSfeature, frozen) - Per-tool-instance options controlling the SEP-2663 long-running task lifecycle. When the server advertises a tool withexecution.taskSupport == "required",MCPTool.call_tooltransparently routes throughcall_tool_as_task, which sends an augmentedtools/call, pollstasks/getuntil terminal, and reinterpretstasks/resultas a normalCallToolResult. Instances are immutable; replace viaMCPTool.task_options = MCPTaskOptions(...). Fields:default_ttl: timedelta | None— forwarded to the server asparams.task.ttl(milliseconds). WhenNone, the server's default applies.cancel_remote_task_on_local_cancellation: bool = True— only gates theCancelledErrorpath. Abandonment paths (see below) always cancel.max_task_wait: timedelta | None— client-side deadline for the whole post-create lifecycle (poll + result fetch). When exceeded, raisesToolExecutionExceptionand fires a best-efforttasks/cancel.None(default) means no client-side bound. Bounds sleeps, sends, AND reconnects viaasyncio.wait_for.
- Permissive fallback: servers that ignore the augmentation (return
CallToolResultdirectly) or reject the unknowntaskfield withMETHOD_NOT_FOUND/INVALID_PARAMSfall back to the plainsession.call_tool(...)path so legacy servers keep working. An unparseable success response (server accepted the augmented call but returned a payload that is neitherCreateTaskResultnorCallToolResult) does not fall back — it raisesToolExecutionExceptionto avoid double-executing a side-effecting tool. - Submit-vs-track reconnect policy: a dropped connection before a
task_idis known raisesToolExecutionException("connection lost; task state unknown")without re-issuing the augmentedtools/call, so a server that accepted the request but lost the response cannot be made to start the same operation twice; once atask_idexists,tasks/get/tasks/resultreconnect once and retry against the same id (a shared_send_with_one_reconnecthelper). - Cancel-on-abandonment vs terminal failure: any path where the remote task may still be running (max-wait exceeded, hard
McpErrorin poll, malformedtasks/get, second connection loss in poll/fetch, reconnect failure) fires best-efforttasks/cancelbefore raising. Terminal failures (failed/cancelled/input_requiredserver-side,completed+isError, malformedtasks/resultafter server completed) do not cancel — the server is already done._MCPTaskAbandonedis the private marker distinguishing the two. - Transient poll retry: a slow
tasks/getthat surfaces asMcpError(code=408 REQUEST_TIMEOUT)is retried (bounded bymax_task_wait). All other non-connectionMcpErrors during poll are treated as abandonment.tasks/resultdoes not get transient retry — the server has already completed, so a slow payload fetch is anomalous.
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(...)