mirror of
https://github.com/open-webui/open-webui.git
synced 2026-10-14 04:08:46 +00:00
refac
This commit is contained in:
parent
aa31e2b203
commit
2e8839a5dd
3 changed files with 57 additions and 47 deletions
|
|
@ -3508,8 +3508,6 @@ async def view_skill(
|
|||
) -> str:
|
||||
"""Read skill instructions and file list. Large skills return an opening preview and section offsets.
|
||||
|
||||
Use grep_skill_files to find specific rules, then read_skill_file for applicable passages, including mandatory rules.
|
||||
|
||||
:param id: Skill ID from the available skills manifest.
|
||||
"""
|
||||
if __request__ is None or not __user__:
|
||||
|
|
@ -3648,12 +3646,13 @@ async def grep_skill_files(
|
|||
__metadata__: dict = None,
|
||||
__event_call__: callable = None,
|
||||
) -> str:
|
||||
"""Find specific rules in skill files without reading every page; then use read_skill_file for relevant passages.
|
||||
"""Search skill files for text or regex matches.
|
||||
|
||||
Literal text or auto-detected RE2 regex; no lookarounds/backreferences, and shorthand character classes are ASCII-only.
|
||||
Searches each line for non-empty, non-overlapping matches and returns merged, bounded excerpts (6000 characters total).
|
||||
Offsets and match_length describe the first match in each excerpt, in original-file characters; total_matches counts
|
||||
occurrences, not excerpts. Terminal skills support SKILL.md only. No skill version is created.
|
||||
occurrences, not excerpts. Omitted excerpts have compact locations in omitted_matches; omitted_matches_truncated
|
||||
indicates when more locations remain. Terminal skills support SKILL.md only. No skill version is created.
|
||||
|
||||
:param id: Skill ID, including terminal: IDs.
|
||||
:param pattern: Text or auto-detected regex to find (up to 4096 characters).
|
||||
|
|
|
|||
|
|
@ -2917,9 +2917,8 @@ async def process_chat_payload(request, form_data, user, metadata, model):
|
|||
root_content = format_skill_content(overview, skill.id, skill_tools_enabled)
|
||||
resources = '\n'.join(overview['files'])
|
||||
resource_hint = (
|
||||
f'\nFind rules with grep_skill_files(id="{skill.id}", pattern=...), '
|
||||
f'then read relevant passages with read_skill_file(id="{skill.id}", path=...).\n{resources}'
|
||||
if use_builtin_tools and model_builtin_tools.get('skills', True)
|
||||
f'\nSupporting files:\n{resources}'
|
||||
if skill_tools_enabled and resources
|
||||
else '\nSupporting files require a model with skill tools enabled.'
|
||||
if resources
|
||||
else ''
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ SKILL_MANIFEST_MAX_ENTRIES = 50
|
|||
SKILL_MANIFEST_MAX_CHARS = 5_000
|
||||
SKILL_GREP_MAX_CHARS = 6_000
|
||||
SKILL_GREP_EXCERPT_MAX_CHARS = 1_200
|
||||
SKILL_GREP_LOCATIONS_MAX_CHARS = 2_000
|
||||
|
||||
|
||||
def skill_content_page(content: str, offset: int = 0, max_chars: int = SKILL_CONTENT_MAX_CHARS) -> dict:
|
||||
|
|
@ -116,64 +117,80 @@ def grep_skill_content(files: list[dict], pattern: str, case_insensitive: bool =
|
|||
raise ValueError(error)
|
||||
max_results = min(30, max(1, max_results))
|
||||
result = {'matches': [], 'total_matches': 0, 'truncated': False}
|
||||
# Reserve the notice while collecting so a full response can still explain omissions.
|
||||
notice = 'Narrow the pattern or specify path.'
|
||||
# Reserve room for locations so large excerpts cannot hide all the remaining matches.
|
||||
notice = 'Excerpts omitted; see omitted_matches.'
|
||||
accepting = True
|
||||
accepting_locations = True
|
||||
for file in sorted(files, key=lambda file: (file['path'] != 'SKILL.md', file['path'])):
|
||||
check_budget()
|
||||
if file.get('encoding'):
|
||||
continue
|
||||
content, offset = file['content'], 0
|
||||
headings = iter_skill_headings(content)
|
||||
heading, next_heading = None, next(headings, None) if accepting else None
|
||||
heading, next_heading = None, next(headings, None) if accepting or accepting_locations else None
|
||||
previous_window = None
|
||||
for line in content.splitlines(keepends=True):
|
||||
check_budget()
|
||||
for start, end in matcher(line.rstrip('\r\n')):
|
||||
check_budget()
|
||||
result['total_matches'] += 1
|
||||
if not accepting:
|
||||
if not accepting and not accepting_locations:
|
||||
continue
|
||||
start, end = offset + start, offset + end
|
||||
while next_heading is not None and next_heading['offset'] <= start:
|
||||
heading, next_heading = next_heading, next(headings, None)
|
||||
check_budget()
|
||||
left, right = _skill_match_window(content, start, end)
|
||||
merge = (
|
||||
previous_window is not None
|
||||
and left < previous_window[1]
|
||||
and max(right, previous_window[1]) - previous_window[0] <= SKILL_GREP_EXCERPT_MAX_CHARS
|
||||
)
|
||||
if merge:
|
||||
left, right = previous_window[0], max(right, previous_window[1])
|
||||
entry = {**result['matches'][-1], 'excerpt': content[left:right]}
|
||||
candidates = [*result['matches'][:-1], entry]
|
||||
else:
|
||||
entry = {
|
||||
'path': file['path'],
|
||||
'offset': start,
|
||||
'match_length': end - start,
|
||||
'section': heading['title'] if heading else None,
|
||||
'section_offset': heading['offset'] if heading else None,
|
||||
'excerpt': content[left:right],
|
||||
if accepting:
|
||||
left, right = _skill_match_window(content, start, end)
|
||||
merge = (
|
||||
previous_window is not None
|
||||
and left < previous_window[1]
|
||||
and max(right, previous_window[1]) - previous_window[0] <= SKILL_GREP_EXCERPT_MAX_CHARS
|
||||
)
|
||||
if merge:
|
||||
left, right = previous_window[0], max(right, previous_window[1])
|
||||
entry = {**result['matches'][-1], 'excerpt': content[left:right]}
|
||||
candidates = [*result['matches'][:-1], entry]
|
||||
else:
|
||||
entry = {
|
||||
'path': file['path'],
|
||||
'offset': start,
|
||||
'match_length': end - start,
|
||||
'section': heading['title'] if heading else None,
|
||||
'section_offset': heading['offset'] if heading else None,
|
||||
'excerpt': content[left:right],
|
||||
}
|
||||
candidates = [*result['matches'], entry]
|
||||
candidate = {
|
||||
**result, 'matches': candidates, 'truncated': True, 'notice': notice,
|
||||
'omitted_matches': [], 'omitted_matches_truncated': False,
|
||||
}
|
||||
candidates = [*result['matches'], entry]
|
||||
candidate = {**result, 'matches': candidates, 'truncated': True, 'notice': notice}
|
||||
if (
|
||||
len(candidates) > max_results
|
||||
or len(JSONCodec.dumps(candidate, ensure_ascii=False)) > SKILL_GREP_MAX_CHARS
|
||||
):
|
||||
if (
|
||||
len(candidates) <= max_results
|
||||
and len(JSONCodec.dumps(candidate, ensure_ascii=False))
|
||||
<= SKILL_GREP_MAX_CHARS - SKILL_GREP_LOCATIONS_MAX_CHARS
|
||||
):
|
||||
result['matches'] = candidates
|
||||
previous_window = left, right
|
||||
continue
|
||||
accepting = False
|
||||
result.update(truncated=True, notice=notice)
|
||||
result.update(truncated=True, notice=notice, omitted_matches=[], omitted_matches_truncated=False)
|
||||
locations = [*result['omitted_matches'], {
|
||||
'path': file['path'], 'offset': start, 'section': heading['title'] if heading else None,
|
||||
}]
|
||||
if len(locations) <= 30 and len(JSONCodec.dumps(locations, ensure_ascii=False)) <= SKILL_GREP_LOCATIONS_MAX_CHARS:
|
||||
result['omitted_matches'] = locations
|
||||
else:
|
||||
result['matches'] = candidates
|
||||
previous_window = left, right
|
||||
accepting_locations = False
|
||||
result['omitted_matches_truncated'] = True
|
||||
result['notice'] = 'More locations omitted; narrow the pattern or specify path.'
|
||||
offset += len(line)
|
||||
check_budget()
|
||||
serialized = JSONCodec.dumps(result, ensure_ascii=False)
|
||||
while len(serialized) > SKILL_GREP_MAX_CHARS:
|
||||
result['matches'].pop()
|
||||
result.update(truncated=True, notice=notice)
|
||||
result['omitted_matches'].pop()
|
||||
result['omitted_matches_truncated'] = True
|
||||
result['notice'] = 'More locations omitted; narrow the pattern or specify path.'
|
||||
serialized = JSONCodec.dumps(result, ensure_ascii=False)
|
||||
return serialized
|
||||
|
||||
|
|
@ -192,11 +209,7 @@ def skill_overview(content: str, files: list, field: str = 'files', tools_enable
|
|||
}
|
||||
if large:
|
||||
overview.update(skill_heading_index(content))
|
||||
overview['reading_hint'] = (
|
||||
'This preview is incomplete: use grep_skill_files to find specific rules, then read_skill_file to read '
|
||||
'applicable passages, including mandatory rules, using character offsets or next_offset '
|
||||
'(up to 20000 characters).'
|
||||
)
|
||||
overview['reading_hint'] = 'Partial preview; offsets are character positions.'
|
||||
return overview
|
||||
|
||||
|
||||
|
|
@ -213,8 +226,7 @@ def format_skill_content(page: dict, skill_id: str, tools_enabled: bool) -> str:
|
|||
content += '\nSkill instructions truncated.'
|
||||
if tools_enabled:
|
||||
content += (
|
||||
f'\nFind specific rules with grep_skill_files(id={json.dumps(skill_id)}, pattern=...), '
|
||||
f'then read passages with read_skill_file(path="SKILL.md", offset={page["next_offset"]}).'
|
||||
f'\nread_skill_file(id={json.dumps(skill_id)}, path="SKILL.md", offset={page["next_offset"]}).'
|
||||
)
|
||||
return content
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue