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.
Files changed (261) hide show
  1. fast_agent/__init__.py +183 -0
  2. fast_agent/acp/__init__.py +19 -0
  3. fast_agent/acp/acp_aware_mixin.py +304 -0
  4. fast_agent/acp/acp_context.py +437 -0
  5. fast_agent/acp/content_conversion.py +136 -0
  6. fast_agent/acp/filesystem_runtime.py +427 -0
  7. fast_agent/acp/permission_store.py +269 -0
  8. fast_agent/acp/server/__init__.py +5 -0
  9. fast_agent/acp/server/agent_acp_server.py +1472 -0
  10. fast_agent/acp/slash_commands.py +1050 -0
  11. fast_agent/acp/terminal_runtime.py +408 -0
  12. fast_agent/acp/tool_permission_adapter.py +125 -0
  13. fast_agent/acp/tool_permissions.py +474 -0
  14. fast_agent/acp/tool_progress.py +814 -0
  15. fast_agent/agents/__init__.py +85 -0
  16. fast_agent/agents/agent_types.py +64 -0
  17. fast_agent/agents/llm_agent.py +350 -0
  18. fast_agent/agents/llm_decorator.py +1139 -0
  19. fast_agent/agents/mcp_agent.py +1337 -0
  20. fast_agent/agents/tool_agent.py +271 -0
  21. fast_agent/agents/workflow/agents_as_tools_agent.py +849 -0
  22. fast_agent/agents/workflow/chain_agent.py +212 -0
  23. fast_agent/agents/workflow/evaluator_optimizer.py +380 -0
  24. fast_agent/agents/workflow/iterative_planner.py +652 -0
  25. fast_agent/agents/workflow/maker_agent.py +379 -0
  26. fast_agent/agents/workflow/orchestrator_models.py +218 -0
  27. fast_agent/agents/workflow/orchestrator_prompts.py +248 -0
  28. fast_agent/agents/workflow/parallel_agent.py +250 -0
  29. fast_agent/agents/workflow/router_agent.py +353 -0
  30. fast_agent/cli/__init__.py +0 -0
  31. fast_agent/cli/__main__.py +73 -0
  32. fast_agent/cli/commands/acp.py +159 -0
  33. fast_agent/cli/commands/auth.py +404 -0
  34. fast_agent/cli/commands/check_config.py +783 -0
  35. fast_agent/cli/commands/go.py +514 -0
  36. fast_agent/cli/commands/quickstart.py +557 -0
  37. fast_agent/cli/commands/serve.py +143 -0
  38. fast_agent/cli/commands/server_helpers.py +114 -0
  39. fast_agent/cli/commands/setup.py +174 -0
  40. fast_agent/cli/commands/url_parser.py +190 -0
  41. fast_agent/cli/constants.py +40 -0
  42. fast_agent/cli/main.py +115 -0
  43. fast_agent/cli/terminal.py +24 -0
  44. fast_agent/config.py +798 -0
  45. fast_agent/constants.py +41 -0
  46. fast_agent/context.py +279 -0
  47. fast_agent/context_dependent.py +50 -0
  48. fast_agent/core/__init__.py +92 -0
  49. fast_agent/core/agent_app.py +448 -0
  50. fast_agent/core/core_app.py +137 -0
  51. fast_agent/core/direct_decorators.py +784 -0
  52. fast_agent/core/direct_factory.py +620 -0
  53. fast_agent/core/error_handling.py +27 -0
  54. fast_agent/core/exceptions.py +90 -0
  55. fast_agent/core/executor/__init__.py +0 -0
  56. fast_agent/core/executor/executor.py +280 -0
  57. fast_agent/core/executor/task_registry.py +32 -0
  58. fast_agent/core/executor/workflow_signal.py +324 -0
  59. fast_agent/core/fastagent.py +1186 -0
  60. fast_agent/core/logging/__init__.py +5 -0
  61. fast_agent/core/logging/events.py +138 -0
  62. fast_agent/core/logging/json_serializer.py +164 -0
  63. fast_agent/core/logging/listeners.py +309 -0
  64. fast_agent/core/logging/logger.py +278 -0
  65. fast_agent/core/logging/transport.py +481 -0
  66. fast_agent/core/prompt.py +9 -0
  67. fast_agent/core/prompt_templates.py +183 -0
  68. fast_agent/core/validation.py +326 -0
  69. fast_agent/event_progress.py +62 -0
  70. fast_agent/history/history_exporter.py +49 -0
  71. fast_agent/human_input/__init__.py +47 -0
  72. fast_agent/human_input/elicitation_handler.py +123 -0
  73. fast_agent/human_input/elicitation_state.py +33 -0
  74. fast_agent/human_input/form_elements.py +59 -0
  75. fast_agent/human_input/form_fields.py +256 -0
  76. fast_agent/human_input/simple_form.py +113 -0
  77. fast_agent/human_input/types.py +40 -0
  78. fast_agent/interfaces.py +310 -0
  79. fast_agent/llm/__init__.py +9 -0
  80. fast_agent/llm/cancellation.py +22 -0
  81. fast_agent/llm/fastagent_llm.py +931 -0
  82. fast_agent/llm/internal/passthrough.py +161 -0
  83. fast_agent/llm/internal/playback.py +129 -0
  84. fast_agent/llm/internal/silent.py +41 -0
  85. fast_agent/llm/internal/slow.py +38 -0
  86. fast_agent/llm/memory.py +275 -0
  87. fast_agent/llm/model_database.py +490 -0
  88. fast_agent/llm/model_factory.py +388 -0
  89. fast_agent/llm/model_info.py +102 -0
  90. fast_agent/llm/prompt_utils.py +155 -0
  91. fast_agent/llm/provider/anthropic/anthropic_utils.py +84 -0
  92. fast_agent/llm/provider/anthropic/cache_planner.py +56 -0
  93. fast_agent/llm/provider/anthropic/llm_anthropic.py +796 -0
  94. fast_agent/llm/provider/anthropic/multipart_converter_anthropic.py +462 -0
  95. fast_agent/llm/provider/bedrock/bedrock_utils.py +218 -0
  96. fast_agent/llm/provider/bedrock/llm_bedrock.py +2207 -0
  97. fast_agent/llm/provider/bedrock/multipart_converter_bedrock.py +84 -0
  98. fast_agent/llm/provider/google/google_converter.py +466 -0
  99. fast_agent/llm/provider/google/llm_google_native.py +681 -0
  100. fast_agent/llm/provider/openai/llm_aliyun.py +31 -0
  101. fast_agent/llm/provider/openai/llm_azure.py +143 -0
  102. fast_agent/llm/provider/openai/llm_deepseek.py +76 -0
  103. fast_agent/llm/provider/openai/llm_generic.py +35 -0
  104. fast_agent/llm/provider/openai/llm_google_oai.py +32 -0
  105. fast_agent/llm/provider/openai/llm_groq.py +42 -0
  106. fast_agent/llm/provider/openai/llm_huggingface.py +85 -0
  107. fast_agent/llm/provider/openai/llm_openai.py +1195 -0
  108. fast_agent/llm/provider/openai/llm_openai_compatible.py +138 -0
  109. fast_agent/llm/provider/openai/llm_openrouter.py +45 -0
  110. fast_agent/llm/provider/openai/llm_tensorzero_openai.py +128 -0
  111. fast_agent/llm/provider/openai/llm_xai.py +38 -0
  112. fast_agent/llm/provider/openai/multipart_converter_openai.py +561 -0
  113. fast_agent/llm/provider/openai/openai_multipart.py +169 -0
  114. fast_agent/llm/provider/openai/openai_utils.py +67 -0
  115. fast_agent/llm/provider/openai/responses.py +133 -0
  116. fast_agent/llm/provider_key_manager.py +139 -0
  117. fast_agent/llm/provider_types.py +34 -0
  118. fast_agent/llm/request_params.py +61 -0
  119. fast_agent/llm/sampling_converter.py +98 -0
  120. fast_agent/llm/stream_types.py +9 -0
  121. fast_agent/llm/usage_tracking.py +445 -0
  122. fast_agent/mcp/__init__.py +56 -0
  123. fast_agent/mcp/common.py +26 -0
  124. fast_agent/mcp/elicitation_factory.py +84 -0
  125. fast_agent/mcp/elicitation_handlers.py +164 -0
  126. fast_agent/mcp/gen_client.py +83 -0
  127. fast_agent/mcp/helpers/__init__.py +36 -0
  128. fast_agent/mcp/helpers/content_helpers.py +352 -0
  129. fast_agent/mcp/helpers/server_config_helpers.py +25 -0
  130. fast_agent/mcp/hf_auth.py +147 -0
  131. fast_agent/mcp/interfaces.py +92 -0
  132. fast_agent/mcp/logger_textio.py +108 -0
  133. fast_agent/mcp/mcp_agent_client_session.py +411 -0
  134. fast_agent/mcp/mcp_aggregator.py +2175 -0
  135. fast_agent/mcp/mcp_connection_manager.py +723 -0
  136. fast_agent/mcp/mcp_content.py +262 -0
  137. fast_agent/mcp/mime_utils.py +108 -0
  138. fast_agent/mcp/oauth_client.py +509 -0
  139. fast_agent/mcp/prompt.py +159 -0
  140. fast_agent/mcp/prompt_message_extended.py +155 -0
  141. fast_agent/mcp/prompt_render.py +84 -0
  142. fast_agent/mcp/prompt_serialization.py +580 -0
  143. fast_agent/mcp/prompts/__init__.py +0 -0
  144. fast_agent/mcp/prompts/__main__.py +7 -0
  145. fast_agent/mcp/prompts/prompt_constants.py +18 -0
  146. fast_agent/mcp/prompts/prompt_helpers.py +238 -0
  147. fast_agent/mcp/prompts/prompt_load.py +186 -0
  148. fast_agent/mcp/prompts/prompt_server.py +552 -0
  149. fast_agent/mcp/prompts/prompt_template.py +438 -0
  150. fast_agent/mcp/resource_utils.py +215 -0
  151. fast_agent/mcp/sampling.py +200 -0
  152. fast_agent/mcp/server/__init__.py +4 -0
  153. fast_agent/mcp/server/agent_server.py +613 -0
  154. fast_agent/mcp/skybridge.py +44 -0
  155. fast_agent/mcp/sse_tracking.py +287 -0
  156. fast_agent/mcp/stdio_tracking_simple.py +59 -0
  157. fast_agent/mcp/streamable_http_tracking.py +309 -0
  158. fast_agent/mcp/tool_execution_handler.py +137 -0
  159. fast_agent/mcp/tool_permission_handler.py +88 -0
  160. fast_agent/mcp/transport_tracking.py +634 -0
  161. fast_agent/mcp/types.py +24 -0
  162. fast_agent/mcp/ui_agent.py +48 -0
  163. fast_agent/mcp/ui_mixin.py +209 -0
  164. fast_agent/mcp_server_registry.py +89 -0
  165. fast_agent/py.typed +0 -0
  166. fast_agent/resources/examples/data-analysis/analysis-campaign.py +189 -0
  167. fast_agent/resources/examples/data-analysis/analysis.py +68 -0
  168. fast_agent/resources/examples/data-analysis/fastagent.config.yaml +41 -0
  169. fast_agent/resources/examples/data-analysis/mount-point/WA_Fn-UseC_-HR-Employee-Attrition.csv +1471 -0
  170. fast_agent/resources/examples/mcp/elicitations/elicitation_account_server.py +88 -0
  171. fast_agent/resources/examples/mcp/elicitations/elicitation_forms_server.py +297 -0
  172. fast_agent/resources/examples/mcp/elicitations/elicitation_game_server.py +164 -0
  173. fast_agent/resources/examples/mcp/elicitations/fastagent.config.yaml +35 -0
  174. fast_agent/resources/examples/mcp/elicitations/fastagent.secrets.yaml.example +17 -0
  175. fast_agent/resources/examples/mcp/elicitations/forms_demo.py +107 -0
  176. fast_agent/resources/examples/mcp/elicitations/game_character.py +65 -0
  177. fast_agent/resources/examples/mcp/elicitations/game_character_handler.py +256 -0
  178. fast_agent/resources/examples/mcp/elicitations/tool_call.py +21 -0
  179. fast_agent/resources/examples/mcp/state-transfer/agent_one.py +18 -0
  180. fast_agent/resources/examples/mcp/state-transfer/agent_two.py +18 -0
  181. fast_agent/resources/examples/mcp/state-transfer/fastagent.config.yaml +27 -0
  182. fast_agent/resources/examples/mcp/state-transfer/fastagent.secrets.yaml.example +15 -0
  183. fast_agent/resources/examples/researcher/fastagent.config.yaml +61 -0
  184. fast_agent/resources/examples/researcher/researcher-eval.py +53 -0
  185. fast_agent/resources/examples/researcher/researcher-imp.py +189 -0
  186. fast_agent/resources/examples/researcher/researcher.py +36 -0
  187. fast_agent/resources/examples/tensorzero/.env.sample +2 -0
  188. fast_agent/resources/examples/tensorzero/Makefile +31 -0
  189. fast_agent/resources/examples/tensorzero/README.md +56 -0
  190. fast_agent/resources/examples/tensorzero/agent.py +35 -0
  191. fast_agent/resources/examples/tensorzero/demo_images/clam.jpg +0 -0
  192. fast_agent/resources/examples/tensorzero/demo_images/crab.png +0 -0
  193. fast_agent/resources/examples/tensorzero/demo_images/shrimp.png +0 -0
  194. fast_agent/resources/examples/tensorzero/docker-compose.yml +105 -0
  195. fast_agent/resources/examples/tensorzero/fastagent.config.yaml +19 -0
  196. fast_agent/resources/examples/tensorzero/image_demo.py +67 -0
  197. fast_agent/resources/examples/tensorzero/mcp_server/Dockerfile +25 -0
  198. fast_agent/resources/examples/tensorzero/mcp_server/entrypoint.sh +35 -0
  199. fast_agent/resources/examples/tensorzero/mcp_server/mcp_server.py +31 -0
  200. fast_agent/resources/examples/tensorzero/mcp_server/pyproject.toml +11 -0
  201. fast_agent/resources/examples/tensorzero/simple_agent.py +25 -0
  202. fast_agent/resources/examples/tensorzero/tensorzero_config/system_schema.json +29 -0
  203. fast_agent/resources/examples/tensorzero/tensorzero_config/system_template.minijinja +11 -0
  204. fast_agent/resources/examples/tensorzero/tensorzero_config/tensorzero.toml +35 -0
  205. fast_agent/resources/examples/workflows/agents_as_tools_extended.py +73 -0
  206. fast_agent/resources/examples/workflows/agents_as_tools_simple.py +50 -0
  207. fast_agent/resources/examples/workflows/chaining.py +37 -0
  208. fast_agent/resources/examples/workflows/evaluator.py +77 -0
  209. fast_agent/resources/examples/workflows/fastagent.config.yaml +26 -0
  210. fast_agent/resources/examples/workflows/graded_report.md +89 -0
  211. fast_agent/resources/examples/workflows/human_input.py +28 -0
  212. fast_agent/resources/examples/workflows/maker.py +156 -0
  213. fast_agent/resources/examples/workflows/orchestrator.py +70 -0
  214. fast_agent/resources/examples/workflows/parallel.py +56 -0
  215. fast_agent/resources/examples/workflows/router.py +69 -0
  216. fast_agent/resources/examples/workflows/short_story.md +13 -0
  217. fast_agent/resources/examples/workflows/short_story.txt +19 -0
  218. fast_agent/resources/setup/.gitignore +30 -0
  219. fast_agent/resources/setup/agent.py +28 -0
  220. fast_agent/resources/setup/fastagent.config.yaml +65 -0
  221. fast_agent/resources/setup/fastagent.secrets.yaml.example +38 -0
  222. fast_agent/resources/setup/pyproject.toml.tmpl +23 -0
  223. fast_agent/skills/__init__.py +9 -0
  224. fast_agent/skills/registry.py +235 -0
  225. fast_agent/tools/elicitation.py +369 -0
  226. fast_agent/tools/shell_runtime.py +402 -0
  227. fast_agent/types/__init__.py +59 -0
  228. fast_agent/types/conversation_summary.py +294 -0
  229. fast_agent/types/llm_stop_reason.py +78 -0
  230. fast_agent/types/message_search.py +249 -0
  231. fast_agent/ui/__init__.py +38 -0
  232. fast_agent/ui/console.py +59 -0
  233. fast_agent/ui/console_display.py +1080 -0
  234. fast_agent/ui/elicitation_form.py +946 -0
  235. fast_agent/ui/elicitation_style.py +59 -0
  236. fast_agent/ui/enhanced_prompt.py +1400 -0
  237. fast_agent/ui/history_display.py +734 -0
  238. fast_agent/ui/interactive_prompt.py +1199 -0
  239. fast_agent/ui/markdown_helpers.py +104 -0
  240. fast_agent/ui/markdown_truncator.py +1004 -0
  241. fast_agent/ui/mcp_display.py +857 -0
  242. fast_agent/ui/mcp_ui_utils.py +235 -0
  243. fast_agent/ui/mermaid_utils.py +169 -0
  244. fast_agent/ui/message_primitives.py +50 -0
  245. fast_agent/ui/notification_tracker.py +205 -0
  246. fast_agent/ui/plain_text_truncator.py +68 -0
  247. fast_agent/ui/progress_display.py +10 -0
  248. fast_agent/ui/rich_progress.py +195 -0
  249. fast_agent/ui/streaming.py +774 -0
  250. fast_agent/ui/streaming_buffer.py +449 -0
  251. fast_agent/ui/tool_display.py +422 -0
  252. fast_agent/ui/usage_display.py +204 -0
  253. fast_agent/utils/__init__.py +5 -0
  254. fast_agent/utils/reasoning_stream_parser.py +77 -0
  255. fast_agent/utils/time.py +22 -0
  256. fast_agent/workflow_telemetry.py +261 -0
  257. fast_agent_mcp-0.4.7.dist-info/METADATA +788 -0
  258. fast_agent_mcp-0.4.7.dist-info/RECORD +261 -0
  259. fast_agent_mcp-0.4.7.dist-info/WHEEL +4 -0
  260. fast_agent_mcp-0.4.7.dist-info/entry_points.txt +7 -0
  261. fast_agent_mcp-0.4.7.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,248 @@
1
+ """
2
+ Prompt templates used by the Orchestrator workflow.
3
+ """
4
+
5
+ # Templates for formatting results
6
+ TASK_RESULT_TEMPLATE = """Task: {task_description}
7
+ Result: {task_result}"""
8
+
9
+ STEP_RESULT_TEMPLATE = """Step: {step_description}
10
+ Step Subtasks:
11
+ {tasks_str}"""
12
+
13
+ PLAN_RESULT_TEMPLATE = """Plan Objective: {plan_objective}
14
+
15
+ Progress So Far (steps completed):
16
+ {steps_str}
17
+
18
+ Result: {plan_result}"""
19
+
20
+ FULL_PLAN_PROMPT_TEMPLATE = """You are tasked with orchestrating a plan to complete an objective.
21
+ You can analyze results from the previous steps already executed to decide if the objective is complete.
22
+
23
+ <fastagent:data>
24
+ <fastagent:objective>
25
+ {objective}
26
+ </fastagent:objective>
27
+
28
+ <fastagent:available-agents>
29
+ {agents}
30
+ </fastagent:available-agents>
31
+
32
+ <fastagent:progress>
33
+ {plan_result}
34
+ </fastagent:progress>
35
+
36
+ <fastagent:status>
37
+ {plan_status}
38
+ {iterations_info}
39
+ </fastagent:status>
40
+ </fastagent:data>
41
+
42
+ Your plan must be structured in sequential steps, with each step containing independent parallel subtasks.
43
+ If the previous results achieve the objective, return is_complete=True.
44
+ Otherwise, generate remaining steps needed.
45
+
46
+ <fastagent:instruction>
47
+ You are operating in "full plan" mode, where you generate a complete plan with ALL remaining steps needed.
48
+ After receiving your plan, the system will execute ALL steps in your plan before asking for your input again.
49
+ If the plan needs multiple iterations, you'll be called again with updated results.
50
+
51
+ Generate a plan with all remaining steps needed.
52
+ Steps are sequential, but each Step can have parallel subtasks.
53
+ For each Step, specify a description of the step and independent subtasks that can run in parallel.
54
+ For each subtask specify:
55
+ 1. Clear description of the task that an LLM can execute
56
+ 2. Name of 1 Agent from the available agents list above
57
+
58
+ CRITICAL: You MUST ONLY use agent names that are EXACTLY as they appear in <fastagent:available-agents> above.
59
+ Do NOT invent new agents. Do NOT modify agent names. The plan will FAIL if you use an agent that doesn't exist.
60
+
61
+ Return your response in the following JSON structure:
62
+ {{
63
+ "steps": [
64
+ {{
65
+ "description": "Description of step 1",
66
+ "tasks": [
67
+ {{
68
+ "description": "Description of task 1",
69
+ "agent": "agent_name" // agent MUST be exactly one of the agent names listed above
70
+ }},
71
+ {{
72
+ "description": "Description of task 2",
73
+ "agent": "agent_name2" // agent MUST be exactly one of the agent names listed above
74
+ }}
75
+ ]
76
+ }}
77
+ ],
78
+ "is_complete": false
79
+ }}
80
+
81
+ Set "is_complete" to true when ANY of these conditions are met:
82
+ 1. The objective has been achieved in full or substantively
83
+ 2. The remaining work is minor or trivial compared to what's been accomplished
84
+ 3. Additional steps provide minimal value toward the core objective
85
+ 4. The plan has gathered sufficient information to answer the original request
86
+
87
+ Be decisive - avoid excessive planning steps that add little value. It's better to complete a plan early than to continue with marginal improvements. Focus on the core intent of the objective, not perfection.
88
+
89
+ You must respond with valid JSON only, with no triple backticks. No markdown formatting.
90
+ No extra text. Do not wrap in ```json code fences.
91
+ </fastagent:instruction>
92
+ """
93
+
94
+ ITERATIVE_PLAN_PROMPT_TEMPLATE = """You are tasked with determining only the next step in a plan
95
+ needed to complete an objective. You must analyze the current state and progress from previous steps
96
+ to decide what to do next.
97
+
98
+ <fastagent:data>
99
+ <fastagent:objective>
100
+ {objective}
101
+ </fastagent:objective>
102
+
103
+ <fastagent:available-agents>
104
+ {agents}
105
+ </fastagent:available-agents>
106
+
107
+ <fastagent:progress>
108
+ {plan_result}
109
+ </fastagent:progress>
110
+
111
+ <fastagent:status>
112
+ {plan_status}
113
+ {iterations_info}
114
+ </fastagent:status>
115
+ </fastagent:data>
116
+
117
+ A Step must be sequential in the plan, but can have independent parallel subtasks. Only return a single Step.
118
+ If the previous results achieve the objective, return is_complete=True.
119
+ Otherwise, generate the next Step.
120
+
121
+ <fastagent:instruction>
122
+ You are operating in "iterative plan" mode, where you generate ONLY ONE STEP at a time.
123
+ After each step is executed, you'll be called again to determine the next step based on updated results.
124
+
125
+ Generate the next step, by specifying a description of the step and independent subtasks that can run in parallel:
126
+ For each subtask specify:
127
+ 1. Clear description of the task that an LLM can execute
128
+ 2. Name of 1 Agent from the available agents list above
129
+
130
+ CRITICAL: You MUST ONLY use agent names that are EXACTLY as they appear in <fastagent:available-agents> above.
131
+ Do NOT invent new agents. Do NOT modify agent names. The plan will FAIL if you use an agent that doesn't exist.
132
+
133
+ Return your response in the following JSON structure:
134
+ {{
135
+ "description": "Description of step 1",
136
+ "tasks": [
137
+ {{
138
+ "description": "Description of task 1",
139
+ "agent": "agent_name" // agent MUST be exactly one of the agent names listed above
140
+ }}
141
+ ],
142
+ "is_complete": false
143
+ }}
144
+
145
+ Set "is_complete" to true when ANY of these conditions are met:
146
+ 1. The objective has been achieved in full or substantively
147
+ 2. The remaining work is minor or trivial compared to what's been accomplished
148
+ 3. Additional steps provide minimal value toward the core objective
149
+ 4. The plan has gathered sufficient information to answer the original request
150
+
151
+ Be decisive - avoid excessive planning steps that add little value. It's better to complete a plan early than to continue with marginal improvements. Focus on the core intent of the objective, not perfection.
152
+
153
+ You must respond with valid JSON only, with no triple backticks. No markdown formatting.
154
+ No extra text. Do not wrap in ```json code fences.
155
+ </fastagent:instruction>
156
+ """
157
+
158
+ TASK_PROMPT_TEMPLATE = """You are part of a larger workflow to achieve an objective.
159
+
160
+ <fastagent:data>
161
+ <fastagent:objective>
162
+ {objective}
163
+ </fastagent:objective>
164
+
165
+ <fastagent:task>
166
+ {task}
167
+ </fastagent:task>
168
+
169
+ <fastagent:context>
170
+ {context}
171
+ </fastagent:context>
172
+ </fastagent:data>
173
+
174
+ <fastagent:instruction>
175
+ Your job is to accomplish only the task specified above.
176
+ Use the context from previous steps to inform your approach.
177
+ The context contains structured XML with the results from previous steps - pay close attention to:
178
+ - The objective in <fastagent:objective>
179
+ - Previous step results in <fastagent:steps>
180
+ - Task results and their attribution in <fastagent:task-result>
181
+
182
+ Provide a direct, focused response that addresses the task.
183
+ </fastagent:instruction>
184
+ """
185
+
186
+ SYNTHESIZE_STEP_PROMPT_TEMPLATE = """You need to synthesize the results of parallel tasks into a cohesive result.
187
+
188
+ <fastagent:data>
189
+ <fastagent:step-results>
190
+ {step_result}
191
+ </fastagent:step-results>
192
+ </fastagent:data>
193
+
194
+ <fastagent:instruction>
195
+ Analyze the results from all tasks in this step.
196
+ Each task was executed by a specific agent (finder, writer, etc.)
197
+ Consider the expertise of each agent when weighing their results.
198
+ Combine the information into a coherent, unified response.
199
+ Focus on key insights and important outcomes.
200
+ Resolve any conflicting information if present.
201
+ </fastagent:instruction>
202
+ """
203
+
204
+ SYNTHESIZE_PLAN_PROMPT_TEMPLATE = """You need to synthesize the results of all completed plan steps into a final response.
205
+
206
+ <fastagent:data>
207
+ <fastagent:plan-results>
208
+ {plan_result}
209
+ </fastagent:plan-results>
210
+ </fastagent:data>
211
+
212
+ <fastagent:instruction>
213
+ Create a comprehensive final response that addresses the original objective.
214
+ Integrate all the information gathered across all plan steps.
215
+ Provide a clear, complete answer that achieves the objective.
216
+ Focus on delivering value through your synthesis, not just summarizing.
217
+
218
+ If the plan was marked as incomplete but the maximum number of iterations was reached,
219
+ make sure to state clearly what was accomplished and what remains to be done.
220
+ </fastagent:instruction>
221
+ """
222
+
223
+ # New template for incomplete plans due to iteration limits
224
+ SYNTHESIZE_INCOMPLETE_PLAN_TEMPLATE = """You need to synthesize the results of all completed plan steps into a final response.
225
+
226
+ <fastagent:data>
227
+ <fastagent:plan-results>
228
+ {plan_result}
229
+ </fastagent:plan-results>
230
+ </fastagent:data>
231
+
232
+ <fastagent:status>
233
+ The maximum number of iterations ({max_iterations}) was reached before the objective could be completed.
234
+ </fastagent:status>
235
+
236
+ <fastagent:instruction>
237
+ Create a comprehensive response that summarizes what was accomplished so far.
238
+ The objective was NOT fully completed due to reaching the maximum number of iterations.
239
+
240
+ In your response:
241
+ 1. Clearly state that the objective was not fully completed
242
+ 2. Summarize what WAS accomplished across all the executed steps
243
+ 3. Identify what remains to be done to complete the objective
244
+ 4. Organize the information to provide maximum value despite being incomplete
245
+
246
+ Focus on being transparent about the incomplete status while providing as much value as possible.
247
+ </fastagent:instruction>
248
+ """
@@ -0,0 +1,250 @@
1
+ import asyncio
2
+ from typing import Any, List, Optional, Tuple
3
+
4
+ from mcp import Tool
5
+ from mcp.types import TextContent
6
+ from opentelemetry import trace
7
+
8
+ from fast_agent.agents.agent_types import AgentConfig, AgentType
9
+ from fast_agent.agents.llm_agent import LlmAgent
10
+ from fast_agent.core.logging.logger import get_logger
11
+ from fast_agent.interfaces import AgentProtocol, ModelT
12
+ from fast_agent.types import PromptMessageExtended, RequestParams
13
+
14
+ logger = get_logger(__name__)
15
+
16
+
17
+ class ParallelAgent(LlmAgent):
18
+ """
19
+ LLMs can sometimes work simultaneously on a task (fan-out)
20
+ and have their outputs aggregated programmatically (fan-in).
21
+ This workflow performs both the fan-out and fan-in operations using LLMs.
22
+ From the user's perspective, an input is specified and the output is returned.
23
+ """
24
+
25
+ @property
26
+ def agent_type(self) -> AgentType:
27
+ """Return the type of this agent."""
28
+ return AgentType.PARALLEL
29
+
30
+ def __init__(
31
+ self,
32
+ config: AgentConfig,
33
+ fan_in_agent: AgentProtocol,
34
+ fan_out_agents: List[AgentProtocol],
35
+ include_request: bool = True,
36
+ **kwargs,
37
+ ) -> None:
38
+ """
39
+ Initialize a ParallelLLM agent.
40
+
41
+ Args:
42
+ config: Agent configuration or name
43
+ fan_in_agent: Agent that aggregates results from fan-out agents
44
+ fan_out_agents: List of agents to execute in parallel
45
+ include_request: Whether to include the original request in the aggregation
46
+ **kwargs: Additional keyword arguments to pass to BaseAgent
47
+ """
48
+ super().__init__(config, **kwargs)
49
+ self.fan_in_agent = fan_in_agent
50
+ self.fan_out_agents = fan_out_agents
51
+ self.include_request = include_request
52
+
53
+ async def generate_impl(
54
+ self,
55
+ messages: List[PromptMessageExtended],
56
+ request_params: Optional[RequestParams] = None,
57
+ tools: List[Tool] | None = None,
58
+ ) -> PromptMessageExtended:
59
+ """
60
+ Execute fan-out agents in parallel and aggregate their results with the fan-in agent.
61
+
62
+ Args:
63
+ normalized_messages: Already normalized list of PromptMessageExtended
64
+ request_params: Optional parameters to configure the request
65
+
66
+ Returns:
67
+ The aggregated response from the fan-in agent
68
+ """
69
+
70
+ tracer = trace.get_tracer(__name__)
71
+ with tracer.start_as_current_span(f"Parallel: '{self._name}' generate"):
72
+ responses: List[PromptMessageExtended] = await self._execute_fan_out(
73
+ messages, request_params
74
+ )
75
+
76
+ # Extract the received message from the input
77
+ received_message: Optional[str] = messages[-1].all_text() if messages else None
78
+
79
+ # Convert responses to strings for aggregation
80
+ string_responses = []
81
+ for response in responses:
82
+ string_responses.append(response.all_text())
83
+
84
+ # Format the responses and send to the fan-in agent
85
+ aggregated_prompt = self._format_responses(string_responses, received_message)
86
+
87
+ # Create a new multipart message with the formatted responses
88
+ formatted_prompt = PromptMessageExtended(
89
+ role="user", content=[TextContent(type="text", text=aggregated_prompt)]
90
+ )
91
+
92
+ # Use the fan-in agent to aggregate the responses
93
+ return await self._fan_in_generate(formatted_prompt, request_params)
94
+
95
+ def _format_responses(self, responses: List[Any], message: Optional[str] = None) -> str:
96
+ """
97
+ Format a list of responses for the fan-in agent.
98
+
99
+ Args:
100
+ responses: List of responses from fan-out agents
101
+ message: Optional original message that was sent to the agents
102
+
103
+ Returns:
104
+ Formatted string with responses
105
+ """
106
+ formatted = []
107
+
108
+ # Include the original message if specified
109
+ if self.include_request and message:
110
+ formatted.append("The following request was sent to the agents:")
111
+ formatted.append(f"<fastagent:request>\n{message}\n</fastagent:request>")
112
+
113
+ # Format each agent's response
114
+ for i, response in enumerate(responses):
115
+ agent_name = self.fan_out_agents[i].name
116
+ formatted.append(
117
+ f'<fastagent:response agent="{agent_name}">\n{response}\n</fastagent:response>'
118
+ )
119
+ return "\n\n".join(formatted)
120
+
121
+ async def structured_impl(
122
+ self,
123
+ messages: List[PromptMessageExtended],
124
+ model: type[ModelT],
125
+ request_params: Optional[RequestParams] = None,
126
+ ) -> Tuple[ModelT | None, PromptMessageExtended]:
127
+ """
128
+ Apply the prompt and return the result as a Pydantic model.
129
+
130
+ This implementation delegates to the fan-in agent's structured method.
131
+
132
+ Args:
133
+ messages: List of PromptMessageExtended objects
134
+ model: The Pydantic model class to parse the result into
135
+ request_params: Optional parameters to configure the LLM request
136
+
137
+ Returns:
138
+ An instance of the specified model, or None if coercion fails
139
+ """
140
+
141
+ tracer = trace.get_tracer(__name__)
142
+ with tracer.start_as_current_span(f"Parallel: '{self._name}' generate"):
143
+ responses: List[PromptMessageExtended] = await self._execute_fan_out(
144
+ messages, request_params
145
+ )
146
+
147
+ # Extract the received message
148
+ received_message: Optional[str] = messages[-1].all_text() if messages else None
149
+
150
+ # Convert responses to strings
151
+ string_responses = [response.all_text() for response in responses]
152
+
153
+ # Format the responses for the fan-in agent
154
+ aggregated_prompt = self._format_responses(string_responses, received_message)
155
+
156
+ # Create a multipart message
157
+ formatted_prompt = PromptMessageExtended(
158
+ role="user", content=[TextContent(type="text", text=aggregated_prompt)]
159
+ )
160
+
161
+ # Use the fan-in agent to parse the structured output
162
+ return await self._fan_in_structured(formatted_prompt, model, request_params)
163
+
164
+ async def initialize(self) -> None:
165
+ """
166
+ Initialize the agent and its fan-in and fan-out agents.
167
+ """
168
+ await super().initialize()
169
+
170
+ # Initialize fan-in and fan-out agents if not already initialized
171
+ if not self.fan_in_agent.initialized:
172
+ await self.fan_in_agent.initialize()
173
+
174
+ for agent in self.fan_out_agents:
175
+ if not agent.initialized:
176
+ await agent.initialize()
177
+
178
+ async def shutdown(self) -> None:
179
+ """
180
+ Shutdown the agent and its fan-in and fan-out agents.
181
+ """
182
+ await super().shutdown()
183
+
184
+ # Shutdown fan-in and fan-out agents
185
+ try:
186
+ await self.fan_in_agent.shutdown()
187
+ except Exception as e:
188
+ logger.warning(f"Error shutting down fan-in agent: {str(e)}")
189
+
190
+ for agent in self.fan_out_agents:
191
+ try:
192
+ await agent.shutdown()
193
+ except Exception as e:
194
+ logger.warning(f"Error shutting down fan-out agent {agent.name}: {str(e)}")
195
+
196
+ async def _execute_fan_out(
197
+ self,
198
+ messages: List[PromptMessageExtended],
199
+ request_params: Optional[RequestParams],
200
+ ) -> List[PromptMessageExtended]:
201
+ """
202
+ Run fan-out agents with telemetry so transports can surface progress.
203
+ """
204
+
205
+ async def _run_agent(agent: AgentProtocol) -> PromptMessageExtended:
206
+ async with self.workflow_telemetry.start_step(
207
+ "parallel.fan_out",
208
+ server_name=self.name,
209
+ arguments={"agent": agent.name},
210
+ ) as step:
211
+ result = await agent.generate(messages, request_params)
212
+ await step.finish(True, text=f"{agent.name} completed fan-out work")
213
+ return result
214
+
215
+ return await asyncio.gather(*[_run_agent(agent) for agent in self.fan_out_agents])
216
+
217
+ async def _fan_in_generate(
218
+ self,
219
+ prompt: PromptMessageExtended,
220
+ request_params: Optional[RequestParams],
221
+ ) -> PromptMessageExtended:
222
+ """
223
+ Aggregate fan-out output with telemetry.
224
+ """
225
+ async with self.workflow_telemetry.start_step(
226
+ "parallel.fan_in",
227
+ server_name=self.name,
228
+ arguments={"agent": self.fan_in_agent.name},
229
+ ) as step:
230
+ result = await self.fan_in_agent.generate([prompt], request_params)
231
+ await step.finish(True, text=f"{self.fan_in_agent.name} aggregated results")
232
+ return result
233
+
234
+ async def _fan_in_structured(
235
+ self,
236
+ prompt: PromptMessageExtended,
237
+ model: type[ModelT],
238
+ request_params: Optional[RequestParams],
239
+ ) -> Tuple[ModelT | None, PromptMessageExtended]:
240
+ """
241
+ Structured aggregation with telemetry.
242
+ """
243
+ async with self.workflow_telemetry.start_step(
244
+ "parallel.fan_in_structured",
245
+ server_name=self.name,
246
+ arguments={"agent": self.fan_in_agent.name},
247
+ ) as step:
248
+ result = await self.fan_in_agent.structured([prompt], model, request_params)
249
+ await step.finish(True, text=f"{self.fan_in_agent.name} produced structured output")
250
+ return result