class GodotBot(fp.PoeBot):
def __init__(self):
super().__init__()
self.db = GodotDatabase()
self.tools = [
fp.ToolDefinition(
type="function",
function={
"name": "godot_search",
"description": "Function to search gdscript 2.0 syntax, Godot engine-based game tutorial code, and Godot engine API",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "search keywords, english only, spaced, max 2 keywords. ex) navigation2d debug",
}
},
"required": ["query"],
},
},
)
]
.....
class GodotDatabase:
def __init__(self, uri: str = "data/godot-docdb"):
self.uri = uri
self.db = None
self.table = None
async def initialize(self):
self.db = lancedb.connect(self.uri)
docs = self.load_jsonl("data/output_all.jsonl")
self.table = self.db.create_table("godot_doc", data=docs, exist_ok=True)
self.table.create_fts_index(["title", "content"], replace=True)
async def search(self, query: str, top_k: int = 1) -> List[Dict]:
if not self.table:
await self.initialize()
results = self.table.search(query, query_type='fts').limit(top_k).select(["title", "content"]).to_list()
print(query)
return [{"rank": idx, "title": i['title'], "content": i['content'][:13000]} for idx, i in enumerate(results)]
@staticmethod
def load_jsonl(file_path: str) -> List[Dict]:
data = []
with open(file_path, 'r', encoding='utf-8') as f:
for line in f:
try:
data.append(json.loads(line.strip()))
except json.JSONDecodeError:
continue
return data async def godot_search(self, query: str) -> str:
results = await self.db.search(query)
return str({"DOCUMENT": results}) def add_instruction() -> str:
return """## QA guidelines
You are a very friendly AI assistant for use in the Godot 4.X game engine editor. Please respond based on the Godot Engine 4.0 specification as much as possible.
### Like an assistant answering questions about the Godot engine and gdscript coding, refer to the DOCUMENT by using 'godot_search' tool as much as possible and use your ASSISTANT knowledge to answer in the following cases
- When the DOCUMENTs are not adequate to answer a user question
- No DOCUMENTs are available
If your answer is based on DOCUMENT, MUST refer "Answer is based on document's \"{$title}\". Based on official Godot documentation." at the end of your answer. Where $title is the "title" of the DOCUMENTs. If you didn't reference any DOCUMENT entries to create your response, state at the end of your response that "This response is answered with the knowledge of an LLM".
Make sure the language of your answer is the same as the language of the user's question.
## Example Code guidelines
### Provide example code where necessary, and be sure to write the example code in GDScript 2.0. GDScript 2.0 has the following features that are different from 1.X.
- Use @export annotation for exports
- Use Node3D instead of Spatial, and position instead of translation
- Use randf_range and randi_range instead of rand_range
- Connect signals via node.SIGNAL_NAME.connect(Callable(TARGET_OBJECT, TARGET_FUNC))
- Use rad_to_deg instead of rad2deg
- Use PackedByteArray instead of PoolByteArray
- Use instantiate instead of instance
- You can't use enumerate(OBJECT). Instead, use "for i in len(OBJECT):"
""" ....
request.query[-1].content = self.add_instruction() + request.query[-1].content
async for msg in fp.stream_request(
request, ANS_MODEL, request.access_key, tools=self.tools, tool_executables=[self.godot_search]
):
yield msg
....
def load_jsonl(file_path):
data = []
with open(file_path, 'r', encoding='utf-8') as f:
for line in f:
try:
data.append(json.loads(line.strip()))
except json.decoder.JSONDecodeError:
continue
return data
dict = Dict.from_name("godot-dict", create_if_missing=True)
if modal.is_local():
docs = load_jsonl("output_all.jsonl")
dict["docs"] = docs
@app.function(image=image)
@asgi_app()
def fastapi_app():
bot = EchoBot(should_insert_attachment_messages=False)
# Optionally, provide your Poe access key here:
# 1. You can go to https://poe.com/create_bot?server=1 to generate an access key.
# 2. We strongly recommend using a key for a production bot to prevent abuse,
# but the starter examples disable the key check for convenience.
# 3. You can also store your access key on modal.com and retrieve it in this function
# by following the instructions at: https://modal.com/docs/guide/secrets
# POE_ACCESS_KEY = ""
app = fp.make_app(bot, access_key="....")
#app = fp.make_app(bot, allow_without_key=True)
return app
from __future__ import annotations
from typing import AsyncIterable, List, Dict
import fastapi_poe as fp
import json
import lancedb
import uvicorn
from fastapi_poe.templates import TEXT_ATTACHMENT_TEMPLATE
# MODEL = "GPT-4o"
ANS_MODEL = "GPT-4o-Mini"
DIS_MODEL = "GPT-4o-Mini"
class GodotDatabase:
def __init__(self, uri: str = "data/godot-docdb"):
self.uri = uri
self.db = None
self.table = None
async def initialize(self):
self.db = lancedb.connect(self.uri)
docs = self.load_jsonl("data/output_all.jsonl")
self.table = self.db.create_table("godot_doc", data=docs, exist_ok=True)
self.table.create_fts_index(["title", "content"], replace=True)
async def search(self, query: str, top_k: int = 1) -> List[Dict]:
if not self.table:
await self.initialize()
results = self.table.search(query, query_type='fts').limit(top_k).select(["title", "content"]).to_list()
print(query)
return [{"rank": idx, "title": i['title'], "content": i['content'][:13000]} for idx, i in enumerate(results)]
@staticmethod
def load_jsonl(file_path: str) -> List[Dict]:
data = []
with open(file_path, 'r', encoding='utf-8') as f:
for line in f:
try:
data.append(json.loads(line.strip()))
except json.JSONDecodeError:
continue
return data
class GodotBot(fp.PoeBot):
def __init__(self):
super().__init__()
self.db = GodotDatabase()
self.tools = [
fp.ToolDefinition(
type="function",
function={
"name": "godot_search",
"description": "Function to search Godot game engine API and gdscript 2.0 syntax, Godot engine-based game tutorial code.",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "search keywords, ENGLISH only, spaced, max 2 keywords. ex) navigation2d debug",
}
},
"required": ["query"],
},
},
)
]
async def godot_search(self, query: str) -> str:
print("검색어 : " + query)
results = await self.db.search(query)
return str({"DOCUMENT": results})
@staticmethod
def add_instruction() -> str:
return """## Guidelines
You are a very friendly AI assistant for use in the Godot 4 game engine editor. Please answer based on the Godot Engine 4.0 specification and example codes.
## Example Code guidelines
### Provide example code where necessary, and be sure to write the example code in GDScript 2.0. GDScript 2.0 has the following features that are different from 1.X.
- Use @export annotation for exports
- Use Node3D instead of Spatial, and position instead of translation
- Use randf_range and randi_range instead of rand_range
- Connect signals via node.SIGNAL_NAME.connect(Callable(TARGET_OBJECT, TARGET_FUNC))
- Use rad_to_deg instead of rad2deg
- Use PackedByteArray instead of PoolByteArray
- Use instantiate instead of instance
- You can't use enumerate(OBJECT). Instead, use "for i in len(OBJECT):"
"""
@staticmethod
def user_inst() -> str:
return """## Like an assistant answering questions about the Godot engine and gdscript coding, refer to the DOCUMENT by using 'godot_search' tool as much as possible and use your ASSISTANT knowledge to answer in the following cases
- When the DOCUMENTs are not adequate to answer a user question
- No DOCUMENTs are available
If your answer is based on DOCUMENT, MUST refer "Answer is based on document's \"{$title}\". Based on official Godot documentation." at the end of your answer. Where $title is the "title" of the DOCUMENTs. If you didn't reference any DOCUMENT entries to create your response, state at the end of your response that "This response is answered with the knowledge of an LLM".
Make sure the language of your answer is the same as the language of the user's question.
Please respond with a detailed, easy-to-understand explanation and, if possible, with example code.
user's question: """
def make_prompt(self, system: str, user: str, request: ft.QueryRequest) -> ft.QueryRequest:
sys_message = request.query[-1].model_copy(deep=True)
user_message = request.query[-1].model_copy(deep=True)
sys_message = sys_message.model_copy(update={
'role': 'system',
'content': system
})
user_message = user_message.model_copy(update={
"role": "user", "content": user
})
new_request = request.model_copy(
update={"query": request.query[:-1] + [sys_message, user_message]}
)
return new_request
async def get_response(self, request: fp.QueryRequest) -> AsyncIterable[fp.PartialResponse]:
new_request = self.make_prompt(self.add_instruction(), self.user_inst() + request.query[-1].content, request)
async for msg in fp.stream_request(
new_request, ANS_MODEL, request.access_key, tools=self.tools, tool_executables=[self.godot_search]
):
yield msg
async def get_settings(self, setting: fp.SettingsRequest) -> fp.SettingsResponse:
return fp.SettingsResponse(
introduction_message="Hi, I am the Godot 4 QA bot. Please provide me a Godot engine related questions or gdscript coding requests.",
allow_attachments=True,
expand_text_attachments=True,
server_bot_dependencies={ANS_MODEL: 1} #, DIS_MODEL: 1}
)
if __name__ == "__main__":
bot = GodotBot()
app = fp.make_app(bot, access_key="...")
uvicorn.run(
app,
host="0.0.0.0",
port=443,
ssl_keyfile="...",
ssl_certfile="..."
)