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
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This demonstrates creating multiple agents and an orchestrator to coordinate them.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
|
|
7
|
+
from fast_agent import FastAgent
|
|
8
|
+
|
|
9
|
+
# Create the application
|
|
10
|
+
fast = FastAgent("Orchestrator-Workers")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@fast.agent(
|
|
14
|
+
"author",
|
|
15
|
+
instruction="""You are to role play a poorly skilled writer,
|
|
16
|
+
who makes frequent grammar, punctuation and spelling errors. You enjoy
|
|
17
|
+
writing short stories, but the narrative doesn't always make sense""",
|
|
18
|
+
servers=["filesystem"],
|
|
19
|
+
)
|
|
20
|
+
# Define worker agents
|
|
21
|
+
@fast.agent(
|
|
22
|
+
name="finder",
|
|
23
|
+
instruction="""You are an agent with access to the filesystem,
|
|
24
|
+
as well as the ability to fetch URLs. Your job is to identify
|
|
25
|
+
the closest match to a user's request, make the appropriate tool calls,
|
|
26
|
+
and return the URI and CONTENTS of the closest match.""",
|
|
27
|
+
servers=["fetch", "filesystem"],
|
|
28
|
+
model="gpt-4.1",
|
|
29
|
+
)
|
|
30
|
+
@fast.agent(
|
|
31
|
+
name="writer",
|
|
32
|
+
instruction="""You are an agent that can write to the filesystem.
|
|
33
|
+
You are tasked with taking the user's input, addressing it, and
|
|
34
|
+
writing the result to disk in the appropriate location.""",
|
|
35
|
+
servers=["filesystem"],
|
|
36
|
+
)
|
|
37
|
+
@fast.agent(
|
|
38
|
+
name="proofreader",
|
|
39
|
+
instruction=""""Review the short story for grammar, spelling, and punctuation errors.
|
|
40
|
+
Identify any awkward phrasing or structural issues that could improve clarity.
|
|
41
|
+
Provide detailed feedback on corrections.""",
|
|
42
|
+
servers=["fetch"],
|
|
43
|
+
model="gpt-4.1",
|
|
44
|
+
)
|
|
45
|
+
# Define the orchestrator to coordinate the other agents
|
|
46
|
+
@fast.iterative_planner(
|
|
47
|
+
name="orchestrate",
|
|
48
|
+
agents=["finder", "writer", "proofreader"],
|
|
49
|
+
model="sonnet",
|
|
50
|
+
plan_iterations=5,
|
|
51
|
+
)
|
|
52
|
+
async def main() -> None:
|
|
53
|
+
async with fast.run() as agent:
|
|
54
|
+
# await agent.author(
|
|
55
|
+
# "write a 250 word short story about kittens discovering a castle, and save it to short_story.md"
|
|
56
|
+
# )
|
|
57
|
+
|
|
58
|
+
# The orchestrator can be used just like any other agent
|
|
59
|
+
task = """Load the student's short story from short_story.md,
|
|
60
|
+
and generate a report with feedback across proofreading,
|
|
61
|
+
factuality/logical consistency and style adherence. Use the style rules from
|
|
62
|
+
https://apastyle.apa.org/learn/quick-guide-on-formatting and
|
|
63
|
+
https://apastyle.apa.org/learn/quick-guide-on-references.
|
|
64
|
+
Write the graded report to graded_report.md in the same directory as short_story.md"""
|
|
65
|
+
|
|
66
|
+
await agent.orchestrate(task)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
if __name__ == "__main__":
|
|
70
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Parallel Workflow showing Fan Out and Fan In agents, using different models
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from fast_agent import FastAgent
|
|
9
|
+
from fast_agent.core.prompt import Prompt
|
|
10
|
+
|
|
11
|
+
# Create the application
|
|
12
|
+
fast = FastAgent(
|
|
13
|
+
"Parallel Workflow",
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@fast.agent(
|
|
18
|
+
name="proofreader",
|
|
19
|
+
instruction=""""Review the short story for grammar, spelling, and punctuation errors.
|
|
20
|
+
Identify any awkward phrasing or structural issues that could improve clarity.
|
|
21
|
+
Provide detailed feedback on corrections.""",
|
|
22
|
+
)
|
|
23
|
+
@fast.agent(
|
|
24
|
+
name="fact_checker",
|
|
25
|
+
instruction="""Verify the factual consistency within the story. Identify any contradictions,
|
|
26
|
+
logical inconsistencies, or inaccuracies in the plot, character actions, or setting.
|
|
27
|
+
Highlight potential issues with reasoning or coherence.""",
|
|
28
|
+
)
|
|
29
|
+
@fast.agent(
|
|
30
|
+
name="style_enforcer",
|
|
31
|
+
instruction="""Analyze the story for adherence to style guidelines.
|
|
32
|
+
Evaluate the narrative flow, clarity of expression, and tone. Suggest improvements to
|
|
33
|
+
enhance storytelling, readability, and engagement.""",
|
|
34
|
+
model="sonnet",
|
|
35
|
+
)
|
|
36
|
+
@fast.agent(
|
|
37
|
+
name="grader",
|
|
38
|
+
instruction="""Compile the feedback from the Proofreader, Fact Checker, and Style Enforcer
|
|
39
|
+
into a structured report. Summarize key issues and categorize them by type.
|
|
40
|
+
Provide actionable recommendations for improving the story,
|
|
41
|
+
and give an overall grade based on the feedback.""",
|
|
42
|
+
)
|
|
43
|
+
@fast.parallel(
|
|
44
|
+
fan_out=["proofreader", "fact_checker", "style_enforcer"],
|
|
45
|
+
fan_in="grader",
|
|
46
|
+
name="parallel",
|
|
47
|
+
)
|
|
48
|
+
async def main() -> None:
|
|
49
|
+
async with fast.run() as agent:
|
|
50
|
+
await agent.parallel.send(
|
|
51
|
+
Prompt.user("Student short story submission", Path("short_story.txt"))
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
if __name__ == "__main__":
|
|
56
|
+
asyncio.run(main()) # type: ignore
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Example MCP Agent application showing router workflow with decorator syntax.
|
|
3
|
+
Demonstrates router's ability to either:
|
|
4
|
+
1. Use tools directly to handle requests
|
|
5
|
+
2. Delegate requests to specialized agents
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import sys
|
|
10
|
+
|
|
11
|
+
from rich.console import Console
|
|
12
|
+
|
|
13
|
+
from fast_agent import FastAgent
|
|
14
|
+
|
|
15
|
+
# Create the application
|
|
16
|
+
fast = FastAgent(
|
|
17
|
+
"Router Workflow",
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
# Sample requests demonstrating direct tool use vs agent delegation
|
|
21
|
+
SAMPLE_REQUESTS = [
|
|
22
|
+
"Download and summarize https://llmindset.co.uk/posts/2024/12/mcp-build-notes/", # Router handles directly with fetch
|
|
23
|
+
"Analyze the quality of the Python codebase in the current working directory", # Delegated to code expert
|
|
24
|
+
"What are the key principles of effective beekeeping?", # Delegated to general assistant
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@fast.agent(
|
|
29
|
+
name="fetcher",
|
|
30
|
+
model="haiku",
|
|
31
|
+
instruction="""You are an agent, with a tool enabling you to fetch URLs.""",
|
|
32
|
+
servers=["fetch"],
|
|
33
|
+
)
|
|
34
|
+
@fast.agent(
|
|
35
|
+
name="code_expert",
|
|
36
|
+
model="haiku",
|
|
37
|
+
instruction="""You are an expert in code analysis and software engineering.
|
|
38
|
+
When asked about code, architecture, or development practices,
|
|
39
|
+
you provide thorough and practical insights.""",
|
|
40
|
+
servers=["filesystem"],
|
|
41
|
+
)
|
|
42
|
+
@fast.agent(
|
|
43
|
+
name="general_assistant",
|
|
44
|
+
model="haiku",
|
|
45
|
+
instruction="""You are a knowledgeable assistant that provides clear,
|
|
46
|
+
well-reasoned responses about general topics, concepts, and principles.""",
|
|
47
|
+
)
|
|
48
|
+
@fast.router(
|
|
49
|
+
name="route",
|
|
50
|
+
model="sonnet",
|
|
51
|
+
default=True,
|
|
52
|
+
agents=["code_expert", "general_assistant", "fetcher"],
|
|
53
|
+
)
|
|
54
|
+
async def main() -> None:
|
|
55
|
+
console = Console(file=sys.stderr)
|
|
56
|
+
console.print(
|
|
57
|
+
"\n[bright_red]Router Workflow Demo[/bright_red]\n\n"
|
|
58
|
+
"Enter a request to route it to the appropriate agent.\nEnter [bright_red]STOP[/bright_red] to run the demo, [bright_red]EXIT[/bright_red] to leave"
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
async with fast.run() as agent:
|
|
62
|
+
await agent.interactive(agent_name="route")
|
|
63
|
+
for request in SAMPLE_REQUESTS:
|
|
64
|
+
await agent.route(request)
|
|
65
|
+
await agent.interactive()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
if __name__ == "__main__":
|
|
69
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
The Kittens Castle Adventuer
|
|
2
|
+
|
|
3
|
+
One sunny day, three lil kittens name Whiskers, Socks, and Mittens was walkin threw a mystirus forrest. They hadnt never seen such a big forrest before! The trees was tall an spooky, an the ground was coverd in moss an stikks.
|
|
4
|
+
|
|
5
|
+
Suddenlee, thru the trees, they sawd somthing HUUUUGE! It was a castell, but not just eny castell. This castell was made of sparkling chese an glittery windos. The turrits was so high they tuch the clowds, an the doars was big enuff for a elefant to walk threw!
|
|
6
|
+
|
|
7
|
+
"Lookk!" sed Whiskers, his tale all poofy wit exsitement. "We fowned a castell!" Socks meowed loudly an jumped up an down. Mittens, who was the smallist kitten, just stared wit her big rond eyes.
|
|
8
|
+
|
|
9
|
+
They climed up the cheesy walls, slip-slidin on the smoth surfase. Inside, they discoverd rooms ful of soft pillows an dangling strings an shiny things that went JINGEL when they tuch them. It was like a kitten paradyse!
|
|
10
|
+
|
|
11
|
+
But then, a big shadowy figur apeared... was it the castell gaurd? Or sumthing mor mystirus? The kittens hudeld togethar, there lil hearts beating fast. What wud happan next in there amazeing adventuer?
|
|
12
|
+
|
|
13
|
+
THE END??
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
The Battle of Glimmerwood
|
|
2
|
+
|
|
3
|
+
In the heart of Glimmerwood, a mystical forest knowed for its radiant trees, a small village thrived.
|
|
4
|
+
The villagers, who were live peacefully, shared their home with the forest's magical creatures,
|
|
5
|
+
especially the Glimmerfoxes whose fur shimmer like moonlight.
|
|
6
|
+
|
|
7
|
+
One fateful evening, the peace was shaterred when the infamous Dark Marauders attack.
|
|
8
|
+
Lead by the cunning Captain Thorn, the bandits aim to steal the precious Glimmerstones which was believed to grant immortality.
|
|
9
|
+
|
|
10
|
+
Amidst the choas, a young girl named Elara stood her ground, she rallied the villagers and devised a clever plan.
|
|
11
|
+
Using the forests natural defenses they lured the marauders into a trap.
|
|
12
|
+
As the bandits aproached the village square, a herd of Glimmerfoxes emerged, blinding them with their dazzling light,
|
|
13
|
+
the villagers seized the opportunity to captured the invaders.
|
|
14
|
+
|
|
15
|
+
Elara's bravery was celebrated and she was hailed as the "Guardian of Glimmerwood".
|
|
16
|
+
The Glimmerstones were secured in a hidden grove protected by an ancient spell.
|
|
17
|
+
|
|
18
|
+
However, not all was as it seemed. The Glimmerstones true power was never confirm,
|
|
19
|
+
and whispers of a hidden agenda linger among the villagers.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Secrets
|
|
2
|
+
fastagent.secrets.yaml
|
|
3
|
+
|
|
4
|
+
# Python cache/bytecode
|
|
5
|
+
__pycache__/
|
|
6
|
+
*.py[cod]
|
|
7
|
+
|
|
8
|
+
# Local env and virtualenvs
|
|
9
|
+
.env
|
|
10
|
+
.venv/
|
|
11
|
+
venv/
|
|
12
|
+
env/
|
|
13
|
+
|
|
14
|
+
# OS cruft
|
|
15
|
+
.DS_Store
|
|
16
|
+
Thumbs.db
|
|
17
|
+
|
|
18
|
+
# Logs/artifacts
|
|
19
|
+
fastagent.jsonl
|
|
20
|
+
*.jsonl
|
|
21
|
+
|
|
22
|
+
# Editors (optional)
|
|
23
|
+
.idea/
|
|
24
|
+
.vscode/
|
|
25
|
+
|
|
26
|
+
# Packaging metadata
|
|
27
|
+
*.dist-info/
|
|
28
|
+
*.egg-info/
|
|
29
|
+
dist/
|
|
30
|
+
build/
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
|
|
3
|
+
from fast_agent import FastAgent
|
|
4
|
+
|
|
5
|
+
# Create the application
|
|
6
|
+
fast = FastAgent("fast-agent example")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
default_instruction = """You are a helpful AI Agent.
|
|
10
|
+
|
|
11
|
+
{{serverInstructions}}
|
|
12
|
+
{{agentSkills}}
|
|
13
|
+
{{file_silent:AGENTS.md}}
|
|
14
|
+
{{env}}
|
|
15
|
+
|
|
16
|
+
The current date is {{currentDate}}."""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# Define the agent
|
|
20
|
+
@fast.agent(instruction=default_instruction)
|
|
21
|
+
async def main():
|
|
22
|
+
# use the --model command line switch or agent arguments to change model
|
|
23
|
+
async with fast.run() as agent:
|
|
24
|
+
await agent.interactive()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
if __name__ == "__main__":
|
|
28
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# fast-agent configuration File
|
|
2
|
+
|
|
3
|
+
# Default Model Configuration:
|
|
4
|
+
#
|
|
5
|
+
# Takes format:
|
|
6
|
+
# <provider>.<model_string>.<reasoning_effort?> (e.g. anthropic.claude-3-5-sonnet-20241022 or openai.o3-mini.low)
|
|
7
|
+
# Accepts aliases for Anthropic Models: haiku, haiku3, sonnet, sonnet35, opus, opus3
|
|
8
|
+
# and OpenAI Models: gpt-4.1, gpt-4.1-mini, o1, o1-mini, o3-mini
|
|
9
|
+
#
|
|
10
|
+
# If not specified, defaults to "gpt-5-mini.low".
|
|
11
|
+
# Can be overriden with a command line switch --model=<model>, or within the Agent constructor.
|
|
12
|
+
|
|
13
|
+
default_model: gpt-5-mini.low
|
|
14
|
+
|
|
15
|
+
# Number of times to retry transient LLM API errors (falls back to FAST_AGENT_RETRIES env var)
|
|
16
|
+
# llm_retries: 0
|
|
17
|
+
|
|
18
|
+
# otel:
|
|
19
|
+
# enabled: true
|
|
20
|
+
|
|
21
|
+
# mcp-ui support: disabled, enabled or auto. "auto" opens the web browser on the asset automatically
|
|
22
|
+
# mcp_ui_output_dir: ".fast-agent/ui" # Where to write MCP-UI HTML files (relative to CWD if not absolute)
|
|
23
|
+
# mcp_ui_mode: enabled
|
|
24
|
+
|
|
25
|
+
# MCP timeline display (adjust activity window/intervals in MCP UI + fast-agent check)
|
|
26
|
+
mcp_timeline:
|
|
27
|
+
steps: 20 # number of timeline buckets to render
|
|
28
|
+
step_seconds: 15 # seconds per bucket (accepts values like "45s", "2m")
|
|
29
|
+
|
|
30
|
+
#shell_execution:
|
|
31
|
+
# length of time before terminating subprocess
|
|
32
|
+
# timeout_seconds: 20
|
|
33
|
+
# warning interval if no output seen
|
|
34
|
+
# warning_seconds: 5
|
|
35
|
+
|
|
36
|
+
# Logging and Console Configuration:
|
|
37
|
+
logger:
|
|
38
|
+
# level: "debug" | "info" | "warning" | "error"
|
|
39
|
+
# type: "none" | "console" | "file" | "http"
|
|
40
|
+
# path: "/path/to/logfile.jsonl"
|
|
41
|
+
|
|
42
|
+
# Switch the progress display on or off
|
|
43
|
+
progress_display: true
|
|
44
|
+
# Show chat User/Assistant messages on the console
|
|
45
|
+
show_chat: true
|
|
46
|
+
# Show tool calls on the console
|
|
47
|
+
show_tools: true
|
|
48
|
+
# Truncate long tool responses on the console
|
|
49
|
+
truncate_tools: true
|
|
50
|
+
# Streaming renderer for assistant responses: "markdown", "plain", or "none"
|
|
51
|
+
streaming: markdown
|
|
52
|
+
|
|
53
|
+
# MCP Servers
|
|
54
|
+
mcp:
|
|
55
|
+
servers:
|
|
56
|
+
fetch:
|
|
57
|
+
command: "uvx"
|
|
58
|
+
args: ["mcp-server-fetch"]
|
|
59
|
+
filesystem:
|
|
60
|
+
command: "npx"
|
|
61
|
+
args: ["-y", "@modelcontextprotocol/server-filesystem", "."]
|
|
62
|
+
huggingface:
|
|
63
|
+
url: "https://huggingface.co/mcp?login"
|
|
64
|
+
# headers:
|
|
65
|
+
# "Authorization": "Bearer ${ENV_VAR}"
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# FastAgent Secrets Configuration
|
|
2
|
+
# WARNING: Keep this file secure and never commit to version control
|
|
3
|
+
|
|
4
|
+
# Alternatively set OPENAI_API_KEY, ANTHROPIC_API_KEY or other environment variables.
|
|
5
|
+
# Keys in the configuration file override environment variables.
|
|
6
|
+
|
|
7
|
+
openai:
|
|
8
|
+
api_key: <your-api-key-here>
|
|
9
|
+
anthropic:
|
|
10
|
+
api_key: <your-api-key-here>
|
|
11
|
+
deepseek:
|
|
12
|
+
api_key: <your-api-key-here>
|
|
13
|
+
openrouter:
|
|
14
|
+
api_key: <your-api-key-here>
|
|
15
|
+
deepseek:
|
|
16
|
+
api_key: <your-api-key-here>
|
|
17
|
+
google:
|
|
18
|
+
api_key: <your-api-key-here>
|
|
19
|
+
googleoai:
|
|
20
|
+
api_key: <your-api-key-here>
|
|
21
|
+
azure:
|
|
22
|
+
api_key: <your-api-key-here>
|
|
23
|
+
base_url: "https://...resource_name....openai.azure.com/" # Resource name (do NOT include if using base_url)
|
|
24
|
+
azure_deployment: "gpt-4.1" # Required - the model deployment name
|
|
25
|
+
# api_version: "2024-10-21"
|
|
26
|
+
xai:
|
|
27
|
+
api_key: <your-api-key-here>
|
|
28
|
+
hf:
|
|
29
|
+
api_key: <your-api-key-here>
|
|
30
|
+
groq:
|
|
31
|
+
api_key: <your-api-key-here>
|
|
32
|
+
|
|
33
|
+
# Example of setting an MCP Server environment variable
|
|
34
|
+
mcp:
|
|
35
|
+
servers:
|
|
36
|
+
brave:
|
|
37
|
+
env:
|
|
38
|
+
BRAVE_API_KEY: <your-api-key-here>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "fast-agent-app"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "a simple fast-agent application"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = "{{python_requires}}"
|
|
7
|
+
dependencies = [
|
|
8
|
+
{{fast_agent_dep}}
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
[tool.uv]
|
|
12
|
+
package = true
|
|
13
|
+
|
|
14
|
+
# Optional convenience entry point if you later package this app:
|
|
15
|
+
[project.scripts]
|
|
16
|
+
fast-agent-app = "agent:main"
|
|
17
|
+
|
|
18
|
+
[build-system]
|
|
19
|
+
requires = ["setuptools>=64", "wheel"]
|
|
20
|
+
build-backend = "setuptools.build_meta"
|
|
21
|
+
|
|
22
|
+
[tool.setuptools]
|
|
23
|
+
py-modules = ["agent"]
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, replace
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Sequence
|
|
6
|
+
|
|
7
|
+
import frontmatter
|
|
8
|
+
|
|
9
|
+
from fast_agent.core.logging.logger import get_logger
|
|
10
|
+
|
|
11
|
+
logger = get_logger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True)
|
|
15
|
+
class SkillManifest:
|
|
16
|
+
"""Represents a single skill description loaded from SKILL.md."""
|
|
17
|
+
|
|
18
|
+
name: str
|
|
19
|
+
description: str
|
|
20
|
+
body: str
|
|
21
|
+
path: Path
|
|
22
|
+
relative_path: Path | None = None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class SkillRegistry:
|
|
26
|
+
"""Simple registry that resolves a single skills directory and parses manifests."""
|
|
27
|
+
|
|
28
|
+
DEFAULT_CANDIDATES = (Path(".fast-agent/skills"), Path(".claude/skills"))
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self, *, base_dir: Path | None = None, override_directory: Path | None = None
|
|
32
|
+
) -> None:
|
|
33
|
+
self._base_dir = base_dir or Path.cwd()
|
|
34
|
+
self._directory: Path | None = None
|
|
35
|
+
self._original_override_directory: Path | None = None # Store original before resolution
|
|
36
|
+
self._override_failed: bool = False
|
|
37
|
+
self._errors: list[dict[str, str]] = []
|
|
38
|
+
if override_directory:
|
|
39
|
+
self._original_override_directory = override_directory
|
|
40
|
+
resolved = self._resolve_directory(override_directory)
|
|
41
|
+
if resolved and resolved.exists() and resolved.is_dir():
|
|
42
|
+
self._directory = resolved
|
|
43
|
+
else:
|
|
44
|
+
logger.warning(
|
|
45
|
+
"Skills directory override not found",
|
|
46
|
+
data={"directory": str(resolved)},
|
|
47
|
+
)
|
|
48
|
+
self._override_failed = True
|
|
49
|
+
if self._directory is None and not self._override_failed:
|
|
50
|
+
self._directory = self._find_default_directory()
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def directory(self) -> Path | None:
|
|
54
|
+
return self._directory
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def override_failed(self) -> bool:
|
|
58
|
+
return self._override_failed
|
|
59
|
+
|
|
60
|
+
def load_manifests(self) -> list[SkillManifest]:
|
|
61
|
+
self._errors = []
|
|
62
|
+
if not self._directory:
|
|
63
|
+
return []
|
|
64
|
+
manifests = self._load_directory(self._directory, self._errors)
|
|
65
|
+
|
|
66
|
+
# Recompute relative paths to be from base_dir (workspace root) instead of skills directory
|
|
67
|
+
adjusted_manifests: list[SkillManifest] = []
|
|
68
|
+
for manifest in manifests:
|
|
69
|
+
try:
|
|
70
|
+
relative_path = manifest.path.relative_to(self._base_dir)
|
|
71
|
+
adjusted_manifest = replace(manifest, relative_path=relative_path)
|
|
72
|
+
adjusted_manifests.append(adjusted_manifest)
|
|
73
|
+
except ValueError:
|
|
74
|
+
# Path is outside workspace - compute relative to skills directory
|
|
75
|
+
# and prepend the original override path (e.g., ../skills/my-skill/SKILL.md)
|
|
76
|
+
if self._original_override_directory is not None:
|
|
77
|
+
try:
|
|
78
|
+
skill_relative = manifest.path.relative_to(self._directory)
|
|
79
|
+
relative_path = self._original_override_directory / skill_relative
|
|
80
|
+
adjusted_manifest = replace(manifest, relative_path=relative_path)
|
|
81
|
+
except ValueError:
|
|
82
|
+
# Fallback to absolute path if we can't compute relative
|
|
83
|
+
adjusted_manifest = replace(manifest, relative_path=None)
|
|
84
|
+
else:
|
|
85
|
+
adjusted_manifest = replace(manifest, relative_path=None)
|
|
86
|
+
adjusted_manifests.append(adjusted_manifest)
|
|
87
|
+
|
|
88
|
+
return adjusted_manifests
|
|
89
|
+
|
|
90
|
+
def load_manifests_with_errors(self) -> tuple[list[SkillManifest], list[dict[str, str]]]:
|
|
91
|
+
manifests = self.load_manifests()
|
|
92
|
+
return manifests, list(self._errors)
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
def errors(self) -> list[dict[str, str]]:
|
|
96
|
+
return list(self._errors)
|
|
97
|
+
|
|
98
|
+
def _find_default_directory(self) -> Path | None:
|
|
99
|
+
for candidate in self.DEFAULT_CANDIDATES:
|
|
100
|
+
resolved = self._resolve_directory(candidate)
|
|
101
|
+
if resolved and resolved.exists() and resolved.is_dir():
|
|
102
|
+
return resolved
|
|
103
|
+
return None
|
|
104
|
+
|
|
105
|
+
def _resolve_directory(self, directory: Path) -> Path:
|
|
106
|
+
if directory.is_absolute():
|
|
107
|
+
return directory
|
|
108
|
+
return (self._base_dir / directory).resolve()
|
|
109
|
+
|
|
110
|
+
@classmethod
|
|
111
|
+
def load_directory(cls, directory: Path) -> list[SkillManifest]:
|
|
112
|
+
if not directory.exists() or not directory.is_dir():
|
|
113
|
+
logger.debug(
|
|
114
|
+
"Skills directory not found",
|
|
115
|
+
data={"directory": str(directory)},
|
|
116
|
+
)
|
|
117
|
+
return []
|
|
118
|
+
return cls._load_directory(directory)
|
|
119
|
+
|
|
120
|
+
@classmethod
|
|
121
|
+
def load_directory_with_errors(
|
|
122
|
+
cls, directory: Path
|
|
123
|
+
) -> tuple[list[SkillManifest], list[dict[str, str]]]:
|
|
124
|
+
errors: list[dict[str, str]] = []
|
|
125
|
+
manifests = cls._load_directory(directory, errors)
|
|
126
|
+
return manifests, errors
|
|
127
|
+
|
|
128
|
+
@classmethod
|
|
129
|
+
def _load_directory(
|
|
130
|
+
cls,
|
|
131
|
+
directory: Path,
|
|
132
|
+
errors: list[dict[str, str]] | None = None,
|
|
133
|
+
) -> list[SkillManifest]:
|
|
134
|
+
manifests: list[SkillManifest] = []
|
|
135
|
+
for entry in sorted(directory.iterdir()):
|
|
136
|
+
if not entry.is_dir():
|
|
137
|
+
continue
|
|
138
|
+
manifest_path = entry / "SKILL.md"
|
|
139
|
+
if not manifest_path.exists():
|
|
140
|
+
continue
|
|
141
|
+
manifest, error = cls._parse_manifest(manifest_path)
|
|
142
|
+
if manifest:
|
|
143
|
+
# Compute relative path from skills directory (not cwd)
|
|
144
|
+
# Old behavior: try both cwd and directory
|
|
145
|
+
# relative_path: Path | None = None
|
|
146
|
+
# for base in (cwd, directory):
|
|
147
|
+
# try:
|
|
148
|
+
# relative_path = manifest_path.relative_to(base)
|
|
149
|
+
# break
|
|
150
|
+
# except ValueError:
|
|
151
|
+
# continue
|
|
152
|
+
|
|
153
|
+
# New behavior: always relative to skills directory
|
|
154
|
+
try:
|
|
155
|
+
relative_path = manifest_path.relative_to(directory)
|
|
156
|
+
except ValueError:
|
|
157
|
+
relative_path = None
|
|
158
|
+
|
|
159
|
+
manifest = replace(manifest, relative_path=relative_path)
|
|
160
|
+
manifests.append(manifest)
|
|
161
|
+
elif errors is not None:
|
|
162
|
+
errors.append(
|
|
163
|
+
{
|
|
164
|
+
"path": str(manifest_path),
|
|
165
|
+
"error": error or "Failed to parse skill manifest",
|
|
166
|
+
}
|
|
167
|
+
)
|
|
168
|
+
return manifests
|
|
169
|
+
|
|
170
|
+
@classmethod
|
|
171
|
+
def _parse_manifest(cls, manifest_path: Path) -> tuple[SkillManifest | None, str | None]:
|
|
172
|
+
try:
|
|
173
|
+
post = frontmatter.loads(manifest_path.read_text(encoding="utf-8"))
|
|
174
|
+
except Exception as exc: # noqa: BLE001
|
|
175
|
+
logger.warning(
|
|
176
|
+
"Failed to parse skill manifest",
|
|
177
|
+
data={"path": str(manifest_path), "error": str(exc)},
|
|
178
|
+
)
|
|
179
|
+
return None, str(exc)
|
|
180
|
+
|
|
181
|
+
metadata = post.metadata or {}
|
|
182
|
+
name = metadata.get("name")
|
|
183
|
+
description = metadata.get("description")
|
|
184
|
+
|
|
185
|
+
if not isinstance(name, str) or not name.strip():
|
|
186
|
+
logger.warning("Skill manifest missing name", data={"path": str(manifest_path)})
|
|
187
|
+
return None, "Missing 'name' field"
|
|
188
|
+
if not isinstance(description, str) or not description.strip():
|
|
189
|
+
logger.warning("Skill manifest missing description", data={"path": str(manifest_path)})
|
|
190
|
+
return None, "Missing 'description' field"
|
|
191
|
+
|
|
192
|
+
body_text = (post.content or "").strip()
|
|
193
|
+
|
|
194
|
+
return SkillManifest(
|
|
195
|
+
name=name.strip(),
|
|
196
|
+
description=description.strip(),
|
|
197
|
+
body=body_text,
|
|
198
|
+
path=manifest_path,
|
|
199
|
+
), None
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def format_skills_for_prompt(manifests: Sequence[SkillManifest]) -> str:
|
|
203
|
+
"""
|
|
204
|
+
Format a collection of skill manifests into an XML-style block suitable for system prompts.
|
|
205
|
+
"""
|
|
206
|
+
if not manifests:
|
|
207
|
+
return ""
|
|
208
|
+
|
|
209
|
+
preamble = (
|
|
210
|
+
"Skills provide specialized capabilities and domain knowledge. Use a Skill if it seems in any way "
|
|
211
|
+
"relevant to the Users task, intent or would increase your effectiveness. \n"
|
|
212
|
+
"Use 'execute' to run shell commands in the agent workspace. Files you create will be visible to the user."
|
|
213
|
+
"To use a Skill you must first read the SKILL.md file (use 'execute' tool).\n "
|
|
214
|
+
"Paths in Skill documentation are relative to that Skill's directory, NOT the workspace root.\n"
|
|
215
|
+
"For example if the 'test' skill has scripts/example.py access it with <skill_folder>/scripts/example.py.\n"
|
|
216
|
+
"Only use Skills listed in <available_skills> below.\n\n"
|
|
217
|
+
)
|
|
218
|
+
formatted_parts: list[str] = []
|
|
219
|
+
|
|
220
|
+
for manifest in manifests:
|
|
221
|
+
description = (manifest.description or "").strip()
|
|
222
|
+
relative_path = manifest.relative_path
|
|
223
|
+
path_attr = f' path="{relative_path}"' if relative_path is not None else ""
|
|
224
|
+
if relative_path is None and manifest.path:
|
|
225
|
+
path_attr = f' path="{manifest.path}"'
|
|
226
|
+
|
|
227
|
+
block_lines: list[str] = [f'<agent-skill name="{manifest.name}"{path_attr}>']
|
|
228
|
+
if description:
|
|
229
|
+
block_lines.append(f"{description}")
|
|
230
|
+
block_lines.append("</agent-skill>")
|
|
231
|
+
formatted_parts.append("\n".join(block_lines))
|
|
232
|
+
|
|
233
|
+
return "".join(
|
|
234
|
+
(f"{preamble}<available_skills>\n", "\n".join(formatted_parts), "\n</available_skills>")
|
|
235
|
+
)
|