solace-agent-mesh 0.1.3__py3-none-any.whl → 0.2.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.
Potentially problematic release.
This version of solace-agent-mesh might be problematic. Click here for more details.
- solace_agent_mesh/agents/global/actions/plantuml_diagram.py +9 -2
- solace_agent_mesh/agents/global/actions/plotly_graph.py +70 -46
- solace_agent_mesh/agents/web_request/actions/do_web_request.py +34 -33
- solace_agent_mesh/cli/__init__.py +1 -1
- solace_agent_mesh/cli/commands/add/copy_from_plugin.py +8 -6
- solace_agent_mesh/cli/commands/add/gateway.py +162 -9
- solace_agent_mesh/cli/commands/build.py +15 -1
- solace_agent_mesh/cli/commands/init/ai_provider_step.py +45 -28
- solace_agent_mesh/cli/commands/init/broker_step.py +1 -4
- solace_agent_mesh/cli/commands/init/create_config_file_step.py +8 -0
- solace_agent_mesh/cli/commands/init/create_other_project_files_step.py +52 -1
- solace_agent_mesh/cli/commands/init/init.py +50 -37
- solace_agent_mesh/cli/commands/plugin/build.py +60 -9
- solace_agent_mesh/cli/commands/run.py +2 -2
- solace_agent_mesh/cli/config.py +4 -0
- solace_agent_mesh/cli/main.py +14 -8
- solace_agent_mesh/cli/utils.py +7 -2
- solace_agent_mesh/common/constants.py +10 -0
- solace_agent_mesh/common/prompt_templates.py +1 -3
- solace_agent_mesh/common/utils.py +104 -30
- solace_agent_mesh/config_portal/__init__.py +0 -0
- solace_agent_mesh/config_portal/backend/__init__.py +0 -0
- solace_agent_mesh/config_portal/backend/common.py +35 -0
- solace_agent_mesh/config_portal/backend/server.py +233 -0
- solace_agent_mesh/config_portal/frontend/static/client/assets/_index-DRPGOzHj.js +42 -0
- solace_agent_mesh/config_portal/frontend/static/client/assets/components-ZIfdTbrV.js +191 -0
- solace_agent_mesh/config_portal/frontend/static/client/assets/entry.client-DX1misIU.js +19 -0
- solace_agent_mesh/config_portal/frontend/static/client/assets/index-BJHAE5s4.js +17 -0
- solace_agent_mesh/config_portal/frontend/static/client/assets/manifest-8147e469.js +1 -0
- solace_agent_mesh/config_portal/frontend/static/client/assets/root-DgMDqKDc.js +10 -0
- solace_agent_mesh/config_portal/frontend/static/client/assets/root-hhS5izs8.css +1 -0
- solace_agent_mesh/config_portal/frontend/static/client/favicon.ico +0 -0
- solace_agent_mesh/config_portal/frontend/static/client/index.html +7 -0
- solace_agent_mesh/configs/orchestrator.yaml +1 -1
- solace_agent_mesh/configs/service_embedding.yaml +1 -1
- solace_agent_mesh/configs/service_llm.yaml +1 -1
- solace_agent_mesh/gateway/components/gateway_base.py +7 -1
- solace_agent_mesh/gateway/components/gateway_input.py +8 -5
- solace_agent_mesh/gateway/components/gateway_output.py +12 -3
- solace_agent_mesh/orchestrator/components/orchestrator_action_manager_timeout_component.py +4 -0
- solace_agent_mesh/orchestrator/components/orchestrator_stimulus_processor_component.py +43 -12
- solace_agent_mesh/orchestrator/components/orchestrator_streaming_output_component.py +19 -5
- solace_agent_mesh/orchestrator/orchestrator_main.py +11 -5
- solace_agent_mesh/orchestrator/orchestrator_prompt.py +184 -60
- solace_agent_mesh/services/file_service/file_service.py +5 -0
- solace_agent_mesh/services/file_service/file_service_constants.py +1 -1
- solace_agent_mesh/services/file_service/file_transformations.py +11 -1
- solace_agent_mesh/services/file_service/file_utils.py +2 -0
- solace_agent_mesh/services/history_service/history_providers/base_history_provider.py +21 -46
- solace_agent_mesh/services/history_service/history_providers/file_history_provider.py +74 -0
- solace_agent_mesh/services/history_service/history_providers/index.py +40 -0
- solace_agent_mesh/services/history_service/history_providers/memory_history_provider.py +19 -156
- solace_agent_mesh/services/history_service/history_providers/mongodb_history_provider.py +66 -0
- solace_agent_mesh/services/history_service/history_providers/redis_history_provider.py +40 -140
- solace_agent_mesh/services/history_service/history_providers/sql_history_provider.py +93 -0
- solace_agent_mesh/services/history_service/history_service.py +315 -41
- solace_agent_mesh/services/history_service/long_term_memory/__init__.py +0 -0
- solace_agent_mesh/services/history_service/long_term_memory/long_term_memory.py +399 -0
- solace_agent_mesh/services/llm_service/components/llm_request_component.py +19 -0
- solace_agent_mesh/templates/gateway-config-template.yaml +2 -1
- solace_agent_mesh/templates/gateway-default-config.yaml +3 -3
- solace_agent_mesh/templates/plugin-gateway-default-config.yaml +29 -0
- solace_agent_mesh/templates/rest-api-default-config.yaml +2 -1
- solace_agent_mesh/templates/slack-default-config.yaml +1 -1
- solace_agent_mesh/templates/solace-agent-mesh-default.yaml +9 -0
- solace_agent_mesh/templates/web-default-config.yaml +2 -1
- solace_agent_mesh-0.2.1.dist-info/METADATA +172 -0
- {solace_agent_mesh-0.1.3.dist-info → solace_agent_mesh-0.2.1.dist-info}/RECORD +71 -52
- solace_agent_mesh/common/prompt_templates_unused_delete.py +0 -161
- solace_agent_mesh-0.1.3.dist-info/METADATA +0 -208
- {solace_agent_mesh-0.1.3.dist-info → solace_agent_mesh-0.2.1.dist-info}/WHEEL +0 -0
- {solace_agent_mesh-0.1.3.dist-info → solace_agent_mesh-0.2.1.dist-info}/entry_points.txt +0 -0
- {solace_agent_mesh-0.1.3.dist-info → solace_agent_mesh-0.2.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,69 +1,60 @@
|
|
|
1
1
|
from typing import Dict, Any, List
|
|
2
2
|
import yaml
|
|
3
|
-
from langchain_core.messages import HumanMessage
|
|
4
3
|
from ..services.file_service import FS_PROTOCOL, Types, LLM_QUERY_OPTIONS, TRANSFORMERS
|
|
4
|
+
from solace_ai_connector.common.log import log
|
|
5
5
|
|
|
6
6
|
# Cap the number of examples so we don't overwhelm the LLM
|
|
7
7
|
MAX_SYSTEM_PROMPT_EXAMPLES = 6
|
|
8
8
|
|
|
9
9
|
# Examples that should always be included in the prompt
|
|
10
10
|
fixed_examples = [
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
<{tp}status_update>To get the latest top story from Hacker News, I\'ll need to access the web. I\'m preparing to do that now.</{tp}status_update>
|
|
32
|
-
|
|
33
|
-
<{tp}invoke_action agent="global" action="change_agent_status">
|
|
34
|
-
<{tp}parameter name="agent_name">web_request</{tp}parameter>
|
|
35
|
-
<{tp}parameter name="new_state">open</{tp}parameter>
|
|
36
|
-
</{tp}invoke_action>
|
|
37
|
-
</example_response>
|
|
38
|
-
</example>
|
|
39
|
-
"""
|
|
11
|
+
{
|
|
12
|
+
"docstring": "This example shows a stimulus from a chatbot gateway in which a user is asking about the top stories on the website hacker news. The web_request is not yet open, so the change_agent_status action is invoked to open the web_request agent.",
|
|
13
|
+
"tag_prefix_placeholder": "{tp}",
|
|
14
|
+
"starting_id": "1",
|
|
15
|
+
"user_input": "What is the top story on hacker news?",
|
|
16
|
+
"metadata": ["local_time: 2024-11-06 15:58:04 EST-0500 (Wednesday)"],
|
|
17
|
+
"reasoning": [
|
|
18
|
+
"- User is asking for the top story on Hacker News",
|
|
19
|
+
"- We need to use the web_request agent to fetch the latest information",
|
|
20
|
+
"- The web_request agent is currently closed, so we need to open it first",
|
|
21
|
+
"- After opening the agent, we'll need to make a web request to Hacker News",
|
|
22
|
+
],
|
|
23
|
+
"response_text": "",
|
|
24
|
+
"status_update": "To get the latest top story from Hacker News, I'll need to access the web. I'm preparing to do that now.",
|
|
25
|
+
"action": {
|
|
26
|
+
"agent": "global",
|
|
27
|
+
"name": "change_agent_status",
|
|
28
|
+
"parameters": {"agent_name": "web_request", "new_state": "open"},
|
|
29
|
+
},
|
|
30
|
+
}
|
|
40
31
|
]
|
|
41
32
|
|
|
42
33
|
|
|
43
34
|
def get_file_handling_prompt(tp: str) -> str:
|
|
44
|
-
|
|
45
|
-
|
|
35
|
+
parameters_desc = ""
|
|
36
|
+
parameter_examples = ""
|
|
46
37
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
38
|
+
for transformer in TRANSFORMERS:
|
|
39
|
+
if transformer.description:
|
|
40
|
+
parameters_desc += "\n" + transformer.description.strip() + "\n"
|
|
50
41
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
42
|
+
if transformer.examples:
|
|
43
|
+
for example in transformer.examples:
|
|
44
|
+
parameter_examples += "\n" + example.strip() + "\n"
|
|
54
45
|
|
|
55
|
-
|
|
56
|
-
|
|
46
|
+
parameters_desc = "\n ".join(parameters_desc.split("\n"))
|
|
47
|
+
parameter_examples = "\n ".join(parameter_examples.split("\n"))
|
|
57
48
|
|
|
58
|
-
|
|
59
|
-
|
|
49
|
+
parameters_desc = parameters_desc.replace("{tp}", tp)
|
|
50
|
+
parameter_examples = parameter_examples.replace("{tp}", tp)
|
|
60
51
|
|
|
61
|
-
|
|
62
|
-
|
|
52
|
+
if parameter_examples:
|
|
53
|
+
parameter_examples = f"""
|
|
63
54
|
Here are some examples of how to use the query parameters:
|
|
64
55
|
{parameter_examples}"""
|
|
65
56
|
|
|
66
|
-
|
|
57
|
+
prompt = f"""
|
|
67
58
|
XML tags are used to represent files. The assistant will use the <{tp}file> tag to represent a file. The file tag has the following format:
|
|
68
59
|
<{tp}file name="filename" mime_type="mimetype" size="size in bytes">
|
|
69
60
|
<schema-yaml>...JSON schema, yaml format...</schema-yaml> (optional)
|
|
@@ -102,6 +93,7 @@ def get_file_handling_prompt(tp: str) -> str:
|
|
|
102
93
|
For all files, there's `encoding` parameter, this is used to encode the file format. The supported values are `datauri`, `base64`, `zip`, and `gzip`. Use this to convert a file to ZIP or datauri for HTML encoding.
|
|
103
94
|
For example (return a file as zip): `{FS_PROTOCOL}://c27e6908-55d5-4ce0-bc93-a8e28f84be12_annual_report.csv?encoding=zip&resolve=true`
|
|
104
95
|
Example 2 (HTML tag must be datauri encoded): `<img src="{FS_PROTOCOL}://c9183b0f-fd11-48b4-ad8f-df221bff3da9_generated_image.png?encoding=datauri&resolve=true" alt="image">`
|
|
96
|
+
Example 3 (return a regular image directly): `{FS_PROTOCOL}://a1b2c3d4-5e6f-7g8h-9i0j-1k2l3m4n5o6p_photo.png?resolve=true` - When returning images directly in messaging platforms like Slack, don't use any encoding parameter
|
|
105
97
|
|
|
106
98
|
For all files, there's `resolve=true` parameter, this is used to resolve the url to the actual data before processing.
|
|
107
99
|
For example: `{FS_PROTOCOL}://577c928c-c126-42d8-8a48-020b93096110_names.csv?resolve=true`
|
|
@@ -140,7 +132,7 @@ def get_file_handling_prompt(tp: str) -> str:
|
|
|
140
132
|
|
|
141
133
|
{parameter_examples}
|
|
142
134
|
"""
|
|
143
|
-
|
|
135
|
+
return prompt
|
|
144
136
|
|
|
145
137
|
|
|
146
138
|
def create_examples(
|
|
@@ -156,11 +148,15 @@ def create_examples(
|
|
|
156
148
|
String containing all examples with replaced placeholders
|
|
157
149
|
"""
|
|
158
150
|
examples = (fixed_examples + agent_examples)[:MAX_SYSTEM_PROMPT_EXAMPLES]
|
|
159
|
-
|
|
151
|
+
formatted_examples = format_examples_by_llm_type(examples)
|
|
152
|
+
|
|
153
|
+
return "\n".join([example.replace("{tp}", tp) for example in formatted_examples])
|
|
160
154
|
|
|
161
155
|
|
|
162
156
|
def SystemPrompt(info: Dict[str, Any], action_examples: List[str]) -> str:
|
|
163
|
-
|
|
157
|
+
tp = info["tag_prefix"]
|
|
158
|
+
response_format_prompt = info.get("response_format_prompt", "") or ""
|
|
159
|
+
response_format_prompt = response_format_prompt.replace("{{tag_prefix}}", tp)
|
|
164
160
|
response_guidelines_prompt = (
|
|
165
161
|
f"<response_guidelines>\nConsider the following when generating a response to the originator:\n"
|
|
166
162
|
f"{response_format_prompt}</response_guidelines>"
|
|
@@ -183,24 +179,23 @@ def SystemPrompt(info: Dict[str, Any], action_examples: List[str]) -> str:
|
|
|
183
179
|
)
|
|
184
180
|
|
|
185
181
|
# Merged
|
|
186
|
-
tp = info["tag_prefix"]
|
|
187
182
|
examples = create_examples(fixed_examples, action_examples, tp)
|
|
188
183
|
|
|
189
184
|
handling_files = get_file_handling_prompt(tp)
|
|
190
185
|
|
|
191
186
|
return f"""
|
|
192
|
-
Note to avoid unintended collisions, all tag names in the assistant response will start with the value {tp}
|
|
187
|
+
Note to avoid unintended collisions, all tag names in the assistant response will start with the value `{tp}`
|
|
193
188
|
<orchestrator_info>
|
|
194
189
|
You are an assistant serving as the orchestrator in an AI agentic system. Your primary functions are to:
|
|
195
190
|
1. Receive stimuli from external sources via the system Gateway
|
|
196
191
|
2. Invoke actions within system agents to address these stimuli
|
|
197
192
|
3. Formulate responses based on agent actions
|
|
198
193
|
|
|
199
|
-
This process is iterative,
|
|
194
|
+
This process is iterative, where the assistant is reinvoked at each step.
|
|
200
195
|
|
|
201
196
|
The Stimulus represents user or application requests.
|
|
202
197
|
|
|
203
|
-
The assistant receives a history of all gateway-orchestrator exchanges, excluding
|
|
198
|
+
The assistant receives a history of all gateway-orchestrator exchanges, excluding responses from agents' action invocations and reasoning. Don't use this as a guide for the responses.
|
|
204
199
|
|
|
205
200
|
The assistant's behavior aligns with the system purpose specified below:
|
|
206
201
|
<system_purpose>
|
|
@@ -214,19 +209,39 @@ The assistant's behavior aligns with the system purpose specified below:
|
|
|
214
209
|
3. After opening agents, the assistant will be reinvoked with an updated list of open agents and their actions.
|
|
215
210
|
4. When opening an agent, provide only a brief status update without detailed explanations.
|
|
216
211
|
5. Do not perform any other actions besides opening the required agents in this step.
|
|
212
|
+
6. All `{tp}` XML tags must be generated as raw text directly within the response stream, seamlessly integrated with any natural language text, as shown in the positive examples.
|
|
213
|
+
7. Crucially, `{tp}` directive tags must *never* be wrapped in markdown code fences (``` ```) of any kind, including ```xml.
|
|
217
214
|
- Report generation:
|
|
218
215
|
1. If a report is requested and no format is specified, create the report in an HTML file.
|
|
219
|
-
2. Generate each section of the report independently and store it in the file service with create_file action. When finishing the report, combine the sections using
|
|
220
|
-
|
|
216
|
+
2. Generate each section of the report independently and store it in the file service with create_file action. When finishing the report, combine the sections using {FS_PROTOCOL} urls with the resolve=true query parameter to insert the sections into the main document. When inserting {FS_PROTOCOL} HTML URLs into the HTML document, place them directly in the document without any surrounding tags or brackets. Here is an example of the body section of an HTML report combining multiple sections:
|
|
217
|
+
<body>
|
|
218
|
+
<!-- Title -->
|
|
219
|
+
<h1>Report Title</h1>
|
|
220
|
+
|
|
221
|
+
<!-- Section 1 -->
|
|
222
|
+
{FS_PROTOCOL}://xxxxxx.html?resolve=true
|
|
223
|
+
|
|
224
|
+
<!-- Section 2 -->
|
|
225
|
+
{FS_PROTOCOL}://yyyyyy.html?resolve=true
|
|
226
|
+
|
|
227
|
+
<!-- Section 3 -->
|
|
228
|
+
{FS_PROTOCOL}://zzzzzz.html?resolve=true
|
|
229
|
+
</body>
|
|
230
|
+
When generating HTML, create the header first with all the necessary CSS and JS links so that it is clear what css the rest of the document will use.
|
|
231
|
+
3. Images are always very useful in reports, so the assistant will add them when appropriate. If images are embedded in html, they must be resolved and converted to datauri format or they won't render in the final document. This can be done by using the encoding=datauri&resolve=true in the {FS_PROTOCOL} link. For example, <img src="{FS_PROTOCOL}://xxxxxx.png?encoding=datauri&resolve=true">. The assistant will take care of the rest. Images can be created in parallel
|
|
221
232
|
4. During report generation in interactive sessions, the assistant will send lots of status messages to indicate what is happening.
|
|
222
233
|
- Handling stimuli with open agents:
|
|
223
234
|
1. Use agents' actions to break down the stimulus into smaller, manageable tasks.
|
|
224
|
-
2.
|
|
225
|
-
3.
|
|
235
|
+
2. Invoke agents' actions to perform these tasks
|
|
236
|
+
3. After invoking an action with the invoke action directive, finish the response and wait for the action to complete.
|
|
237
|
+
4. The action will be run and the results will then be returned on a later step. NEVER guess or fill in the response without waiting for the action's response.
|
|
238
|
+
5. Prioritize using available actions to fulfill the stimulus whenever possible.
|
|
239
|
+
6. If no suitable agents or actions are available, the assistant will:
|
|
226
240
|
a) Use its own knowledge to respond, or
|
|
227
241
|
b) Ask the user for additional information, or
|
|
228
242
|
c) Inform the user that it cannot fulfill the request.
|
|
229
|
-
- The first user message contains the history of all exchanges between the gateway and the orchestrator before now. Note that this history list has removed all the
|
|
243
|
+
- The first user message contains the history of all exchanges between the gateway and the orchestrator before now. Note that this history list has removed all the agent's action invocation outputs and reasoning.
|
|
244
|
+
- Do not use the history as a guide for how to respond. That history is only present to provide some context to the coversation. The format and data have been modified and does not show all the assistant's directives.
|
|
230
245
|
- The assistant will not guess at an answer. No answer is better than a wrong answer.
|
|
231
246
|
- The assistant will invoke the actions and specify the parameters for each action, following the rules of the action. If there is not sufficient context to fill in an action parameter, the assistant will ask the user for more information.
|
|
232
247
|
- After invoking the actions, the assistant will end the response and wait for the action responses. It will not guess at the answers.
|
|
@@ -238,6 +253,7 @@ The assistant's behavior aligns with the system purpose specified below:
|
|
|
238
253
|
2. Within this tag, include:
|
|
239
254
|
a) A brief list of points describing the plan and thoughts.
|
|
240
255
|
b) A list of potential actions needed to fulfill the stimulus.
|
|
256
|
+
c) Always include a statement that the assistant will not follow invoke actions with any other output.
|
|
241
257
|
3. Ensure all content is contained within the <{tp}reasoning> tag.
|
|
242
258
|
4. Keep each point concise and focused.
|
|
243
259
|
- For large grouped output, such as a list of items or a big code block (> 10 lines), the assistant will create a file by surrounding the output with the tags <{tp}file name="filename" mime_type="mimetype"><data> the content </data></{tp}file>. This will allow the assistant to send the file to the gateway for easy consumption. This works well for a csv file, a code file or just a big text file.
|
|
@@ -247,6 +263,7 @@ The assistant's behavior aligns with the system purpose specified below:
|
|
|
247
263
|
- When the stimulus asks what the system can do, the assistant will open all the agents to see their details before creating a nicely formatted list describing the actions available and indicating that it can do normal chatbot things as well. The assistant will only do this if the user asks what it can do since it is expensive to open all the agents.
|
|
248
264
|
- The assistant is concise and professional in its responses. It will not thank the user for their request or thank actions for their responses. It will not provide any unnecessary information in its responses.
|
|
249
265
|
- The assistant will not follow invoke_actions with further comments or explanations
|
|
266
|
+
- After outputing the invoke action tags, the assistant will not add any additional text. It will just end the response always.
|
|
250
267
|
- The assistant will distinguish between normal text and status updates. All status updates will be enclosed in <{tp}status_update/> tags.
|
|
251
268
|
- Responses that are just letting the originator know that progress is being made or what the next step is should be status updates. They should be brief and to the point.
|
|
252
269
|
<action_rules>
|
|
@@ -330,12 +347,12 @@ def UserStimulusPrompt(
|
|
|
330
347
|
f"\tExample of returning a file as zip: <{info['tag_prefix']}file><url>{FS_PROTOCOL}://519321d8-3506-4f8d-9377-e5d6ce74d917_filename.csv?encoding=zip</url></{info['tag_prefix']}file>.\n"
|
|
331
348
|
f"You can also optionally return a non-persistent temporary file using the format <{info['tag_prefix']}file name=\"filename.csv\" mime_type=\"text/csv\">\n<data> data </data>\n</{info['tag_prefix']}file>.\n"
|
|
332
349
|
f" can't nest `<{info['tag_prefix']}file>` tags inside the `<data>` tag. If you need to address another file within the data, use the URL with the `resolve=true` query parameter.\n"
|
|
333
|
-
"When using a {FS_PROTOCOL} URL outside of a file block, always include the 'resolve=true' query parameter to ensure the URL is resolved to the actual data.\n"
|
|
350
|
+
f"When using a {FS_PROTOCOL} URL outside of a file block, always include the 'resolve=true' query parameter to ensure the URL is resolved to the actual data.\n"
|
|
334
351
|
f"</{info['tag_prefix']}file_instructions>"
|
|
335
352
|
)
|
|
336
353
|
|
|
337
354
|
prompt = (
|
|
338
|
-
"NOTE - this history represents the conversation as seen by the user on the other side of the gateway. It does not include the
|
|
355
|
+
"NOTE - this history represents the conversation as seen by the user on the other side of the gateway. It does not include the responses from invoked actions or reasoning. All of that has been removed, so don't use this history as an example for how the assistant should behave\n"
|
|
339
356
|
f"{gateway_history_str}\n"
|
|
340
357
|
f"<{info['tag_prefix']}stimulus>\n"
|
|
341
358
|
f"{stimulus}\n"
|
|
@@ -393,7 +410,7 @@ reputation is on the line.
|
|
|
393
410
|
"""
|
|
394
411
|
|
|
395
412
|
|
|
396
|
-
def ContextQueryPrompt(query: str, context: str) ->
|
|
413
|
+
def ContextQueryPrompt(query: str, context: str) -> str:
|
|
397
414
|
return f"""
|
|
398
415
|
You (orchestrator) are being asked to query, comment on or edit the following text following the originator's request.
|
|
399
416
|
Do your best to give a complete and accurate answer using only the context given below. Ensure that you
|
|
@@ -413,3 +430,110 @@ you should ask the originator for more information. Include links to the source
|
|
|
413
430
|
{context}
|
|
414
431
|
</context>
|
|
415
432
|
"""
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def format_examples_by_llm_type(examples: list, llm_type: str = "anthropic") -> list:
|
|
436
|
+
"""
|
|
437
|
+
Render examples based on llm type
|
|
438
|
+
|
|
439
|
+
Args:
|
|
440
|
+
llm_type (str): The type of LLM to render examples for (default: "anthropic")
|
|
441
|
+
examples (list): List of examples in model-agnostic format
|
|
442
|
+
|
|
443
|
+
Returns:
|
|
444
|
+
list: List of examples formatted for the specified LLM
|
|
445
|
+
"""
|
|
446
|
+
formatted_examples = []
|
|
447
|
+
|
|
448
|
+
if llm_type == "anthropic":
|
|
449
|
+
for example in examples:
|
|
450
|
+
formatted_example = format_example_for_anthropic(example)
|
|
451
|
+
formatted_examples.append(formatted_example)
|
|
452
|
+
else:
|
|
453
|
+
log.error(f"Unsupported LLM type: {llm_type}")
|
|
454
|
+
|
|
455
|
+
return formatted_examples
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
def format_example_for_anthropic(example: dict) -> str:
|
|
459
|
+
"""
|
|
460
|
+
Format an example for the Anthropic's LLMs
|
|
461
|
+
"""
|
|
462
|
+
|
|
463
|
+
tag_prefix = example.get("tag_prefix_placeholder", "t123")
|
|
464
|
+
starting_id = example.get("starting_id", "1")
|
|
465
|
+
docstring = example.get("docstring", "")
|
|
466
|
+
user_input = example.get("user_input", "")
|
|
467
|
+
metadata_lines = example.get("metadata", [])
|
|
468
|
+
reasoning_lines = example.get("reasoning", [])
|
|
469
|
+
response_text = example.get("response_text", "")
|
|
470
|
+
|
|
471
|
+
# Start building the XML structure, add the description and user input
|
|
472
|
+
xml_content = f"""<example>
|
|
473
|
+
<example_docstring>
|
|
474
|
+
{docstring}
|
|
475
|
+
</example_docstring>
|
|
476
|
+
<example_stimulus>
|
|
477
|
+
<{tag_prefix}stimulus starting_id="{starting_id}">
|
|
478
|
+
{user_input}
|
|
479
|
+
</{tag_prefix}stimulus>
|
|
480
|
+
<{tag_prefix}stimulus_metadata>
|
|
481
|
+
"""
|
|
482
|
+
|
|
483
|
+
# Add metadata lines
|
|
484
|
+
for metadata_line in metadata_lines:
|
|
485
|
+
xml_content += f"{metadata_line}\n"
|
|
486
|
+
|
|
487
|
+
xml_content += f"""</{tag_prefix}stimulus_metadata>
|
|
488
|
+
</example_stimulus>
|
|
489
|
+
<example_response>
|
|
490
|
+
<{tag_prefix}reasoning>
|
|
491
|
+
"""
|
|
492
|
+
|
|
493
|
+
# Add reasoning lines
|
|
494
|
+
for reasoning_line in reasoning_lines:
|
|
495
|
+
xml_content += f"{reasoning_line}\n"
|
|
496
|
+
|
|
497
|
+
xml_content += f"""</{tag_prefix}reasoning>
|
|
498
|
+
{response_text}"""
|
|
499
|
+
|
|
500
|
+
# Add action invocation section
|
|
501
|
+
if "action" in example:
|
|
502
|
+
action_data = example.get("action", {})
|
|
503
|
+
status_update = example.get("status_update", "")
|
|
504
|
+
agent_name = action_data.get("agent", "")
|
|
505
|
+
action_name = action_data.get("name", "")
|
|
506
|
+
|
|
507
|
+
xml_content += f"""
|
|
508
|
+
<{tag_prefix}status_update>{status_update}</{tag_prefix}status_update>
|
|
509
|
+
<{tag_prefix}invoke_action agent="{agent_name}" action="{action_name}">"""
|
|
510
|
+
|
|
511
|
+
# Handle parameters as dictionary
|
|
512
|
+
parameter_dict = action_data.get("parameters", {})
|
|
513
|
+
for param_name, param_value in parameter_dict.items():
|
|
514
|
+
xml_content += f"""
|
|
515
|
+
<{tag_prefix}parameter name="{param_name}">"""
|
|
516
|
+
|
|
517
|
+
# Handle parameter names and values (as lists)
|
|
518
|
+
if isinstance(param_value, list):
|
|
519
|
+
for line in param_value:
|
|
520
|
+
xml_content += f"\n{line}"
|
|
521
|
+
xml_content += "\n"
|
|
522
|
+
else:
|
|
523
|
+
# For simple string values
|
|
524
|
+
xml_content += f"{param_value}"
|
|
525
|
+
|
|
526
|
+
xml_content += f"</{tag_prefix}parameter>\n"
|
|
527
|
+
|
|
528
|
+
xml_content += f"</{tag_prefix}invoke_action>"
|
|
529
|
+
|
|
530
|
+
# Close the XML structure
|
|
531
|
+
xml_content += """
|
|
532
|
+
</example_response>
|
|
533
|
+
</example>
|
|
534
|
+
"""
|
|
535
|
+
|
|
536
|
+
return xml_content
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
LONG_TERM_MEMORY_PROMPT = " - You are capable of remembering things and have long-term memory, this happens automatically."
|
|
@@ -392,6 +392,11 @@ class FileService(AutoExpiry, metaclass=AutoExpirySingletonMeta):
|
|
|
392
392
|
url = url[5:-6].strip()
|
|
393
393
|
if url.endswith('"') or url.endswith("'") or url.endswith(","):
|
|
394
394
|
url = url[:-1]
|
|
395
|
+
if url.endswith(")"):
|
|
396
|
+
open_parenthesis_count = url.count("(")
|
|
397
|
+
close_parenthesis_count = url.count(")")
|
|
398
|
+
if close_parenthesis_count > open_parenthesis_count:
|
|
399
|
+
url = url[:-1]
|
|
395
400
|
return url
|
|
396
401
|
|
|
397
402
|
@staticmethod
|
|
@@ -40,7 +40,7 @@ BLOCK_TAG_KEYS = [
|
|
|
40
40
|
Keys to be treated as tags in the file block, and not file attributes.
|
|
41
41
|
"""
|
|
42
42
|
|
|
43
|
-
FS_URL_REGEX = r"""<url>\s*({protocol}:\/\/.+?)\s*<\/url>|{protocol}:\/\/[^\s\?]+(\s|$)|{protocol}:\/\/[^\n\?]+\?.*?(?=\n
|
|
43
|
+
FS_URL_REGEX = r"""<url>\s*({protocol}:\/\/.+?)\s*<\/url>|{protocol}:\/\/[^\s\?]+(\s|$)|{protocol}:\/\/[^\n\?]+\?.*?(?=\n|$|>)|\"({protocol}:\/\/[^\"]+?)(\"|\n)|'({protocol}:\/\/[^']+?)('|\n)""".format(
|
|
44
44
|
protocol=FS_PROTOCOL
|
|
45
45
|
)
|
|
46
46
|
"""
|
|
@@ -82,6 +82,8 @@ def apply_file_transformations(
|
|
|
82
82
|
return file
|
|
83
83
|
text_mime_type_regex = r"text/.*|.*csv|.*json|.*xml|.*yaml|.*x-yaml|.*txt"
|
|
84
84
|
mime_type = metadata.get("mime_type", "")
|
|
85
|
+
if mime_type is None:
|
|
86
|
+
mime_type = ""
|
|
85
87
|
name = metadata.get("name", "unknown")
|
|
86
88
|
other = {
|
|
87
89
|
"mime_type": mime_type,
|
|
@@ -126,6 +128,14 @@ def apply_file_transformations(
|
|
|
126
128
|
return file
|
|
127
129
|
|
|
128
130
|
if not isinstance(data, str):
|
|
129
|
-
|
|
131
|
+
# Convert bytes to string
|
|
132
|
+
if isinstance(data, bytes):
|
|
133
|
+
try:
|
|
134
|
+
data = data.decode("utf-8")
|
|
135
|
+
except UnicodeDecodeError:
|
|
136
|
+
data = base64.b64encode(data).decode("utf-8")
|
|
137
|
+
data = f"data:{mime_type};base64,{data}"
|
|
138
|
+
else:
|
|
139
|
+
data = json.dumps(data)
|
|
130
140
|
|
|
131
141
|
return data
|
|
@@ -1,79 +1,54 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
|
-
from typing import Union
|
|
3
2
|
|
|
4
3
|
class BaseHistoryProvider(ABC):
|
|
5
4
|
|
|
6
5
|
def __init__(self, config=None):
|
|
7
|
-
self.config = config
|
|
8
|
-
|
|
9
|
-
self.max_characters = self.config.get("max_characters")
|
|
10
|
-
self.enforce_alternate_message_roles = self.config.get("enforce_alternate_message_roles")
|
|
11
|
-
|
|
12
|
-
@abstractmethod
|
|
13
|
-
def store_history(self, session_id: str, role: str, content: Union[str, dict]):
|
|
14
|
-
"""
|
|
15
|
-
Store a new entry in the history.
|
|
16
|
-
|
|
17
|
-
:param session_id: The session identifier.
|
|
18
|
-
:param role: The role of the entry to be stored in the history.
|
|
19
|
-
:param content: The content of the entry to be stored in the history.
|
|
20
|
-
"""
|
|
21
|
-
raise NotImplementedError("Method not implemented")
|
|
22
|
-
|
|
6
|
+
self.config = config or {}
|
|
7
|
+
|
|
23
8
|
@abstractmethod
|
|
24
|
-
def
|
|
9
|
+
def get_all_sessions(self) -> list[str]:
|
|
25
10
|
"""
|
|
26
|
-
Retrieve
|
|
27
|
-
|
|
28
|
-
:param session_id: The session identifier.
|
|
29
|
-
:return: The complete history.
|
|
11
|
+
Retrieve all session identifiers.
|
|
30
12
|
"""
|
|
31
13
|
raise NotImplementedError("Method not implemented")
|
|
32
14
|
|
|
33
15
|
@abstractmethod
|
|
34
|
-
def
|
|
16
|
+
def get_session(self, session_id: str)->dict:
|
|
35
17
|
"""
|
|
36
|
-
|
|
18
|
+
Retrieve the session .
|
|
37
19
|
|
|
38
20
|
:param session_id: The session identifier.
|
|
39
|
-
:
|
|
21
|
+
:return: The session metadata.
|
|
40
22
|
"""
|
|
41
23
|
raise NotImplementedError("Method not implemented")
|
|
42
|
-
|
|
24
|
+
|
|
43
25
|
@abstractmethod
|
|
44
|
-
def
|
|
26
|
+
def delete_session(self, session_id: str):
|
|
45
27
|
"""
|
|
46
|
-
|
|
28
|
+
Delete the session.
|
|
47
29
|
|
|
48
30
|
:param session_id: The session identifier.
|
|
49
|
-
:return: The files for the session.
|
|
50
31
|
"""
|
|
51
32
|
raise NotImplementedError("Method not implemented")
|
|
52
33
|
|
|
53
34
|
@abstractmethod
|
|
54
|
-
def
|
|
35
|
+
def store_session(self, session_id: str, data: dict):
|
|
55
36
|
"""
|
|
56
|
-
|
|
37
|
+
Store the session metadata.
|
|
57
38
|
|
|
58
39
|
:param session_id: The session identifier.
|
|
59
|
-
:
|
|
40
|
+
:param data: The session data to be stored.
|
|
60
41
|
"""
|
|
61
42
|
raise NotImplementedError("Method not implemented")
|
|
43
|
+
|
|
62
44
|
|
|
63
|
-
|
|
64
|
-
def clear_history(self, session_id: str, keep_levels=0, clear_files=True):
|
|
65
|
-
"""
|
|
66
|
-
Clear the history and files, optionally keeping a specified number of recent entries.
|
|
67
|
-
|
|
68
|
-
:param session_id: The session identifier.
|
|
69
|
-
:param keep_levels: Number of most recent history entries to keep. Default is 0 (clear all).
|
|
70
|
-
:param clear_files: Whether to clear associated files. Default is True.
|
|
45
|
+
def update_session(self, session_id: str, data: dict):
|
|
71
46
|
"""
|
|
72
|
-
|
|
47
|
+
Update data in the store using the partial data provided.
|
|
73
48
|
|
|
74
|
-
|
|
75
|
-
|
|
49
|
+
:param key: The key to update the data under.
|
|
50
|
+
:param data: The data to update.
|
|
76
51
|
"""
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
52
|
+
history = self.get_session(session_id).copy()
|
|
53
|
+
history.update(data)
|
|
54
|
+
self.store_session(session_id, history)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
from .base_history_provider import BaseHistoryProvider
|
|
4
|
+
|
|
5
|
+
class FileHistoryProvider(BaseHistoryProvider):
|
|
6
|
+
"""
|
|
7
|
+
A simple file-based history provider for storing session data.
|
|
8
|
+
"""
|
|
9
|
+
def __init__(self, config=None):
|
|
10
|
+
super().__init__(config)
|
|
11
|
+
|
|
12
|
+
if not self.config.get("path"):
|
|
13
|
+
raise ValueError("Missing required configuration for FileHistoryProvider, Missing 'path' in configs.")
|
|
14
|
+
|
|
15
|
+
self.path = self.config.get("path")
|
|
16
|
+
|
|
17
|
+
if not self._exists(self.path):
|
|
18
|
+
os.makedirs(self.path, exist_ok=True)
|
|
19
|
+
|
|
20
|
+
def _get_key(self, session_id):
|
|
21
|
+
"""
|
|
22
|
+
Generate a file path for a session.
|
|
23
|
+
|
|
24
|
+
:param session_id: The session identifier.
|
|
25
|
+
:return: A formatted file path.
|
|
26
|
+
"""
|
|
27
|
+
return os.path.join(self.path, f"sessions_{session_id}_history.json")
|
|
28
|
+
|
|
29
|
+
def store_session(self, session_id: str, data: dict):
|
|
30
|
+
"""
|
|
31
|
+
Store the session metadata.
|
|
32
|
+
|
|
33
|
+
:param session_id: The session identifier.
|
|
34
|
+
:param data: The session data to be stored.
|
|
35
|
+
"""
|
|
36
|
+
file_path = self._get_key(session_id)
|
|
37
|
+
with open(file_path, "w", encoding="utf-8") as f:
|
|
38
|
+
f.write(json.dumps(data))
|
|
39
|
+
|
|
40
|
+
def get_session(self, session_id: str)->dict:
|
|
41
|
+
"""
|
|
42
|
+
Retrieve the session.
|
|
43
|
+
|
|
44
|
+
:param session_id: The session identifier.
|
|
45
|
+
:return: The session metadata as a dictionary.
|
|
46
|
+
"""
|
|
47
|
+
file_path = self._get_key(session_id)
|
|
48
|
+
if not self._exists(file_path):
|
|
49
|
+
return {}
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
|
53
|
+
return json.load(f)
|
|
54
|
+
except json.JSONDecodeError:
|
|
55
|
+
return {}
|
|
56
|
+
|
|
57
|
+
def get_all_sessions(self) -> list[str]:
|
|
58
|
+
"""
|
|
59
|
+
Retrieve all session identifiers.
|
|
60
|
+
"""
|
|
61
|
+
return [f[9:-13] for f in os.listdir(self.path) if f.startswith("sessions_") and f.endswith("_history.json")]
|
|
62
|
+
|
|
63
|
+
def delete_session(self, session_id: str):
|
|
64
|
+
"""
|
|
65
|
+
Delete the session.
|
|
66
|
+
|
|
67
|
+
:param session_id: The session identifier.
|
|
68
|
+
"""
|
|
69
|
+
file_path = self._get_key(session_id)
|
|
70
|
+
if self._exists(file_path):
|
|
71
|
+
os.remove(file_path)
|
|
72
|
+
|
|
73
|
+
def _exists(self, path: str):
|
|
74
|
+
return os.path.exists(path)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
class HistoryProviderFactory:
|
|
4
|
+
"""
|
|
5
|
+
Factory class for creating history provider instances.
|
|
6
|
+
"""
|
|
7
|
+
HISTORY_PROVIDERS = ["redis", "memory", "file", "mongodb", "sql"]
|
|
8
|
+
|
|
9
|
+
@staticmethod
|
|
10
|
+
def has_provider(class_name):
|
|
11
|
+
"""
|
|
12
|
+
Check if the history provider is supported.
|
|
13
|
+
"""
|
|
14
|
+
return class_name in HistoryProviderFactory.HISTORY_PROVIDERS
|
|
15
|
+
|
|
16
|
+
@staticmethod
|
|
17
|
+
def get_provider_class(class_name):
|
|
18
|
+
"""
|
|
19
|
+
Get the history provider class based on the class name.
|
|
20
|
+
"""
|
|
21
|
+
if class_name not in HistoryProviderFactory.HISTORY_PROVIDERS:
|
|
22
|
+
raise ValueError(f"Unsupported history provider: {class_name}")
|
|
23
|
+
if class_name == "redis":
|
|
24
|
+
from .redis_history_provider import RedisHistoryProvider
|
|
25
|
+
return RedisHistoryProvider
|
|
26
|
+
elif class_name == "memory":
|
|
27
|
+
from .memory_history_provider import MemoryHistoryProvider
|
|
28
|
+
return MemoryHistoryProvider
|
|
29
|
+
elif class_name == "file":
|
|
30
|
+
from .file_history_provider import FileHistoryProvider
|
|
31
|
+
return FileHistoryProvider
|
|
32
|
+
elif class_name == "mongodb":
|
|
33
|
+
from .mongodb_history_provider import MongoDBHistoryProvider
|
|
34
|
+
return MongoDBHistoryProvider
|
|
35
|
+
elif class_name == "sql":
|
|
36
|
+
from .sql_history_provider import SQLHistoryProvider
|
|
37
|
+
return SQLHistoryProvider
|
|
38
|
+
else:
|
|
39
|
+
raise ValueError(f"Unsupported history provider: {class_name}")
|
|
40
|
+
|