fastworkflow 2.13.5__py3-none-any.whl → 2.14.1__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.
- fastworkflow/_workflows/command_metadata_extraction/_commands/ErrorCorrection/you_misunderstood.py +1 -1
- fastworkflow/_workflows/command_metadata_extraction/_commands/IntentDetection/go_up.py +1 -1
- fastworkflow/_workflows/command_metadata_extraction/_commands/IntentDetection/reset_context.py +1 -1
- fastworkflow/_workflows/command_metadata_extraction/_commands/IntentDetection/what_can_i_do.py +98 -166
- fastworkflow/_workflows/command_metadata_extraction/_commands/wildcard.py +7 -3
- fastworkflow/build/genai_postprocessor.py +143 -149
- fastworkflow/chat_session.py +42 -11
- fastworkflow/command_metadata_api.py +794 -0
- fastworkflow/command_routing.py +4 -1
- fastworkflow/examples/fastworkflow.env +1 -1
- fastworkflow/examples/fastworkflow.passwords.env +1 -0
- fastworkflow/examples/hello_world/_commands/add_two_numbers.py +1 -0
- fastworkflow/examples/retail_workflow/_commands/calculate.py +67 -0
- fastworkflow/examples/retail_workflow/_commands/cancel_pending_order.py +4 -1
- fastworkflow/examples/retail_workflow/_commands/exchange_delivered_order_items.py +13 -1
- fastworkflow/examples/retail_workflow/_commands/find_user_id_by_email.py +6 -1
- fastworkflow/examples/retail_workflow/_commands/find_user_id_by_name_zip.py +6 -1
- fastworkflow/examples/retail_workflow/_commands/get_order_details.py +22 -10
- fastworkflow/examples/retail_workflow/_commands/get_product_details.py +12 -4
- fastworkflow/examples/retail_workflow/_commands/get_user_details.py +21 -5
- fastworkflow/examples/retail_workflow/_commands/list_all_product_types.py +4 -1
- fastworkflow/examples/retail_workflow/_commands/modify_pending_order_address.py +3 -0
- fastworkflow/examples/retail_workflow/_commands/modify_pending_order_items.py +12 -0
- fastworkflow/examples/retail_workflow/_commands/modify_pending_order_payment.py +7 -1
- fastworkflow/examples/retail_workflow/_commands/modify_user_address.py +3 -0
- fastworkflow/examples/retail_workflow/_commands/return_delivered_order_items.py +10 -1
- fastworkflow/examples/retail_workflow/_commands/transfer_to_human_agents.py +1 -1
- fastworkflow/examples/retail_workflow/tools/calculate.py +1 -1
- fastworkflow/mcp_server.py +52 -44
- fastworkflow/run/__main__.py +9 -5
- fastworkflow/run_agent/__main__.py +8 -8
- fastworkflow/run_agent/agent_module.py +6 -16
- fastworkflow/utils/command_dependency_graph.py +130 -143
- fastworkflow/utils/dspy_utils.py +11 -0
- fastworkflow/utils/signatures.py +7 -0
- fastworkflow/workflow_agent.py +186 -0
- {fastworkflow-2.13.5.dist-info → fastworkflow-2.14.1.dist-info}/METADATA +12 -3
- {fastworkflow-2.13.5.dist-info → fastworkflow-2.14.1.dist-info}/RECORD +41 -40
- fastworkflow/agent_integration.py +0 -239
- fastworkflow/examples/retail_workflow/_commands/parameter_dependency_graph.json +0 -36
- {fastworkflow-2.13.5.dist-info → fastworkflow-2.14.1.dist-info}/LICENSE +0 -0
- {fastworkflow-2.13.5.dist-info → fastworkflow-2.14.1.dist-info}/WHEEL +0 -0
- {fastworkflow-2.13.5.dist-info → fastworkflow-2.14.1.dist-info}/entry_points.txt +0 -0
fastworkflow/command_routing.py
CHANGED
|
@@ -76,7 +76,10 @@ class RoutingDefinition(BaseModel):
|
|
|
76
76
|
"""Returns the list of command names available in the given context."""
|
|
77
77
|
if context not in self.contexts:
|
|
78
78
|
raise ValueError(f"Context '{context}' not found in the workflow.")
|
|
79
|
-
|
|
79
|
+
commands = self.contexts[context]
|
|
80
|
+
if 'wildcard' in commands:
|
|
81
|
+
commands = [cmd for cmd in commands if cmd != 'wildcard']
|
|
82
|
+
return commands
|
|
80
83
|
|
|
81
84
|
def get_command_class(self, command_name: str, module_type: ModuleType) -> Optional[Type[Any]]:
|
|
82
85
|
"""
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
LLM_SYNDATA_GEN=mistral/mistral-small-latest
|
|
2
2
|
LLM_PARAM_EXTRACTION=mistral/mistral-small-latest
|
|
3
3
|
LLM_RESPONSE_GEN=mistral/mistral-small-latest
|
|
4
|
-
|
|
4
|
+
LLM_PLANNER=openrouter/openai/gpt-oss-20b:free
|
|
5
5
|
LLM_AGENT=mistral/mistral-small-latest
|
|
6
6
|
|
|
7
7
|
SPEEDDICT_FOLDERNAME=___workflow_contexts
|
|
@@ -2,4 +2,5 @@
|
|
|
2
2
|
LITELLM_API_KEY_SYNDATA_GEN=<API KEY for synthetic data generation model>
|
|
3
3
|
LITELLM_API_KEY_PARAM_EXTRACTION=<API KEY for parameter extraction model>
|
|
4
4
|
LITELLM_API_KEY_RESPONSE_GEN=<API KEY for response generation model>
|
|
5
|
+
LITELLM_API_KEY_PLANNER=<API KEY for the agent's task planner model>
|
|
5
6
|
LITELLM_API_KEY_AGENT=<API KEY for the agent model>
|
|
@@ -11,6 +11,7 @@ from ..application.add_two_numbers import add_two_numbers
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class Signature:
|
|
14
|
+
"""Takes two floating point numbers and returns their sum"""
|
|
14
15
|
class Input(BaseModel):
|
|
15
16
|
first_num: float = Field(description="First number")
|
|
16
17
|
second_num: float = Field(description="Second number")
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
3
|
+
import fastworkflow
|
|
4
|
+
from pydantic import BaseModel, Field, ConfigDict
|
|
5
|
+
from fastworkflow.workflow import Workflow
|
|
6
|
+
from fastworkflow import CommandOutput, CommandResponse
|
|
7
|
+
from fastworkflow.train.generate_synthetic import generate_diverse_utterances
|
|
8
|
+
|
|
9
|
+
from ..retail_data import load_data
|
|
10
|
+
from ..tools.calculate import Calculate
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Signature:
|
|
14
|
+
"""Calculate a mathematical expression"""
|
|
15
|
+
|
|
16
|
+
class Input(BaseModel):
|
|
17
|
+
expression: str = Field(
|
|
18
|
+
default="NOT_FOUND",
|
|
19
|
+
description=(
|
|
20
|
+
"The mathematical expression to calculate. Allowed characters: digits, "
|
|
21
|
+
"+, -, *, /, parentheses, spaces."
|
|
22
|
+
),
|
|
23
|
+
pattern=r"^(NOT_FOUND|[0-9+\-*/().\s]+)$",
|
|
24
|
+
examples=["(2 + 3) * 4", "10 / 2 + 3", "(8-3)/5"],
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
model_config = ConfigDict(arbitrary_types_allowed=True, validate_assignment=True)
|
|
28
|
+
|
|
29
|
+
class Output(BaseModel):
|
|
30
|
+
result: str = Field(
|
|
31
|
+
description=(
|
|
32
|
+
"Calculated result as a string. Returns an error message for invalid input."
|
|
33
|
+
)
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
plain_utterances: List[str] = [
|
|
37
|
+
"What is 2 + 2?",
|
|
38
|
+
"Calculate (2 + 3) * 4",
|
|
39
|
+
"Can you compute 10 / 2 + 3?",
|
|
40
|
+
"Evaluate (8-3)/5",
|
|
41
|
+
"Please calculate 7 * (6 - 2)",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
template_utterances: List[str] = []
|
|
45
|
+
|
|
46
|
+
@staticmethod
|
|
47
|
+
def generate_utterances(workflow: fastworkflow.Workflow, command_name: str) -> List[str]:
|
|
48
|
+
return [
|
|
49
|
+
command_name.split('/')[-1].lower().replace('_', ' ')
|
|
50
|
+
] + generate_diverse_utterances(Signature.plain_utterances, command_name)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class ResponseGenerator:
|
|
54
|
+
def __call__(self, workflow: Workflow, command: str, command_parameters: Signature.Input) -> CommandOutput:
|
|
55
|
+
output = self._process_command(workflow, command_parameters)
|
|
56
|
+
response = f"Result: {output.result}"
|
|
57
|
+
return CommandOutput(
|
|
58
|
+
workflow_id=workflow.id,
|
|
59
|
+
command_responses=[CommandResponse(response=response)],
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
def _process_command(self, workflow: Workflow, input: Signature.Input) -> Signature.Output:
|
|
63
|
+
data = load_data()
|
|
64
|
+
result = Calculate.invoke(data=data, expression=input.expression)
|
|
65
|
+
return Signature.Output(result=result)
|
|
66
|
+
|
|
67
|
+
|
|
@@ -24,7 +24,10 @@ class Signature:
|
|
|
24
24
|
default="NOT_FOUND",
|
|
25
25
|
description="The order ID to cancel (must start with #)",
|
|
26
26
|
pattern=r"^(#[\w\d]+|NOT_FOUND)$",
|
|
27
|
-
examples=["#123", "#abc123", "#order456"]
|
|
27
|
+
examples=["#123", "#abc123", "#order456"],
|
|
28
|
+
json_schema_extra={
|
|
29
|
+
"available_from": ["get_user_details"]
|
|
30
|
+
}
|
|
28
31
|
)
|
|
29
32
|
]
|
|
30
33
|
|
|
@@ -17,21 +17,33 @@ class Signature:
|
|
|
17
17
|
description="The order ID to exchange (must start with #)",
|
|
18
18
|
pattern=r"^(#[\w\d]+|NOT_FOUND)$",
|
|
19
19
|
examples=["#W0000000"],
|
|
20
|
+
json_schema_extra={
|
|
21
|
+
"available_from": ["get_user_details"]
|
|
22
|
+
}
|
|
20
23
|
)
|
|
21
24
|
item_ids: List[str] = Field(
|
|
22
25
|
default_factory=list,
|
|
23
26
|
description="The item IDs to be exchanged",
|
|
24
27
|
examples=["1008292230"],
|
|
28
|
+
json_schema_extra={
|
|
29
|
+
"available_from": ["get_order_details"]
|
|
30
|
+
}
|
|
25
31
|
)
|
|
26
32
|
new_item_ids: List[str] = Field(
|
|
27
33
|
default_factory=list,
|
|
28
34
|
description="The new item IDs to exchange for",
|
|
29
35
|
examples=["1008292230"],
|
|
36
|
+
json_schema_extra={
|
|
37
|
+
"available_from": ["get_product_details"]
|
|
38
|
+
}
|
|
30
39
|
)
|
|
31
40
|
payment_method_id: str = Field(
|
|
32
41
|
default="NOT_FOUND",
|
|
33
|
-
description="Payment method ID for price difference.
|
|
42
|
+
description="Payment method ID for price difference.",
|
|
34
43
|
examples=["gift_card_0000000", "credit_card_0000000"],
|
|
44
|
+
json_schema_extra={
|
|
45
|
+
"available_from": ["get_order_details", "get_user_details"]
|
|
46
|
+
}
|
|
35
47
|
)
|
|
36
48
|
|
|
37
49
|
model_config = ConfigDict(arbitrary_types_allowed=True, validate_assignment=True)
|
|
@@ -28,7 +28,12 @@ class Signature:
|
|
|
28
28
|
model_config = ConfigDict(arbitrary_types_allowed=True, validate_assignment=True)
|
|
29
29
|
|
|
30
30
|
class Output(BaseModel):
|
|
31
|
-
user_id: str = Field(
|
|
31
|
+
user_id: str = Field(
|
|
32
|
+
description="User identifier returned by lookup.",
|
|
33
|
+
json_schema_extra={
|
|
34
|
+
"used_by": ["get_user_details"]
|
|
35
|
+
}
|
|
36
|
+
)
|
|
32
37
|
|
|
33
38
|
plain_utterances: List[str] = [
|
|
34
39
|
"Can you tell me the ID linked to john.doe@example.com?",
|
|
@@ -35,7 +35,12 @@ class Signature:
|
|
|
35
35
|
model_config = ConfigDict(arbitrary_types_allowed=True, validate_assignment=True)
|
|
36
36
|
|
|
37
37
|
class Output(BaseModel):
|
|
38
|
-
user_id: str = Field(
|
|
38
|
+
user_id: str = Field(
|
|
39
|
+
description="User identifier returned by lookup.",
|
|
40
|
+
json_schema_extra={
|
|
41
|
+
"used_by": ["get_user_details"]
|
|
42
|
+
}
|
|
43
|
+
)
|
|
39
44
|
|
|
40
45
|
# ------------------------------------------------------------------
|
|
41
46
|
# Utterances
|
|
@@ -16,24 +16,36 @@ class Signature:
|
|
|
16
16
|
order_id: str = Field(
|
|
17
17
|
default="NOT_FOUND",
|
|
18
18
|
description=(
|
|
19
|
-
"The order ID to get details for (must start with #)
|
|
20
|
-
"from get_user_details, given a user id."
|
|
19
|
+
"The order ID to get details for (must start with #)"
|
|
21
20
|
),
|
|
22
21
|
pattern=r"^(#[\w\d]+|NOT_FOUND)$",
|
|
23
22
|
examples=["#W0000000"],
|
|
23
|
+
json_schema_extra={
|
|
24
|
+
"available_from": ["get_user_details"]
|
|
25
|
+
}
|
|
24
26
|
)
|
|
25
27
|
|
|
26
28
|
model_config = ConfigDict(arbitrary_types_allowed=True, validate_assignment=True)
|
|
27
29
|
|
|
28
30
|
class Output(BaseModel):
|
|
29
|
-
order_details: str = Field(
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
31
|
+
order_details: str = Field(
|
|
32
|
+
description=(
|
|
33
|
+
"Detailed information about the order such as "
|
|
34
|
+
"shipping address, "
|
|
35
|
+
"items ordered with details including name, product and item id, price and options, "
|
|
36
|
+
"fulfillments with details including tracking id and item ids, "
|
|
37
|
+
"order status, and"
|
|
38
|
+
"payment history with details including transaction type, amount and payment method id"
|
|
39
|
+
),
|
|
40
|
+
json_schema_extra={
|
|
41
|
+
"used_by": [
|
|
42
|
+
"exchange_delivered_order_items",
|
|
43
|
+
"get_product_details",
|
|
44
|
+
"modify_pending_order_items",
|
|
45
|
+
"return_delivered_order_items",
|
|
46
|
+
]
|
|
47
|
+
},
|
|
48
|
+
)
|
|
37
49
|
|
|
38
50
|
plain_utterances: List[str] = [
|
|
39
51
|
"Can you tell me what's going on with my order #W1234567?",
|
|
@@ -17,15 +17,23 @@ class Signature:
|
|
|
17
17
|
description="The product ID (numeric string)",
|
|
18
18
|
pattern=r"^(\d{10}|NOT_FOUND)$",
|
|
19
19
|
examples=["6086499569"],
|
|
20
|
+
json_schema_extra={
|
|
21
|
+
"available_from": ["get_order_details", "list_all_product_types"]
|
|
22
|
+
}
|
|
20
23
|
)
|
|
21
24
|
|
|
22
25
|
model_config = ConfigDict(arbitrary_types_allowed=True, validate_assignment=True)
|
|
23
26
|
|
|
24
27
|
class Output(BaseModel):
|
|
25
|
-
product_details: str = Field(
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
product_details: str = Field(
|
|
29
|
+
description=(
|
|
30
|
+
"Detailed product information of all product variants of a given product type"
|
|
31
|
+
"including item id, options, availability and price"
|
|
32
|
+
),
|
|
33
|
+
json_schema_extra={
|
|
34
|
+
"used_by": ["exchange_delivered_order_items", "modify_pending_order_items"]
|
|
35
|
+
}
|
|
36
|
+
)
|
|
29
37
|
|
|
30
38
|
plain_utterances: List[str] = [
|
|
31
39
|
"Can you give me more information about this product?",
|
|
@@ -17,16 +17,32 @@ class Signature:
|
|
|
17
17
|
description="The user ID to get details for",
|
|
18
18
|
pattern=r"^([a-z]+_[a-z]+_\d+|NOT_FOUND)$",
|
|
19
19
|
examples=["sara_doe_496"],
|
|
20
|
+
json_schema_extra={
|
|
21
|
+
"available_from": ["find_user_id_by_email", "find_user_id_by_name_zip"]
|
|
22
|
+
}
|
|
20
23
|
)
|
|
21
24
|
|
|
22
25
|
model_config = ConfigDict(arbitrary_types_allowed=True, validate_assignment=True)
|
|
23
26
|
|
|
24
27
|
class Output(BaseModel):
|
|
25
|
-
user_details: str = Field(
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
user_details: str = Field(
|
|
29
|
+
description=(
|
|
30
|
+
"Detailed user information such as "
|
|
31
|
+
"first and last name, address, email, payment methods and "
|
|
32
|
+
"the list of order id's"
|
|
33
|
+
),
|
|
34
|
+
json_schema_extra={
|
|
35
|
+
"used_by": [
|
|
36
|
+
"cancel_pending_order",
|
|
37
|
+
"exchange_delivered_order_items",
|
|
38
|
+
"get_order_details",
|
|
39
|
+
"modify_pending_order_address",
|
|
40
|
+
"modify_pending_order_items",
|
|
41
|
+
"modify_pending_order_payment",
|
|
42
|
+
"return_delivered_order_items",
|
|
43
|
+
]
|
|
44
|
+
},
|
|
45
|
+
)
|
|
30
46
|
|
|
31
47
|
plain_utterances: List[str] = [
|
|
32
48
|
"Can you pull up my account info?",
|
|
@@ -20,7 +20,10 @@ class Signature:
|
|
|
20
20
|
|
|
21
21
|
class Output(BaseModel):
|
|
22
22
|
status: str = Field(
|
|
23
|
-
description="List of product type
|
|
23
|
+
description="List of product type and product id tuples, or a JSON string representation of that list.",
|
|
24
|
+
json_schema_extra={
|
|
25
|
+
"used_by": ["get_product_details"]
|
|
26
|
+
}
|
|
24
27
|
)
|
|
25
28
|
|
|
26
29
|
# ---------------------------------------------------------------------
|
|
@@ -17,6 +17,9 @@ class Signature:
|
|
|
17
17
|
description="The order ID to modify (must start with #)",
|
|
18
18
|
pattern=r"^(#W\d+|NOT_FOUND)$",
|
|
19
19
|
examples=["#W0000000"],
|
|
20
|
+
json_schema_extra={
|
|
21
|
+
"available_from": ["get_user_details"]
|
|
22
|
+
}
|
|
20
23
|
)
|
|
21
24
|
address1: str = Field(default="NOT_FOUND", description="First line of address", examples=["123 Main St"])
|
|
22
25
|
address2: str = Field(default="NOT_FOUND", description="Second line of address", examples=["Apt 1"])
|
|
@@ -17,22 +17,34 @@ class Signature:
|
|
|
17
17
|
description="The order ID to modify (must start with #)",
|
|
18
18
|
pattern=r"^(#W\d+|NOT_FOUND)$",
|
|
19
19
|
examples=["#W0000000"],
|
|
20
|
+
json_schema_extra={
|
|
21
|
+
"available_from": ["get_user_details"]
|
|
22
|
+
}
|
|
20
23
|
)
|
|
21
24
|
item_ids: List[str] = Field(
|
|
22
25
|
default_factory=list,
|
|
23
26
|
description="List of item IDs to be modified",
|
|
24
27
|
examples=["1008292230", "2468135790"],
|
|
28
|
+
json_schema_extra={
|
|
29
|
+
"available_from": ["get_order_details"]
|
|
30
|
+
}
|
|
25
31
|
)
|
|
26
32
|
new_item_ids: List[str] = Field(
|
|
27
33
|
default_factory=list,
|
|
28
34
|
description="List of new item IDs to replace with",
|
|
29
35
|
examples=["1008292231", "2468135791"],
|
|
36
|
+
json_schema_extra={
|
|
37
|
+
"available_from": ["get_product_details"]
|
|
38
|
+
}
|
|
30
39
|
)
|
|
31
40
|
payment_method_id: str = Field(
|
|
32
41
|
default="NOT_FOUND",
|
|
33
42
|
description="Payment method ID for price differences",
|
|
34
43
|
pattern=r"^((gift_card|credit_card)_\d+|NOT_FOUND)$",
|
|
35
44
|
examples=["gift_card_0000000", "credit_card_0000000"],
|
|
45
|
+
json_schema_extra={
|
|
46
|
+
"available_from": ["get_order_details", "get_user_details"]
|
|
47
|
+
}
|
|
36
48
|
)
|
|
37
49
|
|
|
38
50
|
model_config = ConfigDict(arbitrary_types_allowed=True, validate_assignment=True)
|
|
@@ -10,19 +10,25 @@ from ..tools.modify_pending_order_payment import ModifyPendingOrderPayment
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class Signature:
|
|
13
|
-
"""Modify pending order payment"""
|
|
13
|
+
"""Modify pending order payment method"""
|
|
14
14
|
class Input(BaseModel):
|
|
15
15
|
order_id: str = Field(
|
|
16
16
|
default="NOT_FOUND",
|
|
17
17
|
description="The order ID to modify (must start with #)",
|
|
18
18
|
pattern=r"^(#W\d+|NOT_FOUND)$",
|
|
19
19
|
examples=["#W0000000"],
|
|
20
|
+
json_schema_extra={
|
|
21
|
+
"available_from": ["get_user_details"]
|
|
22
|
+
}
|
|
20
23
|
)
|
|
21
24
|
payment_method_id: str = Field(
|
|
22
25
|
default="NOT_FOUND",
|
|
23
26
|
description="Payment method ID to switch to",
|
|
24
27
|
pattern=r"^((gift_card|credit_card)_\d+|NOT_FOUND)$",
|
|
25
28
|
examples=["gift_card_0000000", "credit_card_0000000"],
|
|
29
|
+
json_schema_extra={
|
|
30
|
+
"available_from": ["get_user_details"]
|
|
31
|
+
}
|
|
26
32
|
)
|
|
27
33
|
|
|
28
34
|
model_config = ConfigDict(arbitrary_types_allowed=True, validate_assignment=True)
|
|
@@ -17,6 +17,9 @@ class Signature:
|
|
|
17
17
|
description="The user ID to modify",
|
|
18
18
|
pattern=r"^([a-z]+_[a-z]+_\d+|NOT_FOUND)$",
|
|
19
19
|
examples=["sara_doe_496"],
|
|
20
|
+
json_schema_extra={
|
|
21
|
+
"available_from": ["find_user_id_by_email", "find_user_id_by_name_zip"]
|
|
22
|
+
}
|
|
20
23
|
)
|
|
21
24
|
address1: str = Field(default="NOT_FOUND", description="First line of address", examples=["123 Main St"])
|
|
22
25
|
address2: str = Field(default="NOT_FOUND", description="Second line of address", examples=["Apt 1"])
|
|
@@ -16,18 +16,27 @@ class Signature:
|
|
|
16
16
|
default="NOT_FOUND",
|
|
17
17
|
description="The order ID for return (must start with #)",
|
|
18
18
|
pattern=r"^(#W\d+|NOT_FOUND)$",
|
|
19
|
-
examples=["#
|
|
19
|
+
examples=["#W3456890"],
|
|
20
|
+
json_schema_extra={
|
|
21
|
+
"available_from": ["get_user_details"]
|
|
22
|
+
}
|
|
20
23
|
)
|
|
21
24
|
item_ids: List[str] = Field(
|
|
22
25
|
default_factory=list,
|
|
23
26
|
description="List of item IDs to be returned",
|
|
24
27
|
examples=["1008292230"],
|
|
28
|
+
json_schema_extra={
|
|
29
|
+
"available_from": ["get_order_details"]
|
|
30
|
+
}
|
|
25
31
|
)
|
|
26
32
|
payment_method_id: str = Field(
|
|
27
33
|
default="NOT_FOUND",
|
|
28
34
|
description="Payment method ID for refund",
|
|
29
35
|
pattern=r"^((gift_card|credit_card)_\d+|NOT_FOUND)$",
|
|
30
36
|
examples=["gift_card_0000000", "credit_card_0000000"],
|
|
37
|
+
json_schema_extra={
|
|
38
|
+
"available_from": ["get_order_details"]
|
|
39
|
+
}
|
|
31
40
|
)
|
|
32
41
|
|
|
33
42
|
model_config = ConfigDict(arbitrary_types_allowed=True, validate_assignment=True)
|
|
@@ -7,7 +7,7 @@ from .tool import Tool
|
|
|
7
7
|
class Calculate(Tool):
|
|
8
8
|
@staticmethod
|
|
9
9
|
def invoke(data: Dict[str, Any], expression: str) -> str:
|
|
10
|
-
if
|
|
10
|
+
if any(char not in "0123456789+-*/(). " for char in expression):
|
|
11
11
|
return "Error: invalid characters in expression"
|
|
12
12
|
try:
|
|
13
13
|
# Evaluate the mathematical expression safely
|
fastworkflow/mcp_server.py
CHANGED
|
@@ -11,6 +11,7 @@ from fastworkflow.command_executor import CommandExecutor
|
|
|
11
11
|
from fastworkflow.command_directory import CommandDirectory
|
|
12
12
|
from fastworkflow.command_routing import RoutingDefinition, RoutingRegistry, ModuleType
|
|
13
13
|
from uuid import uuid4
|
|
14
|
+
from fastworkflow.command_metadata_api import CommandMetadataAPI
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
class FastWorkflowMCPServer:
|
|
@@ -50,64 +51,68 @@ class FastWorkflowMCPServer:
|
|
|
50
51
|
active_ctx = active_ctx_name if active_ctx_name in routing.contexts else '*'
|
|
51
52
|
command_names = routing.get_command_names(active_ctx)
|
|
52
53
|
|
|
54
|
+
# Centralized command metadata (docstrings, inputs, plain_utterances)
|
|
55
|
+
cme_path = fastworkflow.get_internal_workflow_path("command_metadata_extraction")
|
|
56
|
+
enhanced_meta = CommandMetadataAPI.get_enhanced_command_info(
|
|
57
|
+
subject_workflow_path=workflow_folderpath,
|
|
58
|
+
cme_workflow_path=cme_path,
|
|
59
|
+
active_context_name=active_ctx_name,
|
|
60
|
+
)
|
|
61
|
+
meta_by_fq = {m.get("qualified_name"): m for m in enhanced_meta.get("commands", [])}
|
|
62
|
+
|
|
63
|
+
# Centralized parameters for building schemas
|
|
64
|
+
params_by_cmd = CommandMetadataAPI.get_params_for_all_commands(workflow_folderpath)
|
|
65
|
+
|
|
53
66
|
tools = []
|
|
54
67
|
for command_name in command_names:
|
|
55
|
-
#
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
ModuleType.COMMAND_PARAMETERS_CLASS
|
|
59
|
-
)
|
|
60
|
-
|
|
61
|
-
# Build JSON schema from Pydantic model
|
|
68
|
+
# Centralized metadata for this command (if any)
|
|
69
|
+
meta_for_cmd = meta_by_fq.get(command_name)
|
|
70
|
+
# Build JSON schema from centralized API params
|
|
62
71
|
input_schema = {
|
|
63
72
|
"type": "object",
|
|
64
73
|
"properties": {},
|
|
65
74
|
"required": []
|
|
66
75
|
}
|
|
67
76
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
field_descriptions.append(field_name)
|
|
81
|
-
|
|
82
|
-
if hasattr(field_info, 'default') and field_info.default:
|
|
83
|
-
prop["default"] = field_info.default
|
|
84
|
-
# Add to required if default is NOT_FOUND
|
|
85
|
-
if field_info.default == NOT_FOUND:
|
|
86
|
-
input_schema["required"].append(field_name)
|
|
87
|
-
elif field_info.is_required():
|
|
77
|
+
# Build input schema and required fields
|
|
78
|
+
field_descriptions = []
|
|
79
|
+
for param in params_by_cmd.get(command_name, {}).get("inputs", []):
|
|
80
|
+
field_name = param.get("name") or "param"
|
|
81
|
+
prop = {"type": "string"}
|
|
82
|
+
if desc := param.get("description"):
|
|
83
|
+
prop["description"] = desc
|
|
84
|
+
field_descriptions.append(desc)
|
|
85
|
+
else:
|
|
86
|
+
field_descriptions.append(field_name)
|
|
87
|
+
if (default := param.get("default")) is not None:
|
|
88
|
+
if default == NOT_FOUND:
|
|
88
89
|
input_schema["required"].append(field_name)
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
90
|
+
input_schema["properties"][field_name] = prop
|
|
91
|
+
|
|
92
|
+
# Use centralized display generator for a single command for rich description
|
|
93
|
+
description = CommandMetadataAPI.get_command_display_text_for_command(
|
|
94
|
+
subject_workflow_path=workflow_folderpath,
|
|
95
|
+
cme_workflow_path=cme_path,
|
|
96
|
+
active_context_name=active_ctx_name,
|
|
97
|
+
qualified_command_name=command_name,
|
|
98
|
+
for_agents=True,
|
|
99
|
+
omit_command_name=True,
|
|
100
|
+
)
|
|
96
101
|
|
|
97
102
|
# Add standard FastWorkflow parameters
|
|
98
|
-
input_schema["properties"]["command"] = {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
103
|
+
# input_schema["properties"]["command"] = {
|
|
104
|
+
# "type": "string",
|
|
105
|
+
# "description": "Natural language command or query"
|
|
106
|
+
# }
|
|
102
107
|
|
|
103
|
-
input_schema["properties"]["workitem_path"] = {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
108
|
+
# input_schema["properties"]["workitem_path"] = {
|
|
109
|
+
# "type": "string",
|
|
110
|
+
# "description": "Command context (optional)",
|
|
111
|
+
# "default": active_ctx
|
|
112
|
+
# }
|
|
108
113
|
|
|
109
114
|
tool_def = {
|
|
110
|
-
"name": command_name,
|
|
115
|
+
"name": command_name.split("/")[-1],
|
|
111
116
|
"description": description,
|
|
112
117
|
"inputSchema": input_schema,
|
|
113
118
|
"annotations": {
|
|
@@ -118,6 +123,9 @@ class FastWorkflowMCPServer:
|
|
|
118
123
|
"openWorldHint": True # FastWorkflow can interact with external systems
|
|
119
124
|
}
|
|
120
125
|
}
|
|
126
|
+
# Attach plain_utterances from centralized metadata if present
|
|
127
|
+
if meta_for_cmd and meta_for_cmd.get("plain_utterances"):
|
|
128
|
+
tool_def["annotations"]["plain_utterances"] = meta_for_cmd["plain_utterances"]
|
|
121
129
|
tools.append(tool_def)
|
|
122
130
|
|
|
123
131
|
return tools
|
fastworkflow/run/__main__.py
CHANGED
|
@@ -73,14 +73,18 @@ def run_main(args):
|
|
|
73
73
|
all_renderables.append(Rule(style="dim"))
|
|
74
74
|
|
|
75
75
|
# Process each command response
|
|
76
|
+
num_responses = len(command_output.command_responses)
|
|
76
77
|
for i, command_response in enumerate(command_output.command_responses, start=1):
|
|
77
78
|
if command_response.response:
|
|
78
|
-
|
|
79
|
-
(
|
|
80
|
-
|
|
81
|
-
|
|
79
|
+
if num_responses > 1:
|
|
80
|
+
all_renderables.extend(
|
|
81
|
+
(
|
|
82
|
+
Text(f"Command Response {i}", style="bold green"),
|
|
83
|
+
Text(command_response.response, style="green")
|
|
84
|
+
)
|
|
82
85
|
)
|
|
83
|
-
|
|
86
|
+
else:
|
|
87
|
+
all_renderables.append(Text(command_response.response, style="green"))
|
|
84
88
|
|
|
85
89
|
if command_response.artifacts:
|
|
86
90
|
all_renderables.extend(
|