fast-agent-mcp 0.4.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fast_agent/__init__.py +183 -0
- fast_agent/acp/__init__.py +19 -0
- fast_agent/acp/acp_aware_mixin.py +304 -0
- fast_agent/acp/acp_context.py +437 -0
- fast_agent/acp/content_conversion.py +136 -0
- fast_agent/acp/filesystem_runtime.py +427 -0
- fast_agent/acp/permission_store.py +269 -0
- fast_agent/acp/server/__init__.py +5 -0
- fast_agent/acp/server/agent_acp_server.py +1472 -0
- fast_agent/acp/slash_commands.py +1050 -0
- fast_agent/acp/terminal_runtime.py +408 -0
- fast_agent/acp/tool_permission_adapter.py +125 -0
- fast_agent/acp/tool_permissions.py +474 -0
- fast_agent/acp/tool_progress.py +814 -0
- fast_agent/agents/__init__.py +85 -0
- fast_agent/agents/agent_types.py +64 -0
- fast_agent/agents/llm_agent.py +350 -0
- fast_agent/agents/llm_decorator.py +1139 -0
- fast_agent/agents/mcp_agent.py +1337 -0
- fast_agent/agents/tool_agent.py +271 -0
- fast_agent/agents/workflow/agents_as_tools_agent.py +849 -0
- fast_agent/agents/workflow/chain_agent.py +212 -0
- fast_agent/agents/workflow/evaluator_optimizer.py +380 -0
- fast_agent/agents/workflow/iterative_planner.py +652 -0
- fast_agent/agents/workflow/maker_agent.py +379 -0
- fast_agent/agents/workflow/orchestrator_models.py +218 -0
- fast_agent/agents/workflow/orchestrator_prompts.py +248 -0
- fast_agent/agents/workflow/parallel_agent.py +250 -0
- fast_agent/agents/workflow/router_agent.py +353 -0
- fast_agent/cli/__init__.py +0 -0
- fast_agent/cli/__main__.py +73 -0
- fast_agent/cli/commands/acp.py +159 -0
- fast_agent/cli/commands/auth.py +404 -0
- fast_agent/cli/commands/check_config.py +783 -0
- fast_agent/cli/commands/go.py +514 -0
- fast_agent/cli/commands/quickstart.py +557 -0
- fast_agent/cli/commands/serve.py +143 -0
- fast_agent/cli/commands/server_helpers.py +114 -0
- fast_agent/cli/commands/setup.py +174 -0
- fast_agent/cli/commands/url_parser.py +190 -0
- fast_agent/cli/constants.py +40 -0
- fast_agent/cli/main.py +115 -0
- fast_agent/cli/terminal.py +24 -0
- fast_agent/config.py +798 -0
- fast_agent/constants.py +41 -0
- fast_agent/context.py +279 -0
- fast_agent/context_dependent.py +50 -0
- fast_agent/core/__init__.py +92 -0
- fast_agent/core/agent_app.py +448 -0
- fast_agent/core/core_app.py +137 -0
- fast_agent/core/direct_decorators.py +784 -0
- fast_agent/core/direct_factory.py +620 -0
- fast_agent/core/error_handling.py +27 -0
- fast_agent/core/exceptions.py +90 -0
- fast_agent/core/executor/__init__.py +0 -0
- fast_agent/core/executor/executor.py +280 -0
- fast_agent/core/executor/task_registry.py +32 -0
- fast_agent/core/executor/workflow_signal.py +324 -0
- fast_agent/core/fastagent.py +1186 -0
- fast_agent/core/logging/__init__.py +5 -0
- fast_agent/core/logging/events.py +138 -0
- fast_agent/core/logging/json_serializer.py +164 -0
- fast_agent/core/logging/listeners.py +309 -0
- fast_agent/core/logging/logger.py +278 -0
- fast_agent/core/logging/transport.py +481 -0
- fast_agent/core/prompt.py +9 -0
- fast_agent/core/prompt_templates.py +183 -0
- fast_agent/core/validation.py +326 -0
- fast_agent/event_progress.py +62 -0
- fast_agent/history/history_exporter.py +49 -0
- fast_agent/human_input/__init__.py +47 -0
- fast_agent/human_input/elicitation_handler.py +123 -0
- fast_agent/human_input/elicitation_state.py +33 -0
- fast_agent/human_input/form_elements.py +59 -0
- fast_agent/human_input/form_fields.py +256 -0
- fast_agent/human_input/simple_form.py +113 -0
- fast_agent/human_input/types.py +40 -0
- fast_agent/interfaces.py +310 -0
- fast_agent/llm/__init__.py +9 -0
- fast_agent/llm/cancellation.py +22 -0
- fast_agent/llm/fastagent_llm.py +931 -0
- fast_agent/llm/internal/passthrough.py +161 -0
- fast_agent/llm/internal/playback.py +129 -0
- fast_agent/llm/internal/silent.py +41 -0
- fast_agent/llm/internal/slow.py +38 -0
- fast_agent/llm/memory.py +275 -0
- fast_agent/llm/model_database.py +490 -0
- fast_agent/llm/model_factory.py +388 -0
- fast_agent/llm/model_info.py +102 -0
- fast_agent/llm/prompt_utils.py +155 -0
- fast_agent/llm/provider/anthropic/anthropic_utils.py +84 -0
- fast_agent/llm/provider/anthropic/cache_planner.py +56 -0
- fast_agent/llm/provider/anthropic/llm_anthropic.py +796 -0
- fast_agent/llm/provider/anthropic/multipart_converter_anthropic.py +462 -0
- fast_agent/llm/provider/bedrock/bedrock_utils.py +218 -0
- fast_agent/llm/provider/bedrock/llm_bedrock.py +2207 -0
- fast_agent/llm/provider/bedrock/multipart_converter_bedrock.py +84 -0
- fast_agent/llm/provider/google/google_converter.py +466 -0
- fast_agent/llm/provider/google/llm_google_native.py +681 -0
- fast_agent/llm/provider/openai/llm_aliyun.py +31 -0
- fast_agent/llm/provider/openai/llm_azure.py +143 -0
- fast_agent/llm/provider/openai/llm_deepseek.py +76 -0
- fast_agent/llm/provider/openai/llm_generic.py +35 -0
- fast_agent/llm/provider/openai/llm_google_oai.py +32 -0
- fast_agent/llm/provider/openai/llm_groq.py +42 -0
- fast_agent/llm/provider/openai/llm_huggingface.py +85 -0
- fast_agent/llm/provider/openai/llm_openai.py +1195 -0
- fast_agent/llm/provider/openai/llm_openai_compatible.py +138 -0
- fast_agent/llm/provider/openai/llm_openrouter.py +45 -0
- fast_agent/llm/provider/openai/llm_tensorzero_openai.py +128 -0
- fast_agent/llm/provider/openai/llm_xai.py +38 -0
- fast_agent/llm/provider/openai/multipart_converter_openai.py +561 -0
- fast_agent/llm/provider/openai/openai_multipart.py +169 -0
- fast_agent/llm/provider/openai/openai_utils.py +67 -0
- fast_agent/llm/provider/openai/responses.py +133 -0
- fast_agent/llm/provider_key_manager.py +139 -0
- fast_agent/llm/provider_types.py +34 -0
- fast_agent/llm/request_params.py +61 -0
- fast_agent/llm/sampling_converter.py +98 -0
- fast_agent/llm/stream_types.py +9 -0
- fast_agent/llm/usage_tracking.py +445 -0
- fast_agent/mcp/__init__.py +56 -0
- fast_agent/mcp/common.py +26 -0
- fast_agent/mcp/elicitation_factory.py +84 -0
- fast_agent/mcp/elicitation_handlers.py +164 -0
- fast_agent/mcp/gen_client.py +83 -0
- fast_agent/mcp/helpers/__init__.py +36 -0
- fast_agent/mcp/helpers/content_helpers.py +352 -0
- fast_agent/mcp/helpers/server_config_helpers.py +25 -0
- fast_agent/mcp/hf_auth.py +147 -0
- fast_agent/mcp/interfaces.py +92 -0
- fast_agent/mcp/logger_textio.py +108 -0
- fast_agent/mcp/mcp_agent_client_session.py +411 -0
- fast_agent/mcp/mcp_aggregator.py +2175 -0
- fast_agent/mcp/mcp_connection_manager.py +723 -0
- fast_agent/mcp/mcp_content.py +262 -0
- fast_agent/mcp/mime_utils.py +108 -0
- fast_agent/mcp/oauth_client.py +509 -0
- fast_agent/mcp/prompt.py +159 -0
- fast_agent/mcp/prompt_message_extended.py +155 -0
- fast_agent/mcp/prompt_render.py +84 -0
- fast_agent/mcp/prompt_serialization.py +580 -0
- fast_agent/mcp/prompts/__init__.py +0 -0
- fast_agent/mcp/prompts/__main__.py +7 -0
- fast_agent/mcp/prompts/prompt_constants.py +18 -0
- fast_agent/mcp/prompts/prompt_helpers.py +238 -0
- fast_agent/mcp/prompts/prompt_load.py +186 -0
- fast_agent/mcp/prompts/prompt_server.py +552 -0
- fast_agent/mcp/prompts/prompt_template.py +438 -0
- fast_agent/mcp/resource_utils.py +215 -0
- fast_agent/mcp/sampling.py +200 -0
- fast_agent/mcp/server/__init__.py +4 -0
- fast_agent/mcp/server/agent_server.py +613 -0
- fast_agent/mcp/skybridge.py +44 -0
- fast_agent/mcp/sse_tracking.py +287 -0
- fast_agent/mcp/stdio_tracking_simple.py +59 -0
- fast_agent/mcp/streamable_http_tracking.py +309 -0
- fast_agent/mcp/tool_execution_handler.py +137 -0
- fast_agent/mcp/tool_permission_handler.py +88 -0
- fast_agent/mcp/transport_tracking.py +634 -0
- fast_agent/mcp/types.py +24 -0
- fast_agent/mcp/ui_agent.py +48 -0
- fast_agent/mcp/ui_mixin.py +209 -0
- fast_agent/mcp_server_registry.py +89 -0
- fast_agent/py.typed +0 -0
- fast_agent/resources/examples/data-analysis/analysis-campaign.py +189 -0
- fast_agent/resources/examples/data-analysis/analysis.py +68 -0
- fast_agent/resources/examples/data-analysis/fastagent.config.yaml +41 -0
- fast_agent/resources/examples/data-analysis/mount-point/WA_Fn-UseC_-HR-Employee-Attrition.csv +1471 -0
- fast_agent/resources/examples/mcp/elicitations/elicitation_account_server.py +88 -0
- fast_agent/resources/examples/mcp/elicitations/elicitation_forms_server.py +297 -0
- fast_agent/resources/examples/mcp/elicitations/elicitation_game_server.py +164 -0
- fast_agent/resources/examples/mcp/elicitations/fastagent.config.yaml +35 -0
- fast_agent/resources/examples/mcp/elicitations/fastagent.secrets.yaml.example +17 -0
- fast_agent/resources/examples/mcp/elicitations/forms_demo.py +107 -0
- fast_agent/resources/examples/mcp/elicitations/game_character.py +65 -0
- fast_agent/resources/examples/mcp/elicitations/game_character_handler.py +256 -0
- fast_agent/resources/examples/mcp/elicitations/tool_call.py +21 -0
- fast_agent/resources/examples/mcp/state-transfer/agent_one.py +18 -0
- fast_agent/resources/examples/mcp/state-transfer/agent_two.py +18 -0
- fast_agent/resources/examples/mcp/state-transfer/fastagent.config.yaml +27 -0
- fast_agent/resources/examples/mcp/state-transfer/fastagent.secrets.yaml.example +15 -0
- fast_agent/resources/examples/researcher/fastagent.config.yaml +61 -0
- fast_agent/resources/examples/researcher/researcher-eval.py +53 -0
- fast_agent/resources/examples/researcher/researcher-imp.py +189 -0
- fast_agent/resources/examples/researcher/researcher.py +36 -0
- fast_agent/resources/examples/tensorzero/.env.sample +2 -0
- fast_agent/resources/examples/tensorzero/Makefile +31 -0
- fast_agent/resources/examples/tensorzero/README.md +56 -0
- fast_agent/resources/examples/tensorzero/agent.py +35 -0
- fast_agent/resources/examples/tensorzero/demo_images/clam.jpg +0 -0
- fast_agent/resources/examples/tensorzero/demo_images/crab.png +0 -0
- fast_agent/resources/examples/tensorzero/demo_images/shrimp.png +0 -0
- fast_agent/resources/examples/tensorzero/docker-compose.yml +105 -0
- fast_agent/resources/examples/tensorzero/fastagent.config.yaml +19 -0
- fast_agent/resources/examples/tensorzero/image_demo.py +67 -0
- fast_agent/resources/examples/tensorzero/mcp_server/Dockerfile +25 -0
- fast_agent/resources/examples/tensorzero/mcp_server/entrypoint.sh +35 -0
- fast_agent/resources/examples/tensorzero/mcp_server/mcp_server.py +31 -0
- fast_agent/resources/examples/tensorzero/mcp_server/pyproject.toml +11 -0
- fast_agent/resources/examples/tensorzero/simple_agent.py +25 -0
- fast_agent/resources/examples/tensorzero/tensorzero_config/system_schema.json +29 -0
- fast_agent/resources/examples/tensorzero/tensorzero_config/system_template.minijinja +11 -0
- fast_agent/resources/examples/tensorzero/tensorzero_config/tensorzero.toml +35 -0
- fast_agent/resources/examples/workflows/agents_as_tools_extended.py +73 -0
- fast_agent/resources/examples/workflows/agents_as_tools_simple.py +50 -0
- fast_agent/resources/examples/workflows/chaining.py +37 -0
- fast_agent/resources/examples/workflows/evaluator.py +77 -0
- fast_agent/resources/examples/workflows/fastagent.config.yaml +26 -0
- fast_agent/resources/examples/workflows/graded_report.md +89 -0
- fast_agent/resources/examples/workflows/human_input.py +28 -0
- fast_agent/resources/examples/workflows/maker.py +156 -0
- fast_agent/resources/examples/workflows/orchestrator.py +70 -0
- fast_agent/resources/examples/workflows/parallel.py +56 -0
- fast_agent/resources/examples/workflows/router.py +69 -0
- fast_agent/resources/examples/workflows/short_story.md +13 -0
- fast_agent/resources/examples/workflows/short_story.txt +19 -0
- fast_agent/resources/setup/.gitignore +30 -0
- fast_agent/resources/setup/agent.py +28 -0
- fast_agent/resources/setup/fastagent.config.yaml +65 -0
- fast_agent/resources/setup/fastagent.secrets.yaml.example +38 -0
- fast_agent/resources/setup/pyproject.toml.tmpl +23 -0
- fast_agent/skills/__init__.py +9 -0
- fast_agent/skills/registry.py +235 -0
- fast_agent/tools/elicitation.py +369 -0
- fast_agent/tools/shell_runtime.py +402 -0
- fast_agent/types/__init__.py +59 -0
- fast_agent/types/conversation_summary.py +294 -0
- fast_agent/types/llm_stop_reason.py +78 -0
- fast_agent/types/message_search.py +249 -0
- fast_agent/ui/__init__.py +38 -0
- fast_agent/ui/console.py +59 -0
- fast_agent/ui/console_display.py +1080 -0
- fast_agent/ui/elicitation_form.py +946 -0
- fast_agent/ui/elicitation_style.py +59 -0
- fast_agent/ui/enhanced_prompt.py +1400 -0
- fast_agent/ui/history_display.py +734 -0
- fast_agent/ui/interactive_prompt.py +1199 -0
- fast_agent/ui/markdown_helpers.py +104 -0
- fast_agent/ui/markdown_truncator.py +1004 -0
- fast_agent/ui/mcp_display.py +857 -0
- fast_agent/ui/mcp_ui_utils.py +235 -0
- fast_agent/ui/mermaid_utils.py +169 -0
- fast_agent/ui/message_primitives.py +50 -0
- fast_agent/ui/notification_tracker.py +205 -0
- fast_agent/ui/plain_text_truncator.py +68 -0
- fast_agent/ui/progress_display.py +10 -0
- fast_agent/ui/rich_progress.py +195 -0
- fast_agent/ui/streaming.py +774 -0
- fast_agent/ui/streaming_buffer.py +449 -0
- fast_agent/ui/tool_display.py +422 -0
- fast_agent/ui/usage_display.py +204 -0
- fast_agent/utils/__init__.py +5 -0
- fast_agent/utils/reasoning_stream_parser.py +77 -0
- fast_agent/utils/time.py +22 -0
- fast_agent/workflow_telemetry.py +261 -0
- fast_agent_mcp-0.4.7.dist-info/METADATA +788 -0
- fast_agent_mcp-0.4.7.dist-info/RECORD +261 -0
- fast_agent_mcp-0.4.7.dist-info/WHEEL +4 -0
- fast_agent_mcp-0.4.7.dist-info/entry_points.txt +7 -0
- fast_agent_mcp-0.4.7.dist-info/licenses/LICENSE +201 -0
fast_agent/constants.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Global constants for fast_agent with minimal dependencies to avoid circular imports.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
# Canonical tool name for the human input/elicitation tool
|
|
6
|
+
HUMAN_INPUT_TOOL_NAME = "__human_input"
|
|
7
|
+
MCP_UI = "mcp-ui"
|
|
8
|
+
REASONING = "reasoning"
|
|
9
|
+
FAST_AGENT_ERROR_CHANNEL = "fast-agent-error"
|
|
10
|
+
FAST_AGENT_REMOVED_METADATA_CHANNEL = "fast-agent-removed-meta"
|
|
11
|
+
FAST_AGENT_TIMING = "fast-agent-timing"
|
|
12
|
+
FAST_AGENT_TOOL_TIMING = "fast-agent-tool-timing"
|
|
13
|
+
# should we have MAX_TOOL_CALLS instead to constrain by number of tools rather than turns...?
|
|
14
|
+
DEFAULT_MAX_ITERATIONS = 99
|
|
15
|
+
"""Maximum number of User/Assistant turns to take"""
|
|
16
|
+
|
|
17
|
+
DEFAULT_TERMINAL_OUTPUT_BYTE_LIMIT = 8192
|
|
18
|
+
"""Baseline byte limit for ACP terminal output when no model info exists."""
|
|
19
|
+
|
|
20
|
+
TERMINAL_OUTPUT_TOKEN_RATIO = 0.25
|
|
21
|
+
"""Target fraction of model max output tokens to budget for terminal output."""
|
|
22
|
+
|
|
23
|
+
TERMINAL_OUTPUT_TOKEN_HEADROOM_RATIO = 0.2
|
|
24
|
+
"""Leave headroom for tool wrapper text and other turn data."""
|
|
25
|
+
|
|
26
|
+
TERMINAL_AVG_BYTES_PER_TOKEN = 4
|
|
27
|
+
"""Conservative bytes-per-token estimate for mapping token budgets to byte limits."""
|
|
28
|
+
|
|
29
|
+
MAX_TERMINAL_OUTPUT_BYTE_LIMIT = 32768
|
|
30
|
+
"""Hard cap on default ACP terminal output to avoid oversized tool payloads."""
|
|
31
|
+
|
|
32
|
+
DEFAULT_AGENT_INSTRUCTION = """You are a helpful AI Agent.
|
|
33
|
+
|
|
34
|
+
{{serverInstructions}}
|
|
35
|
+
{{agentSkills}}
|
|
36
|
+
{{file_silent:AGENTS.md}}
|
|
37
|
+
{{env}}
|
|
38
|
+
|
|
39
|
+
The current date is {{currentDate}}."""
|
|
40
|
+
|
|
41
|
+
CONTROL_MESSAGE_SAVE_HISTORY = "***SAVE_HISTORY"
|
fast_agent/context.py
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import concurrent.futures
|
|
5
|
+
import logging
|
|
6
|
+
import uuid
|
|
7
|
+
from os import PathLike
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import TYPE_CHECKING, Any
|
|
10
|
+
|
|
11
|
+
from opentelemetry import trace
|
|
12
|
+
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
|
|
13
|
+
|
|
14
|
+
# from opentelemetry.instrumentation.anthropic import AnthropicInstrumentor
|
|
15
|
+
from opentelemetry.propagate import set_global_textmap
|
|
16
|
+
from opentelemetry.sdk.resources import Resource
|
|
17
|
+
from opentelemetry.sdk.trace import TracerProvider
|
|
18
|
+
from opentelemetry.sdk.trace.export import BatchSpanProcessor, ConsoleSpanExporter
|
|
19
|
+
from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator
|
|
20
|
+
from pydantic import BaseModel, ConfigDict
|
|
21
|
+
|
|
22
|
+
from fast_agent.config import Settings, get_settings
|
|
23
|
+
from fast_agent.core.executor.executor import AsyncioExecutor, Executor
|
|
24
|
+
from fast_agent.core.executor.task_registry import ActivityRegistry
|
|
25
|
+
from fast_agent.core.logging.events import EventFilter, StreamingExclusionFilter
|
|
26
|
+
from fast_agent.core.logging.logger import LoggingConfig, get_logger
|
|
27
|
+
from fast_agent.core.logging.transport import create_transport
|
|
28
|
+
from fast_agent.mcp_server_registry import ServerRegistry
|
|
29
|
+
from fast_agent.skills import SkillRegistry
|
|
30
|
+
|
|
31
|
+
if TYPE_CHECKING:
|
|
32
|
+
from fast_agent.acp.acp_context import ACPContext
|
|
33
|
+
from fast_agent.core.executor.workflow_signal import SignalWaitCallback
|
|
34
|
+
from fast_agent.mcp.mcp_connection_manager import MCPConnectionManager
|
|
35
|
+
else:
|
|
36
|
+
# Runtime placeholders for the types
|
|
37
|
+
ACPContext = Any
|
|
38
|
+
SignalWaitCallback = Any
|
|
39
|
+
MCPConnectionManager = Any
|
|
40
|
+
|
|
41
|
+
logger = get_logger(__name__)
|
|
42
|
+
|
|
43
|
+
"""
|
|
44
|
+
A central context object to store global state that is shared across the application.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class Context(BaseModel):
|
|
49
|
+
"""
|
|
50
|
+
Context that is passed around through the application.
|
|
51
|
+
This is a global context that is shared across the application.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
config: Settings | None = None
|
|
55
|
+
executor: Executor | None = None
|
|
56
|
+
human_input_handler: Any | None = None
|
|
57
|
+
signal_notification: SignalWaitCallback | None = None
|
|
58
|
+
|
|
59
|
+
# Registries
|
|
60
|
+
server_registry: ServerRegistry | None = None
|
|
61
|
+
task_registry: ActivityRegistry | None = None
|
|
62
|
+
skill_registry: SkillRegistry | None = None
|
|
63
|
+
|
|
64
|
+
tracer: trace.Tracer | None = None
|
|
65
|
+
_connection_manager: "MCPConnectionManager | None" = None
|
|
66
|
+
|
|
67
|
+
# ACP context - set when running in ACP mode
|
|
68
|
+
# Provides agents access to ACP capabilities (mode switching, commands, etc.)
|
|
69
|
+
acp: "ACPContext | None" = None
|
|
70
|
+
|
|
71
|
+
model_config = ConfigDict(
|
|
72
|
+
extra="allow",
|
|
73
|
+
arbitrary_types_allowed=True, # Tell Pydantic to defer type evaluation
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
async def configure_otel(config: "Settings") -> None:
|
|
78
|
+
"""
|
|
79
|
+
Configure OpenTelemetry based on the application config.
|
|
80
|
+
"""
|
|
81
|
+
if not config.otel or not config.otel.enabled:
|
|
82
|
+
return
|
|
83
|
+
|
|
84
|
+
# Set up global textmap propagator first
|
|
85
|
+
set_global_textmap(TraceContextTextMapPropagator())
|
|
86
|
+
|
|
87
|
+
service_name = config.otel.service_name
|
|
88
|
+
from importlib.metadata import version
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
app_version = version("fast-agent-mcp")
|
|
92
|
+
except: # noqa: E722
|
|
93
|
+
app_version = "unknown"
|
|
94
|
+
|
|
95
|
+
resource = Resource.create(
|
|
96
|
+
attributes={
|
|
97
|
+
key: value
|
|
98
|
+
for key, value in {
|
|
99
|
+
"service.name": service_name,
|
|
100
|
+
"service.instance.id": str(uuid.uuid4())[:6],
|
|
101
|
+
"service.version": app_version,
|
|
102
|
+
}.items()
|
|
103
|
+
if value is not None
|
|
104
|
+
}
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
# Create provider with resource
|
|
108
|
+
tracer_provider = TracerProvider(resource=resource)
|
|
109
|
+
|
|
110
|
+
# Add exporters based on config
|
|
111
|
+
otlp_endpoint = config.otel.otlp_endpoint
|
|
112
|
+
if otlp_endpoint:
|
|
113
|
+
exporter = OTLPSpanExporter(endpoint=otlp_endpoint)
|
|
114
|
+
tracer_provider.add_span_processor(BatchSpanProcessor(exporter))
|
|
115
|
+
|
|
116
|
+
if config.otel.console_debug:
|
|
117
|
+
tracer_provider.add_span_processor(BatchSpanProcessor(ConsoleSpanExporter()))
|
|
118
|
+
else:
|
|
119
|
+
# Default to console exporter in development
|
|
120
|
+
tracer_provider.add_span_processor(BatchSpanProcessor(ConsoleSpanExporter()))
|
|
121
|
+
|
|
122
|
+
# Set as global tracer provider
|
|
123
|
+
trace.set_tracer_provider(tracer_provider)
|
|
124
|
+
|
|
125
|
+
# Attempt to instrument optional SDKs if available; continue silently if missing
|
|
126
|
+
try:
|
|
127
|
+
from opentelemetry.instrumentation.openai import OpenAIInstrumentor
|
|
128
|
+
|
|
129
|
+
OpenAIInstrumentor().instrument()
|
|
130
|
+
except Exception: # pragma: no cover - optional instrumentation
|
|
131
|
+
pass
|
|
132
|
+
|
|
133
|
+
try:
|
|
134
|
+
from opentelemetry.instrumentation.google_genai import GoogleGenAiSdkInstrumentor
|
|
135
|
+
|
|
136
|
+
GoogleGenAiSdkInstrumentor().instrument()
|
|
137
|
+
except Exception: # pragma: no cover - optional instrumentation
|
|
138
|
+
pass
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
# McpInstrumentor().instrument()
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
async def configure_logger(config: "Settings") -> None:
|
|
145
|
+
"""
|
|
146
|
+
Configure logging and tracing based on the application config.
|
|
147
|
+
"""
|
|
148
|
+
settings = config.logger
|
|
149
|
+
|
|
150
|
+
# Configure the standard Python logger used by LoggingListener so it respects settings.
|
|
151
|
+
python_logger = logging.getLogger("fast_agent")
|
|
152
|
+
python_logger.handlers.clear()
|
|
153
|
+
python_logger.setLevel(settings.level.upper())
|
|
154
|
+
python_logger.propagate = False
|
|
155
|
+
|
|
156
|
+
transport = None
|
|
157
|
+
if settings.type == "console":
|
|
158
|
+
# Console mode: use the Python logger to emit to stdout and skip additional transport output
|
|
159
|
+
handler = logging.StreamHandler()
|
|
160
|
+
handler.setLevel(settings.level.upper())
|
|
161
|
+
handler.setFormatter(logging.Formatter("%(message)s"))
|
|
162
|
+
python_logger.addHandler(handler)
|
|
163
|
+
else:
|
|
164
|
+
# For all other modes, rely on transports (file/http/none) and keep the Python logger quiet
|
|
165
|
+
python_logger.addHandler(logging.NullHandler())
|
|
166
|
+
|
|
167
|
+
# Use StreamingExclusionFilter to prevent streaming events from flooding logs
|
|
168
|
+
event_filter: EventFilter = StreamingExclusionFilter(min_level=settings.level)
|
|
169
|
+
logger.info(f"Configuring logger with level: {settings.level}")
|
|
170
|
+
if settings.type == "console":
|
|
171
|
+
from fast_agent.core.logging.transport import NoOpTransport
|
|
172
|
+
|
|
173
|
+
transport = NoOpTransport(event_filter=event_filter)
|
|
174
|
+
else:
|
|
175
|
+
transport = create_transport(settings=settings, event_filter=event_filter)
|
|
176
|
+
await LoggingConfig.configure(
|
|
177
|
+
event_filter=event_filter,
|
|
178
|
+
transport=transport,
|
|
179
|
+
batch_size=settings.batch_size,
|
|
180
|
+
flush_interval=settings.flush_interval,
|
|
181
|
+
progress_display=settings.progress_display,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
async def configure_executor(config: "Settings"):
|
|
186
|
+
"""
|
|
187
|
+
Configure the executor based on the application config.
|
|
188
|
+
"""
|
|
189
|
+
return AsyncioExecutor()
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
async def initialize_context(
|
|
193
|
+
config: Settings | str | PathLike[str] | None = None, store_globally: bool = False
|
|
194
|
+
):
|
|
195
|
+
"""
|
|
196
|
+
Initialize the global application context.
|
|
197
|
+
"""
|
|
198
|
+
if config is None:
|
|
199
|
+
config = get_settings()
|
|
200
|
+
elif isinstance(config, (str, PathLike)):
|
|
201
|
+
# Accept pathlib.Path and other path-like objects for convenience in tests
|
|
202
|
+
config = get_settings(config_path=str(config))
|
|
203
|
+
|
|
204
|
+
context = Context()
|
|
205
|
+
context.config = config
|
|
206
|
+
context.server_registry = ServerRegistry(config=config)
|
|
207
|
+
|
|
208
|
+
skills_settings = getattr(config, "skills", None)
|
|
209
|
+
override_directory = None
|
|
210
|
+
if skills_settings and getattr(skills_settings, "directory", None):
|
|
211
|
+
override_directory = Path(skills_settings.directory).expanduser()
|
|
212
|
+
context.skill_registry = SkillRegistry(
|
|
213
|
+
base_dir=Path.cwd(),
|
|
214
|
+
override_directory=override_directory,
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
# Configure logging and telemetry
|
|
218
|
+
await configure_otel(config)
|
|
219
|
+
await configure_logger(config)
|
|
220
|
+
|
|
221
|
+
# Configure the executor
|
|
222
|
+
context.executor = await configure_executor(config)
|
|
223
|
+
context.task_registry = ActivityRegistry()
|
|
224
|
+
|
|
225
|
+
# Store the tracer in context if needed
|
|
226
|
+
if config.otel:
|
|
227
|
+
context.tracer = trace.get_tracer(config.otel.service_name)
|
|
228
|
+
|
|
229
|
+
if store_globally:
|
|
230
|
+
global _global_context
|
|
231
|
+
_global_context = context
|
|
232
|
+
|
|
233
|
+
return context
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
async def cleanup_context() -> None:
|
|
237
|
+
"""
|
|
238
|
+
Cleanup the global application context.
|
|
239
|
+
"""
|
|
240
|
+
|
|
241
|
+
# Shutdown logging and telemetry
|
|
242
|
+
await LoggingConfig.shutdown()
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
_global_context: Context | None = None
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def get_current_context() -> Context:
|
|
249
|
+
"""
|
|
250
|
+
Synchronous initializer/getter for global application context.
|
|
251
|
+
"""
|
|
252
|
+
global _global_context
|
|
253
|
+
if _global_context is None:
|
|
254
|
+
try:
|
|
255
|
+
# Try to get the current event loop
|
|
256
|
+
loop = asyncio.get_event_loop()
|
|
257
|
+
if loop.is_running():
|
|
258
|
+
# Create a new loop in a separate thread
|
|
259
|
+
def run_async():
|
|
260
|
+
new_loop = asyncio.new_event_loop()
|
|
261
|
+
asyncio.set_event_loop(new_loop)
|
|
262
|
+
return new_loop.run_until_complete(initialize_context())
|
|
263
|
+
|
|
264
|
+
with concurrent.futures.ThreadPoolExecutor() as pool:
|
|
265
|
+
_global_context = pool.submit(run_async).result()
|
|
266
|
+
else:
|
|
267
|
+
_global_context = loop.run_until_complete(initialize_context())
|
|
268
|
+
except RuntimeError:
|
|
269
|
+
_global_context = asyncio.run(initialize_context())
|
|
270
|
+
return _global_context
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def get_current_config():
|
|
274
|
+
"""
|
|
275
|
+
Get the current application config.
|
|
276
|
+
|
|
277
|
+
Returns the context config if available, otherwise falls back to global settings.
|
|
278
|
+
"""
|
|
279
|
+
return get_current_context().config or get_settings()
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from contextlib import contextmanager
|
|
2
|
+
from typing import TYPE_CHECKING, Any
|
|
3
|
+
|
|
4
|
+
if TYPE_CHECKING:
|
|
5
|
+
from fast_agent.context import Context
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ContextDependent:
|
|
9
|
+
"""
|
|
10
|
+
Mixin class for components that need context access.
|
|
11
|
+
Provides both global fallback and instance-specific context support.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
# Ensure the attribute always exists even if a subclass
|
|
15
|
+
# does not call this mixin's __init__.
|
|
16
|
+
_context: "Context | None" = None
|
|
17
|
+
|
|
18
|
+
def __init__(self, context: "Context | None" = None, **kwargs: dict[str, Any]) -> None:
|
|
19
|
+
self._context = context
|
|
20
|
+
super().__init__()
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def context(self) -> "Context":
|
|
24
|
+
"""
|
|
25
|
+
Get context, with graceful fallback to global context if needed.
|
|
26
|
+
Raises clear error if no context is available.
|
|
27
|
+
"""
|
|
28
|
+
# First try instance context
|
|
29
|
+
if self._context is not None:
|
|
30
|
+
return self._context
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
# Fall back to global context if available
|
|
34
|
+
from fast_agent.context import get_current_context
|
|
35
|
+
|
|
36
|
+
return get_current_context()
|
|
37
|
+
except Exception as e:
|
|
38
|
+
raise RuntimeError(
|
|
39
|
+
f"No context available for {self.__class__.__name__}. Either initialize Core first or pass context explicitly."
|
|
40
|
+
) from e
|
|
41
|
+
|
|
42
|
+
@contextmanager
|
|
43
|
+
def use_context(self, context: "Context"):
|
|
44
|
+
"""Temporarily use a different context."""
|
|
45
|
+
old_context = self._context
|
|
46
|
+
self._context = context
|
|
47
|
+
try:
|
|
48
|
+
yield
|
|
49
|
+
finally:
|
|
50
|
+
self._context = old_context
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Core interfaces and decorators for fast-agent.
|
|
3
|
+
|
|
4
|
+
Public API:
|
|
5
|
+
- `Core`: The core application container
|
|
6
|
+
- `AgentApp`: Container for interacting with agents
|
|
7
|
+
- `FastAgent`: High-level, decorator-driven application class
|
|
8
|
+
- Decorators: `agent`, `custom`, `orchestrator`, `iterative_planner`,
|
|
9
|
+
`router`, `chain`, `parallel`, `evaluator_optimizer`
|
|
10
|
+
|
|
11
|
+
Exports are resolved lazily to avoid circular imports during package init.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from typing import TYPE_CHECKING
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def __getattr__(name: str):
|
|
18
|
+
if name == "AgentApp":
|
|
19
|
+
from .agent_app import AgentApp
|
|
20
|
+
|
|
21
|
+
return AgentApp
|
|
22
|
+
elif name == "Core":
|
|
23
|
+
from .core_app import Core
|
|
24
|
+
|
|
25
|
+
return Core
|
|
26
|
+
elif name == "FastAgent":
|
|
27
|
+
from .fastagent import FastAgent
|
|
28
|
+
|
|
29
|
+
return FastAgent
|
|
30
|
+
elif name in (
|
|
31
|
+
"agent",
|
|
32
|
+
"custom",
|
|
33
|
+
"orchestrator",
|
|
34
|
+
"iterative_planner",
|
|
35
|
+
"router",
|
|
36
|
+
"chain",
|
|
37
|
+
"parallel",
|
|
38
|
+
"evaluator_optimizer",
|
|
39
|
+
):
|
|
40
|
+
from . import direct_decorators as _dd
|
|
41
|
+
|
|
42
|
+
return getattr(
|
|
43
|
+
_dd,
|
|
44
|
+
name,
|
|
45
|
+
)
|
|
46
|
+
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
if TYPE_CHECKING: # pragma: no cover - typing aid only
|
|
50
|
+
from .agent_app import AgentApp as AgentApp # noqa: F401
|
|
51
|
+
from .core_app import Core as Core # noqa: F401
|
|
52
|
+
from .direct_decorators import ( # noqa: F401
|
|
53
|
+
agent as agent,
|
|
54
|
+
)
|
|
55
|
+
from .direct_decorators import (
|
|
56
|
+
chain as chain,
|
|
57
|
+
)
|
|
58
|
+
from .direct_decorators import (
|
|
59
|
+
custom as custom,
|
|
60
|
+
)
|
|
61
|
+
from .direct_decorators import (
|
|
62
|
+
evaluator_optimizer as evaluator_optimizer,
|
|
63
|
+
)
|
|
64
|
+
from .direct_decorators import (
|
|
65
|
+
iterative_planner as iterative_planner,
|
|
66
|
+
)
|
|
67
|
+
from .direct_decorators import (
|
|
68
|
+
orchestrator as orchestrator,
|
|
69
|
+
)
|
|
70
|
+
from .direct_decorators import (
|
|
71
|
+
parallel as parallel,
|
|
72
|
+
)
|
|
73
|
+
from .direct_decorators import (
|
|
74
|
+
router as router,
|
|
75
|
+
)
|
|
76
|
+
from .fastagent import FastAgent as FastAgent # noqa: F401
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
__all__ = [
|
|
80
|
+
"Core",
|
|
81
|
+
"AgentApp",
|
|
82
|
+
"FastAgent",
|
|
83
|
+
# Decorators
|
|
84
|
+
"agent",
|
|
85
|
+
"custom",
|
|
86
|
+
"orchestrator",
|
|
87
|
+
"iterative_planner",
|
|
88
|
+
"router",
|
|
89
|
+
"chain",
|
|
90
|
+
"parallel",
|
|
91
|
+
"evaluator_optimizer",
|
|
92
|
+
]
|