Compare commits
2 Commits
f762721948
...
8c611414e8
Author | SHA1 | Date |
---|---|---|
|
8c611414e8 | |
|
d0c51793af |
29
exif.py
29
exif.py
|
@ -131,10 +131,10 @@ class OsmSearcher:
|
|||
data = cache.get(cache_key)
|
||||
|
||||
if data:
|
||||
print(f"[OSM] Got nominatim search data for {query} from cache!")
|
||||
print(f"[EXIF-OSM] Got nominatim search data for {query} from cache!")
|
||||
return data[:limit]
|
||||
|
||||
print(f"[OSM] Searching Nominatim for: {query}")
|
||||
print(f"[EXIF-OSM] Searching Nominatim for: {query}")
|
||||
|
||||
url = urljoin(self.valves.nominatim_url, "search")
|
||||
params = {
|
||||
|
@ -184,7 +184,7 @@ class OsmSearcher:
|
|||
place_name = ",".join(nominatim_result['display_name'].split(",")[:3])
|
||||
|
||||
if place_name is None:
|
||||
print(f"WARN: Could not find display name for coords: {lat},{lon}")
|
||||
print(f"[EXIF-OSM] WARNING: Could not find display name for coords: {lat},{lon}")
|
||||
place_name = f"{lat},{lon}"
|
||||
|
||||
return place_name
|
||||
|
@ -194,21 +194,24 @@ def extract_gps(img_bytes):
|
|||
fp.write(img_bytes)
|
||||
fp.close()
|
||||
|
||||
data = gpsphoto.getGPSData(fp.name)
|
||||
lat = data.get('Latitude', None)
|
||||
lon = data.get('Longitude', None)
|
||||
try:
|
||||
data = gpsphoto.getGPSData(fp.name)
|
||||
lat = data.get('Latitude', None)
|
||||
lon = data.get('Longitude', None)
|
||||
os.unlink(fp.name)
|
||||
|
||||
os.unlink(fp.name)
|
||||
if lat and lon:
|
||||
return (round(lat, 4), round(lon, 4))
|
||||
else:
|
||||
return None
|
||||
if lat and lon:
|
||||
return (round(lat, 4), round(lon, 4))
|
||||
else:
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"[EXIF-OSM] WARNING: Could not load image for GPS processing: {e}")
|
||||
|
||||
def exif_instructions(geocoding, user_image_count):
|
||||
if geocoding:
|
||||
return valid_instructions(geocoding, user_image_count)
|
||||
else:
|
||||
return invalid_instructions(user_image_count)
|
||||
return invalid_instructions()
|
||||
|
||||
def valid_instructions(geocoding, user_image_count):
|
||||
lat = geocoding.get("lat", "unknown")
|
||||
|
@ -308,12 +311,10 @@ class Filter:
|
|||
) -> dict:
|
||||
messages = body.get("messages")
|
||||
if messages is None:
|
||||
# Handle the case where messages is None
|
||||
return body
|
||||
|
||||
user_message = get_most_recent_user_message_with_image(messages)
|
||||
if user_message is None:
|
||||
# Handle the case where user_message is None
|
||||
return body
|
||||
|
||||
user_message_content = user_message.get("content")
|
||||
|
|
173
thinking.py
173
thinking.py
|
@ -1,173 +0,0 @@
|
|||
"""
|
||||
title: Collapsible Thought Filter
|
||||
author: projectmoon
|
||||
author_url: https://git.agnos.is/projectmoon/open-webui-filters
|
||||
version: 0.2.0
|
||||
license: AGPL-3.0+, MIT
|
||||
required_open_webui_version: 0.3.32
|
||||
"""
|
||||
|
||||
#########################################################
|
||||
# OpenWebUI Filter that collapses model reasoning/thinking into a
|
||||
# separate section in the reply.
|
||||
|
||||
# Based on the Add or Delete Text Filter by anfi.
|
||||
# https://openwebui.com/f/anfi/add_or_delete_text
|
||||
#
|
||||
# Therefore, portions of this code are licensed under the MIT license.
|
||||
# The modifications made for "thought enclosure" etc are licensed
|
||||
# under the AGPL using the MIT's sublicensing clause.
|
||||
#
|
||||
# For those portions under the MIT license, the following applies:
|
||||
#
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2024 anfi
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
#########################################################
|
||||
|
||||
from typing import Optional, Dict, List
|
||||
import re
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
THOUGHT_ENCLOSURE = """
|
||||
<details>
|
||||
<summary>{{THOUGHT_TITLE}}</summary>
|
||||
{{THOUGHTS}}
|
||||
</details>
|
||||
"""
|
||||
|
||||
DETAIL_DELETION_REGEX = r"</?details>[\s\S]*?</details>"
|
||||
|
||||
class Filter:
|
||||
class Valves(BaseModel):
|
||||
priority: int = Field(
|
||||
default=0, description="Priority level for the filter operations."
|
||||
)
|
||||
thought_title: str = Field(
|
||||
default="Thought Process",
|
||||
description="Title for the collapsible reasoning section."
|
||||
)
|
||||
thought_tag: str = Field(
|
||||
default="thinking",
|
||||
description="The XML tag for model thinking output."
|
||||
)
|
||||
output_tag: str = Field(
|
||||
default="output",
|
||||
description="The XML tag for model final output."
|
||||
)
|
||||
use_thoughts_as_context: bool = Field(
|
||||
default=False,
|
||||
description=("Include previous thought processes as context for the AI. "
|
||||
"Disabled by default.")
|
||||
)
|
||||
pass
|
||||
|
||||
def __init__(self):
|
||||
self.valves = self.Valves()
|
||||
|
||||
def _create_thought_regex(self) -> str:
|
||||
tag = self.valves.thought_tag
|
||||
return f"<{tag}>(.*?)</{tag}>"
|
||||
|
||||
def _create_output_regex(self) -> str:
|
||||
tag = self.valves.output_tag
|
||||
return f"<{tag}>(.*?)</{tag}>"
|
||||
|
||||
def _create_thought_tag_deletion_regex(self) -> str:
|
||||
tag = self.valves.thought_tag
|
||||
return "</?{{THINK}}>[\s\S]*?</{{THINK}}>".replace("{{THINK}}", tag)
|
||||
|
||||
def _create_output_tag_deletion_regex(self) -> str:
|
||||
tag = self.valves.output_tag
|
||||
return r"</?{{OUT}}>[\s\S]*?</{{OUT}}>".replace("{{OUT}}", tag)
|
||||
|
||||
def _enclose_thoughts(self, messages: List[Dict[str, str]]) -> None:
|
||||
if not messages:
|
||||
return
|
||||
|
||||
# collapsible thinking process section
|
||||
thought_regex = self._create_thought_regex()
|
||||
output_regex = self._create_output_regex()
|
||||
reply = messages[-1]["content"]
|
||||
|
||||
thoughts = re.findall(thought_regex, reply, re.DOTALL)
|
||||
thoughts = "\n".join(thoughts).strip()
|
||||
|
||||
output = re.findall(output_regex, reply, re.DOTALL)
|
||||
output = "\n".join(output).strip()
|
||||
|
||||
enclosure = THOUGHT_ENCLOSURE.replace("{{THOUGHT_TITLE}}", self.valves.thought_title)
|
||||
enclosure = enclosure.replace("{{THOUGHTS}}", thoughts).strip()
|
||||
|
||||
# remove processed thinking and output tags.
|
||||
# some models do not close output tags properly.
|
||||
thought_tag_deletion_regex = self._create_thought_tag_deletion_regex()
|
||||
output_tag_deletion_regex = self._create_output_tag_deletion_regex()
|
||||
reply = re.sub(thought_tag_deletion_regex, "", reply, count=1)
|
||||
reply = re.sub(output_tag_deletion_regex, "", reply, count=1)
|
||||
reply = reply.replace(f"<{self.valves.output_tag}>", "", 1)
|
||||
reply = reply.replace(f"</{self.valves.output_tag}>", "", 1)
|
||||
|
||||
# because some models do not close the output tag, we prefer
|
||||
# using the captured output via regex, but if that does not
|
||||
# work, we use whatever's left over as the output (which is
|
||||
# already set).
|
||||
if output is not None and len(output) > 0:
|
||||
reply = output
|
||||
|
||||
# prevents empty thought process blocks when filter used with
|
||||
# malformed LLM output.
|
||||
if len(enclosure) > 0:
|
||||
reply = f"{enclosure}\n{reply}"
|
||||
|
||||
messages[-1]["content"] = reply
|
||||
|
||||
def _handle_include_thoughts(self, messages: List[Dict[str, str]]) -> None:
|
||||
"""Remove <details> tags from input, if configured to do so."""
|
||||
# <details> tags are created by the outlet filter for display
|
||||
# in OWUI.
|
||||
if self.valves.use_thoughts_as_context:
|
||||
return
|
||||
|
||||
for message in messages:
|
||||
message["content"] = re.sub(
|
||||
DETAIL_DELETION_REGEX, "", message["content"], count=1
|
||||
)
|
||||
|
||||
def inlet(self, body: Dict[str, any], __user__: Optional[Dict[str, any]] = None) -> Dict[str, any]:
|
||||
try:
|
||||
original_messages: List[Dict[str, str]] = body.get("messages", [])
|
||||
self._handle_include_thoughts(original_messages)
|
||||
body["messages"] = original_messages
|
||||
return body
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return body
|
||||
|
||||
def outlet(self, body: Dict[str, any], __user__: Optional[Dict[str, any]] = None) -> Dict[str, any]:
|
||||
try:
|
||||
original_messages: List[Dict[str, str]] = body.get("messages", [])
|
||||
self._enclose_thoughts(original_messages)
|
||||
body["messages"] = original_messages
|
||||
return body
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return body
|
Loading…
Reference in New Issue