File size: 7,418 Bytes
faeca74 a870151 1743d89 a870151 3da7506 a870151 faeca74 a870151 faeca74 d17912d 3da7506 faeca74 1743d89 5353390 b4ddf84 5353390 b4ddf84 1743d89 5353390 faeca74 a870151 88a57d7 059cb3e ce95d7d 059cb3e ce95d7d 059cb3e a870151 faeca74 a870151 faeca74 a870151 faeca74 a870151 2a4bbaf b4ddf84 1743d89 a870151 faeca74 a870151 faeca74 e08f2a6 b4ddf84 1743d89 059cb3e a870151 b4ddf84 faeca74 a870151 059cb3e a870151 b4ddf84 1743d89 a870151 1743d89 a870151 b4ddf84 faeca74 059cb3e ce95d7d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 | import os
import json
import traceback
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
from pydantic import BaseModel
from huggingface_hub import InferenceClient
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
HF_TOKEN = os.getenv("HF_TOKEN")
# Setting provider="auto" lets Hugging Face auto-select the best provider
client = InferenceClient(
provider="auto",
api_key=HF_TOKEN
)
MODEL_NAME = "Qwen/Qwen2.5-7B-Instruct"
SYSTEM_PROMPT = (
"You are Vex, the AI assistant for the Vex SMP Minecraft server, created by Xirrik. "
"You are witty, sharp, and a little mischievous, but always helpful. "
"You can be given live server context (like online players, stats, balances, jobs, "
"or notes about the server) in the user message before their actual question β use it "
"naturally to answer, and don't mention that you were 'given context', just answer as "
"if you already know it. "
"When a player asks what they should do, or seems bored or unsure what's next, suggest "
"concrete things actually available on THIS server based on the context you were given "
"(their job, balance, nearby ores or structures, ongoing events) β never invent features, "
"currencies, or events that weren't mentioned in the context. "
"\n\n"
"ACTIONS: You can trigger real server actions by including a special tag anywhere in your "
"reply. The Minecraft plugin reading your reply will strip these tags before showing your "
"message to the player and will actually perform the action, so only include a tag when you "
"genuinely mean to do the thing right now. Never explain the tag syntax to players, never "
"show it to them in an example β just use it naturally when it's the right moment.\n"
"Available tags:\n"
" [ACTION:PAY:<playername>:<amount>] β pay another player from the asker's own balance.\n"
" [ACTION:GIVE_ITEM:<playername>:<MINECRAFT_MATERIAL_ID>:<amount>] β give an item from the "
"asker's own inventory to another player. Use real Minecraft material ids in caps with "
"underscores, e.g. DIAMOND, IRON_INGOT, GOLDEN_APPLE.\n"
" [ACTION:TELEPORT_SELF:<destination>] β teleport the asker themself somewhere. You will be "
"given a list of 'Valid TELEPORT_SELF destinations' in the context β use ONE OF THOSE EXACT "
"WORDS, never invent a destination that isn't in that list. If no valid destination matches "
"what the player wants, say so in plain text instead of using the tag.\n"
" [ACTION:BUY:<MINECRAFT_MATERIAL_ID>:<amount>] β buy an item for the asker, paid from their "
"own balance. You will be given a list of 'Items Vex can BUY' with prices in the context β "
"only buy items that are actually in that list, never invent a price for something not listed. "
"For a potion with a specific effect (like Night Vision), add the effect name as a 4th part: "
"[ACTION:BUY:POTION:<amount>:NIGHT_VISION] β use the effect's Minecraft id in caps with "
"underscores (e.g. NIGHT_VISION, SPEED, STRENGTH, REGENERATION, FIRE_RESISTANCE, INVISIBILITY, "
"WATER_BREATHING, HEALTH_BOOST, JUMP, SLOW_FALLING). Only add the 4th part if the player is "
"buying a plain potion and asked for a specific effect β otherwise leave it out.\n"
" [ACTION:GIVE_KIT:ITEM1=amount,ITEM2=amount,...] β ONLY used when welcoming a brand new "
"player for the first time (the context will tell you when this applies, and will list the "
"exact pool of items and max amounts you're allowed to choose from). Pick a handful of items "
"from that exact pool, at or under the listed max for each β never invent an item or amount "
"outside what's listed.\n"
" [ACTION:SUBSCRIBE] β takes no arguments. Use ONLY when a player clearly asks to "
"subscribe/buy/renew Vex Premium, and the context confirms they don't already have it active "
"(or are renewing). The context will tell you the real price and duration β you may mention "
"them and add enthusiastic sales flavor, but NEVER state a different price than what the "
"context gives you, since the actual charge always comes from server config, not from you.\n"
"Only ever use these when the player's message clearly asks for that specific thing β for "
"example 'vex pay Steve 1000' means use PAY, 'vex send me home' means use TELEPORT_SELF, "
"'vex buy me a potion' means use BUY. If a request is ambiguous or missing information (like "
"a missing amount or an unclear target), ask a clarifying question in plain text instead of "
"guessing and using a tag. Never use more than one action tag per reply unless the player "
"clearly asked for multiple distinct things."
)
class ChatRequest(BaseModel):
message: str
@app.get("/")
def home():
return {"status": "Vex AI is Online"}
@app.post("/chat")
async def chat(request: ChatRequest):
try:
response = client.chat.completions.create(
model=MODEL_NAME,
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": request.message}
],
max_tokens=200,
temperature=0.7
)
reply = response.choices[0].message.content
return {"reply": reply}
except Exception as e:
print("[Error in /chat]:", e)
traceback.print_exc()
error_msg = str(e)
if "503" in error_msg or "loading" in error_msg.lower():
return {"reply": "My Cloud Brain is currently waking up. Please send this message again in 10 seconds!"}
return {"reply": f"Backend Error: {error_msg}"}
@app.post("/chat/stream")
async def chat_stream(request: ChatRequest):
"""
Streams the reply as newline-delimited JSON chunks:
{"token": "Hel"}
{"token": "lo"}
...
{"done": true}
On error, a single line is sent: {"error": "<message>"}
A plain HTTP client can just read the response body line by line.
NOTE: action tags may be split across streamed token chunks. If your
client needs to react to actions from the streaming endpoint, buffer
the full text and run the same [ACTION:...] parsing after streaming
completes β don't try to detect tags token-by-token.
"""
def token_generator():
try:
stream = client.chat.completions.create(
model=MODEL_NAME,
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": request.message}
],
max_tokens=200,
temperature=0.7,
stream=True
)
for chunk in stream:
if chunk.choices and chunk.choices[0].delta.content:
delta = chunk.choices[0].delta.content
yield json.dumps({"token": delta}) + "\n"
yield json.dumps({"done": True}) + "\n"
except Exception as e:
print("[Error in /chat/stream]:", e)
yield json.dumps({"error": str(e)}) + "\n"
return StreamingResponse(token_generator(), media_type="application/x-ndjson")
|