flowcept 0.8.11__py3-none-any.whl → 0.8.12__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 (56) hide show
  1. flowcept/__init__.py +7 -4
  2. flowcept/agents/__init__.py +5 -0
  3. flowcept/{flowceptor/consumers/agent/client_agent.py → agents/agent_client.py} +22 -12
  4. flowcept/agents/agents_utils.py +181 -0
  5. flowcept/agents/dynamic_schema_tracker.py +191 -0
  6. flowcept/agents/flowcept_agent.py +30 -0
  7. flowcept/agents/flowcept_ctx_manager.py +175 -0
  8. flowcept/agents/gui/__init__.py +5 -0
  9. flowcept/agents/gui/agent_gui.py +76 -0
  10. flowcept/agents/gui/gui_utils.py +239 -0
  11. flowcept/agents/llms/__init__.py +1 -0
  12. flowcept/agents/llms/claude_gcp.py +139 -0
  13. flowcept/agents/llms/gemini25.py +119 -0
  14. flowcept/agents/prompts/__init__.py +1 -0
  15. flowcept/{flowceptor/adapters/agents/prompts.py → agents/prompts/general_prompts.py} +18 -0
  16. flowcept/agents/prompts/in_memory_query_prompts.py +297 -0
  17. flowcept/agents/tools/__init__.py +1 -0
  18. flowcept/agents/tools/general_tools.py +102 -0
  19. flowcept/agents/tools/in_memory_queries/__init__.py +1 -0
  20. flowcept/agents/tools/in_memory_queries/in_memory_queries_tools.py +704 -0
  21. flowcept/agents/tools/in_memory_queries/pandas_agent_utils.py +309 -0
  22. flowcept/cli.py +286 -44
  23. flowcept/commons/daos/docdb_dao/mongodb_dao.py +47 -0
  24. flowcept/commons/daos/mq_dao/mq_dao_base.py +24 -13
  25. flowcept/commons/daos/mq_dao/mq_dao_kafka.py +18 -2
  26. flowcept/commons/flowcept_dataclasses/task_object.py +16 -21
  27. flowcept/commons/flowcept_dataclasses/workflow_object.py +9 -1
  28. flowcept/commons/task_data_preprocess.py +260 -60
  29. flowcept/commons/utils.py +25 -6
  30. flowcept/configs.py +41 -26
  31. flowcept/flowcept_api/flowcept_controller.py +73 -6
  32. flowcept/flowceptor/adapters/base_interceptor.py +11 -5
  33. flowcept/flowceptor/consumers/agent/base_agent_context_manager.py +25 -1
  34. flowcept/flowceptor/consumers/base_consumer.py +4 -0
  35. flowcept/flowceptor/consumers/consumer_utils.py +5 -4
  36. flowcept/flowceptor/consumers/document_inserter.py +2 -2
  37. flowcept/flowceptor/telemetry_capture.py +5 -2
  38. flowcept/instrumentation/flowcept_agent_task.py +294 -0
  39. flowcept/instrumentation/flowcept_decorator.py +43 -0
  40. flowcept/instrumentation/flowcept_loop.py +3 -3
  41. flowcept/instrumentation/flowcept_task.py +64 -24
  42. flowcept/instrumentation/flowcept_torch.py +5 -5
  43. flowcept/instrumentation/task_capture.py +83 -6
  44. flowcept/version.py +1 -1
  45. {flowcept-0.8.11.dist-info → flowcept-0.8.12.dist-info}/METADATA +42 -14
  46. {flowcept-0.8.11.dist-info → flowcept-0.8.12.dist-info}/RECORD +50 -36
  47. resources/sample_settings.yaml +12 -4
  48. flowcept/flowceptor/adapters/agents/__init__.py +0 -1
  49. flowcept/flowceptor/adapters/agents/agents_utils.py +0 -89
  50. flowcept/flowceptor/adapters/agents/flowcept_agent.py +0 -292
  51. flowcept/flowceptor/adapters/agents/flowcept_llm_prov_capture.py +0 -186
  52. flowcept/flowceptor/consumers/agent/flowcept_agent_context_manager.py +0 -145
  53. flowcept/flowceptor/consumers/agent/flowcept_qa_manager.py +0 -112
  54. {flowcept-0.8.11.dist-info → flowcept-0.8.12.dist-info}/WHEEL +0 -0
  55. {flowcept-0.8.11.dist-info → flowcept-0.8.12.dist-info}/entry_points.txt +0 -0
  56. {flowcept-0.8.11.dist-info → flowcept-0.8.12.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,297 @@
1
+ # flake8: noqa: E501
2
+ # flake8: noqa: D103
3
+
4
+ COMMON_TASK_FIELDS = """
5
+ | Column | Data Type | Description |
6
+ |-------------------------------|-------------|
7
+ | `workflow_id` | string | Workflow the task belongs to. Use this field when the query is asking about workflow execution |
8
+ | `task_id` | string | Task identifier. |
9
+ | `parent_task_id` | string | A task may be directly linked to others. Use this field when the query asks for a task informed by (or associated with or linked to) other task. |
10
+ | `activity_id` | string | Type of task (e.g., 'choose_option'). Use this for "task type" queries. One activity_id is linked to multiple task_ids. |
11
+ | `campaign_id` | string | A group of workflows. |
12
+ | `hostname` | string | Compute node name. |
13
+ | `agent_id` | string | Set if executed by an agent. |
14
+ | `started_at` | datetime64[ns, UTC] | Start time of a task. Always use this field when the query is has any temporal reference related to the workflow execution, such as 'get the first 10 workflow executions' or 'the last workflow execution'. |
15
+ | `ended_at` | datetime64[ns, UTC] | End time of a task. |
16
+ | `subtype` | string | Subtype of a task. |
17
+ | `tags` | List[str] | List of descriptive tags. |
18
+ | `telemetry_summary.duration_sec` | float | Task duration (seconds). |
19
+ | `telemetry_summary.cpu.percent_all_diff` | float | Difference in overall CPU utilization percentage across all cores between task end and start.|
20
+ | `telemetry_summary.cpu.user_time_diff` | float | Difference average per core CPU user time ( seconds ) between task start and end times.|
21
+ | `telemetry_summary.cpu.system_time_diff` | float | Difference in CPU system (kernel) time (seconds) used during the task execution.|
22
+ | `telemetry_summary.cpu.idle_time_diff` | float | Difference in CPU idle time (seconds) during task end and start.|
23
+ ---
24
+ For any queries involving CPU, use fields that begin with telemetry_summary.cpu
25
+ """
26
+
27
+ DF_FORM = "The user has a pandas DataFrame called `df`, created from flattened task objects using `pd.json_normalize`."
28
+
29
+
30
+ def get_df_schema_prompt(dynamic_schema, example_values):
31
+ schema_prompt = f"""
32
+ ## DATAFRAME STRUCTURE
33
+
34
+ Each row in `df` represents a single task.
35
+
36
+ ### 1. Structured task fields:
37
+
38
+ - **in**: input parameters (columns starting with `used.`)
39
+ - **out**: output metrics/results (columns starting with `generated.`)
40
+
41
+ The schema for these fields is defined in the dictionary below.
42
+ It maps each activity ID to its inputs (i) and outputs (o), using flattened field names that include `used.` or `generated.` prefixes to indicate the role the field played in the task. These names match the columns in the dataframe `df`.
43
+
44
+ ```python
45
+ {dynamic_schema}
46
+ ```
47
+ Use this schema and fields to understand what inputs and outputs are valid for each activity.
48
+
49
+ ### 2. Additional fields for tasks:
50
+
51
+ {COMMON_TASK_FIELDS}
52
+ ---
53
+ """
54
+
55
+ values_prompt = f"""
56
+ Now, this other dictionary below provides type (t), up to 3 example values (v), and, for lists, shape (s) and element type (et) for each field.
57
+ Field names do not include `used.` or `generated.` They represent the unprefixed form shared across roles. String values may be truncated if they exceed the length limit.
58
+ ```python
59
+ {example_values}
60
+ ```
61
+ """
62
+
63
+ # values_prompt = ""
64
+ prompt = schema_prompt + values_prompt
65
+ return prompt
66
+
67
+
68
+ def generate_plot_code_prompt(query, dynamic_schema, example_values) -> str:
69
+ PLOT_PROMPT = f"""
70
+ You are a Streamlit chart expert.
71
+ {DF_FORM}
72
+
73
+ {get_df_schema_prompt(dynamic_schema, example_values)}
74
+
75
+ ### 3. Guidelines
76
+
77
+ - When plotting from a grouped or aggregated result, set an appropriate column (like activity_id, started_at, etc.) as the index before plotting to ensure x-axis labels are correct.
78
+ - When aggregating by "activity_id", remember to include .set_index('activity_id') in your response.
79
+
80
+ ### 4. Output Format
81
+
82
+ You must write Python code using Streamlit (st) to visualize the requested data.
83
+
84
+ - Always assume `df` is already defined.
85
+ - First, assign the query result to a variable called `result` using pandas.
86
+ - Then, write the plotting code based on `result`.
87
+ - Return a Python dictionary with two fields:
88
+ - `"result_code"`: the pandas code that assigns `result`
89
+ - `"plot_code"`: the code that creates the Streamlit plot
90
+ ---
91
+
92
+ ### 5. Few-Shot Examples
93
+
94
+ ```python
95
+ # Q: Plot the number of tasks by activity
96
+ {{
97
+ "result_code": "result = df['activity_id'].value_counts().reset_index().rename(columns={{'index': 'activity_id', 'activity_id': 'count'}})",
98
+ "plot_code": "st.bar_chart(result.set_index('activity_id'))"
99
+ }}
100
+
101
+ # Q: Show a line chart of task duration per task start time
102
+ {{
103
+ "result_code": "result = df[['started_at', 'telemetry_summary.duration_sec']].dropna().set_index('started_at')",
104
+ "plot_code": "st.line_chart(result)"
105
+ }}
106
+
107
+ # Q: Plot average scores for simulate_layer tasks
108
+ {{
109
+ "result_code": "result = df[df['activity_id'] == 'simulate_layer'][['generated.scores']].copy()\nresult['avg_score'] = result['generated.scores'].apply(lambda x: sum(eval(str(x))) / len(eval(str(x))) if x else 0)",
110
+ "plot_code": "st.bar_chart(result['avg_score'])"
111
+ }}
112
+
113
+ # Q: Plot histogram of planned controls count for choose_option
114
+ {{
115
+ "result_code": "result = df[df['activity_id'] == 'choose_option'][['used.planned_controls']].copy()\nresult['n_controls'] = result['used.planned_controls'].apply(lambda x: len(eval(str(x))) if x else 0)",
116
+ "plot_code": "import matplotlib.pyplot as plt\nplt.hist(result['n_controls'])\nst.pyplot(plt)"
117
+ }}
118
+
119
+ User request:
120
+ {query}
121
+
122
+ THE OUTPUT MUST BE A VALID JSON ONLY. DO NOT SAY ANYTHING ELSE.
123
+
124
+ """
125
+ return PLOT_PROMPT
126
+
127
+
128
+ JOB = "You will generate a pandas dataframe code to solve the query."
129
+ ROLE = """You are an expert in HPC workflow provenance data analysis with a deep knowledge of data lineage tracing, workflow management, and computing systems.
130
+ You are analyzing provenance data from a complex workflow consisting of numerous tasks."""
131
+ QUERY_GUIDELINES = """
132
+
133
+ ### 3. Query Guidelines
134
+
135
+ - Use `df` as the base DataFrame.
136
+ - Use `activity_id` to filter by task type (valid values = schema keys).
137
+ - Use `used.` for parameters (inputs) and `generated.` for outputs (metrics).
138
+ - Use `telemetry_summary.duration_sec` for performance-related questions.
139
+ - Use `hostname` when user mentions *where* a task ran.
140
+ - Use `agent_id` when the user refers to agents (non-null means task was agent-run).
141
+
142
+ ### 4. Hard Constraints (obey strictly, YOUR LIFE DEPENDS ON THEM. DO NOT HALLUCINATE!!!)
143
+
144
+ - Always return code in the form `result = df[<filter>][[...]]` or `result = df.loc[<filter>, [...]]`
145
+ -**THERE ARE NOT INDIVIDUAL FIELDS NAMED `used` OR `generated`, they are ONLY are prefixes to the field names.**
146
+ - If the query needs fields that begin with `used.` or `generated.`, your generated query needs to iterate over the df.columns to select the used or generated fields only, such as (adapt when needed): `[col for col in df.columns if col.startswith('generated.')]` or `[col for col in df.columns if col.startswith('used.')]`
147
+ **THERE ABSOLUTELY ARE NO FIELDS NAMED `used` or `generated`. DO NOT, NEVER use the string 'used' or 'generated' in your generated code!!!**
148
+ **THE COLUMN 'used' DOES NOT EXIST**
149
+ **THE COLUMN 'generated' DOES NOT EXIST**
150
+ - **When filtering by `activity_id`, only select columns that belong to that activity’s schema.**
151
+ - Use only `used.` and `generated.` fields listed in the schema for that `activity_id`.
152
+ - Explicitly list the selected columns — **never return all columns**
153
+ - **Only include telemetry columns if used in the query logic.**
154
+ -THERE IS NOT A FIELD NAMED `telemetry_summary.start_time` or `telemetry_summary.end_time` or `used.start_time` or `used.end_time`. Use `started_at` and `ended_at` instead when you want to find the duration of a task, activity, or workflow execution.
155
+ -THE GENERATED FIELDS ARE LABELED AS SUCH: `generated.()` NOT `generated_output`. Any reference to `generated_output` is incorrect and should be replaced with `generated.` prefix.
156
+ -THERE IS NOT A FIELD NAMED `execution_id` or `used.execution_id`. Look at the QUERY to decide what correct _id field to use. Any mentions of workflow use `workflow_id`. Any mentions of task use `task_id`. Any mentions of activity use `activity_id`.
157
+ -DO NOT USE `nlargest` or `nsmallest` in the query code, use `sort_values` instead.
158
+ -An activity with a value in the `generated.` column created that value. Whereas an activity that has a value in the `used.` column used that value from another activity. IF THE `used.` and `generated.` fields share the same letter after the dot, that means that the activity associated with the `generated.` was created by another activity and the one with `used.` used that SAME value that was created by the activity with that same value in the `generated.` field.
159
+ -WHEN user requests about workflow time (e.g., total time or duration" or elapsed time or total execution time or elapsed time or makespan about workflow executions or asking about workflows that took longer than a certain threshold or other workflow-related timing question of one or many workflow executions (each is identified by `workflow_id`), get its latest task's `ended_at` and its earliest task's `started_at`and compute the difference between them, like this (adapt when needed): `df.groupby('workflow_id').apply(lambda x: (x['ended_at'].max() - x['started_at'].min()).total_seconds())`
160
+ -WHEN user requests duration or execution time per task or for individual tasks, utilize `telemetry_summary.duration_sec`.
161
+ -WHEN user requests execution time per activity within workflows compute durations using the difference between the last `ended_at` and the first `started_at` grouping by activitiy_id, workflow_id rather than using `telemetry_summary.duration_sec`.
162
+
163
+ -The first (or the earliest) workflow execution is the one that has the task with earliest `started_at`, so you need to sort the DataFrame based on `started_at` to get the associated workflow_id.
164
+ -The last (or the latest or the most recent) workflow execution is the one that has the task with the latest `ended_at`, so you need to sort the DataFrame based on `ended_at` to get the associated workflow_id.
165
+ - Use this to select the tasks in the first workflow (or in the earliest workflow): df[df.workflow_id == df.loc[df.started_at.idxmin(), 'workflow_id']]
166
+ - Use this to select the tasks in the last workflow (or in the latest workflow or in the most recent workflow or the workflow that started or ended most recently): df[df.workflow_id == df.loc[df.ended_at.idxmax(), 'workflow_id']]
167
+ -WHEN the user requests the "first workflow" (or earliest workflow), you must identify the workflow by using workflow_id of the task with the earliest started_at. DO NOT use the min workflow_id.
168
+ -WHEN the user requests the "last workflow" (or latest workflow or most recent workflow), you must identify the workflow by using workflow_id of the task with the latest `ended_at`. DO NOT use the max workflow_id.
169
+ -Do not use df['workflow_id'].max() or df['workflow_id'].min() to find the first or last workflow execution.
170
+
171
+ -To select the first (or earliest) N workflow executions, use or adapt the following: `df.groupby('workflow_id', as_index=False).agg({{"started_at": 'min'}}).sort_values(by='started_at', ascending=True).head(N)['workflow_id']` - utilize `started_at` to sort!
172
+ -To select the last (or latest or most recent) N workflow executions, use or adapt the following: `df.groupby('workflow_id', as_index=False).agg({{"ended_at": 'max'}}).sort_values(by='ended_at', ascending=False).head(N)['workflow_id']` - utilize `ended_at` to sort!
173
+
174
+ -WHEN the user requests a "summary" of activities, you must incorporate relevant summary statistics such as min, max, and mean, into the code you generate.
175
+ -Do NOT use df[0] or df[integer value] or df[df[<field name>].idxmax()] or df[df[<field name>].idxmin()] because these are obviously not valid Pandas Code!
176
+ -**Do NOT use any of those: df[df['started_at'].idxmax()], df[df['started_at'].idxmin()], df[df['ended_at'].idxmin()], df[df['ended_at'].idxmax()]. Those are not valid Pandas Code.**
177
+ - When the query mentions "each task", or "each activity", or "each workflow", make sure you show (project) the correct id column in the results (i.e., respectively: `task_id`, `activity_id`, `workflow_id`) to identify those in the results.
178
+ - Use df[<role>.field_name] == True or df[<role>.field_name] == False when user queries boolean fields, where <role> is either used or generated, depending on the field name. Make sure field_name is a valid field in the DataFrame.
179
+
180
+ - **Do not include metadata columns unless explicitly required by the user query.**
181
+ """
182
+
183
+ FEW_SHOTS = """
184
+ ### 5. Few-Shot Examples
185
+
186
+ # Q: How many tasks were processed?
187
+ result = len(df))
188
+
189
+ # Q: How many tasks for each activity?
190
+ result = df['activity_id'].value_counts()
191
+
192
+ # Q: What is the average loss across all tasks?
193
+ result = df['generated.loss'].mean()
194
+
195
+ # Q: select the 'choose_option' tasks executed by the agent, and show the planned controls, generated option, scores, explanations
196
+ result = df[(df['activity_id'] == 'choose_option') & (df['agent_id'].notna())][['used.planned_controls', 'generated.option', 'used.scores.scores', 'generated.explanation']].copy()
197
+
198
+ # Q: Show duration and generated scores for 'simulate_layer' tasks
199
+ result = df[df['activity_id'] == 'simulate_layer'][['telemetry_summary.duration_sec', 'generated.scores']]
200
+ """
201
+
202
+ OUTPUT_FORMATTING = """
203
+ 6. Final Instructions
204
+ Return only valid pandas code assigned to the variable result.
205
+
206
+ Your response must be only the raw Python code in the format:
207
+ result = ...
208
+
209
+ Do not include: Explanations, Markdown formatting, Triple backticks, Comments, or Any text before or after the code block.
210
+ The output cannot have any markdown, no ```python or ``` at all.
211
+
212
+ THE OUTPUT MUST BE ONE LINE OF VALID PYTHON CODE ONLY, DO NOT SAY ANYTHING ELSE.
213
+
214
+ Strictly follow the constraints above.
215
+ """
216
+
217
+
218
+ def generate_pandas_code_prompt(query: str, dynamic_schema, example_values):
219
+ prompt = (
220
+ f"{ROLE}"
221
+ f"{JOB}"
222
+ f"{DF_FORM}"
223
+ f"{get_df_schema_prompt(dynamic_schema, example_values)}" # main tester
224
+ # f"{QUERY_GUIDELINES}" # main tester
225
+ f"{FEW_SHOTS}" # main tester
226
+ f"{OUTPUT_FORMATTING}"
227
+ "User Query:"
228
+ f"{query}"
229
+ )
230
+ return prompt
231
+
232
+
233
+ def dataframe_summarizer_context(code, reduced_df, query) -> str:
234
+ prompt = f"""
235
+ You are a Workflow Provenance Specialist analyzing a DataFrame that was obtained to answer a query. Given:
236
+
237
+ **User Query**:
238
+ {query}
239
+
240
+ **Query_Code**:
241
+ {code}
242
+
243
+ **Reduced DataFrame** (rows sampled from full result):
244
+ {reduced_df}
245
+
246
+ Your task is to:
247
+ 1. Analyze the DataFrame values and columns for any meaningful or notable information.
248
+ 2. Compare the query_code with the data content to understand what the result represents. THIS IS A REDUCED DATAFRAME, the original dataframe, used to answer the query, may be much bigger. IT IS ALREADY KNOWN! Do not need to restate this.
249
+ 3. Provide a concise and direct answer to the user query. Your final response to the query should be within ```text .
250
+
251
+ Note that the user should not know that this is a reduced dataframe.
252
+
253
+ Keep your response short and focused.
254
+
255
+ """
256
+ return prompt
257
+
258
+
259
+ def extract_or_fix_json_code_prompt(raw_text) -> str:
260
+ prompt = f"""
261
+ You are a JSON extractor and fixer.
262
+ You are given a raw message that may include explanations, markdown fences, or partial JSON.
263
+
264
+ Your task:
265
+ 1. Check if the message contains a JSON object or array.
266
+ 2. If it does, extract and fix the JSON if needed.
267
+ 3. Ensure all keys and string values are properly quoted.
268
+ 4. Return only valid, parseable JSON — no markdown, no explanations.
269
+
270
+ THE OUTPUT MUST BE A VALID JSON ONLY. DO NOT SAY ANYTHING ELSE.
271
+
272
+ User message:
273
+ {raw_text}
274
+ """
275
+ return prompt
276
+
277
+
278
+ def extract_or_fix_python_code_prompt(raw_text):
279
+ prompt = f"""
280
+ You are a Pandas DataFrame code extractor and fixer. Pandas is a well-known data science Python library for querying datasets.
281
+ You are given a raw user message that may include explanations, markdown fences, or partial DataFrame code that queries a DataFrame `df`.
282
+
283
+ Your task:
284
+ 1. Check if the message contains a valid DataFrame code.
285
+ 2. If it does, extract the code.
286
+ 3. If there are any syntax errors, fix them.
287
+ 4. Return only the corrected DataFrame query code — no explanations, no comments, no markdown.
288
+
289
+ The output must be valid Python code, and must not include any other text.
290
+ This output will be parsed by another program.
291
+
292
+ ONCE AGAIN, ONLY PRODUCE THE PYTHON CODE. DO NOT SAY ANYTHING ELSE!
293
+
294
+ User message:
295
+ {raw_text}
296
+ """
297
+ return prompt
@@ -0,0 +1 @@
1
+ """Agent Tools Package."""
@@ -0,0 +1,102 @@
1
+ import json
2
+
3
+ from flowcept.agents.agents_utils import build_llm_model, ToolResult
4
+ from flowcept.agents.flowcept_ctx_manager import mcp_flowcept
5
+ from flowcept.agents.prompts.general_prompts import ROUTING_PROMPT, SMALL_TALK_PROMPT
6
+
7
+ from flowcept.agents.tools.in_memory_queries.in_memory_queries_tools import run_df_query
8
+
9
+
10
+ @mcp_flowcept.tool()
11
+ def get_latest(n: int = None) -> str:
12
+ """
13
+ Return the most recent task(s) from the task buffer.
14
+
15
+ Parameters
16
+ ----------
17
+ n : int, optional
18
+ Number of most recent tasks to return. If None, return only the latest.
19
+
20
+ Returns
21
+ -------
22
+ str
23
+ JSON-encoded task(s).
24
+ """
25
+ ctx = mcp_flowcept.get_context()
26
+ tasks = ctx.request_context.lifespan_context.tasks
27
+ if not tasks:
28
+ return "No tasks available."
29
+ if n is None:
30
+ return json.dumps(tasks[-1])
31
+ return json.dumps(tasks[-n])
32
+
33
+
34
+ @mcp_flowcept.tool()
35
+ def check_liveness() -> str:
36
+ """
37
+ Confirm the agent is alive and responding.
38
+
39
+ Returns
40
+ -------
41
+ str
42
+ Liveness status string.
43
+ """
44
+ return f"I'm {mcp_flowcept.name} and I'm ready!"
45
+
46
+
47
+ @mcp_flowcept.tool()
48
+ def check_llm() -> str:
49
+ """
50
+ Check connectivity and response from the LLM backend.
51
+
52
+ Returns
53
+ -------
54
+ str
55
+ LLM response, formatted with MCP metadata.
56
+ """
57
+ llm = build_llm_model()
58
+ response = llm("Hello?")
59
+ return response
60
+
61
+
62
+ @mcp_flowcept.tool()
63
+ def prompt_handler(message: str) -> ToolResult:
64
+ """
65
+ Routes a user message using an LLM to classify its intent.
66
+
67
+ Parameters
68
+ ----------
69
+ message : str
70
+ User's natural language input.
71
+
72
+ Returns
73
+ -------
74
+ TextContent
75
+ The AI response or routing feedback.
76
+ """
77
+ df_key_words = {"save", "result = df", "reset context"}
78
+ for key in df_key_words:
79
+ if key in message:
80
+ return run_df_query(llm=None, query=message, plot=False)
81
+
82
+ llm = build_llm_model()
83
+
84
+ prompt = ROUTING_PROMPT + message
85
+ route = llm.invoke(prompt)
86
+
87
+ if route == "small_talk":
88
+ prompt = SMALL_TALK_PROMPT + message
89
+ response = llm.invoke(prompt)
90
+ return ToolResult(code=201, result=response)
91
+ elif route == "plot":
92
+ return run_df_query(llm, message, plot=True)
93
+ elif route == "historical_prov_query":
94
+ return ToolResult(code=201, result="We need to query the Provenance Database. Feature coming soon.")
95
+ elif route == "in_context_query":
96
+ return run_df_query(llm, message, plot=False)
97
+ elif route == "in_chat_query":
98
+ prompt = SMALL_TALK_PROMPT + message
99
+ response = llm.invoke(prompt)
100
+ return ToolResult(code=201, result=response)
101
+ else:
102
+ return ToolResult(code=404, result="I don't know how to route.")
@@ -0,0 +1 @@
1
+ """In-memory Agent Queries Package."""