Skip to content

Commit

Permalink
perf: optimize tool processing. allowing different types of processin…
Browse files Browse the repository at this point in the history
…g methods, template parsing supports complete plugin syntax

chore: optimize audio processing

chore: split built-in app in-app purchase queries
  • Loading branch information
adolphnov committed Nov 30, 2024
1 parent 3602647 commit 211d1b5
Show file tree
Hide file tree
Showing 14 changed files with 239 additions and 124 deletions.
7 changes: 4 additions & 3 deletions src/agent/model_middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ import type { ChatStreamTextHandler } from './types';
import { createLlmModel } from '.';
import { getLogSingleton } from '../log/logDecortor';
import { log } from '../log/logger';
import { tools } from '../tools';

type Writeable<T> = { -readonly [P in keyof T]: T[P] };

export function AIMiddleware({ config, tools, activeTools, onStream, toolChoice, messageReferencer, chatModel }: { config: AgentUserConfig; tools: Record<string, any>; activeTools: string[]; onStream: ChatStreamTextHandler | null; toolChoice: ToolChoice[] | []; messageReferencer: string[]; chatModel: string }): LanguageModelV1Middleware & { onChunk: (data: any) => void; onStepFinish: (data: StepResult<any>, context: AgentUserConfig) => void } {
export function AIMiddleware({ config, activeTools, onStream, toolChoice, messageReferencer, chatModel }: { config: AgentUserConfig; activeTools: string[]; onStream: ChatStreamTextHandler | null; toolChoice: ToolChoice[] | []; messageReferencer: string[]; chatModel: string }): LanguageModelV1Middleware & { onChunk: (data: any) => void; onStepFinish: (data: StepResult<any>, context: AgentUserConfig) => void } {
let startTime: number | undefined;
let sendToolCall = false;
let step = 0;
Expand Down Expand Up @@ -55,7 +56,7 @@ export function AIMiddleware({ config, tools, activeTools, onStream, toolChoice,

onChunk: (data: any) => {
const { chunk } = data;
log.debug(`chunk: ${JSON.stringify(chunk)}`);
// log.debug(`chunk: ${JSON.stringify(chunk)}`);
if (chunk.type === 'tool-call' && !sendToolCall) {
onStream?.send(`${messageReferencer.join('')}...\n` + `tool call will start: ${chunk.toolName}`);
sendToolCall = true;
Expand Down Expand Up @@ -124,7 +125,7 @@ function warpMessages(params: LanguageModelV1CallOptions, tools: Record<string,
if (activeTools.length > 0) {
if (messages[0].role === 'system') {
messages[0].content = `${rawSystemPrompt}\n\nYou can consider using the following tools:\n##TOOLS${activeTools.map(name =>
`\n\n### ${name}\n- desc: ${tools[name].description} \n${tools[name].prompt || ''}`,
`\n\n### ${name}\n- desc: ${tools[name]?.schema?.description || ''} \n${tools[name]?.prompt || ''}`,
).join('')}`;
}
} else {
Expand Down
1 change: 0 additions & 1 deletion src/agent/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,6 @@ export async function requestChatCompletionsV2(params: { model: LanguageModelV1;
try {
const middleware = AIMiddleware({
config: params.context,
tools: params.tools || {},
activeTools: params.activeTools || [],
onStream,
toolChoice: params.toolChoice || [],
Expand Down
4 changes: 2 additions & 2 deletions src/plugins/interpolate.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
/* eslint-disable regexp/no-potentially-useless-backreference */
const INTERPOLATE_LOOP_REGEXP = /\{\{#each(?::(\w+))?\s+(\w+)\s+in\s+([\w.[\]]+)\}\}([\s\S]*?)\{\{\/each(?::\1)?\}\}/g;
const INTERPOLATE_CONDITION_REGEXP = /\{\{#if(?::(\w+))?\s+([\w.[\]]+)\}\}([\s\S]*?)(?:\{\{#else(?::\1)?\}\}([\s\S]*?))?\{\{\/if(?::\1)?\}\}/g;
export const INTERPOLATE_VARIABLE_REGEXP = /\{\{([\w.[\]]+)\}\}/g;
const INTERPOLATE_VARIABLE_REGEXP = /\{\{([\w.[\]]+)\}\}/g;

export function evaluateExpression(expr: string, localData: any): undefined | any {
function evaluateExpression(expr: string, localData: any): undefined | any {
if (expr === '.') {
return localData['.'] ?? localData;
}
Expand Down
40 changes: 22 additions & 18 deletions src/tools/external/app_iap.json
Original file line number Diff line number Diff line change
@@ -1,42 +1,46 @@
{
"schema": {
"name": "app_iap",
"description": "Search for an app in App Store and retrieve its IAP information.",
"description": "Retrive detailed in-app purchase information based on country code, app id.",
"parameters": {
"type": "object",
"properties": {
"app_name": {
"type": "string",
"description": "The name of the app to search for in App Store",
"examples": ["WeChat", "YouTube"]
},
"country": {
"type": "string",
"description": "The country code for App Store search",
"default": "us",
"examples": ["us", "cn", "jp"]
"examples": [
"us",
"cn",
"tr",
"ng"
]
},
"trackId": {
"type": "string",
"description": "The trackId to be queried",
"examples": ["363590051"]
}
},
"required": ["app_name", "country"],
"required": [
"country",
"trackId"
],
"additionalProperties": false
}
},
"payload": {
"url": "https://itunes.apple.com/search?entity=software&limit=1&term={{app_name}}&country={{country}}&fields=trackId,trackCensoredName",
"method": "GET",
"headers": {
"Accept": "application/json"
}
"url": "https://apps.apple.com/{{country}}/app/id{{trackId}}"
},
"webcrawler": {
"template": "{{results[0].trackViewUrl}}",
"handler": {
"type": "webclean",
"patterns": [
{
"pattern": "<li class=\\\"list-with-numbers__item\\\">([\\s\\S]+?)<\\/li>",
"group": 1,
"cleanPattern": "<[^>]*>?"
"clean": ["<[^>]*>?", ""]
}
]
}

},
"prompt": "Please provide the detailed in-app purchase information for the app in the specified country. The default regions that users want to follow include Turkey and Nigeria, two low-cost areas, unless the user explicitly does not need to query these two regions."
}
57 changes: 57 additions & 0 deletions src/tools/external/app_lookup.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
{
"schema": {
"name": "app_lookup",
"description": "Retrive five sets of related app id and app name through region code and keywords. The app id is consistent across different countries. Only one piece of data is valid.",
"parameters": {
"type": "object",
"properties": {
"app_name": {
"type": "string",
"description": "The name of the app to search for in App Store",
"examples": [
"WeChat",
"YouTube"
]
},
"country": {
"type": "string",
"description": "The country code for App Store search",
"default": "us",
"examples": [
"us",
"cn",
"jp"
]
},
"entity": {
"type": "string",
"description": "Types of search software",
"default": "software",
"enum": [
"software",
"iPadSoftware",
"macSoftware"
]
}
},
"required": [
"app_name",
"country",
"entity"
],
"additionalProperties": false
}
},
"payload": {
"url": "https://itunes.apple.com/search?entity={{entity}}&limit=3&term={{app_name}}&country={{country}}&fields=trackId,trackCensoredName",
"method": "GET",
"headers": {
"Accept": "application/json"
}
},
"handler": {
"type": "template",
"data": "{{#each i in results}}{\"trackId\":{{i.trackId}},\"trackName\":\"{{i.trackName}}\",\"primaryGenreName\":{{i.primaryGenreName}}}{{/each}}"
},
"prompt": "This tool should only be called once, and the app ID is the same in different regions. You should select the most relevant one."
}
3 changes: 2 additions & 1 deletion src/tools/external/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import app_iap from './app_iap.json';
import app_lookup from './app_lookup.json';
import jina_reader from './jina.json';
import qw_lookup from './lookup.json';
import qw_weather from './qweather.json';

export default { app_iap, jina_reader, qw_lookup, qw_weather };
export default { app_iap, app_lookup, jina_reader, qw_lookup, qw_weather };
15 changes: 8 additions & 7 deletions src/tools/external/jina.json
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
{
"schema": {
"name": "jina_reader",
"description":
"Grab text content from provided URL links. Can be used to retrieve text information for web pages, articles, or other online resources",
"description": "Grab text content from provided URL links. Can be used to retrieve text information for web pages, articles, or other online resources",
"parameters": {
"type": "object",
"properties": {
"url": {
"type": "string",
"description":
"The full URL address of the content to be crawled. If the user explicitly requests to read/analyze the content of the link, then call the function. If the data provided by the user is web content with links, but the content is sufficient to answer the question, then there is no need to call the function."
"description": "The full URL address of the content to be crawled. If the user explicitly requests to read/analyze the content of the link, then call the function. If the data provided by the user is web content with links, but the content is sufficient to answer the question, then there is no need to call the function."
}
},
"required": ["url"],
"required": [
"url"
],
"additionalProperties": false
}
},
Expand All @@ -25,7 +25,8 @@
"X-Timeout": "10"
}
},
"handler": "content => content",
"type": "reader",
"required": ["JINA_API_KEY"]
"required": [
"JINA_API_KEY"
]
}
10 changes: 7 additions & 3 deletions src/tools/external/lookup.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"schema": {
"name": "qweather_city_lookup",
"name": "qw_lookup",
"description": "Retrieve city information based on location name using QWeather API. This can be used to get city details such as city ID, name, and coordinates.",
"parameters": {
"type": "object",
Expand All @@ -10,7 +10,9 @@
"description": "The name of the location to look up. This can be a city name, administrative region, or other geographical name."
}
},
"required": ["location"],
"required": [
"location"
],
"additionalProperties": false
}
},
Expand All @@ -22,5 +24,7 @@
}
},
"type": "lookup",
"required": ["QWEATHER_TOKEN"]
"required": [
"QWEATHER_TOKEN"
]
}
25 changes: 20 additions & 5 deletions src/tools/external/qweather.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,34 @@
"location": {
"type": "string",
"description": "The location ID or coordinates (longitude,latitude) for which to retrieve the current weather. This ID is typically obtained from a city lookup API. For example, location=101010100 or location=116.41,39.92",
"examples": ["101010100", "116.41,39.92"]
"examples": [
"101010100",
"116.41,39.92"
]
},
"lang": {
"type": "string",
"description": "Language setting for the response. Based on the language used by the user. Default is zh-hans",
"examples": ["zh-hans", "zh-hant", "en", "de"]
"examples": [
"zh-hans",
"zh-hant",
"en",
"de"
]
},
"unit": {
"type": "string",
"description": "Unit of measurement for the response data. Options include unit=m (metric units, default) and unit=i (imperial units). Default is m",
"enum": ["m", "i"],
"enum": [
"m",
"i"
],
"default": "m"
}
},
"required": ["location"],
"required": [
"location"
],
"additionalProperties": false
}
},
Expand All @@ -34,5 +47,7 @@
}
},
"type": "weather",
"required": ["QWEATHER_TOKEN"]
"required": [
"QWEATHER_TOKEN"
]
}
39 changes: 39 additions & 0 deletions src/tools/internal/web.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import type { PatternInfo } from '../types';
import { log } from '../../log/logger';
import { interpolate } from '../../plugins/interpolate';

export function processHtmlText(patterns: PatternInfo[], text: string): string {
let results: string[] = [text];
for (const { pattern, group = 0, clean } of patterns) {
results = results.flatMap((text) => {
const regex = new RegExp(pattern, 'g');
const matches = Array.from(text.matchAll(regex));
return matches.map((match) => {
let extractedText = match[group];
if (clean) {
const cleanRegex = new RegExp(clean[0], 'g');
extractedText = extractedText.replace(cleanRegex, clean[1] ?? '');
}
return extractedText.replace(/\s+/g, ' ').trim();
});
});
}
return results.join('\n');
}

export interface WebCrawlerInfo {
url: string;
patterns?: PatternInfo[];
}

export async function webCrawler(webcrawler: WebCrawlerInfo, data: Record<string, any>) {
let result: string | Record<string, any> = '';
const url = interpolate(webcrawler.url, data);
log.info(`webcrawler url: ${url}`);
const resp = await fetch(url).then(r => r.text());
result = {
result: processHtmlText(webcrawler.patterns || [], resp),
source: url,
};
return result;
}
41 changes: 0 additions & 41 deletions src/tools/internal/webclean.ts

This file was deleted.

Loading

0 comments on commit 211d1b5

Please sign in to comment.