flowcept 0.8.10__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.
- flowcept/__init__.py +7 -4
- flowcept/agents/__init__.py +5 -0
- flowcept/agents/agent_client.py +58 -0
- flowcept/agents/agents_utils.py +181 -0
- flowcept/agents/dynamic_schema_tracker.py +191 -0
- flowcept/agents/flowcept_agent.py +30 -0
- flowcept/agents/flowcept_ctx_manager.py +175 -0
- flowcept/agents/gui/__init__.py +5 -0
- flowcept/agents/gui/agent_gui.py +76 -0
- flowcept/agents/gui/gui_utils.py +239 -0
- flowcept/agents/llms/__init__.py +1 -0
- flowcept/agents/llms/claude_gcp.py +139 -0
- flowcept/agents/llms/gemini25.py +119 -0
- flowcept/agents/prompts/__init__.py +1 -0
- flowcept/agents/prompts/general_prompts.py +69 -0
- flowcept/agents/prompts/in_memory_query_prompts.py +297 -0
- flowcept/agents/tools/__init__.py +1 -0
- flowcept/agents/tools/general_tools.py +102 -0
- flowcept/agents/tools/in_memory_queries/__init__.py +1 -0
- flowcept/agents/tools/in_memory_queries/in_memory_queries_tools.py +704 -0
- flowcept/agents/tools/in_memory_queries/pandas_agent_utils.py +309 -0
- flowcept/cli.py +459 -17
- flowcept/commons/daos/docdb_dao/mongodb_dao.py +47 -0
- flowcept/commons/daos/keyvalue_dao.py +19 -23
- flowcept/commons/daos/mq_dao/mq_dao_base.py +49 -38
- flowcept/commons/daos/mq_dao/mq_dao_kafka.py +20 -3
- flowcept/commons/daos/mq_dao/mq_dao_mofka.py +4 -0
- flowcept/commons/daos/mq_dao/mq_dao_redis.py +38 -5
- flowcept/commons/daos/redis_conn.py +47 -0
- flowcept/commons/flowcept_dataclasses/task_object.py +50 -27
- flowcept/commons/flowcept_dataclasses/workflow_object.py +9 -1
- flowcept/commons/settings_factory.py +2 -4
- flowcept/commons/task_data_preprocess.py +400 -0
- flowcept/commons/utils.py +26 -7
- flowcept/configs.py +48 -29
- flowcept/flowcept_api/flowcept_controller.py +102 -18
- flowcept/flowceptor/adapters/base_interceptor.py +24 -11
- flowcept/flowceptor/adapters/brokers/__init__.py +1 -0
- flowcept/flowceptor/adapters/brokers/mqtt_interceptor.py +132 -0
- flowcept/flowceptor/adapters/mlflow/mlflow_interceptor.py +3 -3
- flowcept/flowceptor/adapters/tensorboard/tensorboard_interceptor.py +3 -3
- flowcept/flowceptor/consumers/agent/__init__.py +1 -0
- flowcept/flowceptor/consumers/agent/base_agent_context_manager.py +125 -0
- flowcept/flowceptor/consumers/base_consumer.py +94 -0
- flowcept/flowceptor/consumers/consumer_utils.py +5 -4
- flowcept/flowceptor/consumers/document_inserter.py +135 -36
- flowcept/flowceptor/telemetry_capture.py +6 -3
- flowcept/instrumentation/flowcept_agent_task.py +294 -0
- flowcept/instrumentation/flowcept_decorator.py +43 -0
- flowcept/instrumentation/flowcept_loop.py +3 -3
- flowcept/instrumentation/flowcept_task.py +64 -24
- flowcept/instrumentation/flowcept_torch.py +5 -5
- flowcept/instrumentation/task_capture.py +87 -4
- flowcept/version.py +1 -1
- {flowcept-0.8.10.dist-info → flowcept-0.8.12.dist-info}/METADATA +48 -11
- flowcept-0.8.12.dist-info/RECORD +101 -0
- resources/sample_settings.yaml +46 -14
- flowcept/flowceptor/adapters/zambeze/__init__.py +0 -1
- flowcept/flowceptor/adapters/zambeze/zambeze_dataclasses.py +0 -41
- flowcept/flowceptor/adapters/zambeze/zambeze_interceptor.py +0 -102
- flowcept-0.8.10.dist-info/RECORD +0 -75
- {flowcept-0.8.10.dist-info → flowcept-0.8.12.dist-info}/WHEEL +0 -0
- {flowcept-0.8.10.dist-info → flowcept-0.8.12.dist-info}/entry_points.txt +0 -0
- {flowcept-0.8.10.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."""
|