solace-agent-mesh 0.1.3__py3-none-any.whl → 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +38 -40
- 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/gateway.py +162 -9
- solace_agent_mesh/cli/commands/build.py +0 -1
- solace_agent_mesh/cli/commands/init/create_other_project_files_step.py +52 -1
- solace_agent_mesh/cli/commands/plugin/build.py +11 -2
- solace_agent_mesh/cli/config.py +4 -0
- solace_agent_mesh/cli/utils.py +7 -2
- solace_agent_mesh/common/constants.py +10 -0
- solace_agent_mesh/common/utils.py +16 -11
- 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_stimulus_processor_component.py +23 -5
- solace_agent_mesh/orchestrator/orchestrator_prompt.py +155 -35
- 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/web-default-config.yaml +2 -1
- {solace_agent_mesh-0.1.3.dist-info → solace_agent_mesh-0.2.0.dist-info}/METADATA +4 -3
- {solace_agent_mesh-0.1.3.dist-info → solace_agent_mesh-0.2.0.dist-info}/RECORD +45 -38
- {solace_agent_mesh-0.1.3.dist-info → solace_agent_mesh-0.2.0.dist-info}/WHEEL +0 -0
- {solace_agent_mesh-0.1.3.dist-info → solace_agent_mesh-0.2.0.dist-info}/entry_points.txt +0 -0
- {solace_agent_mesh-0.1.3.dist-info → solace_agent_mesh-0.2.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -3,15 +3,17 @@ from solace_ai_connector.common.log import log
|
|
|
3
3
|
from ...services.history_service import HistoryService
|
|
4
4
|
from ..identity.identity_provider import IdentityProvider
|
|
5
5
|
from ...common.constants import DEFAULT_IDENTITY_KEY_FIELD
|
|
6
|
+
from ...orchestrator.orchestrator_prompt import LONG_TERM_MEMORY_PROMPT
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
class GatewayBase(ComponentBase):
|
|
9
10
|
def __init__(self, info, **kwargs):
|
|
10
11
|
super().__init__(info, **kwargs)
|
|
11
12
|
self.gateway_id = self.get_config("gateway_id", "default-change-me")
|
|
13
|
+
self.system_purpose_prompt_suffix = ""
|
|
12
14
|
self.history_instance = self._initialize_history()
|
|
13
15
|
|
|
14
|
-
def _initialize_history(self):
|
|
16
|
+
def _initialize_history(self) -> HistoryService:
|
|
15
17
|
self.use_history = self.get_config("retain_history", True)
|
|
16
18
|
|
|
17
19
|
if not self.use_history:
|
|
@@ -19,6 +21,9 @@ class GatewayBase(ComponentBase):
|
|
|
19
21
|
|
|
20
22
|
history_config = self.get_config("history_config", {})
|
|
21
23
|
|
|
24
|
+
if history_config.get("enable_long_term_memory", False):
|
|
25
|
+
self.system_purpose_prompt_suffix = LONG_TERM_MEMORY_PROMPT
|
|
26
|
+
|
|
22
27
|
try:
|
|
23
28
|
return HistoryService(
|
|
24
29
|
history_config, identifier=self.gateway_id + "_history"
|
|
@@ -27,6 +32,7 @@ class GatewayBase(ComponentBase):
|
|
|
27
32
|
log.error("Failed to load history class: %s", e)
|
|
28
33
|
raise
|
|
29
34
|
|
|
35
|
+
|
|
30
36
|
def _initialize_identity_component(self):
|
|
31
37
|
identity_config = self.get_config("identity", {})
|
|
32
38
|
identity_key_field = self.get_config("identity_key_field", DEFAULT_IDENTITY_KEY_FIELD)
|
|
@@ -6,7 +6,7 @@ from uuid import uuid4
|
|
|
6
6
|
from solace_ai_connector.common.message import Message
|
|
7
7
|
from solace_ai_connector.common.log import log
|
|
8
8
|
from ...services.file_service import FileService
|
|
9
|
-
from ...common.constants import DEFAULT_IDENTITY_KEY_FIELD
|
|
9
|
+
from ...common.constants import DEFAULT_IDENTITY_KEY_FIELD, HISTORY_USER_ROLE
|
|
10
10
|
from .gateway_base import GatewayBase
|
|
11
11
|
|
|
12
12
|
info = {
|
|
@@ -130,7 +130,7 @@ class GatewayInput(GatewayBase):
|
|
|
130
130
|
|
|
131
131
|
def __init__(self, **kwargs):
|
|
132
132
|
super().__init__(info, **kwargs)
|
|
133
|
-
self.system_purpose = self.get_config("system_purpose", DEFAULT_SYSTEM_PURPOSE)
|
|
133
|
+
self.system_purpose = self.get_config("system_purpose", DEFAULT_SYSTEM_PURPOSE) + self.system_purpose_prompt_suffix
|
|
134
134
|
self.interaction_type = self.get_config(
|
|
135
135
|
"interaction_type", DEFAULT_INTERACTION_TYPE
|
|
136
136
|
)
|
|
@@ -209,17 +209,20 @@ class GatewayInput(GatewayBase):
|
|
|
209
209
|
|
|
210
210
|
copied_data["history"] = []
|
|
211
211
|
if self.use_history:
|
|
212
|
+
other_history_props = {
|
|
213
|
+
"identity": identity_value,
|
|
214
|
+
}
|
|
212
215
|
prompt = data.get("text", "")
|
|
213
|
-
self.history_instance.store_history(session_id,
|
|
216
|
+
self.history_instance.store_history(session_id, HISTORY_USER_ROLE, prompt, other_history_props)
|
|
214
217
|
|
|
215
218
|
for file in attached_files:
|
|
216
|
-
self.history_instance.store_file(session_id, file)
|
|
219
|
+
self.history_instance.store_file(session_id, file )
|
|
217
220
|
|
|
218
221
|
# retrieve all files for the session
|
|
219
222
|
available_files = self.history_instance.get_files(session_id)
|
|
220
223
|
|
|
221
224
|
# Add history to the data
|
|
222
|
-
copied_data["history"] = self.history_instance.get_history(session_id)
|
|
225
|
+
copied_data["history"] = self.history_instance.get_history(session_id, other_history_props)
|
|
223
226
|
|
|
224
227
|
available_files = json.dumps(available_files)
|
|
225
228
|
except Exception as e:
|
|
@@ -5,7 +5,7 @@ from solace_ai_connector.common.log import log
|
|
|
5
5
|
from .gateway_base import GatewayBase
|
|
6
6
|
from ...services.file_service import FileService
|
|
7
7
|
from ...common.utils import files_to_block_text
|
|
8
|
-
|
|
8
|
+
from ...common.constants import HISTORY_ASSISTANT_ROLE
|
|
9
9
|
|
|
10
10
|
info = {
|
|
11
11
|
"class_name": "GatewayOutput",
|
|
@@ -170,6 +170,7 @@ class GatewayOutput(GatewayBase):
|
|
|
170
170
|
file_service = FileService()
|
|
171
171
|
user_properties = message.get_user_properties()
|
|
172
172
|
session_id = user_properties.get("session_id")
|
|
173
|
+
identity_value = user_properties.get("identity")
|
|
173
174
|
files = data.get("files", [])
|
|
174
175
|
|
|
175
176
|
# Extract the interface queue ID
|
|
@@ -181,6 +182,10 @@ class GatewayOutput(GatewayBase):
|
|
|
181
182
|
)
|
|
182
183
|
|
|
183
184
|
if self.use_history and session_id:
|
|
185
|
+
other_history_props = {
|
|
186
|
+
"identity": identity_value,
|
|
187
|
+
}
|
|
188
|
+
|
|
184
189
|
topic = message.get_topic()
|
|
185
190
|
content = data.get("text") or ""
|
|
186
191
|
|
|
@@ -193,14 +198,18 @@ class GatewayOutput(GatewayBase):
|
|
|
193
198
|
and data.get("last_chunk")
|
|
194
199
|
and "text" in data
|
|
195
200
|
):
|
|
201
|
+
actions_called = user_properties.get("actions_called", [])
|
|
202
|
+
if actions_called:
|
|
203
|
+
self.history_instance.store_actions(session_id, actions_called)
|
|
204
|
+
|
|
196
205
|
if content:
|
|
197
206
|
self.history_instance.store_history(
|
|
198
|
-
session_id,
|
|
207
|
+
session_id, HISTORY_ASSISTANT_ROLE, content, other_history_props
|
|
199
208
|
)
|
|
200
209
|
|
|
201
210
|
for file in files:
|
|
202
211
|
self.history_instance.store_history(
|
|
203
|
-
session_id,
|
|
212
|
+
session_id, HISTORY_ASSISTANT_ROLE, f'\n[Returned file: {{name: {file.get("name")}, url: {file.get("url")}}}]\n', other_history_props
|
|
204
213
|
)
|
|
205
214
|
self.history_instance.store_file(session_id, file)
|
|
206
215
|
|
|
@@ -14,7 +14,7 @@ import yaml
|
|
|
14
14
|
from solace_ai_connector.common.log import log
|
|
15
15
|
from solace_ai_connector.common.message import Message
|
|
16
16
|
|
|
17
|
-
from ...common.constants import ORCHESTRATOR_COMPONENT_NAME
|
|
17
|
+
from ...common.constants import ORCHESTRATOR_COMPONENT_NAME, HISTORY_MEMORY_ROLE
|
|
18
18
|
from ...services.llm_service.components.llm_request_component import LLMRequestComponent, info as base_info
|
|
19
19
|
from ...services.middleware_service.middleware_service import MiddlewareService
|
|
20
20
|
from ...services.file_service import FileService
|
|
@@ -128,6 +128,18 @@ class OrchestratorStimulusProcessorComponent(LLMRequestComponent):
|
|
|
128
128
|
|
|
129
129
|
user_properties = message.get_user_properties()
|
|
130
130
|
user_properties['timestamp_end'] = time()
|
|
131
|
+
|
|
132
|
+
actions_called = []
|
|
133
|
+
if results:
|
|
134
|
+
for result in results:
|
|
135
|
+
if result.get("payload", {}).get("action_name"):
|
|
136
|
+
actions_called.append({
|
|
137
|
+
"agent_name": result.get("payload", {}).get("agent_name"),
|
|
138
|
+
"action_name": result.get("payload", {}).get("action_name"),
|
|
139
|
+
"action_params": result.get("payload", {}).get("action_params"),
|
|
140
|
+
})
|
|
141
|
+
user_properties['actions_called'] = actions_called
|
|
142
|
+
|
|
131
143
|
message.set_user_properties(user_properties)
|
|
132
144
|
|
|
133
145
|
return results
|
|
@@ -206,7 +218,7 @@ class OrchestratorStimulusProcessorComponent(LLMRequestComponent):
|
|
|
206
218
|
}
|
|
207
219
|
|
|
208
220
|
# Get the prompts
|
|
209
|
-
gateway_history = self.get_gateway_history(data)
|
|
221
|
+
gateway_history, memory_history = self.get_gateway_history(data)
|
|
210
222
|
system_prompt = SystemPrompt(full_input, examples)
|
|
211
223
|
if action_response_reinvoke:
|
|
212
224
|
user_prompt = ActionResponsePrompt(
|
|
@@ -217,6 +229,9 @@ class OrchestratorStimulusProcessorComponent(LLMRequestComponent):
|
|
|
217
229
|
user_prompt = UserStimulusPrompt(
|
|
218
230
|
full_input, gateway_history, errors, has_files
|
|
219
231
|
)
|
|
232
|
+
if memory_history:
|
|
233
|
+
self.history.store_history(stimulus_uuid, "system", memory_history)
|
|
234
|
+
|
|
220
235
|
|
|
221
236
|
# Store the user prompt in the history
|
|
222
237
|
self.history.store_history(stimulus_uuid, "user", user_prompt)
|
|
@@ -375,6 +390,9 @@ class OrchestratorStimulusProcessorComponent(LLMRequestComponent):
|
|
|
375
390
|
|
|
376
391
|
def get_gateway_history(self, data):
|
|
377
392
|
gateway_history = data.get("history", [])
|
|
393
|
+
memory_history = None
|
|
394
|
+
if gateway_history and gateway_history[0].get("role") == HISTORY_MEMORY_ROLE:
|
|
395
|
+
memory_history =gateway_history[0].get("content")
|
|
378
396
|
# Returning the history from the last user message
|
|
379
397
|
first_user_idx = None
|
|
380
398
|
last_user_idx = None
|
|
@@ -385,13 +403,13 @@ class OrchestratorStimulusProcessorComponent(LLMRequestComponent):
|
|
|
385
403
|
last_user_idx = idx
|
|
386
404
|
|
|
387
405
|
if first_user_idx is None:
|
|
388
|
-
return [] # No user query found
|
|
406
|
+
return [], memory_history # No user query found
|
|
389
407
|
|
|
390
408
|
if not last_user_idx > first_user_idx:
|
|
391
409
|
# Latest user message is already handled by orchestator history
|
|
392
|
-
return []
|
|
410
|
+
return [], memory_history
|
|
393
411
|
|
|
394
|
-
return gateway_history[first_user_idx:last_user_idx]
|
|
412
|
+
return gateway_history[first_user_idx:last_user_idx], memory_history
|
|
395
413
|
|
|
396
414
|
def get_user_input(self, chat_text):
|
|
397
415
|
|
|
@@ -2,42 +2,39 @@ from typing import Dict, Any, List
|
|
|
2
2
|
import yaml
|
|
3
3
|
from langchain_core.messages import HumanMessage
|
|
4
4
|
from ..services.file_service import FS_PROTOCOL, Types, LLM_QUERY_OPTIONS, TRANSFORMERS
|
|
5
|
+
from solace_ai_connector.common.log import log
|
|
5
6
|
|
|
6
7
|
# Cap the number of examples so we don't overwhelm the LLM
|
|
7
8
|
MAX_SYSTEM_PROMPT_EXAMPLES = 6
|
|
8
9
|
|
|
9
10
|
# Examples that should always be included in the prompt
|
|
10
11
|
fixed_examples = [
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
</example_response>
|
|
38
|
-
</example>
|
|
39
|
-
"""
|
|
40
|
-
]
|
|
12
|
+
{
|
|
13
|
+
"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.",
|
|
14
|
+
"tag_prefix_placeholder": "{tp}",
|
|
15
|
+
"starting_id": "1",
|
|
16
|
+
"user_input": "What is the top story on hacker news?",
|
|
17
|
+
"metadata": [
|
|
18
|
+
"local_time: 2024-11-06 15:58:04 EST-0500 (Wednesday)"
|
|
19
|
+
],
|
|
20
|
+
"reasoning": [
|
|
21
|
+
"- User is asking for the top story on Hacker News",
|
|
22
|
+
"- We need to use the web_request agent to fetch the latest information",
|
|
23
|
+
"- The web_request agent is currently closed, so we need to open it first",
|
|
24
|
+
"- After opening the agent, we'll need to make a web request to Hacker News"
|
|
25
|
+
],
|
|
26
|
+
"response_text": "",
|
|
27
|
+
"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.",
|
|
28
|
+
"action": {
|
|
29
|
+
"agent": "global",
|
|
30
|
+
"name": "change_agent_status",
|
|
31
|
+
"parameters": {
|
|
32
|
+
"agent_name": "web_request",
|
|
33
|
+
"new_state": "open"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
]
|
|
41
38
|
|
|
42
39
|
|
|
43
40
|
def get_file_handling_prompt(tp: str) -> str:
|
|
@@ -102,6 +99,7 @@ def get_file_handling_prompt(tp: str) -> str:
|
|
|
102
99
|
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
100
|
For example (return a file as zip): `{FS_PROTOCOL}://c27e6908-55d5-4ce0-bc93-a8e28f84be12_annual_report.csv?encoding=zip&resolve=true`
|
|
104
101
|
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">`
|
|
102
|
+
Example 3 (return a regular image directly): `amfs://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
103
|
|
|
106
104
|
For all files, there's `resolve=true` parameter, this is used to resolve the url to the actual data before processing.
|
|
107
105
|
For example: `{FS_PROTOCOL}://577c928c-c126-42d8-8a48-020b93096110_names.csv?resolve=true`
|
|
@@ -156,11 +154,15 @@ def create_examples(
|
|
|
156
154
|
String containing all examples with replaced placeholders
|
|
157
155
|
"""
|
|
158
156
|
examples = (fixed_examples + agent_examples)[:MAX_SYSTEM_PROMPT_EXAMPLES]
|
|
159
|
-
|
|
157
|
+
formatted_examples = format_examples_by_llm_type(examples)
|
|
158
|
+
|
|
159
|
+
return "\n".join([example.replace("{tp}", tp) for example in formatted_examples])
|
|
160
160
|
|
|
161
161
|
|
|
162
162
|
def SystemPrompt(info: Dict[str, Any], action_examples: List[str]) -> str:
|
|
163
|
-
|
|
163
|
+
tp = info["tag_prefix"]
|
|
164
|
+
response_format_prompt = info.get("response_format_prompt", "") or ""
|
|
165
|
+
response_format_prompt = response_format_prompt.replace("{{tag_prefix}}", tp)
|
|
164
166
|
response_guidelines_prompt = (
|
|
165
167
|
f"<response_guidelines>\nConsider the following when generating a response to the originator:\n"
|
|
166
168
|
f"{response_format_prompt}</response_guidelines>"
|
|
@@ -183,7 +185,6 @@ def SystemPrompt(info: Dict[str, Any], action_examples: List[str]) -> str:
|
|
|
183
185
|
)
|
|
184
186
|
|
|
185
187
|
# Merged
|
|
186
|
-
tp = info["tag_prefix"]
|
|
187
188
|
examples = create_examples(fixed_examples, action_examples, tp)
|
|
188
189
|
|
|
189
190
|
handling_files = get_file_handling_prompt(tp)
|
|
@@ -216,7 +217,21 @@ The assistant's behavior aligns with the system purpose specified below:
|
|
|
216
217
|
5. Do not perform any other actions besides opening the required agents in this step.
|
|
217
218
|
- Report generation:
|
|
218
219
|
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 amfs urls with the resolve=true query parameter to insert the sections into the main document. When
|
|
220
|
+
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 amfs urls with the resolve=true query parameter to insert the sections into the main document. When inserting amfs 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:
|
|
221
|
+
<body>
|
|
222
|
+
<!-- Title -->
|
|
223
|
+
<h1>Report Title</h1>
|
|
224
|
+
|
|
225
|
+
<!-- Section 1 -->
|
|
226
|
+
amfs://xxxxxx.html?resolve=true
|
|
227
|
+
|
|
228
|
+
<!-- Section 2 -->
|
|
229
|
+
amfs://yyyyyy.html?resolve=true
|
|
230
|
+
|
|
231
|
+
<!-- Section 3 -->
|
|
232
|
+
amfs://zzzzzz.html?resolve=true
|
|
233
|
+
</body>
|
|
234
|
+
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.
|
|
220
235
|
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 amfs link. For example, <img src="amfs://xxxxxx.png?encoding=datauri&resolve=true". The assistant will take care of the rest. Images can be created in parallel
|
|
221
236
|
4. During report generation in interactive sessions, the assistant will send lots of status messages to indicate what is happening.
|
|
222
237
|
- Handling stimuli with open agents:
|
|
@@ -330,7 +345,7 @@ def UserStimulusPrompt(
|
|
|
330
345
|
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
346
|
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
347
|
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"
|
|
348
|
+
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
349
|
f"</{info['tag_prefix']}file_instructions>"
|
|
335
350
|
)
|
|
336
351
|
|
|
@@ -413,3 +428,108 @@ you should ask the originator for more information. Include links to the source
|
|
|
413
428
|
{context}
|
|
414
429
|
</context>
|
|
415
430
|
"""
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
def format_examples_by_llm_type(examples: list, llm_type: str = "anthropic") -> list:
|
|
434
|
+
"""
|
|
435
|
+
Render examples based on llm type
|
|
436
|
+
|
|
437
|
+
Args:
|
|
438
|
+
llm_type (str): The type of LLM to render examples for (default: "anthropic")
|
|
439
|
+
examples (list): List of examples in model-agnostic format
|
|
440
|
+
|
|
441
|
+
Returns:
|
|
442
|
+
list: List of examples formatted for the specified LLM
|
|
443
|
+
"""
|
|
444
|
+
formatted_examples = []
|
|
445
|
+
|
|
446
|
+
if llm_type == "anthropic":
|
|
447
|
+
for example in examples:
|
|
448
|
+
formatted_example = format_example_for_anthropic(example)
|
|
449
|
+
formatted_examples.append(formatted_example)
|
|
450
|
+
else:
|
|
451
|
+
log.error(f"Unsupported LLM type: {llm_type}")
|
|
452
|
+
|
|
453
|
+
return formatted_examples
|
|
454
|
+
|
|
455
|
+
def format_example_for_anthropic(example: dict) -> str:
|
|
456
|
+
"""
|
|
457
|
+
Format an example for the Anthropic's LLMs
|
|
458
|
+
"""
|
|
459
|
+
|
|
460
|
+
tag_prefix = example.get("tag_prefix_placeholder", "t123")
|
|
461
|
+
starting_id = example.get("starting_id", "1")
|
|
462
|
+
docstring = example.get("docstring", "")
|
|
463
|
+
user_input = example.get("user_input", "")
|
|
464
|
+
metadata_lines = example.get("metadata", [])
|
|
465
|
+
reasoning_lines = example.get("reasoning", [])
|
|
466
|
+
response_text = example.get("response_text", "")
|
|
467
|
+
|
|
468
|
+
# Start building the XML structure, add the description and user input
|
|
469
|
+
xml_content = f"""<example>
|
|
470
|
+
<example_docstring>
|
|
471
|
+
{docstring}
|
|
472
|
+
</example_docstring>
|
|
473
|
+
<example_stimulus>
|
|
474
|
+
<{tag_prefix}stimulus starting_id="{starting_id}">
|
|
475
|
+
{user_input}
|
|
476
|
+
</{tag_prefix}stimulus>
|
|
477
|
+
<{tag_prefix}stimulus_metadata>
|
|
478
|
+
"""
|
|
479
|
+
|
|
480
|
+
# Add metadata lines
|
|
481
|
+
for metadata_line in metadata_lines:
|
|
482
|
+
xml_content += f"{metadata_line}\n"
|
|
483
|
+
|
|
484
|
+
xml_content += f"""</{tag_prefix}stimulus_metadata>
|
|
485
|
+
</example_stimulus>
|
|
486
|
+
<example_response>
|
|
487
|
+
<{tag_prefix}reasoning>
|
|
488
|
+
"""
|
|
489
|
+
|
|
490
|
+
# Add reasoning lines
|
|
491
|
+
for reasoning_line in reasoning_lines:
|
|
492
|
+
xml_content += f"{reasoning_line}\n"
|
|
493
|
+
|
|
494
|
+
xml_content += f"""</{tag_prefix}reasoning>
|
|
495
|
+
{response_text}"""
|
|
496
|
+
|
|
497
|
+
# Add action invocation section
|
|
498
|
+
if "action" in example:
|
|
499
|
+
action_data = example.get("action", {})
|
|
500
|
+
status_update = example.get("status_update", "")
|
|
501
|
+
agent_name = action_data.get("agent", "")
|
|
502
|
+
action_name = action_data.get("name", "")
|
|
503
|
+
|
|
504
|
+
xml_content += f"""
|
|
505
|
+
<{tag_prefix}status_update>{status_update}</{tag_prefix}status_update>
|
|
506
|
+
<{tag_prefix}invoke_action agent="{agent_name}" action="{action_name}">"""
|
|
507
|
+
|
|
508
|
+
# Handle parameters as dictionary
|
|
509
|
+
parameter_dict = action_data.get("parameters", {})
|
|
510
|
+
for param_name, param_value in parameter_dict.items():
|
|
511
|
+
xml_content += f"""
|
|
512
|
+
<{tag_prefix}parameter name="{param_name}">"""
|
|
513
|
+
|
|
514
|
+
# Handle parameter names and values (as lists)
|
|
515
|
+
if isinstance(param_value, list):
|
|
516
|
+
for line in param_value:
|
|
517
|
+
xml_content += f"\n{line}"
|
|
518
|
+
xml_content += "\n"
|
|
519
|
+
else:
|
|
520
|
+
# For simple string values
|
|
521
|
+
xml_content += f"{param_value}"
|
|
522
|
+
|
|
523
|
+
xml_content += f"</{tag_prefix}parameter>\n"
|
|
524
|
+
|
|
525
|
+
xml_content += f"</{tag_prefix}invoke_action>"
|
|
526
|
+
|
|
527
|
+
# Close the XML structure
|
|
528
|
+
xml_content += """
|
|
529
|
+
</example_response>
|
|
530
|
+
</example>
|
|
531
|
+
"""
|
|
532
|
+
|
|
533
|
+
return xml_content
|
|
534
|
+
|
|
535
|
+
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)
|