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,138 @@
1
+ from typing import Type
2
+
3
+ from fast_agent.interfaces import ModelT
4
+ from fast_agent.llm.model_database import ModelDatabase
5
+ from fast_agent.llm.provider.openai.llm_openai import OpenAILLM
6
+ from fast_agent.mcp.helpers.content_helpers import split_thinking_content
7
+ from fast_agent.types import PromptMessageExtended, RequestParams
8
+
9
+
10
+ class OpenAICompatibleLLM(OpenAILLM):
11
+ """Shared helpers for OpenAI-compatible providers that need structured output prompting."""
12
+
13
+ STRUCTURED_PROMPT_TEMPLATE = """YOU MUST RESPOND WITH A JSON OBJECT IN EXACTLY THIS FORMAT:
14
+ {format_description}
15
+
16
+ IMPORTANT RULES:
17
+ - Respond ONLY with the JSON object, no other text
18
+ - Do NOT include "properties" or "schema" wrappers
19
+ - Do NOT use code fences or markdown
20
+ - The response must be valid JSON that matches the format above
21
+ - All required fields must be included"""
22
+
23
+ async def _apply_prompt_provider_specific_structured(
24
+ self,
25
+ multipart_messages: list[PromptMessageExtended],
26
+ model: Type[ModelT],
27
+ request_params: RequestParams | None = None,
28
+ ) -> tuple[ModelT | None, PromptMessageExtended]:
29
+ if not self._supports_structured_prompt():
30
+ return await super()._apply_prompt_provider_specific_structured(
31
+ multipart_messages, model, request_params
32
+ )
33
+
34
+ request_params = self.get_request_params(request_params)
35
+
36
+ prompt_format = self._structured_prompt_format()
37
+ if prompt_format == "json_object" and not request_params.response_format:
38
+ request_params.response_format = {"type": "json_object"}
39
+
40
+ instructions = self._build_structured_prompt_instruction(model)
41
+ if instructions:
42
+ multipart_messages[-1].add_text(instructions)
43
+
44
+ return await super()._apply_prompt_provider_specific_structured(
45
+ multipart_messages, model, request_params
46
+ )
47
+
48
+ def _supports_structured_prompt(self) -> bool:
49
+ """Allow subclasses to opt-out of shared structured prompting."""
50
+ return True
51
+
52
+ def _structured_prompt_format(self) -> str | None:
53
+ """Return the response_format type this provider expects."""
54
+ return "json_object"
55
+
56
+ def _build_structured_prompt_instruction(self, model: Type[ModelT]) -> str | None:
57
+ template = self._structured_prompt_template()
58
+ if not template:
59
+ return None
60
+
61
+ schema = model.model_json_schema()
62
+ format_description = self._schema_to_json_object(schema, schema.get("$defs"))
63
+ return template.format(format_description=format_description)
64
+
65
+ def _structured_prompt_template(self) -> str | None:
66
+ return self.STRUCTURED_PROMPT_TEMPLATE
67
+
68
+ def _prepare_structured_text(self, text: str) -> str:
69
+ reasoning_mode = self._structured_reasoning_mode()
70
+ if reasoning_mode == "tags":
71
+ thinking, trimmed = split_thinking_content(text)
72
+ if thinking is None:
73
+ closing_tag = "</think>"
74
+ closing_index = text.find(closing_tag)
75
+ if closing_index != -1:
76
+ trimmed = text[closing_index + len(closing_tag) :].lstrip()
77
+ else:
78
+ trimmed = text
79
+ return trimmed
80
+
81
+ if "</think>" in text:
82
+ logger = getattr(self, "logger", None)
83
+ if logger:
84
+ logger.warning(
85
+ "Model emitted reasoning tags without 'tags' reasoning mode",
86
+ data={
87
+ "model": getattr(self.default_request_params, "model", None),
88
+ "text_preview": text[:200],
89
+ },
90
+ )
91
+ return text
92
+
93
+ def _structured_reasoning_mode(self) -> str | None:
94
+ model_name = self.default_request_params.model if self.default_request_params else None
95
+ return ModelDatabase.get_reasoning(model_name) if model_name else None
96
+
97
+ def _schema_to_json_object(
98
+ self, schema: dict, defs: dict | None = None, visited: set | None = None
99
+ ) -> str:
100
+ """Render a compact, human-friendly shape of the JSON schema."""
101
+ visited = visited or set()
102
+
103
+ if id(schema) in visited:
104
+ return '"<recursive>"'
105
+ visited.add(id(schema))
106
+
107
+ if "$ref" in schema:
108
+ ref = schema.get("$ref", "")
109
+ if ref.startswith("#/$defs/"):
110
+ target = ref.split("/")[-1]
111
+ if defs and target in defs:
112
+ return self._schema_to_json_object(defs[target], defs, visited)
113
+ return f'"<ref:{ref}>"'
114
+
115
+ schema_type = schema.get("type")
116
+ description = schema.get("description", "")
117
+ required = schema.get("required", [])
118
+
119
+ if schema_type == "object":
120
+ props = schema.get("properties", {})
121
+ result = "{\n"
122
+ for prop_name, prop_schema in props.items():
123
+ is_required = prop_name in required
124
+ prop_str = self._schema_to_json_object(prop_schema, defs, visited)
125
+ if is_required:
126
+ prop_str += " // REQUIRED"
127
+ result += f' "{prop_name}": {prop_str},\n'
128
+ result += "}"
129
+ return result
130
+ elif schema_type == "array":
131
+ items = schema.get("items", {})
132
+ items_str = self._schema_to_json_object(items, defs, visited)
133
+ return f"[{items_str}]"
134
+ elif schema_type:
135
+ comment = f" // {description}" if description else ""
136
+ return f'"{schema_type}"' + comment
137
+
138
+ return '"<unknown>"'
@@ -0,0 +1,45 @@
1
+ import os
2
+
3
+ from fast_agent.llm.provider.openai.llm_openai import OpenAILLM
4
+ from fast_agent.llm.provider_types import Provider
5
+ from fast_agent.types import RequestParams
6
+
7
+ DEFAULT_OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1"
8
+ # No single default model for OpenRouter, users must specify full path
9
+ DEFAULT_OPENROUTER_MODEL = None
10
+
11
+
12
+ class OpenRouterLLM(OpenAILLM):
13
+ """Augmented LLM provider for OpenRouter, using an OpenAI-compatible API."""
14
+
15
+ def __init__(self, *args, **kwargs) -> None:
16
+ super().__init__(*args, provider=Provider.OPENROUTER, **kwargs)
17
+
18
+ def _initialize_default_params(self, kwargs: dict) -> RequestParams:
19
+ """Initialize OpenRouter-specific default parameters."""
20
+ # Get base defaults from parent (includes ModelDatabase lookup)
21
+ base_params = super()._initialize_default_params(kwargs)
22
+
23
+ # Override with OpenRouter-specific settings
24
+ # OpenRouter model names include the provider, e.g., "google/gemini-flash-1.5"
25
+ # The model should be passed in the 'model' kwarg during factory creation.
26
+ chosen_model = kwargs.get("model", DEFAULT_OPENROUTER_MODEL)
27
+ if chosen_model:
28
+ base_params.model = chosen_model
29
+ # If it's still None here, it indicates an issue upstream (factory or user input).
30
+ # However, the base class _get_model handles the error if model is None.
31
+
32
+ return base_params
33
+
34
+ def _base_url(self) -> str:
35
+ """Retrieve the OpenRouter base URL from config or use the default."""
36
+ base_url = os.getenv("OPENROUTER_BASE_URL", DEFAULT_OPENROUTER_BASE_URL) # Default
37
+ config = self.context.config
38
+
39
+ # Check config file for override
40
+ if config and hasattr(config, "openrouter") and config.openrouter:
41
+ config_base_url = getattr(config.openrouter, "base_url", None)
42
+ if config_base_url:
43
+ base_url = config_base_url
44
+
45
+ return base_url
@@ -0,0 +1,128 @@
1
+ from typing import Any
2
+
3
+ from openai.types.chat import ChatCompletionMessageParam, ChatCompletionSystemMessageParam
4
+
5
+ from fast_agent.llm.provider.openai.llm_openai import OpenAILLM
6
+ from fast_agent.llm.provider_types import Provider
7
+ from fast_agent.types import RequestParams
8
+
9
+
10
+ class TensorZeroOpenAILLM(OpenAILLM):
11
+ """
12
+ An LLM augmentation that interacts with TensorZero's OpenAI-compatible inference endpoint.
13
+ This class extends the base OpenAIAugmentedLLM to handle TensorZero-specific
14
+ features, such as system template variables and custom parameters.
15
+ """
16
+
17
+ def __init__(self, *args, **kwargs) -> None:
18
+ """
19
+ Initializes the TensorZeroOpenAIAugmentedLLM.
20
+
21
+ Args:
22
+ *args: Variable length argument list.
23
+ **kwargs: Arbitrary keyword arguments.
24
+ """
25
+ self._t0_episode_id = kwargs.pop("episode_id", None)
26
+ self._t0_function_name = kwargs.get("model", "")
27
+
28
+ super().__init__(*args, provider=Provider.TENSORZERO, **kwargs)
29
+ self.logger.info("TensorZeroOpenAILLM initialized.")
30
+
31
+ def _initialize_default_params(self, kwargs: dict) -> RequestParams:
32
+ """
33
+ Initializes TensorZero-specific default parameters. Ensures the model name
34
+ is correctly prefixed for the TensorZero API.
35
+ """
36
+ model = kwargs.get("model", "")
37
+ if not model.startswith("tensorzero::"):
38
+ model = f"tensorzero::function_name::{model}"
39
+
40
+ self.logger.debug(f"Initializing with TensorZero model: {model}")
41
+
42
+ return RequestParams(
43
+ model=model,
44
+ systemPrompt=self.instruction,
45
+ parallel_tool_calls=True,
46
+ max_iterations=10,
47
+ use_history=True,
48
+ )
49
+
50
+ def _base_url(self) -> str:
51
+ """
52
+ Constructs the TensorZero OpenAI-compatible endpoint URL.
53
+ """
54
+ default_url = "http://localhost:3000/openai/v1"
55
+ if self.context and self.context.config and hasattr(self.context.config, "tensorzero"):
56
+ base_url = getattr(self.context.config.tensorzero, "base_url", default_url)
57
+ # Ensure the path is correctly appended
58
+ if not base_url.endswith("/openai/v1"):
59
+ base_url = f"{base_url.rstrip('/')}/openai/v1"
60
+ self.logger.debug(f"Using TensorZero base URL from config: {base_url}")
61
+ return base_url
62
+ self.logger.debug(f"Using default TensorZero base URL: {default_url}")
63
+ return default_url
64
+
65
+ def _prepare_api_request(
66
+ self,
67
+ messages: list[ChatCompletionMessageParam],
68
+ tools: list[Any] | None,
69
+ request_params: RequestParams,
70
+ ) -> dict[str, Any]:
71
+ """
72
+ Prepares the API request for the TensorZero OpenAI-compatible endpoint.
73
+ This method injects system template variables and other TensorZero-specific
74
+ parameters into the request. It also handles multimodal inputs.
75
+ """
76
+ self.logger.debug("Preparing API request for TensorZero OpenAI endpoint.")
77
+
78
+ # Start with the base arguments from the parent class
79
+ arguments = super()._prepare_api_request(messages, tools, request_params)
80
+
81
+ # Handle system template variables
82
+ if request_params.template_vars:
83
+ self.logger.debug(f"Injecting template variables: {request_params.template_vars}")
84
+ system_message_found = False
85
+ for i, msg in enumerate(messages):
86
+ if msg.get("role") == "system":
87
+ # If content is a string, convert it to the TensorZero format
88
+ if isinstance(msg.get("content"), str):
89
+ messages[i] = ChatCompletionSystemMessageParam(
90
+ role="system", content=[request_params.template_vars]
91
+ )
92
+ elif isinstance(msg.get("content"), list):
93
+ # If content is already a list, merge the template vars
94
+ msg["content"][0].update(request_params.template_vars)
95
+ system_message_found = True
96
+ break
97
+
98
+ if not system_message_found:
99
+ # If no system message exists, create one
100
+ messages.insert(
101
+ 0,
102
+ ChatCompletionSystemMessageParam(
103
+ role="system", content=[request_params.template_vars]
104
+ ),
105
+ )
106
+
107
+ # Add TensorZero-specific extra body parameters
108
+ extra_body = arguments.get("extra_body", {})
109
+
110
+ if self._t0_episode_id:
111
+ extra_body["tensorzero::episode_id"] = str(self._t0_episode_id)
112
+ self.logger.debug(f"Added tensorzero::episode_id: {self._t0_episode_id}")
113
+
114
+ # Merge metadata arguments
115
+ if request_params.metadata and isinstance(request_params.metadata, dict):
116
+ t0_args = request_params.metadata.get("tensorzero_arguments")
117
+ if t0_args:
118
+ self.logger.debug(f"Merging tensorzero_arguments from metadata: {t0_args}")
119
+ for msg in messages:
120
+ if msg.get("role") == "system" and isinstance(msg.get("content"), list):
121
+ msg["content"][0].update(t0_args)
122
+ break
123
+
124
+ if extra_body:
125
+ arguments["extra_body"] = extra_body
126
+
127
+ self.logger.debug(f"Final API request arguments: {arguments}")
128
+ return arguments
@@ -0,0 +1,38 @@
1
+ import os
2
+
3
+ from fast_agent.llm.provider.openai.llm_openai import OpenAILLM
4
+ from fast_agent.llm.provider_types import Provider
5
+ from fast_agent.types import RequestParams
6
+
7
+ XAI_BASE_URL = "https://api.x.ai/v1"
8
+ DEFAULT_XAI_MODEL = "grok-3"
9
+
10
+
11
+ class XAILLM(OpenAILLM):
12
+ def __init__(self, *args, **kwargs) -> None:
13
+ super().__init__(
14
+ *args, provider=Provider.XAI, **kwargs
15
+ ) # Properly pass args and kwargs to parent
16
+
17
+ def _initialize_default_params(self, kwargs: dict) -> RequestParams:
18
+ """Initialize xAI parameters"""
19
+ # Get base defaults from parent (includes ModelDatabase lookup)
20
+ base_params = super()._initialize_default_params(kwargs)
21
+
22
+ # Override with xAI-specific settings
23
+ chosen_model = kwargs.get("model", DEFAULT_XAI_MODEL)
24
+ base_params.model = chosen_model
25
+ base_params.parallel_tool_calls = False
26
+
27
+ return base_params
28
+
29
+ def _base_url(self) -> str:
30
+ base_url = os.getenv("XAI_BASE_URL", XAI_BASE_URL)
31
+ if self.context.config and self.context.config.xai:
32
+ base_url = self.context.config.xai.base_url
33
+
34
+ return base_url
35
+
36
+ async def _is_tool_stop_reason(self, finish_reason: str) -> bool:
37
+ # grok uses Null as the finish reason for tool calls?
38
+ return await super()._is_tool_stop_reason(finish_reason) or finish_reason is None