fast-agent-mcp 0.1.12__py3-none-any.whl → 0.2.0__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_mcp-0.1.12.dist-info → fast_agent_mcp-0.2.0.dist-info}/METADATA +3 -4
- fast_agent_mcp-0.2.0.dist-info/RECORD +123 -0
- mcp_agent/__init__.py +75 -0
- mcp_agent/agents/agent.py +61 -415
- mcp_agent/agents/base_agent.py +522 -0
- mcp_agent/agents/workflow/__init__.py +1 -0
- mcp_agent/agents/workflow/chain_agent.py +173 -0
- mcp_agent/agents/workflow/evaluator_optimizer.py +362 -0
- mcp_agent/agents/workflow/orchestrator_agent.py +591 -0
- mcp_agent/{workflows/orchestrator → agents/workflow}/orchestrator_models.py +11 -21
- mcp_agent/agents/workflow/parallel_agent.py +182 -0
- mcp_agent/agents/workflow/router_agent.py +307 -0
- mcp_agent/app.py +15 -19
- mcp_agent/cli/commands/bootstrap.py +19 -38
- mcp_agent/cli/commands/config.py +4 -4
- mcp_agent/cli/commands/setup.py +7 -14
- mcp_agent/cli/main.py +7 -10
- mcp_agent/cli/terminal.py +3 -3
- mcp_agent/config.py +25 -40
- mcp_agent/context.py +12 -21
- mcp_agent/context_dependent.py +3 -5
- mcp_agent/core/agent_types.py +10 -7
- mcp_agent/core/direct_agent_app.py +179 -0
- mcp_agent/core/direct_decorators.py +443 -0
- mcp_agent/core/direct_factory.py +476 -0
- mcp_agent/core/enhanced_prompt.py +23 -55
- mcp_agent/core/exceptions.py +8 -8
- mcp_agent/core/fastagent.py +145 -371
- mcp_agent/core/interactive_prompt.py +424 -0
- mcp_agent/core/mcp_content.py +17 -17
- mcp_agent/core/prompt.py +6 -9
- mcp_agent/core/request_params.py +6 -3
- mcp_agent/core/validation.py +92 -18
- mcp_agent/executor/decorator_registry.py +9 -17
- mcp_agent/executor/executor.py +8 -17
- mcp_agent/executor/task_registry.py +2 -4
- mcp_agent/executor/temporal.py +19 -41
- mcp_agent/executor/workflow.py +3 -5
- mcp_agent/executor/workflow_signal.py +15 -21
- mcp_agent/human_input/handler.py +4 -7
- mcp_agent/human_input/types.py +2 -3
- mcp_agent/llm/__init__.py +2 -0
- mcp_agent/llm/augmented_llm.py +450 -0
- mcp_agent/llm/augmented_llm_passthrough.py +162 -0
- mcp_agent/llm/augmented_llm_playback.py +83 -0
- mcp_agent/llm/memory.py +103 -0
- mcp_agent/{workflows/llm → llm}/model_factory.py +22 -16
- mcp_agent/{workflows/llm → llm}/prompt_utils.py +1 -3
- mcp_agent/llm/providers/__init__.py +8 -0
- mcp_agent/{workflows/llm → llm/providers}/anthropic_utils.py +8 -25
- mcp_agent/{workflows/llm → llm/providers}/augmented_llm_anthropic.py +56 -194
- mcp_agent/llm/providers/augmented_llm_deepseek.py +53 -0
- mcp_agent/{workflows/llm → llm/providers}/augmented_llm_openai.py +99 -190
- mcp_agent/{workflows/llm → llm}/providers/multipart_converter_anthropic.py +72 -71
- mcp_agent/{workflows/llm → llm}/providers/multipart_converter_openai.py +65 -71
- mcp_agent/{workflows/llm → llm}/providers/openai_multipart.py +16 -44
- mcp_agent/{workflows/llm → llm/providers}/openai_utils.py +4 -4
- mcp_agent/{workflows/llm → llm}/providers/sampling_converter_anthropic.py +9 -11
- mcp_agent/{workflows/llm → llm}/providers/sampling_converter_openai.py +8 -12
- mcp_agent/{workflows/llm → llm}/sampling_converter.py +3 -31
- mcp_agent/llm/sampling_format_converter.py +37 -0
- mcp_agent/logging/events.py +1 -5
- mcp_agent/logging/json_serializer.py +7 -6
- mcp_agent/logging/listeners.py +20 -23
- mcp_agent/logging/logger.py +17 -19
- mcp_agent/logging/rich_progress.py +10 -8
- mcp_agent/logging/tracing.py +4 -6
- mcp_agent/logging/transport.py +22 -22
- mcp_agent/mcp/gen_client.py +1 -3
- mcp_agent/mcp/interfaces.py +117 -110
- mcp_agent/mcp/logger_textio.py +97 -0
- mcp_agent/mcp/mcp_agent_client_session.py +7 -7
- mcp_agent/mcp/mcp_agent_server.py +8 -8
- mcp_agent/mcp/mcp_aggregator.py +102 -143
- mcp_agent/mcp/mcp_connection_manager.py +20 -27
- mcp_agent/mcp/prompt_message_multipart.py +68 -16
- mcp_agent/mcp/prompt_render.py +77 -0
- mcp_agent/mcp/prompt_serialization.py +30 -48
- mcp_agent/mcp/prompts/prompt_constants.py +18 -0
- mcp_agent/mcp/prompts/prompt_helpers.py +327 -0
- mcp_agent/mcp/prompts/prompt_load.py +109 -0
- mcp_agent/mcp/prompts/prompt_server.py +155 -195
- mcp_agent/mcp/prompts/prompt_template.py +35 -66
- mcp_agent/mcp/resource_utils.py +7 -14
- mcp_agent/mcp/sampling.py +17 -17
- mcp_agent/mcp_server/agent_server.py +13 -17
- mcp_agent/mcp_server_registry.py +13 -22
- mcp_agent/resources/examples/{workflows → in_dev}/agent_build.py +3 -2
- mcp_agent/resources/examples/in_dev/slides.py +110 -0
- mcp_agent/resources/examples/internal/agent.py +6 -3
- mcp_agent/resources/examples/internal/fastagent.config.yaml +8 -2
- mcp_agent/resources/examples/internal/job.py +2 -1
- mcp_agent/resources/examples/internal/prompt_category.py +1 -1
- mcp_agent/resources/examples/internal/prompt_sizing.py +3 -5
- mcp_agent/resources/examples/internal/sizer.py +2 -1
- mcp_agent/resources/examples/internal/social.py +2 -1
- mcp_agent/resources/examples/prompting/agent.py +2 -1
- mcp_agent/resources/examples/prompting/image_server.py +4 -8
- mcp_agent/resources/examples/prompting/work_with_image.py +19 -0
- mcp_agent/ui/console_display.py +16 -20
- fast_agent_mcp-0.1.12.dist-info/RECORD +0 -161
- mcp_agent/core/agent_app.py +0 -646
- mcp_agent/core/agent_utils.py +0 -71
- mcp_agent/core/decorators.py +0 -455
- mcp_agent/core/factory.py +0 -463
- mcp_agent/core/proxies.py +0 -269
- mcp_agent/core/types.py +0 -24
- mcp_agent/eval/__init__.py +0 -0
- mcp_agent/mcp/stdio.py +0 -111
- mcp_agent/resources/examples/data-analysis/analysis-campaign.py +0 -188
- mcp_agent/resources/examples/data-analysis/analysis.py +0 -65
- mcp_agent/resources/examples/data-analysis/fastagent.config.yaml +0 -41
- mcp_agent/resources/examples/data-analysis/mount-point/WA_Fn-UseC_-HR-Employee-Attrition.csv +0 -1471
- mcp_agent/resources/examples/mcp_researcher/researcher-eval.py +0 -53
- mcp_agent/resources/examples/researcher/fastagent.config.yaml +0 -66
- mcp_agent/resources/examples/researcher/researcher-eval.py +0 -53
- mcp_agent/resources/examples/researcher/researcher-imp.py +0 -190
- mcp_agent/resources/examples/researcher/researcher.py +0 -38
- mcp_agent/resources/examples/workflows/chaining.py +0 -44
- mcp_agent/resources/examples/workflows/evaluator.py +0 -78
- mcp_agent/resources/examples/workflows/fastagent.config.yaml +0 -24
- mcp_agent/resources/examples/workflows/human_input.py +0 -25
- mcp_agent/resources/examples/workflows/orchestrator.py +0 -73
- mcp_agent/resources/examples/workflows/parallel.py +0 -78
- mcp_agent/resources/examples/workflows/router.py +0 -53
- mcp_agent/resources/examples/workflows/sse.py +0 -23
- mcp_agent/telemetry/__init__.py +0 -0
- mcp_agent/telemetry/usage_tracking.py +0 -18
- mcp_agent/workflows/__init__.py +0 -0
- mcp_agent/workflows/embedding/__init__.py +0 -0
- mcp_agent/workflows/embedding/embedding_base.py +0 -61
- mcp_agent/workflows/embedding/embedding_cohere.py +0 -49
- mcp_agent/workflows/embedding/embedding_openai.py +0 -46
- mcp_agent/workflows/evaluator_optimizer/__init__.py +0 -0
- mcp_agent/workflows/evaluator_optimizer/evaluator_optimizer.py +0 -481
- mcp_agent/workflows/intent_classifier/__init__.py +0 -0
- mcp_agent/workflows/intent_classifier/intent_classifier_base.py +0 -120
- mcp_agent/workflows/intent_classifier/intent_classifier_embedding.py +0 -134
- mcp_agent/workflows/intent_classifier/intent_classifier_embedding_cohere.py +0 -45
- mcp_agent/workflows/intent_classifier/intent_classifier_embedding_openai.py +0 -45
- mcp_agent/workflows/intent_classifier/intent_classifier_llm.py +0 -161
- mcp_agent/workflows/intent_classifier/intent_classifier_llm_anthropic.py +0 -60
- mcp_agent/workflows/intent_classifier/intent_classifier_llm_openai.py +0 -60
- mcp_agent/workflows/llm/__init__.py +0 -0
- mcp_agent/workflows/llm/augmented_llm.py +0 -753
- mcp_agent/workflows/llm/augmented_llm_passthrough.py +0 -241
- mcp_agent/workflows/llm/augmented_llm_playback.py +0 -109
- mcp_agent/workflows/llm/providers/__init__.py +0 -8
- mcp_agent/workflows/llm/sampling_format_converter.py +0 -22
- mcp_agent/workflows/orchestrator/__init__.py +0 -0
- mcp_agent/workflows/orchestrator/orchestrator.py +0 -578
- mcp_agent/workflows/parallel/__init__.py +0 -0
- mcp_agent/workflows/parallel/fan_in.py +0 -350
- mcp_agent/workflows/parallel/fan_out.py +0 -187
- mcp_agent/workflows/parallel/parallel_llm.py +0 -166
- mcp_agent/workflows/router/__init__.py +0 -0
- mcp_agent/workflows/router/router_base.py +0 -368
- mcp_agent/workflows/router/router_embedding.py +0 -240
- mcp_agent/workflows/router/router_embedding_cohere.py +0 -59
- mcp_agent/workflows/router/router_embedding_openai.py +0 -59
- mcp_agent/workflows/router/router_llm.py +0 -320
- mcp_agent/workflows/swarm/__init__.py +0 -0
- mcp_agent/workflows/swarm/swarm.py +0 -320
- mcp_agent/workflows/swarm/swarm_anthropic.py +0 -42
- mcp_agent/workflows/swarm/swarm_openai.py +0 -41
- {fast_agent_mcp-0.1.12.dist-info → fast_agent_mcp-0.2.0.dist-info}/WHEEL +0 -0
- {fast_agent_mcp-0.1.12.dist-info → fast_agent_mcp-0.2.0.dist-info}/entry_points.txt +0 -0
- {fast_agent_mcp-0.1.12.dist-info → fast_agent_mcp-0.2.0.dist-info}/licenses/LICENSE +0 -0
- /mcp_agent/{workflows/orchestrator → agents/workflow}/orchestrator_prompts.py +0 -0
@@ -1,39 +1,37 @@
|
|
1
1
|
import os
|
2
|
-
from typing import List, Type
|
2
|
+
from typing import List, Type
|
3
3
|
|
4
|
-
from
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
OpenAISamplingConverter,
|
4
|
+
from mcp.types import (
|
5
|
+
CallToolRequest,
|
6
|
+
CallToolRequestParams,
|
7
|
+
CallToolResult,
|
9
8
|
)
|
10
|
-
|
11
|
-
if TYPE_CHECKING:
|
12
|
-
from mcp_agent.mcp.prompt_message_multipart import PromptMessageMultipart
|
13
|
-
from openai import OpenAI, AuthenticationError
|
9
|
+
from openai import AuthenticationError, OpenAI
|
14
10
|
|
15
11
|
# from openai.types.beta.chat import
|
16
12
|
from openai.types.chat import (
|
17
|
-
ChatCompletionMessageParam,
|
18
13
|
ChatCompletionMessage,
|
14
|
+
ChatCompletionMessageParam,
|
19
15
|
ChatCompletionSystemMessageParam,
|
20
16
|
ChatCompletionToolParam,
|
21
17
|
ChatCompletionUserMessageParam,
|
22
18
|
)
|
23
|
-
from
|
24
|
-
|
25
|
-
CallToolRequest,
|
26
|
-
CallToolResult,
|
27
|
-
)
|
19
|
+
from pydantic_core import from_json
|
20
|
+
from rich.text import Text
|
28
21
|
|
29
|
-
from mcp_agent.
|
22
|
+
from mcp_agent.core.exceptions import ProviderKeyError
|
23
|
+
from mcp_agent.core.prompt import Prompt
|
24
|
+
from mcp_agent.llm.augmented_llm import (
|
30
25
|
AugmentedLLM,
|
31
26
|
ModelT,
|
32
27
|
RequestParams,
|
33
28
|
)
|
34
|
-
from mcp_agent.
|
29
|
+
from mcp_agent.llm.providers.multipart_converter_openai import OpenAIConverter
|
30
|
+
from mcp_agent.llm.providers.sampling_converter_openai import (
|
31
|
+
OpenAISamplingConverter,
|
32
|
+
)
|
35
33
|
from mcp_agent.logging.logger import get_logger
|
36
|
-
from
|
34
|
+
from mcp_agent.mcp.prompt_message_multipart import PromptMessageMultipart
|
37
35
|
|
38
36
|
_logger = get_logger(__name__)
|
39
37
|
|
@@ -41,23 +39,21 @@ DEFAULT_OPENAI_MODEL = "gpt-4o"
|
|
41
39
|
DEFAULT_REASONING_EFFORT = "medium"
|
42
40
|
|
43
41
|
|
44
|
-
class OpenAIAugmentedLLM(
|
45
|
-
AugmentedLLM[ChatCompletionMessageParam, ChatCompletionMessage]
|
46
|
-
):
|
42
|
+
class OpenAIAugmentedLLM(AugmentedLLM[ChatCompletionMessageParam, ChatCompletionMessage]):
|
47
43
|
"""
|
48
44
|
The basic building block of agentic systems is an LLM enhanced with augmentations
|
49
45
|
such as retrieval, tools, and memory provided from a collection of MCP servers.
|
50
46
|
This implementation uses OpenAI's ChatCompletion as the LLM.
|
51
47
|
"""
|
52
48
|
|
53
|
-
def __init__(self, *args, **kwargs):
|
49
|
+
def __init__(self, provider_name: str = "OpenAI", *args, **kwargs) -> None:
|
54
50
|
# Set type_converter before calling super().__init__
|
55
51
|
if "type_converter" not in kwargs:
|
56
52
|
kwargs["type_converter"] = OpenAISamplingConverter
|
57
53
|
|
58
54
|
super().__init__(*args, **kwargs)
|
59
55
|
|
60
|
-
self.provider =
|
56
|
+
self.provider = provider_name
|
61
57
|
# Initialize logger with name if available
|
62
58
|
self.logger = get_logger(f"{__name__}.{self.name}" if self.name else __name__)
|
63
59
|
|
@@ -70,9 +66,7 @@ class OpenAIAugmentedLLM(
|
|
70
66
|
self._reasoning_effort = self.context.config.openai.reasoning_effort
|
71
67
|
|
72
68
|
# Determine if we're using a reasoning model
|
73
|
-
chosen_model =
|
74
|
-
self.default_request_params.model if self.default_request_params else None
|
75
|
-
)
|
69
|
+
chosen_model = self.default_request_params.model if self.default_request_params else None
|
76
70
|
self._reasoning = chosen_model and (
|
77
71
|
chosen_model.startswith("o3") or chosen_model.startswith("o1")
|
78
72
|
)
|
@@ -85,11 +79,6 @@ class OpenAIAugmentedLLM(
|
|
85
79
|
"""Initialize OpenAI-specific default parameters"""
|
86
80
|
chosen_model = kwargs.get("model", DEFAULT_OPENAI_MODEL)
|
87
81
|
|
88
|
-
# Get default model from config if available
|
89
|
-
if self.context and self.context.config and self.context.config.openai:
|
90
|
-
if hasattr(self.context.config.openai, "default_model"):
|
91
|
-
chosen_model = self.context.config.openai.default_model
|
92
|
-
|
93
82
|
return RequestParams(
|
94
83
|
model=chosen_model,
|
95
84
|
systemPrompt=self.instruction,
|
@@ -120,11 +109,9 @@ class OpenAIAugmentedLLM(
|
|
120
109
|
return api_key
|
121
110
|
|
122
111
|
def _base_url(self) -> str:
|
123
|
-
return
|
124
|
-
self.context.config.openai.base_url if self.context.config.openai else None
|
125
|
-
)
|
112
|
+
return self.context.config.openai.base_url if self.context.config.openai else None
|
126
113
|
|
127
|
-
async def
|
114
|
+
async def generate_internal(
|
128
115
|
self,
|
129
116
|
message,
|
130
117
|
request_params: RequestParams | None = None,
|
@@ -149,25 +136,21 @@ class OpenAIAugmentedLLM(
|
|
149
136
|
|
150
137
|
system_prompt = self.instruction or params.systemPrompt
|
151
138
|
if system_prompt:
|
152
|
-
messages.append(
|
153
|
-
ChatCompletionSystemMessageParam(role="system", content=system_prompt)
|
154
|
-
)
|
139
|
+
messages.append(ChatCompletionSystemMessageParam(role="system", content=system_prompt))
|
155
140
|
|
156
141
|
# Always include prompt messages, but only include conversation history
|
157
142
|
# if use_history is True
|
158
143
|
messages.extend(self.history.get(include_history=params.use_history))
|
159
144
|
|
160
145
|
if isinstance(message, str):
|
161
|
-
messages.append(
|
162
|
-
ChatCompletionUserMessageParam(role="user", content=message)
|
163
|
-
)
|
146
|
+
messages.append(ChatCompletionUserMessageParam(role="user", content=message))
|
164
147
|
elif isinstance(message, list):
|
165
148
|
messages.extend(message)
|
166
149
|
else:
|
167
150
|
messages.append(message)
|
168
151
|
|
169
152
|
response = await self.aggregator.list_tools()
|
170
|
-
available_tools: List[ChatCompletionToolParam] = [
|
153
|
+
available_tools: List[ChatCompletionToolParam] | None = [
|
171
154
|
ChatCompletionToolParam(
|
172
155
|
type="function",
|
173
156
|
function={
|
@@ -180,17 +163,10 @@ class OpenAIAugmentedLLM(
|
|
180
163
|
for tool in response.tools
|
181
164
|
]
|
182
165
|
if not available_tools:
|
183
|
-
available_tools =
|
166
|
+
available_tools = None # deepseek does not allow empty array
|
184
167
|
|
185
168
|
responses: List[ChatCompletionMessage] = []
|
186
|
-
model =
|
187
|
-
chat_turn = len(messages) // 2
|
188
|
-
if self._reasoning:
|
189
|
-
self.show_user_message(
|
190
|
-
str(message), f"{model} ({self._reasoning_effort})", chat_turn
|
191
|
-
)
|
192
|
-
else:
|
193
|
-
self.show_user_message(str(message), model, chat_turn)
|
169
|
+
model = self.default_request_params.model
|
194
170
|
|
195
171
|
# we do NOT send stop sequences as this causes errors with mutlimodal processing
|
196
172
|
for i in range(params.max_iterations):
|
@@ -214,7 +190,7 @@ class OpenAIAugmentedLLM(
|
|
214
190
|
arguments = {**arguments, **params.metadata}
|
215
191
|
|
216
192
|
self.logger.debug(f"{arguments}")
|
217
|
-
self._log_chat_progress(chat_turn, model=model)
|
193
|
+
self._log_chat_progress(self.chat_turn(), model=model)
|
218
194
|
|
219
195
|
if response_model is None:
|
220
196
|
executor_result = await self.executor.execute(
|
@@ -248,21 +224,14 @@ class OpenAIAugmentedLLM(
|
|
248
224
|
# No response from the model, we're done
|
249
225
|
break
|
250
226
|
|
251
|
-
# TODO: saqadri - handle multiple choices for more complex interactions.
|
252
|
-
# Keeping it simple for now because multiple choices will also complicate memory management
|
253
227
|
choice = response.choices[0]
|
254
228
|
message = choice.message
|
255
229
|
responses.append(message)
|
256
230
|
|
257
|
-
converted_message = self.convert_message_to_message_param(
|
258
|
-
message, name=self.name
|
259
|
-
)
|
231
|
+
converted_message = self.convert_message_to_message_param(message, name=self.name)
|
260
232
|
messages.append(converted_message)
|
261
233
|
message_text = converted_message.content
|
262
|
-
if
|
263
|
-
choice.finish_reason in ["tool_calls", "function_call"]
|
264
|
-
and message.tool_calls
|
265
|
-
):
|
234
|
+
if choice.finish_reason in ["tool_calls", "function_call"] and message.tool_calls:
|
266
235
|
if message_text:
|
267
236
|
await self.show_assistant_message(
|
268
237
|
message_text,
|
@@ -290,9 +259,7 @@ class OpenAIAugmentedLLM(
|
|
290
259
|
method="tools/call",
|
291
260
|
params=CallToolRequestParams(
|
292
261
|
name=tool_call.function.name,
|
293
|
-
arguments=from_json(
|
294
|
-
tool_call.function.arguments, allow_partial=True
|
295
|
-
),
|
262
|
+
arguments=from_json(tool_call.function.arguments, allow_partial=True),
|
296
263
|
),
|
297
264
|
)
|
298
265
|
result = await self.call_tool(tool_call_request, tool_call.id)
|
@@ -300,18 +267,14 @@ class OpenAIAugmentedLLM(
|
|
300
267
|
|
301
268
|
tool_results.append((tool_call.id, result))
|
302
269
|
|
303
|
-
messages.extend(
|
304
|
-
OpenAIConverter.convert_function_results_to_openai(tool_results)
|
305
|
-
)
|
270
|
+
messages.extend(OpenAIConverter.convert_function_results_to_openai(tool_results))
|
306
271
|
|
307
272
|
self.logger.debug(
|
308
273
|
f"Iteration {i}: Tool call results: {str(tool_results) if tool_results else 'None'}"
|
309
274
|
)
|
310
275
|
elif choice.finish_reason == "length":
|
311
276
|
# We have reached the max tokens limit
|
312
|
-
self.logger.debug(
|
313
|
-
f"Iteration {i}: Stopping because finish_reason is 'length'"
|
314
|
-
)
|
277
|
+
self.logger.debug(f"Iteration {i}: Stopping because finish_reason is 'length'")
|
315
278
|
if request_params and request_params.maxTokens is not None:
|
316
279
|
message_text = Text(
|
317
280
|
f"the assistant has reached the maximum token limit ({request_params.maxTokens})",
|
@@ -334,9 +297,7 @@ class OpenAIAugmentedLLM(
|
|
334
297
|
# TODO: saqadri - would be useful to return the reason for stopping to the caller
|
335
298
|
break
|
336
299
|
elif choice.finish_reason == "stop":
|
337
|
-
self.logger.debug(
|
338
|
-
f"Iteration {i}: Stopping because finish_reason is 'stop'"
|
339
|
-
)
|
300
|
+
self.logger.debug(f"Iteration {i}: Stopping because finish_reason is 'stop'")
|
340
301
|
if message_text:
|
341
302
|
await self.show_assistant_message(message_text, "")
|
342
303
|
break
|
@@ -361,7 +322,7 @@ class OpenAIAugmentedLLM(
|
|
361
322
|
self,
|
362
323
|
message,
|
363
324
|
request_params: RequestParams | None = None,
|
364
|
-
):
|
325
|
+
) -> str:
|
365
326
|
"""
|
366
327
|
Process a query using an LLM and available tools.
|
367
328
|
The default implementation uses OpenAI's ChatCompletion as the LLM.
|
@@ -371,11 +332,8 @@ class OpenAIAugmentedLLM(
|
|
371
332
|
- "***SAVE_HISTORY <filename.md>" - Saves the conversation history to the specified file
|
372
333
|
in MCP prompt format with user/assistant delimiters.
|
373
334
|
"""
|
374
|
-
# Check if this is a special command to save history
|
375
|
-
if isinstance(message, str) and message.startswith("***SAVE_HISTORY "):
|
376
|
-
return await self._save_history_to_file(message)
|
377
335
|
|
378
|
-
responses = await self.
|
336
|
+
responses = await self.generate_internal(
|
379
337
|
message=message,
|
380
338
|
request_params=request_params,
|
381
339
|
)
|
@@ -393,23 +351,11 @@ class OpenAIAugmentedLLM(
|
|
393
351
|
|
394
352
|
return "\n".join(final_text)
|
395
353
|
|
396
|
-
async def
|
354
|
+
async def _apply_prompt_provider_specific(
|
397
355
|
self,
|
398
356
|
multipart_messages: List["PromptMessageMultipart"],
|
399
357
|
request_params: RequestParams | None = None,
|
400
|
-
) ->
|
401
|
-
"""
|
402
|
-
OpenAI-specific implementation of apply_prompt_template that handles
|
403
|
-
multimodal content natively.
|
404
|
-
|
405
|
-
Args:
|
406
|
-
multipart_messages: List of PromptMessageMultipart objects parsed from the prompt template
|
407
|
-
|
408
|
-
Returns:
|
409
|
-
String representation of the assistant's response if generated,
|
410
|
-
or the last assistant message in the prompt
|
411
|
-
"""
|
412
|
-
|
358
|
+
) -> PromptMessageMultipart:
|
413
359
|
# TODO -- this is very similar to Anthropic (just the converter class changes).
|
414
360
|
# TODO -- potential refactor to base class, standardize Converter interface
|
415
361
|
# Check the last message role
|
@@ -417,9 +363,7 @@ class OpenAIAugmentedLLM(
|
|
417
363
|
|
418
364
|
# Add all previous messages to history (or all messages if last is from assistant)
|
419
365
|
messages_to_add = (
|
420
|
-
multipart_messages[:-1]
|
421
|
-
if last_message.role == "user"
|
422
|
-
else multipart_messages
|
366
|
+
multipart_messages[:-1] if last_message.role == "user" else multipart_messages
|
423
367
|
)
|
424
368
|
converted = []
|
425
369
|
for msg in messages_to_add:
|
@@ -428,95 +372,87 @@ class OpenAIAugmentedLLM(
|
|
428
372
|
|
429
373
|
if last_message.role == "user":
|
430
374
|
# For user messages: Generate response to the last one
|
431
|
-
self.logger.debug(
|
432
|
-
"Last message in prompt is from user, generating assistant response"
|
433
|
-
)
|
375
|
+
self.logger.debug("Last message in prompt is from user, generating assistant response")
|
434
376
|
message_param = OpenAIConverter.convert_to_openai(last_message)
|
435
|
-
return await self.generate_str(message_param, request_params)
|
377
|
+
return Prompt.assistant(await self.generate_str(message_param, request_params))
|
436
378
|
else:
|
437
379
|
# For assistant messages: Return the last message content as text
|
438
|
-
self.logger.debug(
|
439
|
-
|
440
|
-
)
|
441
|
-
return str(last_message)
|
380
|
+
self.logger.debug("Last message in prompt is from assistant, returning it directly")
|
381
|
+
return last_message
|
442
382
|
|
443
|
-
async def
|
383
|
+
async def structured(
|
384
|
+
self,
|
385
|
+
prompt: List[PromptMessageMultipart],
|
386
|
+
model: Type[ModelT],
|
387
|
+
request_params: RequestParams | None = None,
|
388
|
+
) -> ModelT | None:
|
444
389
|
"""
|
445
|
-
|
390
|
+
Apply the prompt and return the result as a Pydantic model.
|
391
|
+
|
392
|
+
Uses OpenAI's beta parse feature when compatible, falling back to standard
|
393
|
+
JSON parsing if the beta feature fails or is unavailable.
|
446
394
|
|
447
395
|
Args:
|
448
|
-
|
396
|
+
prompt: List of messages to process
|
397
|
+
model: Pydantic model to parse the response into
|
398
|
+
request_params: Optional request parameters
|
449
399
|
|
450
400
|
Returns:
|
451
|
-
|
401
|
+
The parsed response as a Pydantic model, or None if parsing fails
|
452
402
|
"""
|
453
|
-
try:
|
454
|
-
# Extract the filename from the command
|
455
|
-
parts = command.split(" ", 1)
|
456
|
-
if len(parts) != 2 or not parts[1].strip():
|
457
|
-
return "Error: Invalid format. Expected '***SAVE_HISTORY <filename.md>'"
|
458
403
|
|
459
|
-
|
404
|
+
if not "OpenAI" == self.provider:
|
405
|
+
return await super().structured(prompt, model, request_params)
|
460
406
|
|
461
|
-
|
462
|
-
messages = self.history.get(include_history=True)
|
407
|
+
logger = get_logger(__name__)
|
463
408
|
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
409
|
+
# First try to use OpenAI's beta.chat.completions.parse feature
|
410
|
+
try:
|
411
|
+
# Convert the multipart messages to OpenAI format
|
412
|
+
messages = []
|
413
|
+
for msg in prompt:
|
414
|
+
messages.append(OpenAIConverter.convert_to_openai(msg))
|
415
|
+
|
416
|
+
# Add system prompt if available and not already present
|
417
|
+
if self.instruction and not any(m.get("role") == "system" for m in messages):
|
418
|
+
system_msg = ChatCompletionSystemMessageParam(
|
419
|
+
role="system", content=self.instruction
|
420
|
+
)
|
421
|
+
messages.insert(0, system_msg)
|
471
422
|
|
472
|
-
#
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
if isinstance(msg, dict) and msg.get("role") == "system":
|
477
|
-
continue
|
423
|
+
# Use the beta parse feature
|
424
|
+
try:
|
425
|
+
openai_client = OpenAI(api_key=self._api_key(), base_url=self._base_url())
|
426
|
+
model_name = self.default_request_params.model
|
478
427
|
|
479
|
-
|
480
|
-
|
481
|
-
|
428
|
+
logger.debug(
|
429
|
+
f"Using OpenAI beta parse with model {model_name} for structured output"
|
430
|
+
)
|
431
|
+
response = await self.executor.execute(
|
432
|
+
openai_client.beta.chat.completions.parse,
|
433
|
+
model=model_name,
|
434
|
+
messages=messages,
|
435
|
+
response_format=model,
|
482
436
|
)
|
483
437
|
|
484
|
-
|
485
|
-
|
486
|
-
multipart_messages,
|
487
|
-
user_delimiter="---USER",
|
488
|
-
assistant_delimiter="---ASSISTANT",
|
489
|
-
)
|
438
|
+
if response and isinstance(response[0], BaseException):
|
439
|
+
raise response[0]
|
490
440
|
|
491
|
-
|
492
|
-
|
493
|
-
|
441
|
+
parsed_result = response[0].choices[0].message
|
442
|
+
logger.debug("Successfully used OpenAI beta parse feature for structured output")
|
443
|
+
return parsed_result.parsed
|
494
444
|
|
495
|
-
|
496
|
-
|
445
|
+
except (ImportError, AttributeError, NotImplementedError) as e:
|
446
|
+
# Beta feature not available, log and continue to fallback
|
447
|
+
logger.debug(f"OpenAI beta parse feature not available: {str(e)}")
|
448
|
+
# Continue to fallback
|
497
449
|
|
498
450
|
except Exception as e:
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
async def generate_structured(
|
503
|
-
self,
|
504
|
-
message,
|
505
|
-
response_model: Type[ModelT],
|
506
|
-
request_params: RequestParams | None = None,
|
507
|
-
) -> ModelT:
|
508
|
-
responses = await self.generate(
|
509
|
-
message=message,
|
510
|
-
request_params=request_params,
|
511
|
-
response_model=response_model,
|
512
|
-
)
|
513
|
-
return responses[0].parsed
|
451
|
+
logger.debug(f"OpenAI beta parse failed: {str(e)}, falling back to standard method")
|
452
|
+
# Continue to standard method as fallback
|
514
453
|
|
515
|
-
|
516
|
-
|
517
|
-
) -> str:
|
518
|
-
converted_prompt = OpenAIConverter.convert_to_openai(prompt)
|
519
|
-
return await self.generate_str(converted_prompt, request_params)
|
454
|
+
# Fallback to standard method (inheriting from base class)
|
455
|
+
return await super().structured(prompt, model, request_params)
|
520
456
|
|
521
457
|
async def pre_tool_call(self, tool_call_id: str | None, request: CallToolRequest):
|
522
458
|
return request
|
@@ -525,30 +461,3 @@ class OpenAIAugmentedLLM(
|
|
525
461
|
self, tool_call_id: str | None, request: CallToolRequest, result: CallToolResult
|
526
462
|
):
|
527
463
|
return result
|
528
|
-
|
529
|
-
def message_param_str(self, message: ChatCompletionMessageParam) -> str:
|
530
|
-
"""Convert an input message to a string representation."""
|
531
|
-
if message.get("content"):
|
532
|
-
content = message["content"]
|
533
|
-
if isinstance(content, str):
|
534
|
-
return content
|
535
|
-
else: # content is a list
|
536
|
-
final_text: List[str] = []
|
537
|
-
for part in content:
|
538
|
-
text_part = part.get("text")
|
539
|
-
if text_part:
|
540
|
-
final_text.append(str(text_part))
|
541
|
-
else:
|
542
|
-
final_text.append(str(part))
|
543
|
-
|
544
|
-
return "\n".join(final_text)
|
545
|
-
|
546
|
-
return str(message)
|
547
|
-
|
548
|
-
def message_str(self, message: ChatCompletionMessage) -> str:
|
549
|
-
"""Convert an output message to a string representation."""
|
550
|
-
content = message.content
|
551
|
-
if content:
|
552
|
-
return content
|
553
|
-
|
554
|
-
return str(message)
|