Initial Commit
commit
e9f6a4a746
|
@ -0,0 +1,4 @@
|
|||
config.json
|
||||
|
||||
__pycache__
|
||||
|
|
@ -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>
|
|
@ -0,0 +1,6 @@
|
|||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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 sys.path.extend([WORKING_DIR_AND_PYTHON_PATHS]) from flask.cli import ScriptInfo locals().update(ScriptInfo(create_app=None).load_app().make_shell_context()) print("Python %s on %s\nApp: %s [%s]\nInstance: %s" % (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 sys.path.extend([WORKING_DIR_AND_PYTHON_PATHS]) from flask.cli import ScriptInfo locals().update(ScriptInfo(create_app=None).load_app().make_shell_context()) print("Python %s on %s\nApp: %s [%s]\nInstance: %s" % (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">{
|
||||
"associatedIndex": 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>
|
|
@ -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"],
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
|
@ -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!
|
||||
#
|
||||
# """
|
||||
|
||||
|
|
@ -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]))
|
||||
|
|
@ -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
|
||||
})
|
||||
|
|
@ -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"
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
from huggingface.serverless import HuggingFaceServerless
|
||||
from huggingface.dedicated import HuggingFaceDedicated
|
|
@ -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]"
|
|
@ -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]"
|
|
@ -0,0 +1 @@
|
|||
from local.vllm import VLLM
|
|
@ -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 ""
|
|
@ -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))
|
||||
# #
|
||||
# #
|
|
@ -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)
|
|
@ -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}}
|
|
@ -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
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,2 @@
|
|||
|
||||
from websearch.websearch import WebSearch
|
|
@ -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 []
|
||||
|
Loading…
Reference in New Issue