@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.
Files changed (28) hide show
  1. package/config.js +1 -1
  2. package/helper-apps/cortex-browser/.funcignore +8 -0
  3. package/helper-apps/cortex-browser/Dockerfile +52 -0
  4. package/helper-apps/cortex-browser/function_app.py +181 -0
  5. package/helper-apps/cortex-browser/host.json +15 -0
  6. package/helper-apps/cortex-browser/requirements.txt +24 -0
  7. package/lib/requestExecutor.js +8 -2
  8. package/package.json +1 -1
  9. package/pathways/chat_title.js +1 -1
  10. package/pathways/system/entity/sys_entity_agent.js +15 -19
  11. package/pathways/system/entity/sys_get_entities.js +1 -0
  12. package/pathways/system/entity/tools/sys_tool_bing_search.js +8 -1
  13. package/pathways/system/entity/tools/sys_tool_callmodel.js +1 -1
  14. package/pathways/system/entity/tools/sys_tool_coding.js +4 -1
  15. package/pathways/system/entity/tools/sys_tool_codingagent.js +2 -2
  16. package/pathways/system/entity/tools/sys_tool_cognitive_search.js +2 -2
  17. package/pathways/system/entity/tools/sys_tool_image.js +1 -1
  18. package/pathways/system/entity/tools/sys_tool_readfile.js +3 -0
  19. package/pathways/system/entity/tools/sys_tool_reasoning.js +4 -1
  20. package/pathways/system/entity/tools/sys_tool_remember.js +3 -0
  21. package/pathways/system/entity/tools/sys_tool_verify.js +63 -0
  22. package/server/modelExecutor.js +9 -1
  23. package/server/plugins/azureBingPlugin.js +1 -1
  24. package/server/plugins/modelPlugin.js +4 -1
  25. package/server/plugins/openAiReasoningPlugin.js +2 -0
  26. package/server/plugins/openAiReasoningVisionPlugin.js +3 -2
  27. package/server/plugins/openAiVisionPlugin.js +22 -26
  28. 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 have access to a powerful set of tools that you can use to help accomplish tasks and provide better responses. Here's how to use them effectively:\n\n1. Take your time and use tools as many times as you need to be sure you have all the information to make a good response. In many cases you will want to make multiple tool calls. You can call multiple tools in parallel or you can chain them, waiting for the results of one for information before you call another. This allows you to dig deeper, compile more information, read various sources, and even double check and verify your information before responding.\n\n2. Tool Selection and Planning:\n- Carefully review your available tools before responding\n- For complex multi-step tasks, use your planning tool first to create a step-by-step plan to achieve the goal using the tools at your disposal\n- Consider which tools would be most appropriate for each step\n\n3. Best Practices:\n- Always verify tool capabilities before telling users something can't be done\n- Use tools proactively when they can provide better or more accurate information\n- If a user explicitly requests tool usage, you must comply\n- When using multiple tools, maintain context between tool calls\n- If a tool fails, consider alternative approaches or tools\n\n4. Common Use Cases:\n- Research: Use search tools across multiple sources to gather information before responding\n- Analysis: Use tools to process and analyze data or content\n- Generation: Use appropriate tools for creating content, images, or code\n- Verification: Use tools to validate information or check facts\n\nRemember: Your goal is to provide the most helpful and accurate responses possible. Don't hesitate to use tools when they can improve your response quality or accuracy.",
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 cant 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,8 @@
1
+ .git*
2
+ .vscode
3
+ __azurite_db*__.json
4
+ __blobstorage__
5
+ __queuestorage__
6
+ local.settings.json
7
+ test
8
+ .venv
@@ -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
@@ -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
- throw { ...error, duration: endpoint?.monitor?.incrementErrorCount(callId, error?.response?.status || null) };
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 = 10; // retries for error handling
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.40",
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": {
@@ -20,7 +20,7 @@ export default {
20
20
  title: '',
21
21
  text: '',
22
22
  },
23
- model: 'oai-gpt41-nano',
23
+ model: 'oai-gpt41-mini',
24
24
  useInputChunking: false,
25
25
  temperature: 0,
26
26
  enableDuplicateRequests: false
@@ -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}&nbsp;&nbsp;${toolUserMessage}` : toolUserMessage;
67
68
  await say(pathwayResolver.rootRequestId || pathwayResolver.requestId, `${messageWithIcon}\n\n`, 1000, false);
68
69
 
69
- if (toolArgs.detailedInstructions) {
70
- toolMessages.push({role: "user", content: toolArgs.detailedInstructions});
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;
@@ -6,6 +6,7 @@ import { getAvailableEntities } from './tools/shared/sys_entity_tools.js';
6
6
  export default {
7
7
  prompt: [],
8
8
  inputParameters: {},
9
+ model: 'oai-gpt41-mini',
9
10
  executePathway: async ({ args }) => {
10
11
  try {
11
12
  const entities = getAvailableEntities();
@@ -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 advancedreasoning, scientific analysis, evaluating evidence, strategic planning, problem-solving, logic puzzles, mathematical calculations, or any questions that require careful thought or complex choices.",
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
+ }
@@ -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
- return await this.plugin.execute(text, parameters, prompt, cortexRequest);
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 = `${error?.response?.data?.message || error?.response?.data?.error?.message || error?.message || error}`;
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
- role: message.role === 'system' ? 'developer' : message.role,
12
- content: message.content
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
- role: message.role,
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
- message.content = await Promise.all(message.content.map(async item => {
46
- const parsedItem = safeJsonParse(item);
35
+ return {
36
+ ...message,
37
+ content: await Promise.all(message.content.map(async item => {
38
+ const parsedItem = safeJsonParse(item);
47
39
 
48
- if (typeof parsedItem === 'string') {
49
- return { type: 'text', text: parsedItem };
50
- }
40
+ if (typeof parsedItem === 'string') {
41
+ return { type: 'text', text: parsedItem };
42
+ }
51
43
 
52
- if (typeof parsedItem === 'object' && parsedItem !== null && parsedItem.type === 'image_url') {
53
- const url = parsedItem.url || parsedItem.image_url?.url;
54
- if (url && await this.validateImageUrl(url)) {
55
- return {type: parsedItem.type, image_url: {url}};
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
- return { type: 'text', text: typeof item === 'string' ? item : JSON.stringify(item) };
58
- }
59
-
60
- return parsedItem;
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;