@aj-archipelago/cortex 1.3.40 → 1.3.42
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.
- package/config.js +1 -1
- package/helper-apps/cortex-browser/.funcignore +8 -0
- package/helper-apps/cortex-browser/Dockerfile +52 -0
- package/helper-apps/cortex-browser/function_app.py +181 -0
- package/helper-apps/cortex-browser/host.json +15 -0
- package/helper-apps/cortex-browser/requirements.txt +24 -0
- package/lib/requestExecutor.js +8 -2
- package/package.json +1 -1
- package/pathways/chat_title.js +1 -1
- package/pathways/system/entity/sys_entity_agent.js +15 -19
- package/pathways/system/entity/sys_get_entities.js +1 -0
- package/pathways/system/entity/tools/sys_tool_bing_search.js +8 -1
- package/pathways/system/entity/tools/sys_tool_callmodel.js +1 -1
- package/pathways/system/entity/tools/sys_tool_coding.js +4 -1
- package/pathways/system/entity/tools/sys_tool_codingagent.js +2 -2
- package/pathways/system/entity/tools/sys_tool_cognitive_search.js +2 -2
- package/pathways/system/entity/tools/sys_tool_image.js +1 -1
- package/pathways/system/entity/tools/sys_tool_readfile.js +3 -0
- package/pathways/system/entity/tools/sys_tool_reasoning.js +4 -1
- package/pathways/system/entity/tools/sys_tool_remember.js +3 -0
- package/pathways/system/entity/tools/sys_tool_verify.js +63 -0
- package/server/modelExecutor.js +9 -1
- package/server/plugins/azureBingPlugin.js +1 -1
- package/server/plugins/modelPlugin.js +4 -1
- package/server/plugins/openAiReasoningPlugin.js +2 -0
- package/server/plugins/openAiReasoningVisionPlugin.js +3 -2
- package/server/plugins/openAiVisionPlugin.js +22 -26
- package/server/plugins/openAiWhisperPlugin.js +7 -0
package/config.js
CHANGED
|
@@ -118,7 +118,7 @@ var config = convict({
|
|
|
118
118
|
default: {
|
|
119
119
|
AI_MEMORY: `<SHORT_TERM_MEMORY>\n<SELF>\n{{{memorySelf}}}\n</SELF>\n<USER>\n{{{memoryUser}}}\n</USER>\n<DIRECTIVES>\n{{{memoryDirectives}}}\n</DIRECTIVES>\n<TOPICS>\n{{{memoryTopics}}}\n</TOPICS>\n</SHORT_TERM_MEMORY>`,
|
|
120
120
|
AI_MEMORY_INSTRUCTIONS: "You have persistent memories of important details, instructions, and context - consult your memories when formulating a response to make sure you're applying your learnings.\nIf you don't see relevant information in your short term memory, you should use your SearchMemory tool to search your long term memory for details.\nAlso included in your memories are some details about the user to help you personalize your responses.\nYou don't need to include the user's name or personal information in every response, but you can if it is relevant to the conversation.\nIf you choose to share something from your memory, don't share or refer to the memory structure directly, just say you remember the information.\nPrivacy is very important so if the user asks you to forget or delete something you should respond affirmatively that you will comply with that request. If there is user information in your memories you have talked to this user before.",
|
|
121
|
-
AI_TOOLS: "You
|
|
121
|
+
AI_TOOLS: "You can execute tools in a loop agentically - you will have a chance to evaluate every tool response before deciding what action to take next - there is no time or execution limit. You have access to a powerful set of tools to help accomplish tasks and deliver the best responses. Instructions for tool use:\n\n1. Always dig deep, verify and cross-check:\n- Take your time and use tools as many times as needed to ensure truth, accuracy, depth, and completeness.\n- Leverage both parallel and sequential tool calls for thorough investigation: start broadly, then dive deeper on leads, cross-check facts, and synthesize findings before responding.\n\n2. Plan carefully:\n- Carefully review all available tools before responding.\n- For complex or investigative tasks, use the planning tool first to break the goal into clear steps.\n- Select the most appropriate tool(s) for each step—think beyond single searches to multi-pass, multi-source discovery.\n\n3. Always dive deeper and use as many of your tools as apply:\n- Proactively use tools to refine, verify, and expand on initial findings—don’t settle for the first result if more depth or confirmation may help.\n- Always verify tool capabilities before concluding something can’t be done.\n- If a user explicitly requests tool usage, comply.\n- Maintain context across tool calls to ensure continuity and coherence.\n- If a tool fails, try alternatives or creative approaches.\n\n4. Common Use Cases:\n- Research: Explore multiple sources and perspectives to build a complete picture.\n- Analysis: Use tools to process, compare, and critically assess data or content.\n- Generation: Employ tools for creating content, visuals, or code as needed.\n- Verification: Prioritize cross-checking and fact validation, especially for claims or evolving news.\n\n5. Reflect and Personalize:\n- Synthesize findings into concise, relevant, and personalized responses.\n- If user preferences or past feedback are available, tailor responses accordingly.\n- Before finalizing, review your answer for clarity, completeness, and alignment with user expectations.\n- If you see a recent <VERIFICATION_PLAN> from a tool call, you MUST follow it step by step before giving your final response.\n\nRemember: Your responsibility is to provide the most helpful, well-reasoned, and accurate responses possible. Use tools iteratively and reflectively—don't hesitate to dig deeper or double-check when it improves response quality!",
|
|
122
122
|
AI_DIRECTIVES: `These are your directives and learned behaviors:\n<DIRECTIVES>\n{{{memoryDirectives}}}\n</DIRECTIVES>`,
|
|
123
123
|
AI_CONVERSATION_HISTORY: "<CONVERSATION_HISTORY>\n{{{toJSON chatHistory}}}\n</CONVERSATION_HISTORY>",
|
|
124
124
|
AI_COMMON_INSTRUCTIONS: "{{#if voiceResponse}}{{renderTemplate AI_COMMON_INSTRUCTIONS_VOICE}}{{/if}}{{^if voiceResponse}}{{renderTemplate AI_COMMON_INSTRUCTIONS_MARKDOWN}}{{/if}}",
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Use an official Python runtime as a parent image suitable for Azure Functions
|
|
2
|
+
FROM mcr.microsoft.com/azure-functions/python:4-python3.9
|
|
3
|
+
|
|
4
|
+
# Set environment variables for Azure Functions runtime
|
|
5
|
+
ENV AzureWebJobsScriptRoot=/home/site/wwwroot
|
|
6
|
+
ENV AzureFunctionsJobHost__Logging__Console__IsEnabled=true
|
|
7
|
+
|
|
8
|
+
# Install OS dependencies needed by Playwright browsers (Debian-based)
|
|
9
|
+
# This list is based on Playwright documentation and common needs
|
|
10
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
11
|
+
libnss3 \
|
|
12
|
+
libnspr4 \
|
|
13
|
+
libdbus-glib-1-2 \
|
|
14
|
+
libatk1.0-0 \
|
|
15
|
+
libatk-bridge2.0-0 \
|
|
16
|
+
libcups2 \
|
|
17
|
+
libdrm2 \
|
|
18
|
+
libexpat1 \
|
|
19
|
+
libgbm1 \
|
|
20
|
+
libpango-1.0-0 \
|
|
21
|
+
libx11-6 \
|
|
22
|
+
libxcb1 \
|
|
23
|
+
libxcomposite1 \
|
|
24
|
+
libxdamage1 \
|
|
25
|
+
libxext6 \
|
|
26
|
+
libxfixes3 \
|
|
27
|
+
libxrandr2 \
|
|
28
|
+
libxrender1 \
|
|
29
|
+
libxtst6 \
|
|
30
|
+
lsb-release \
|
|
31
|
+
wget \
|
|
32
|
+
xvfb \
|
|
33
|
+
# Clean up apt cache
|
|
34
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
35
|
+
|
|
36
|
+
# Copy requirements file first to leverage Docker cache
|
|
37
|
+
COPY requirements.txt /tmp/
|
|
38
|
+
WORKDIR /tmp
|
|
39
|
+
|
|
40
|
+
# Install Python dependencies
|
|
41
|
+
RUN pip install --no-cache-dir -r requirements.txt
|
|
42
|
+
|
|
43
|
+
# Install Playwright browsers and their dependencies within the container
|
|
44
|
+
# Using --with-deps helps install system dependencies needed by the browsers
|
|
45
|
+
# Installing only chromium as it's specified in the code
|
|
46
|
+
RUN playwright install --with-deps chromium
|
|
47
|
+
|
|
48
|
+
# Copy the function app code to the final location
|
|
49
|
+
COPY . /home/site/wwwroot
|
|
50
|
+
|
|
51
|
+
# Set the working directory for the function app
|
|
52
|
+
WORKDIR /home/site/wwwroot
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import azure.functions as func
|
|
2
|
+
import logging
|
|
3
|
+
import json
|
|
4
|
+
from playwright.sync_api import sync_playwright
|
|
5
|
+
import trafilatura
|
|
6
|
+
import base64
|
|
7
|
+
|
|
8
|
+
app = func.FunctionApp(http_auth_level=func.AuthLevel.ANONYMOUS)
|
|
9
|
+
|
|
10
|
+
def scrape_and_screenshot(url: str, should_screenshot: bool = True) -> dict:
|
|
11
|
+
"""Scrapes text and takes a screenshot of a given URL, attempting to reject cookies."""
|
|
12
|
+
screenshot_bytes = None
|
|
13
|
+
html_content = None
|
|
14
|
+
extracted_text = None
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
with sync_playwright() as p:
|
|
18
|
+
browser = p.chromium.launch(headless=True)
|
|
19
|
+
try:
|
|
20
|
+
context = browser.new_context()
|
|
21
|
+
page = context.new_page()
|
|
22
|
+
page.goto(url, wait_until='load', timeout=60000) # Increased timeout
|
|
23
|
+
|
|
24
|
+
# --- Attempt to reject cookies ---
|
|
25
|
+
# Add more selectors here if needed for different sites
|
|
26
|
+
reject_selectors = [
|
|
27
|
+
"button:has-text('Reject All')",
|
|
28
|
+
"button:has-text('Decline')",
|
|
29
|
+
"button:has-text('Only necessary')",
|
|
30
|
+
"button:has-text('Tümünü Reddet')", # From your example
|
|
31
|
+
"button:has-text('Reject')",
|
|
32
|
+
"[aria-label*='Reject']", # Common aria labels
|
|
33
|
+
"[id*='reject']",
|
|
34
|
+
"[class*='reject']",
|
|
35
|
+
# Add more specific selectors based on common banner frameworks if known
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
cookie_banner_found = False
|
|
39
|
+
for selector in reject_selectors:
|
|
40
|
+
try:
|
|
41
|
+
# Wait briefly for the banner element to appear
|
|
42
|
+
reject_button = page.locator(selector).first
|
|
43
|
+
if reject_button.is_visible(timeout=2000): # Wait up to 2 seconds
|
|
44
|
+
logging.info(f"Found potential cookie reject button with selector: {selector}")
|
|
45
|
+
reject_button.click(timeout=5000) # Click with a timeout
|
|
46
|
+
logging.info("Clicked cookie reject button.")
|
|
47
|
+
# Wait a tiny bit for the banner to disappear/page to settle
|
|
48
|
+
page.wait_for_timeout(500)
|
|
49
|
+
cookie_banner_found = True
|
|
50
|
+
break # Stop searching once one is clicked
|
|
51
|
+
except Exception as e:
|
|
52
|
+
# Ignore timeout errors if the element doesn't appear or other exceptions
|
|
53
|
+
# logging.debug(f"Cookie reject selector '{selector}' not found or failed: {e}")
|
|
54
|
+
pass # Try the next selector
|
|
55
|
+
|
|
56
|
+
if not cookie_banner_found:
|
|
57
|
+
logging.info("No common cookie reject button found or clicked.")
|
|
58
|
+
# ---------------------------------
|
|
59
|
+
|
|
60
|
+
html_content = page.content()
|
|
61
|
+
# Take FULL page screenshot before closing
|
|
62
|
+
if should_screenshot:
|
|
63
|
+
screenshot_bytes = page.screenshot(full_page=True) # Added full_page=True
|
|
64
|
+
finally:
|
|
65
|
+
browser.close()
|
|
66
|
+
except Exception as e:
|
|
67
|
+
logging.error(f"Playwright error accessing {url}: {e}")
|
|
68
|
+
return {"url": url, "error": f"Playwright error: {e}"}
|
|
69
|
+
|
|
70
|
+
if html_content:
|
|
71
|
+
try:
|
|
72
|
+
extracted_text = trafilatura.extract(html_content, include_comments=False)
|
|
73
|
+
except Exception as e:
|
|
74
|
+
logging.error(f"Trafilatura error processing {url}: {e}")
|
|
75
|
+
# Still return screenshot if Playwright succeeded
|
|
76
|
+
extracted_text = f"Trafilatura extraction failed: {e}"
|
|
77
|
+
|
|
78
|
+
screenshot_base64 = base64.b64encode(screenshot_bytes).decode('utf-8') if screenshot_bytes else None
|
|
79
|
+
|
|
80
|
+
response_data = {
|
|
81
|
+
"url": url,
|
|
82
|
+
"text": extracted_text or "",
|
|
83
|
+
}
|
|
84
|
+
if screenshot_base64:
|
|
85
|
+
response_data["screenshot_base64"] = screenshot_base64
|
|
86
|
+
|
|
87
|
+
return response_data
|
|
88
|
+
|
|
89
|
+
@app.route(route="scrape") # Changed route name
|
|
90
|
+
def http_scrape_trigger(req: func.HttpRequest) -> func.HttpResponse:
|
|
91
|
+
logging.info('Python HTTP scrape trigger function processed a request.')
|
|
92
|
+
|
|
93
|
+
url = None
|
|
94
|
+
take_screenshot = True # Default value
|
|
95
|
+
|
|
96
|
+
# 1. Try getting parameters from query string first
|
|
97
|
+
try:
|
|
98
|
+
url = req.params.get('url')
|
|
99
|
+
if url:
|
|
100
|
+
logging.info(f"Found URL in query parameters: {url}")
|
|
101
|
+
# Handle take_screenshot from query params
|
|
102
|
+
ss_param = req.params.get('take_screenshot', 'true') # Query params are strings
|
|
103
|
+
take_screenshot = ss_param.lower() != 'false'
|
|
104
|
+
else:
|
|
105
|
+
logging.info("URL not found in query parameters.")
|
|
106
|
+
except Exception as e:
|
|
107
|
+
# This shouldn't generally happen with req.params, but good practice
|
|
108
|
+
logging.warning(f"Error reading query parameters: {e}")
|
|
109
|
+
url = None # Ensure url is None if error occurs here
|
|
110
|
+
|
|
111
|
+
# 2. If URL not found in query, try getting from JSON body
|
|
112
|
+
if not url:
|
|
113
|
+
logging.info("Attempting to read URL from JSON body.")
|
|
114
|
+
try:
|
|
115
|
+
req_body = req.get_json()
|
|
116
|
+
if req_body:
|
|
117
|
+
url = req_body.get('url')
|
|
118
|
+
if url:
|
|
119
|
+
logging.info(f"Found URL in JSON body: {url}")
|
|
120
|
+
# Handle take_screenshot from JSON body
|
|
121
|
+
ss_param = req_body.get('take_screenshot', True)
|
|
122
|
+
if isinstance(ss_param, str):
|
|
123
|
+
take_screenshot = ss_param.lower() != 'false'
|
|
124
|
+
else:
|
|
125
|
+
take_screenshot = bool(ss_param) # Convert other types
|
|
126
|
+
logging.info(f"Screenshot parameter from JSON: {take_screenshot}")
|
|
127
|
+
else:
|
|
128
|
+
logging.info("URL key not found in JSON body.")
|
|
129
|
+
else:
|
|
130
|
+
logging.info("JSON body is empty.")
|
|
131
|
+
except ValueError:
|
|
132
|
+
logging.info("Request body is not valid JSON or missing.")
|
|
133
|
+
# url remains None
|
|
134
|
+
except Exception as e:
|
|
135
|
+
logging.warning(f"Error reading JSON body: {e}")
|
|
136
|
+
url = None # Ensure url is None if error occurs here
|
|
137
|
+
|
|
138
|
+
# 3. Process the request if URL was found
|
|
139
|
+
if url:
|
|
140
|
+
try:
|
|
141
|
+
# Validate URL basic structure (optional but recommended)
|
|
142
|
+
if not url.startswith(('http://', 'https://')):
|
|
143
|
+
raise ValueError("Invalid URL format. Must start with http:// or https://")
|
|
144
|
+
|
|
145
|
+
result_data = scrape_and_screenshot(url, should_screenshot=take_screenshot) # Pass the flag
|
|
146
|
+
return func.HttpResponse(
|
|
147
|
+
json.dumps(result_data),
|
|
148
|
+
mimetype="application/json",
|
|
149
|
+
status_code=200
|
|
150
|
+
)
|
|
151
|
+
except ValueError as ve:
|
|
152
|
+
logging.error(f"Invalid URL provided: {ve}")
|
|
153
|
+
return func.HttpResponse(
|
|
154
|
+
json.dumps({"error": str(ve)}),
|
|
155
|
+
mimetype="application/json",
|
|
156
|
+
status_code=400
|
|
157
|
+
)
|
|
158
|
+
except Exception as e:
|
|
159
|
+
logging.error(f"Error processing scrape request for {url}: {e}")
|
|
160
|
+
return func.HttpResponse(
|
|
161
|
+
json.dumps({"error": f"An internal error occurred: {e}"}),
|
|
162
|
+
mimetype="application/json",
|
|
163
|
+
status_code=500
|
|
164
|
+
)
|
|
165
|
+
else:
|
|
166
|
+
logging.warning("URL not provided in request body or query string.")
|
|
167
|
+
return func.HttpResponse(
|
|
168
|
+
json.dumps({"error": "Please pass a 'url' in the JSON request body or query string"}),
|
|
169
|
+
mimetype="application/json",
|
|
170
|
+
status_code=400
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
# Keep this if you might have other triggers, otherwise it can be removed
|
|
174
|
+
# if the scrape trigger is the only one.
|
|
175
|
+
# Example of another potential trigger (e.g., timer)
|
|
176
|
+
# @app.timer_trigger(schedule="0 */5 * * * *", arg_name="myTimer", run_on_startup=True,
|
|
177
|
+
# use_monitor=False)
|
|
178
|
+
# def timer_trigger_handler(myTimer: func.TimerRequest) -> None:
|
|
179
|
+
# if myTimer.past_due:
|
|
180
|
+
# logging.info('The timer is past due!')
|
|
181
|
+
# logging.info('Python timer trigger function executed.')
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "2.0",
|
|
3
|
+
"logging": {
|
|
4
|
+
"applicationInsights": {
|
|
5
|
+
"samplingSettings": {
|
|
6
|
+
"isEnabled": true,
|
|
7
|
+
"excludedTypes": "Request"
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
"extensionBundle": {
|
|
12
|
+
"id": "Microsoft.Azure.Functions.ExtensionBundle",
|
|
13
|
+
"version": "[4.*, 5.0.0)"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
azure-functions==1.23.0
|
|
2
|
+
babel==2.17.0
|
|
3
|
+
certifi==2025.4.26
|
|
4
|
+
charset-normalizer==3.4.2
|
|
5
|
+
courlan==1.3.2
|
|
6
|
+
dateparser==1.2.1
|
|
7
|
+
greenlet==3.2.1
|
|
8
|
+
htmldate==1.9.3
|
|
9
|
+
jusText==3.0.2
|
|
10
|
+
lxml==5.4.0
|
|
11
|
+
lxml_html_clean==0.4.2
|
|
12
|
+
MarkupSafe==3.0.2
|
|
13
|
+
playwright==1.52.0
|
|
14
|
+
pyee==13.0.0
|
|
15
|
+
python-dateutil==2.9.0.post0
|
|
16
|
+
pytz==2025.2
|
|
17
|
+
regex==2024.11.6
|
|
18
|
+
six==1.17.0
|
|
19
|
+
tld==0.13
|
|
20
|
+
trafilatura==2.0.0
|
|
21
|
+
typing_extensions==4.13.2
|
|
22
|
+
tzlocal==5.3.1
|
|
23
|
+
urllib3==2.4.0
|
|
24
|
+
Werkzeug==3.1.3
|
package/lib/requestExecutor.js
CHANGED
|
@@ -195,7 +195,12 @@ const requestWithMonitor = async (endpoint, url, data, axiosConfigObj) => {
|
|
|
195
195
|
}
|
|
196
196
|
} catch (error) {
|
|
197
197
|
// throw new error with duration as part of the error data
|
|
198
|
-
|
|
198
|
+
const { code, name } = error;
|
|
199
|
+
const finalStatus = error?.response?.status ?? error?.status
|
|
200
|
+
const statusText = error?.response?.statusText ?? error?.statusText
|
|
201
|
+
const errorMessage = error?.response?.data?.message ?? error?.response?.data?.error?.message ?? error?.message ?? String(error);
|
|
202
|
+
|
|
203
|
+
throw { code, message: errorMessage, status: finalStatus, statusText, name, duration: endpoint?.monitor?.incrementErrorCount(callId, finalStatus) };
|
|
199
204
|
}
|
|
200
205
|
let duration;
|
|
201
206
|
if (response.status >= 200 && response.status < 300) {
|
|
@@ -207,7 +212,7 @@ const requestWithMonitor = async (endpoint, url, data, axiosConfigObj) => {
|
|
|
207
212
|
return { response, duration };
|
|
208
213
|
}
|
|
209
214
|
|
|
210
|
-
const MAX_RETRY =
|
|
215
|
+
const MAX_RETRY = 5; // retries for error handling
|
|
211
216
|
const MAX_DUPLICATE_REQUESTS = 3; // duplicate requests to manage latency spikes
|
|
212
217
|
const DUPLICATE_REQUEST_AFTER = 10; // 10 seconds
|
|
213
218
|
|
|
@@ -318,6 +323,7 @@ const makeRequest = async (cortexRequest) => {
|
|
|
318
323
|
if (cortexRequest.model.endpoints.length === 1) {
|
|
319
324
|
if (status !== 429 &&
|
|
320
325
|
status !== 408 &&
|
|
326
|
+
status !== 500 &&
|
|
321
327
|
status !== 502 &&
|
|
322
328
|
status !== 503 &&
|
|
323
329
|
status !== 504) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aj-archipelago/cortex",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.42",
|
|
4
4
|
"description": "Cortex is a GraphQL API for AI. It provides a simple, extensible interface for using AI services from OpenAI, Azure and others.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"repository": {
|
package/pathways/chat_title.js
CHANGED
|
@@ -27,6 +27,7 @@ export default {
|
|
|
27
27
|
codeRequestId: ``,
|
|
28
28
|
skipCallbackMessage: false,
|
|
29
29
|
entityId: ``,
|
|
30
|
+
researchMode: false,
|
|
30
31
|
model: 'oai-gpt41'
|
|
31
32
|
},
|
|
32
33
|
timeout: 600,
|
|
@@ -66,10 +67,15 @@ export default {
|
|
|
66
67
|
const messageWithIcon = toolIcon ? `${toolIcon} ${toolUserMessage}` : toolUserMessage;
|
|
67
68
|
await say(pathwayResolver.rootRequestId || pathwayResolver.requestId, `${messageWithIcon}\n\n`, 1000, false);
|
|
68
69
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
const toolResult = await callTool(toolFunction, {
|
|
71
|
+
...args,
|
|
72
|
+
...toolArgs,
|
|
73
|
+
toolFunction,
|
|
74
|
+
chatHistory: toolMessages,
|
|
75
|
+
stream: false
|
|
76
|
+
}, entityTools, pathwayResolver);
|
|
72
77
|
|
|
78
|
+
// Tool calls and results need to be paired together in the message history
|
|
73
79
|
// Add the tool call to the isolated message history
|
|
74
80
|
toolMessages.push({
|
|
75
81
|
role: "assistant",
|
|
@@ -84,14 +90,6 @@ export default {
|
|
|
84
90
|
}]
|
|
85
91
|
});
|
|
86
92
|
|
|
87
|
-
const toolResult = await callTool(toolFunction, {
|
|
88
|
-
...args,
|
|
89
|
-
...toolArgs,
|
|
90
|
-
toolFunction,
|
|
91
|
-
chatHistory: toolMessages,
|
|
92
|
-
stream: false
|
|
93
|
-
}, entityTools, pathwayResolver);
|
|
94
|
-
|
|
95
93
|
// Add the tool result to the isolated message history
|
|
96
94
|
let toolResultContent = typeof toolResult === 'string' ? toolResult : JSON.stringify(toolResult);
|
|
97
95
|
|
|
@@ -182,7 +180,7 @@ export default {
|
|
|
182
180
|
let pathwayResolver = resolver;
|
|
183
181
|
|
|
184
182
|
// Load input parameters and information into args
|
|
185
|
-
const { entityId, voiceResponse, aiMemorySelfModify, chatId } = { ...pathwayResolver.pathway.inputParameters, ...args };
|
|
183
|
+
const { entityId, voiceResponse, aiMemorySelfModify, chatId, researchMode } = { ...pathwayResolver.pathway.inputParameters, ...args };
|
|
186
184
|
|
|
187
185
|
const entityConfig = loadEntityConfig(entityId);
|
|
188
186
|
const { entityTools, entityToolsOpenAiFormat } = getToolsForEntity(entityConfig);
|
|
@@ -202,18 +200,21 @@ export default {
|
|
|
202
200
|
entityInstructions,
|
|
203
201
|
voiceResponse,
|
|
204
202
|
aiMemorySelfModify,
|
|
205
|
-
chatId
|
|
203
|
+
chatId,
|
|
204
|
+
researchMode
|
|
206
205
|
};
|
|
207
206
|
|
|
208
207
|
pathwayResolver.args = {...args};
|
|
209
208
|
|
|
209
|
+
const promptPrefix = researchMode ? 'Formatting re-enabled\n' : '';
|
|
210
|
+
|
|
210
211
|
const memoryTemplates = entityUseMemory ?
|
|
211
212
|
`{{renderTemplate AI_MEMORY}}\n\n{{renderTemplate AI_MEMORY_INSTRUCTIONS}}\n\n` : '';
|
|
212
213
|
|
|
213
214
|
const instructionTemplates = entityInstructions ? (entityInstructions + '\n\n') : `{{renderTemplate AI_EXPERTISE}}\n\n{{renderTemplate AI_COMMON_INSTRUCTIONS}}\n\n`;
|
|
214
215
|
|
|
215
216
|
const promptMessages = [
|
|
216
|
-
{"role": "system", "content": `${memoryTemplates}${instructionTemplates}{{renderTemplate AI_TOOLS}}\n\n{{renderTemplate AI_GROUNDING_INSTRUCTIONS}}\n\n{{renderTemplate AI_DATETIME}}`},
|
|
217
|
+
{"role": "system", "content": `${promptPrefix}${memoryTemplates}${instructionTemplates}{{renderTemplate AI_TOOLS}}\n\n{{renderTemplate AI_GROUNDING_INSTRUCTIONS}}\n\n{{renderTemplate AI_DATETIME}}`},
|
|
217
218
|
"{{chatHistory}}",
|
|
218
219
|
];
|
|
219
220
|
|
|
@@ -221,11 +222,6 @@ export default {
|
|
|
221
222
|
new Prompt({ messages: promptMessages }),
|
|
222
223
|
];
|
|
223
224
|
|
|
224
|
-
// if the model has been overridden, make sure to use it
|
|
225
|
-
if (pathwayResolver.modelName) {
|
|
226
|
-
pathwayResolver.args.model = pathwayResolver.modelName;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
225
|
// set the style model if applicable
|
|
230
226
|
const { aiStyle, AI_STYLE_ANTHROPIC, AI_STYLE_OPENAI } = args;
|
|
231
227
|
const styleModel = aiStyle === "Anthropic" ? AI_STYLE_ANTHROPIC : AI_STYLE_OPENAI;
|
|
@@ -55,7 +55,14 @@ export default {
|
|
|
55
55
|
// Call the Bing search pathway
|
|
56
56
|
const response = await callPathway('bing', {
|
|
57
57
|
...args
|
|
58
|
-
});
|
|
58
|
+
}, resolver);
|
|
59
|
+
|
|
60
|
+
if (resolver.errors && resolver.errors.length > 0) {
|
|
61
|
+
const errorMessages = Array.isArray(resolver.errors)
|
|
62
|
+
? resolver.errors.map(err => err.message || err)
|
|
63
|
+
: [resolver.errors.message || resolver.errors];
|
|
64
|
+
return JSON.stringify({ _type: "SearchError", value: errorMessages });
|
|
65
|
+
}
|
|
59
66
|
|
|
60
67
|
const parsedResponse = JSON.parse(response);
|
|
61
68
|
const results = [];
|
|
@@ -34,7 +34,7 @@ export default {
|
|
|
34
34
|
},
|
|
35
35
|
userPrompt: {
|
|
36
36
|
type: "string",
|
|
37
|
-
description: "The complete prompt to send as a user message to the model instructing the model to perform the task you need."
|
|
37
|
+
description: "The complete prompt to send as a user message to the model instructing the model to perform the task you need. Keep in mind this model does not share your context, conversation history, tool call results, or memories - so include all relevant information in the user prompt."
|
|
38
38
|
},
|
|
39
39
|
model: {
|
|
40
40
|
type: "string",
|
|
@@ -8,7 +8,7 @@ export default {
|
|
|
8
8
|
[
|
|
9
9
|
new Prompt({ messages: [
|
|
10
10
|
{"role": "system", "content": `You are the part of an AI entity named {{aiName}} that provides advanced coding and programming capabilities. You excel at writing, reviewing, and explaining code across various programming languages. You can help with code generation, debugging, optimization, and best practices. Think carefully about the latest request and provide a detailed, well thought out, carefully reviewed response.\n{{renderTemplate AI_DATETIME}}`},
|
|
11
|
-
"{{chatHistory}}"
|
|
11
|
+
"{{chatHistory}}"
|
|
12
12
|
]}),
|
|
13
13
|
],
|
|
14
14
|
inputParameters: {
|
|
@@ -46,6 +46,9 @@ export default {
|
|
|
46
46
|
}],
|
|
47
47
|
|
|
48
48
|
executePathway: async ({args, runAllPrompts, resolver}) => {
|
|
49
|
+
if (args.detailedInstructions) {
|
|
50
|
+
args.chatHistory.push({role: "user", content: args.detailedInstructions});
|
|
51
|
+
}
|
|
49
52
|
let result = await runAllPrompts({ ...args, stream: false });
|
|
50
53
|
resolver.tool = JSON.stringify({ toolUsed: "coding" });
|
|
51
54
|
return result;
|
|
@@ -48,13 +48,13 @@ export default {
|
|
|
48
48
|
icon: "🤖",
|
|
49
49
|
function: {
|
|
50
50
|
name: "CodeExecution",
|
|
51
|
-
description: "Use when explicitly asked to run or execute code, or when a coding agent is needed to perform specific tasks.",
|
|
51
|
+
description: "Use when explicitly asked to run or execute code, or when a coding agent is needed to perform specific tasks - examples include data analysis, file manipulation, or other tasks that require code execution.",
|
|
52
52
|
parameters: {
|
|
53
53
|
type: "object",
|
|
54
54
|
properties: {
|
|
55
55
|
codingTask: {
|
|
56
56
|
type: "string",
|
|
57
|
-
description: "Detailed task description for the coding agent. Include all necessary information as this is the only message the coding agent receives. Let the agent decide how to solve it without making assumptions about its capabilities."
|
|
57
|
+
description: "Detailed task description for the coding agent. Include all necessary information as this is the only message the coding agent receives. Let the agent decide how to solve it without making assumptions about its capabilities. IMPORTANT: The coding agent does not share your context, so you must provide it with all the information in this message. If you are asking it to operate on files or other data from your context, you must provide the fully-qualified URL to each of the files you want it to use. Also make sure you explicitly instruct the agent to use those files."
|
|
58
58
|
},
|
|
59
59
|
userMessage: {
|
|
60
60
|
type: "string",
|
|
@@ -24,7 +24,7 @@ export default {
|
|
|
24
24
|
icon: "📂",
|
|
25
25
|
function: {
|
|
26
26
|
name: "SearchPersonal",
|
|
27
|
-
description: "Search through the user's personal documents and uploaded files. Use this for finding information in user-provided content.",
|
|
27
|
+
description: "Search through the user's index of personal documents and indexed uploaded files. Use this for finding information in user-provided content or if the user refers to a file or a document that you don't see elsewhere in your context.",
|
|
28
28
|
parameters: {
|
|
29
29
|
type: "object",
|
|
30
30
|
properties: {
|
|
@@ -55,9 +55,9 @@ export default {
|
|
|
55
55
|
},
|
|
56
56
|
{
|
|
57
57
|
type: "function",
|
|
58
|
+
icon: "📰",
|
|
58
59
|
function: {
|
|
59
60
|
name: "SearchAJA",
|
|
60
|
-
icon: "📰",
|
|
61
61
|
description: "Search through Al Jazeera Arabic news articles. Use this for finding Arabic news content.",
|
|
62
62
|
parameters: {
|
|
63
63
|
type: "object",
|
|
@@ -42,7 +42,7 @@ export default {
|
|
|
42
42
|
|
|
43
43
|
try {
|
|
44
44
|
let model = "replicate-flux-11-pro";
|
|
45
|
-
let prompt = args.detailedInstructions;
|
|
45
|
+
let prompt = args.detailedInstructions || "";
|
|
46
46
|
let numberResults = args.numberResults || 1;
|
|
47
47
|
let negativePrompt = args.negativePrompt || "";
|
|
48
48
|
|
|
@@ -112,6 +112,9 @@ export default {
|
|
|
112
112
|
}],
|
|
113
113
|
|
|
114
114
|
executePathway: async ({args, runAllPrompts, resolver}) => {
|
|
115
|
+
if (args.detailedInstructions) {
|
|
116
|
+
args.chatHistory.push({role: "user", content: args.detailedInstructions});
|
|
117
|
+
}
|
|
115
118
|
const result = await runAllPrompts({ ...args });
|
|
116
119
|
resolver.tool = JSON.stringify({ toolUsed: "vision" });
|
|
117
120
|
return result;
|
|
@@ -49,7 +49,7 @@ export default {
|
|
|
49
49
|
icon: "🧠",
|
|
50
50
|
function: {
|
|
51
51
|
name: "Reason",
|
|
52
|
-
description: "Employ for
|
|
52
|
+
description: "Employ for advanced reasoning, scientific analysis, evaluating evidence, strategic planning, problem-solving, logic puzzles, mathematical calculations, or any questions that require careful thought or complex choices.",
|
|
53
53
|
parameters: {
|
|
54
54
|
type: "object",
|
|
55
55
|
properties: {
|
|
@@ -68,6 +68,9 @@ export default {
|
|
|
68
68
|
}],
|
|
69
69
|
|
|
70
70
|
executePathway: async ({args, runAllPrompts, resolver}) => {
|
|
71
|
+
if (args.detailedInstructions) {
|
|
72
|
+
args.chatHistory.push({role: "user", content: args.detailedInstructions});
|
|
73
|
+
}
|
|
71
74
|
let result = await runAllPrompts({ ...args, stream: false });
|
|
72
75
|
resolver.tool = JSON.stringify({ toolUsed: "reasoning" });
|
|
73
76
|
return result;
|
|
@@ -53,6 +53,9 @@ export default {
|
|
|
53
53
|
}],
|
|
54
54
|
|
|
55
55
|
executePathway: async ({args, runAllPrompts, resolver}) => {
|
|
56
|
+
if (args.detailedInstructions) {
|
|
57
|
+
args.chatHistory.push({role: "user", content: args.detailedInstructions});
|
|
58
|
+
}
|
|
56
59
|
resolver.tool = JSON.stringify({ toolUsed: "memory" });
|
|
57
60
|
return await callPathway('sys_search_memory', { ...args, stream: false, section: 'memoryAll', updateContext: true });
|
|
58
61
|
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// sys_tool_verify.js
|
|
2
|
+
// Entity tool that provides response verification and critical evaluation capabilities
|
|
3
|
+
|
|
4
|
+
import { Prompt } from '../../../../server/prompt.js';
|
|
5
|
+
|
|
6
|
+
export default {
|
|
7
|
+
prompt:
|
|
8
|
+
[
|
|
9
|
+
new Prompt({ messages: [
|
|
10
|
+
{"role": "system", "content": `You are the part of an AI entity named {{aiName}} that provides critical evaluation and verification capabilities. You excel at analyzing responses for accuracy, completeness, and potential issues. You do not have the tools to do the verification yourself, but you can help identify gaps, inconsistencies, questions, and areas that need further verification. Think carefully about the response and provide a detailed plan for verification including questions and follow-up actions to ensure the response is robust and reliable. Always include a section called <VERIFICATION_PLAN> that outlines step by step actions to take to answer questions or further research the response.\n{{renderTemplate AI_DATETIME}}`},
|
|
11
|
+
"{{chatHistory}}",
|
|
12
|
+
{"role": "user", "content": `Please provide a {{verificationLevel}} review of the following draft response:\n\n<DRAFT_RESPONSE>\n{{responseText}}\n</DRAFT_RESPONSE>\n\n{{context}}`}
|
|
13
|
+
]}),
|
|
14
|
+
],
|
|
15
|
+
inputParameters: {
|
|
16
|
+
chatHistory: [{role: '', content: []}],
|
|
17
|
+
contextId: ``,
|
|
18
|
+
aiName: "Jarvis",
|
|
19
|
+
language: "English",
|
|
20
|
+
model: 'oai-o3'
|
|
21
|
+
},
|
|
22
|
+
max_tokens: 100000,
|
|
23
|
+
useInputChunking: false,
|
|
24
|
+
enableDuplicateRequests: false,
|
|
25
|
+
timeout: 600,
|
|
26
|
+
toolDefinition: [{
|
|
27
|
+
type: "function",
|
|
28
|
+
icon: "🔍",
|
|
29
|
+
function: {
|
|
30
|
+
name: "Verify",
|
|
31
|
+
description: "Critically evaluate a response and provide verification questions and follow-up actions. You should call this tool and execute the steps provided in the resulting verification plan before presenting a final response to the user to ensure it is accurate, complete, and consistent with the user's request.",
|
|
32
|
+
parameters: {
|
|
33
|
+
type: "object",
|
|
34
|
+
properties: {
|
|
35
|
+
responseText: {
|
|
36
|
+
type: "string",
|
|
37
|
+
description: "The draft response text to be verified"
|
|
38
|
+
},
|
|
39
|
+
context: {
|
|
40
|
+
type: "string",
|
|
41
|
+
description: "Additional context or instructions about the verification request"
|
|
42
|
+
},
|
|
43
|
+
verificationLevel: {
|
|
44
|
+
type: "string",
|
|
45
|
+
description: "The level of verification required (basic, thorough, or critical)",
|
|
46
|
+
enum: ["basic", "thorough", "critical"]
|
|
47
|
+
},
|
|
48
|
+
userMessage: {
|
|
49
|
+
type: "string",
|
|
50
|
+
description: "A user-friendly message that describes what you're doing with this tool"
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
required: ["responseText", "verificationLevel", "userMessage"]
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}],
|
|
57
|
+
|
|
58
|
+
executePathway: async ({args, runAllPrompts, resolver}) => {
|
|
59
|
+
let result = await runAllPrompts({ ...args, stream: false });
|
|
60
|
+
resolver.tool = JSON.stringify({ toolUsed: "verification" });
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
}
|
package/server/modelExecutor.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// ModelExecutor.js
|
|
2
2
|
import CortexRequest from '../lib/cortexRequest.js';
|
|
3
|
+
import logger from '../lib/logger.js';
|
|
3
4
|
|
|
4
5
|
import OpenAIChatPlugin from './plugins/openAiChatPlugin.js';
|
|
5
6
|
import OpenAICompletionPlugin from './plugins/openAiCompletionPlugin.js';
|
|
@@ -125,7 +126,14 @@ class ModelExecutor {
|
|
|
125
126
|
|
|
126
127
|
async execute(text, parameters, prompt, pathwayResolver) {
|
|
127
128
|
const cortexRequest = new CortexRequest({ pathwayResolver });
|
|
128
|
-
|
|
129
|
+
try {
|
|
130
|
+
return await this.plugin.execute(text, parameters, prompt, cortexRequest);
|
|
131
|
+
} catch (error) {
|
|
132
|
+
logger.error(`Error executing model plugin for pathway ${pathwayResolver?.pathway?.name}: ${error.message}`);
|
|
133
|
+
logger.debug(error.stack);
|
|
134
|
+
pathwayResolver.errors.push(error.message);
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
129
137
|
}
|
|
130
138
|
}
|
|
131
139
|
|
|
@@ -75,7 +75,7 @@ class AzureBingPlugin extends ModelPlugin {
|
|
|
75
75
|
|
|
76
76
|
// Override the logging function to display the request and response
|
|
77
77
|
logRequestData(data, responseData, prompt) {
|
|
78
|
-
this.logAIRequestFinished();
|
|
78
|
+
//this.logAIRequestFinished();
|
|
79
79
|
|
|
80
80
|
logger.verbose(`${this.parseResponse(responseData)}`);
|
|
81
81
|
|
|
@@ -565,7 +565,10 @@ class ModelPlugin {
|
|
|
565
565
|
return parsedData;
|
|
566
566
|
} catch (error) {
|
|
567
567
|
// Log the error and continue
|
|
568
|
-
const errorMessage =
|
|
568
|
+
const errorMessage = error?.response?.data?.message
|
|
569
|
+
?? error?.response?.data?.error?.message
|
|
570
|
+
?? error?.message
|
|
571
|
+
?? String(error); // Fallback to string representation
|
|
569
572
|
logger.error(`Error in executeRequest for ${this.pathwayName}: ${errorMessage}`);
|
|
570
573
|
if (error.data) {
|
|
571
574
|
logger.error(`Additional error data: ${JSON.stringify(error.data)}`);
|
|
@@ -8,12 +8,14 @@ class OpenAIReasoningPlugin extends OpenAIChatPlugin {
|
|
|
8
8
|
for (const message of messages) {
|
|
9
9
|
if (message.role === 'user' || message.role === 'assistant') {
|
|
10
10
|
newMessages.push({
|
|
11
|
+
...message,
|
|
11
12
|
role: message.role,
|
|
12
13
|
content: this.parseContent(message.content)
|
|
13
14
|
});
|
|
14
15
|
} else if (message.role === 'system') {
|
|
15
16
|
// System messages to developer: https://platform.openai.com/docs/guides/text-generation#messages-and-roles
|
|
16
17
|
newMessages.push({
|
|
18
|
+
...message,
|
|
17
19
|
role: "developer",
|
|
18
20
|
content: this.parseContent(message.content)
|
|
19
21
|
});
|
|
@@ -7,9 +7,10 @@ class OpenAIReasoningVisionPlugin extends OpenAIVisionPlugin {
|
|
|
7
7
|
|
|
8
8
|
let newMessages = [];
|
|
9
9
|
|
|
10
|
+
// System messages to developer: https://platform.openai.com/docs/guides/text-generation#messages-and-roles
|
|
10
11
|
newMessages = parsedMessages.map(message => ({
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
...message,
|
|
13
|
+
role: message.role === 'system' ? 'developer' : message.role
|
|
13
14
|
})).filter(message => ['user', 'assistant', 'developer', 'tool'].includes(message.role));
|
|
14
15
|
|
|
15
16
|
return newMessages;
|
|
@@ -25,40 +25,36 @@ class OpenAIVisionPlugin extends OpenAIChatPlugin {
|
|
|
25
25
|
return await Promise.all(messages.map(async message => {
|
|
26
26
|
try {
|
|
27
27
|
// Handle tool-related message types
|
|
28
|
-
if (message.role === "tool") {
|
|
28
|
+
if (message.role === "tool" || (message.role === "assistant" && message.tool_calls)) {
|
|
29
29
|
return {
|
|
30
|
-
|
|
31
|
-
content: message.content,
|
|
32
|
-
tool_call_id: message.tool_call_id
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (message.role === "assistant" && message.tool_calls) {
|
|
37
|
-
return {
|
|
38
|
-
role: message.role,
|
|
39
|
-
content: message.content,
|
|
40
|
-
tool_calls: message.tool_calls
|
|
30
|
+
...message
|
|
41
31
|
};
|
|
42
32
|
}
|
|
43
33
|
|
|
44
34
|
if (Array.isArray(message.content)) {
|
|
45
|
-
|
|
46
|
-
|
|
35
|
+
return {
|
|
36
|
+
...message,
|
|
37
|
+
content: await Promise.all(message.content.map(async item => {
|
|
38
|
+
const parsedItem = safeJsonParse(item);
|
|
47
39
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
40
|
+
if (typeof parsedItem === 'string') {
|
|
41
|
+
return { type: 'text', text: parsedItem };
|
|
42
|
+
}
|
|
51
43
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
44
|
+
if (typeof parsedItem === 'object' && parsedItem !== null) {
|
|
45
|
+
// Handle both 'image' and 'image_url' types
|
|
46
|
+
if (parsedItem.type === 'image' || parsedItem.type === 'image_url') {
|
|
47
|
+
const url = parsedItem.image_url?.url || parsedItem.url;
|
|
48
|
+
if (url && await this.validateImageUrl(url)) {
|
|
49
|
+
return { type: 'image_url', image_url: { url } };
|
|
50
|
+
}
|
|
51
|
+
return { type: 'text', text: typeof item === 'string' ? item : JSON.stringify(item) };
|
|
52
|
+
}
|
|
56
53
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}));
|
|
54
|
+
|
|
55
|
+
return parsedItem;
|
|
56
|
+
}))
|
|
57
|
+
};
|
|
62
58
|
}
|
|
63
59
|
} catch (e) {
|
|
64
60
|
return message;
|
|
@@ -90,6 +90,9 @@ class OpenAIWhisperPlugin extends ModelPlugin {
|
|
|
90
90
|
sendProgress(true, true);
|
|
91
91
|
try {
|
|
92
92
|
res = await this.executeRequest(cortexRequest);
|
|
93
|
+
if (!res) {
|
|
94
|
+
throw new Error('Received null or empty response');
|
|
95
|
+
}
|
|
93
96
|
if(res?.statusCode && res?.statusCode >= 400){
|
|
94
97
|
throw new Error(res?.message || 'An error occurred.');
|
|
95
98
|
}
|
|
@@ -107,6 +110,10 @@ class OpenAIWhisperPlugin extends ModelPlugin {
|
|
|
107
110
|
|
|
108
111
|
if(!wordTimestamped && !responseFormat){
|
|
109
112
|
//if no response format, convert to text
|
|
113
|
+
if (!res) {
|
|
114
|
+
logger.warn("Received null or empty response from timestamped API when expecting SRT/VTT format. Returning empty string.");
|
|
115
|
+
return "";
|
|
116
|
+
}
|
|
110
117
|
return convertSrtToText(res);
|
|
111
118
|
}
|
|
112
119
|
return res;
|