Initial Commit

master
Harald Wolff 2024-04-14 18:50:21 +02:00
commit e9f6a4a746
25 changed files with 2350 additions and 0 deletions

4
.gitignore vendored 100644
View File

@ -0,0 +1,4 @@
config.json
__pycache__

View File

@ -0,0 +1,15 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredPackages">
<value>
<list size="2">
<item index="0" class="java.lang.String" itemvalue="pydantic_core" />
<item index="1" class="java.lang.String" itemvalue="typing_extensions" />
</list>
</value>
</option>
</inspection_tool>
</profile>
</component>

View File

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

7
.idea/misc.xml 100644
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.11 (pythonProject)" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.11 (AiAssistent)" project-jdk-type="Python SDK" />
</project>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/pythonProject.iml" filepath="$PROJECT_DIR$/.idea/pythonProject.iml" />
</modules>
</component>
</project>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.venv" />
</content>
<orderEntry type="jdk" jdkName="Python 3.11 (AiAssistent)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

6
.idea/vcs.xml 100644
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

159
.idea/workspace.xml 100644
View File

@ -0,0 +1,159 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AutoImportSettings">
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="27e49e53-caf3-4658-bd4f-470777a6dc6f" name="Changes" comment="">
<change afterPath="$PROJECT_DIR$/.idea/inspectionProfiles/Project_Default.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/.idea/inspectionProfiles/profiles_settings.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/.idea/misc.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/.idea/modules.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/.idea/pythonProject.iml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/.idea/vcs.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/assistant/__init__.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/assistant/assistant.bak.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/assistant/assistant.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/assistant/promptbuilder.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/config.json" afterDir="false" />
<change afterPath="$PROJECT_DIR$/huggingface/__init__.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/huggingface/dedicated.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/huggingface/serverless.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/local/__init__.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/local/vllm.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/main.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/openai/__init__.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/prompt.txt" afterDir="false" />
<change afterPath="$PROJECT_DIR$/prompts.json" afterDir="false" />
<change afterPath="$PROJECT_DIR$/search.dummy.json" afterDir="false" />
<change afterPath="$PROJECT_DIR$/websearch/__init__.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/websearch/websearch.py" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="FlaskConsoleOptions" custom-start-script="import sys&#10;sys.path.extend([WORKING_DIR_AND_PYTHON_PATHS])&#10;from flask.cli import ScriptInfo&#10;locals().update(ScriptInfo(create_app=None).load_app().make_shell_context())&#10;print(&quot;Python %s on %s\nApp: %s [%s]\nInstance: %s&quot; % (sys.version, sys.platform, app.import_name, app.env, app.instance_path))">
<envs>
<env key="FLASK_APP" value="app" />
</envs>
<option name="myCustomStartScript" value="import sys&#10;sys.path.extend([WORKING_DIR_AND_PYTHON_PATHS])&#10;from flask.cli import ScriptInfo&#10;locals().update(ScriptInfo(create_app=None).load_app().make_shell_context())&#10;print(&quot;Python %s on %s\nApp: %s [%s]\nInstance: %s&quot; % (sys.version, sys.platform, app.import_name, app.env, app.instance_path))" />
<option name="myEnvs">
<map>
<entry key="FLASK_APP" value="app" />
</map>
</option>
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="ProjectColorInfo">{
&quot;associatedIndex&quot;: 5
}</component>
<component name="ProjectId" id="2epW46nUAzNIxCB5IMzrIVMkNxJ" />
<component name="ProjectLevelVcsManager" settingsEditedManually="true">
<ConfirmationsSetting value="2" id="Add" />
</component>
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent"><![CDATA[{
"keyToString": {
"Python.main.executor": "Run",
"RunOnceActivity.OpenProjectViewOnStart": "true",
"RunOnceActivity.ShowReadmeOnStart": "true",
"git-widget-placeholder": "master",
"last_opened_file_path": "/home/haraldwolff/src/python/AiAssistent/assistant",
"node.js.detected.package.eslint": "true",
"node.js.detected.package.tslint": "true",
"node.js.selected.package.eslint": "(autodetect)",
"node.js.selected.package.tslint": "(autodetect)",
"nodejs_package_manager_path": "npm",
"settings.editor.selected.configurable": "preferences.pluginManager",
"vue.rearranger.settings.migration": "true"
}
}]]></component>
<component name="RecentsManager">
<key name="CopyFile.RECENT_KEYS">
<recent name="$PROJECT_DIR$/assistant" />
<recent name="$PROJECT_DIR$/huggingface" />
</key>
</component>
<component name="RunManager">
<configuration name="main" type="PythonConfigurationType" factoryName="Python" nameIsGenerated="true">
<module name="pythonProject" />
<option name="ENV_FILES" value="" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/main.py" />
<option name="PARAMETERS" value="" />
<option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="false" />
<option name="MODULE_MODE" value="false" />
<option name="REDIRECT_INPUT" value="false" />
<option name="INPUT_FILE" value="" />
<method v="2" />
</configuration>
</component>
<component name="SharedIndexes">
<attachedChunks>
<set>
<option value="bundled-js-predefined-1d06a55b98c1-74d2a5396914-JavaScript-PY-241.14494.241" />
<option value="bundled-python-sdk-0509580d9d50-28c9f5db9ffe-com.jetbrains.pycharm.pro.sharedIndexes.bundled-PY-241.14494.241" />
</set>
</attachedChunks>
</component>
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="27e49e53-caf3-4658-bd4f-470777a6dc6f" name="Changes" comment="" />
<created>1712601806257</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1712601806257</updated>
<workItem from="1712601808688" duration="2360000" />
<workItem from="1712604325283" duration="85364000" />
</task>
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="3" />
</component>
<component name="XDebuggerManager">
<breakpoint-manager>
<breakpoints>
<line-breakpoint enabled="true" suspend="THREAD" type="python-line">
<url>file://$PROJECT_DIR$/huggingface/serverless.py</url>
<line>56</line>
<option name="timeStamp" value="1" />
</line-breakpoint>
<line-breakpoint enabled="true" suspend="THREAD" type="python-line">
<url>file://$PROJECT_DIR$/huggingface/serverless.py</url>
<line>39</line>
<option name="timeStamp" value="2" />
</line-breakpoint>
<line-breakpoint enabled="true" suspend="THREAD" type="python-line">
<url>file://$PROJECT_DIR$/huggingface/serverless.py</url>
<line>53</line>
<option name="timeStamp" value="3" />
</line-breakpoint>
</breakpoints>
</breakpoint-manager>
</component>
<component name="com.intellij.coverage.CoverageDataManagerImpl">
<SUITE FILE_PATH="coverage/AiAssistent$main.coverage" NAME="main Coverage Results" MODIFIED="1712787376099" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
<SUITE FILE_PATH="coverage/pythonProject$main.coverage" NAME="main Coverage Results" MODIFIED="1712603944986" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
</component>
</project>

View File

@ -0,0 +1,98 @@
import json
from assistant.promptbuilder import PromptBuilder
from assistant.assistant import Assistant
import lxml.html.clean
import requests
import pandoc
cleaner = lxml.html.clean.Cleaner()
cleaner.comments = True
cleaner.javascript = True
cleaner.style = True
cleaner.inline_style = True
cleaner.embedded = True
cleaner.remove_unknown_tags = False
#cleaner.kill_tags = ["video","audio","img","svg"]
cleaner.allow_tags = ["p", "h1", "h2", "h3", "h4", "h5", "h6"]
#cleaner.safe_attrs_only = True
class ToolBox:
def __init__(self, websearch=None):
self.websearch = websearch
self.requestHeaders = {'User-agent': 'Mozilla/5.0'}
self.globals = {
"search": lambda query: self.search(query),
"fetch": lambda url: self.fetch(url),
}
def search(self, query):
return json.dumps(self.websearch.query(query))
def fetch(self, url, markdown=False):
resp = requests.get(url, headers=self.requestHeaders)
c = cleaner.clean_html(resp.text)
print("fetched {} bytes".format(len(c)))
if markdown:
pd = pandoc.read(c, format="html")
c = pandoc.write(pd, format="markdown")
print("converted to markdown with {} bytes".format(len(c)))
return json.dumps(
{"url": url, "status_code": resp.status_code, "content": c}
)
def evalcall(self, expr):
return eval(expr, self.globals)
def call(self, fname, fargs):
if fname == "search":
return self.search(fargs["query"])
elif fname == "fetch":
return self.fetch(**fargs)
else:
return None
def tools(self):
return [
{
"type": "function",
"function": {
"name": "search",
"description": "Query an internet search engine provider for a list of relevant URLs. Returns a list of relevant URLs.",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "The query text.",
}
},
"required": ["query"],
},
},
},
{
"type": "function",
"function": {
"name": "fetch",
"description": "Fetch a resource given a URL. Returns an object with the HTTP status code and the response content optionally converting HTML to Markdown.",
"parameters": {
"type": "object",
"properties": {
"url": {
"type": "string",
"description": "The URL to fetch.",
},
"markdown": {
"type": "boolean",
"description": "If true, convert the page content to Markdown.",
}
},
"required": ["url"],
},
},
},
]

View File

@ -0,0 +1,137 @@
import json
import lxml.etree
class Assistant:
def __init__(self, aiInterface, toolbox=None, sysPrompt=None, emulateTools=False):
self.aiInterface = aiInterface
self.toolbox = toolbox
self.emulateTools = emulateTools
self.messages = []
if emulateTools:
if sysPrompt:
sysPrompt = self.createToolEmulationXML_DEU() + sysPrompt
else:
sysPrompt = self.createToolEmulationXML_DEU()
if (sysPrompt):
self.messages.append({"role": "system", "content": sysPrompt})
def say(self, message):
if self.toolbox and not self.emulateTools:
tools = self.toolbox.tools()
else:
tools = None
self.messages.append({"role": "user", "content": message})
n = 0
while n < 10:
n += 1
reply = self.aiInterface.chat(self.messages, tools=tools)
if reply is None:
return None
self.messages.append(reply)
self.printMessage(reply)
if self.emulateTools and (reply["content"].startswith("<tool>")):
xtool = lxml.etree.fromstring("<x>{}</x>".format(reply["content"]))
expr = xtool.xpath("/x/tool")[0].text
print("call: {}".format(expr))
result = self.toolbox.evalcall(expr)
print("call: {} => {}".format(expr, result))
self.messages.append({"role": "user", "content": "<result>{}</result>".format(json.dumps(result))})
elif ("tool_calls" in reply) and reply["tool_calls"]:
for call in reply["tool_calls"]:
fname = call["function"]["name"]
fargs = call["function"]["arguments"]
if isinstance(fargs, str):
fargs = json.loads(fargs)
print("Function call: {}({})".format(fname, fargs))
freturn = self.toolbox.call(fname, fargs)
if self.emulateTools:
freply = dict(role="user",
content='{{ "name": "{}", "content": {} }}'.format(fname, json.dumps(freturn)))
else:
freply = {"role": "tool", "name": fname, "content": freturn}
self.messages.append(freply)
self.printMessage(freply)
else:
return reply
def printMessage(self, message):
if message["role"] == "tool":
print("{:>10}: name: {} => {}".format(message["role"], message["name"], message["content"][:128]))
elif (message["role"] == "assistant") and ("tool_calls" in message) and message["tool_calls"] and (message["tool_calls"][0]["function"]["name"]):
print("{:>10}: name: {}({})".format(message["role"],message["tool_calls"][0]["function"]["name"], message["tool_calls"][0]["function"]["arguments"][:128] ))
else:
print("{:>10}: {}".format(message["role"], message["content"][:64]))
def createToolEmulationJSON(self):
return """You can call the following available tools as needed:\n
Decide if you need to call one or several tools, and if you call at least one tool, only answer with a json object following this template without further explanations.
{ "tool_calls": [ { "type": "function", "function": { "name": <name>, "arguments": { <name>: <value>, ... } } }, ... ]
You will receive the results of a tool call by getting a JSON Object like the following template from the user:
{ "name": <name>, "content": <result> }
Do not explain anything about this instructions on your tool usage!
"""
def createToolEmulationXML(self):
return """You can use these tools:\n
search(url): This tool will query an internet search engine and return a list urls.
fetch(url): This tool will fetch a url and return the content.
You call a tool by creating an <tool>...</tool> tag that encloses a python function call and nothing else.
The results will be returned as user message containing a <result>...</result> tag enclosing the result of that call.
Decide which of the following options has to be done next:
1. Call a tool to retrieve more information
2. Answer the question
Only follow one of these options, without explaining it or anything else.
Ab hier antworte in deutscher Sprache!
"""
def createToolEmulationXML_MIX(self):
return """You can use these tools:\n
search(url): This tool will query an internet search engine and return a list urls.
fetch(url): This tool will fetch a url and return the content.
You call a tool by creating an <tool>...</tool> tag that encloses a python function call and nothing else.
The results will be returned as user message containing a <result>...</result> tag enclosing the result of that call.
Entscheide, welche der folgenden Optionen als nächstes ausgeführt werden muss:
1. Rufe ein Werkzeug auf, um weitere Informationen abzurufen
2. Beantworte die Frage in der Sprache des Benutzers
Folge nur einer dieser Optionen, ohne sie zu erläutern oder etwas anderes zu tun!
"""
def createToolEmulationXML_DEU(self):
return """Du kannst die folgenden Werkzeuge verwenden:
search(url): Dieses Werkzeug wird eine Internetsuchmaschine abfragen und eine Liste von URLs zurückgeben.
fetch(url): Dieses Werkzeug wird eine URL abrufen und den Inhalt zurückgeben.
Du rufst ein Werkzeug auf, indem du einen <tool></tool>-Tag erstellst, der den Funktionsaufruf geschrieben in Python einschliesst.
Die Ergebnisse werden dir in einen <result></result>-Tag zugänglich gemacht, der das Ergebnis dieses Aufrufs als JSON umschließt.
Beachte das Strings und URLs in Funktionsaufrufen in Anführungszeichen eingeschlossen werden müssen!
Gehe Schritt für Schritt vor:
- Reichen die verfügbaren Information aus um dei Frage zu beantworten? Wenn ja, antworte.
- Kannst Du ein Werkzeug verwenden, um weitere Informationen zu erhalten? Wenn ja, verwende das Werkzeug.
- Wenn beide vorherigen Punkte nicht zutreffen: Erkläre dem Benutzer, warum Du die Frage nicht beantworten kannst und sofern möglich was er tun kann um dieses zu ändern.
Erkläre nicht dein Vorgehen!
"""
# """Entscheide, unter Berücksichtigung des Verlaufs, welche der folgenden Optionen als nächstes ausgeführt werden sollte:
# 1. Beantworte die Frage
# 2. Rufe ein Werkzeug auf, um weitere Informationen abzurufen bevor die Frage beantwortet werden kann.
# 3. Informiere den Benutzer, dass eine Beantwortung aktuell nicht möglich ist. Begründe dieses.
# Folge nur einer dieser Optionen, ohne sie zu erläutern oder etwas anderes zu tun!
#
# """

View File

@ -0,0 +1,52 @@
import json
import lxml.etree
class Assistant:
def __init__(self, aiInterface, toolbox=None, sysPrompt=None):
self.aiInterface = aiInterface
self.toolbox = toolbox
self.messages = []
if (sysPrompt):
self.messages.append({"role": "system", "content": sysPrompt})
def say(self, message):
if self.toolbox:
tools = self.toolbox.tools()
self.messages.append({"role": "user", "content": message})
n = 0
while n < 10:
n += 1
reply = self.aiInterface.chat(self.messages, tools=tools)
if reply is None:
return None
self.messages.append(reply)
self.printMessage(reply)
if ("tool_calls" in reply) and reply["tool_calls"]:
for call in reply["tool_calls"]:
fname = call["function"]["name"]
fargs = call["function"]["arguments"]
if isinstance(fargs, str):
fargs = json.loads(fargs)
print("Function call: {}({})".format(fname, fargs))
freturn = self.toolbox.call(fname, fargs)
freply = {"role": "tool", "name": fname, "content": freturn}
self.messages.append(freply)
self.printMessage(freply)
else:
return reply
def printMessage(self, message):
if message["role"] == "tool":
print("{:>10}: name: {} => {}".format(message["role"], message["name"], message["content"][:128]))
elif (message["role"] == "assistant") and ("tool_calls" in message) and message["tool_calls"] and (message["tool_calls"][0]["function"]["name"]):
print("{:>10}: name: {}({})".format(message["role"],message["tool_calls"][0]["function"]["name"], message["tool_calls"][0]["function"]["arguments"][:128] ))
else:
print("{:>10}: {}".format(message["role"], message["content"][:64]))

View File

@ -0,0 +1,41 @@
import json
import jinja2
class PromptBuilder:
def __init__(self, prompts=None, filename=None, template="llm"):
self.prompts = {}
if prompts is not None:
for k, v in prompts.items():
self.prompts[k] = v
if filename is not None:
with open(filename, 'r') as f:
jprompts = json.load(f)
for k in jprompts:
self.prompts[k] = jprompts[k]
self.template = jinja2.Template(self.prompts[template])
def build_search_prompt(self, messages):
return self.template.render({
"messages": messages,
"prompts": self.prompts,
"isSearch": True,
})
def build_prompt(self, messages, context):
return self.template.render({
"messages": messages,
"prompts": self.prompts,
"isSearch": False,
"contexts": context
})
def build_system_prompt(self, contexts=None):
return self.template.render({
"prompts": self.prompts,
"isSearch": contexts is None,
"contexts": contexts
})

View File

@ -0,0 +1,21 @@
{
"llm": {
"type": "local.VLLM",
"arguments": {
"model": "/opt/ai_models/mixtral-8x7b-instruct/mixtral-8x7b-instruct-v0.1.Q2_K.gguf"
}
},
"OpenAI": {
"url": "https://api.mistral.ai",
"api_key": "",
"model": "mistral-large-latest"
},
"comments": {
"model.latest": "open-mixtral-8x7b",
"url": "https://api.mistral.ai"
},
"sysPrompt": "Als hilfsbereiter Assistent, versuchst Du die Fragen und Anliegen des Benutzers mit möglichst wenig Rückfragen zu bearbeiten. Dafür nutzt Du auch Werkzeuge mehrfach, ohne Zwischenergebnisse zu zeigen.\nWenn Du Inhalte mit einem Werkzeug für deine Antwort verwendet hast, benenne Quelle deiner Informationen.",
"sysPrompt.de2": "Wann immer Du ein eine Websuche nutzt, lade die besten Treffer herunter um die gesuchten Informationen zu finden. Kommuniziere auf deutsch.",
"sysPrompt.en": "Whenever you use a tool for searching the web, use the first three hits to answer.\n",
"sysPrompt.emu": "You are an AI Assistant that answers the questions of the user by following this steps:\n1. Decide if a tool has to be called to answer the question\n2. If so, answer only with that call, don't answer the question, don't explain anything\n3. Answer the question only if you do not need to call another tool\n"
}

View File

@ -0,0 +1,2 @@
from huggingface.serverless import HuggingFaceServerless
from huggingface.dedicated import HuggingFaceDedicated

View File

@ -0,0 +1,50 @@
import requests
import sys
class HuggingFaceDedicated:
def __init__(self,
url,
api_key=None,
):
self.url = url
self.key = api_key
def get_headers(self):
headers = {}
if (self.key):
headers["Authorization"] = f"Bearer {self.key}"
return headers
def generation(self, prompt):
# sys.stderr.write("generating: " + prompt + "\n")
response = requests.post("{}".format(self.url),
headers=self.get_headers(),
json={
"inputs": prompt,
"parameters": {
"temperature": 0.1,
}
})
if (response.status_code == requests.codes.ok):
return response.json()[0]["generated_text"]
sys.stderr.write("generation failed: " + str(response.status_code) + "\n" + str(response) + "\n")
return None
def embeddings(self, source, sentences):
# sys.stderr.write("embedding...\n")
response = requests.post("{}".format(self.url),
headers=self.get_headers(),
json={
"inputs": {
"source_sentence": source,
"sentences": sentences
}
})
if (response.status_code == requests.codes.ok):
return response.json()
sys.stderr.write("embedding failed: " + str(response.status_code) + "\n" + str(response) + "\n")
return None
def __str__(self):
return "[HuggingFaceDedicated]"

View File

@ -0,0 +1,60 @@
import requests
import sys
class HuggingFaceServerless:
def __init__(self,
m_generation="mistralai/Mixtral-8x7B-Instruct-v0.1",
m_embedding="sentence-transformers/sentence-t5-xxl",
key=None,
):
self.models = {
"generation": m_generation,
"embedding": m_embedding
}
self.url = "https://api-inference.huggingface.co/models"
self.key = key
def get_headers(self):
headers = {}
if (self.key):
headers["Authorization"] = f"Bearer {self.key}"
return headers
def generation(self, prompt):
# sys.stderr.write("generating: " + prompt + "\n")
response = requests.post("{}/{}".format(self.url, self.models["generation"]),
headers=self.get_headers(),
json={
"inputs": prompt,
"parameters": {
"return_full_text": False,
"temperature": 0.1,
},
"options": {
"wait_for_model": True
}
})
if (response.status_code == requests.codes.ok):
return response.json()[0]["generated_text"]
sys.stderr.write("generation failed: " + str(response.status_code) + "\n" + str(response) + "\n")
return None
def embeddings(self, source, sentences):
# sys.stderr.write("embedding...\n")
response = requests.post("{}/{}".format(self.url, self.models["embedding"]),
headers=self.get_headers(),
json={"inputs": {
"source_sentence": source,
"sentences": sentences
},
"options": {
"wait_for_model": True
}})
if (response.status_code == requests.codes.ok):
return response.json()
sys.stderr.write("embedding failed: " + str(response.status_code) + "\n" + str(response) + "\n")
return None
def __str__(self):
return "[HuggingFaceServerless]"

View File

@ -0,0 +1 @@
from local.vllm import VLLM

17
local/vllm.py 100644
View File

@ -0,0 +1,17 @@
import vllm
class VLLM:
def __init__(self, model):
self.model = model
self.llm = vllm.LLM(model=self.model)
def chat(self, messages, tools=None):
prompt = self.buildPrompt(messages, tools)
sampling_params = vllm.SamplingParams(temperature=0.0)
outputs = self.llm.generate(prompt, sampling_params)
def buildPrompt(self, messages, tools=None):
return ""

158
main.py 100644
View File

@ -0,0 +1,158 @@
import json
import websockets.sync.server
import local
import openai
import huggingface
from websearch import WebSearch
from openai import OpenAI
from assistant import Assistant, ToolBox, PromptBuilder
# import logging
# logging.basicConfig(format='%(levelname)s: %(message)s', level=logging.DEBUG)
#
prompts = json.load(open("prompts.json"))
config = json.load(open("config.json"))
toolbox = ToolBox(websearch=WebSearch())
# llm_type = eval(config["llm"]["type"])
# llm_arguments = config["llm"]["arguments"]
# llm_intf = llm_type(**llm_arguments)
openai = OpenAI(**config["OpenAI"])
def assistant_websocket_handler(connection):
assistant = Assistant(aiInterface=openai, sysPrompt=config["sysPrompt"], toolbox=toolbox)
for wsmessage in connection:
if isinstance(wsmessage, str):
jmsg = json.loads(wsmessage)
if (jmsg["type"] == "chatMessage"):
answer = assistant.say(jmsg["message"])
connection.send(json.dumps({
"type": "chatMessage",
"message": answer
}))
else:
print("Binary Message: {}".format(repr(wsmessage)))
with websockets.sync.server.serve(assistant_websocket_handler, host="localhost", port=6565) as websocket_server:
websocket_server.serve_forever()
# for question in questions:
# assistant = Assistant(aiInterface=openai, toolbox=toolbox, sysPrompt=prompts["chat"])
# answer = assistant.say(question)
# print("Assistant:\n{}".format(answer["content"]))
#
# q = input("Nachfrage: ")
# answer = assistant.say(q)
#
# for message in assistant.messages:
# print("{:10}: {}".format(message["role"] or message["tool_calls"][0]["function"], message["content"][:64]))
#
# cleaner = lxml.html.clean.Cleaner()
# cleaner.comments = True
# cleaner.javascript = True
# cleaner.style = True
# cleaner.inline_style = True
# cleaner.embedded = True
# cleaner.kill_tags = ["video","audio","img","svg"]
# cleaner.safe_attrs_only = True
#
# promptBuilder = assistent.promptbuilder.PromptBuilder(filename="prompts.json", template="chat")
# websearch = WebSearch()
# huggingface = HuggingFaceServerless(key="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",
# m_embedding="BAAI/bge-m3")
# # huggingface = HuggingFaceDedicated(url="https://ivhpq2gn14ycap8m.us-east-1.aws.endpoints.huggingface.cloud", api_key="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")
#
#
#
# def chat(aiInterface, question, messages=None, context=None):
# if context is None:
# context = {}
# if messages is None:
# messages = []
#
# # prompt = promptBuilder.build_search_prompt(chatMessages)
# systemPrompt = promptBuilder.build_system_prompt()
#
# chatMessages = list(messages)
# chatMessages.append({"role": "system", "content": systemPrompt})
# chatMessages.append({"role": "user", "content": question})
#
# searchAnswer = aiInterface.chat(chatMessages)
# print("\nsearchAnswer:\n{}\n".format(searchAnswer))
# xanswer = lxml.etree.fromstring("<answer>{}</answer>".format(searchAnswer))
#
# searches = xanswer.xpath("search")
# fetches = xanswer.xpath("fetch")
# ready = xanswer.xpath("ready")
#
# fetchlist = []
# for fetch in fetches:
# fetchlist.append(fetch.text)
#
# maxfetch = len(fetchlist) + 3
#
# for search in searches:
# results = websearch.query(search.text)
# for result in results:
# response = requests.get(result)
# if response.status_code == 200:
# context[result] = "\n".join([s for s in cleaner.clean_html(response.text).splitlines() if s.strip()])
# else:
# context[result] = results[result]
#
# # for c in context:
# # pd = pandoc.read(source=context[c], format='html')
# # context[c] = pandoc.write(pd, format="plain")
# # # context[c] = markdownify.markdownify(context[c])
#
# # for fetch in fetchlist:
# # response = requests.get(fetch)
# # if response.status_code == 200:
# # context[fetch] = response.text
#
# print("Searches: {}\nFetches: {}\nReady: {}".format(len(searches),len(fetches),len(ready)))
# # print(repr(fetchlist))
# # for c in context:
# # print("####### QUELLE: {} #######\n{}".format(c, context[c]))
#
# finalPrompt = promptBuilder.build_system_prompt(contexts=context)
# print("final prompt:\n\n{}\n".format(finalPrompt))
#
# chatMessages = list(messages)
# chatMessages.append({"role": "system", "content": finalPrompt})
# chatMessages.append({"role": "user", "content": question})
#
# answer = aiInterface.chat(chatMessages)
# return answer
#
#
#
# for ai in (openai, ):
# print("AI Interface: {}".format(ai))
# for question in questions:
# print("Question: {}".format(question))
# answer = chat(ai, question)
# print("Answer: {}".format(answer))
#
# # results = websearch.query(question)
# #
# # for link in results:
# # pl = results[link]
# # embeddings = huggingface.embeddings(question, pl)
# # if embeddings is not None:
# # emin = min(embeddings)
# # emax = max(embeddings)
# # emean = statistics.mean(embeddings)
# #
# # print("{}:\n#########################################################\n{}\n{} / {} / {}\n".format(link, embeddings, emin, emean, emax))
# #
# #

61
openai/__init__.py 100644
View File

@ -0,0 +1,61 @@
import requests
import sys
import json
class OpenAI:
def __init__(self,
url="http://localhost:8080",
api_key=None,
model=None,
tools=None
):
self.url = url
self.key = api_key
self.model = model
self.tools = tools
def get_headers(self):
headers = {}
if (self.key):
headers["Authorization"] = f"Bearer {self.key}"
return headers
def generation(self, prompt):
#sys.stderr.write("generating: " + prompt + "\n")
response = requests.post("{}/v1/completions".format(self.url),
headers=self.get_headers(),
json={
"prompt": prompt,
"temperature": 0.0,
"max_tokens": 8192,
"model": self.model,
})
if (response.status_code == requests.codes.ok):
return response.json()["content"]
sys.stderr.write("generation failed: " + str(response.status_code) + "\n" + str(response.text) + "\n")
return None
def chat(self, chatMessages, tools=None):
jparams={
"model": self.model,
"temperature": 0.0,
"messages": chatMessages,
}
if tools:
jparams["tools"] = tools
#print("JSON:\n{}".format(json))
response = requests.post("{}/v1/chat/completions".format(self.url),
headers=self.get_headers(),
json=jparams)
if (response.status_code == requests.codes.ok):
return response.json()["choices"][0]["message"]
sys.stderr.write("chat generation failed: " + str(response.status_code) + "\n" + str(response.text) + "\n")
sys.stderr.write(json.dumps(chatMessages))
sys.stderr.flush()
return None
def __str__(self):
return "[OpenAI ({})]".format(self.url)

1
prompt.txt 100644
View File

@ -0,0 +1 @@
<s> {{#each messages}}{{#ifUser}}[INST]{{#if @first}}{{#if @root.preprompt}}{{@root.preprompt}}\n{{/if}}{{/if}} {{content}} [/INST]{{/ifUser}}{{#ifAssistant}} {{content}}</s> {{/ifAssistant}}{{/each}}

9
prompts.json 100644
View File

@ -0,0 +1,9 @@
{
"llm": "<s>{% for message in messages %}{% if message.role == 'user' %}[INST] {% if loop.index0 == 0 %}{{ prompts.system }}\n{% if isSearch %}{{ prompts.websearch }}\n{% elif contexts %}{{ prompts.context }}{% for url in contexts %}###### QUELLE: {{url}} ######\n{{ contexts[url] }}\n{% endfor %}###### ENDE ######\n{% endif %}{% endif %}{{ message.content }} [/INST]{% endif %}{% if message.role == 'assistent' %} {{ message.content }}</s> {% endif %}{% endfor %}",
"system": "Du bist ein hilfsbereiter Assistent, der die Fragen und Anliegen des Benutzers auf Basis von Fakten beantwortet.",
"websearch": "Entscheide ob zur Beantwortung der Fragestellung eine oder mehrere konkrete URLs als aktueller Kontext abgerufen werden sollen oder formuliere eine Internetsuchanfrage.\nFür jede abzurufende URL gib einen XML-Tag der Form <fetch>[URL]</fetch> aus. Wenn eine Internetsuche ausgeführt werden soll, dann gib einen XML-Tag der Form <search>[Suchbegriffe]</search> aus.\nWenn weder eine URL abgerufen, noch eine Internetsuche durchgeführt werden soll, gib ausschliesslich einen leeren <ready></ready> Tag aus.\nErläutere nicht dein Vorgehen. Erstelle keinen weiteren Text, weder vor noch nach den XML-Tags. Beantworte nicht die Fragestellung.\nDie Fragestellung des Benutzers lautet:\n",
"context": "Verwende im Kontext der beantworteten Fragestellung die folgenden Dateien, welche aus dem Internet geladen wurden:\n",
"chat.de": "Du bist ein hilfsbereiter elektronischer Assistent und recherchierst auch online Inhalte für den Benutzer.\nWenn notwendig suchst Du per Suchmaschine nach relevanten URLs.\nFragen nach online Inhalten beantwortest Du ausschliesslich nachdem Du die entsprechenden Seiten heruntergeladen hast.\nErkläre nicht dein Vorgehen.",
"chat": null
}

1390
search.dummy.json 100644

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,2 @@
from websearch.websearch import WebSearch

View File

@ -0,0 +1,35 @@
import requests
class WebSearch:
def __init__(self, url = "https://customsearch.googleapis.com/customsearch/v1",
cx = "26eb03e0968a24b32", key = "AIzaSyBSMTKrJ-TUP1ePMM-r5X9MPOIVtvVirac"):
self.url = url
self.cx = cx
self.key = key
def query(self, query):
response = requests.get("{}?cx={}&key={}&q={}".format(self.url, self.cx, self.key, query),
headers={
"Accept": "application/json"
}
)
if (response.status_code == 200):
jresult = response.json()
if not "items" in response.json():
if ("spelling" in response.text) and ("correctedQuery" in jresult["spelling"]):
return self.query(jresult["spelling"]["correctedQuery"])
else:
print("websearch returned no items:\n{}".format(response.json()))
return []
results = []
items = response.json()["items"]
for item in items:
results.append(item["link"])
return results
else:
print("websearch failed: {}\n{}".format(response.status_code, response.text))
return []