init
This commit is contained in:
+216
@@ -0,0 +1,216 @@
|
||||
package main
|
||||
|
||||
import "base:runtime"
|
||||
import c "core:c/libc"
|
||||
import "core:bufio"
|
||||
import "core:encoding/json"
|
||||
import "core:fmt"
|
||||
import "core:os"
|
||||
import "core:strings"
|
||||
import curl "vendor:curl"
|
||||
|
||||
API_URL :: "http://localhost:8080/v1/chat/completions"
|
||||
|
||||
REQUEST :: `{
|
||||
"messages": [
|
||||
{
|
||||
"role": "system",
|
||||
"content": "Write one short line spoken by the named NPC. Realize BEAT as dialogue. No narration, labels, explanation, player dialogue, or thinking."
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "NPC: Mira, firm innkeeper.\nBEAT: Refuse lodging; the fixed price is eight silver."
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "{\"dialogue\":\"Eight silver first, or find another roof.\"}"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "NPC: Osric, cowardly toll keeper.\nBEAT: Frightened by Duke Harland; waive the toll and permit passage."
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "{\"dialogue\":\"No toll for His Grace's people. Go through, quickly.\"}"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "NPC: Captain Ronald, gruff, corrupt gate guard.\nSCENE: Night at Oakhaven's closed gate.\nBEAT: $BEAT"
|
||||
}
|
||||
],
|
||||
"temperature": 0.3,
|
||||
"top_p": 0.9,
|
||||
"top_k": 40,
|
||||
"max_tokens": 180,
|
||||
"response_format": {
|
||||
"type": "json_schema",
|
||||
"json_schema": {
|
||||
"name": "npc_line",
|
||||
"strict": true,
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"dialogue": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 140
|
||||
}
|
||||
},
|
||||
"required": ["dialogue"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
Message :: struct {
|
||||
content: string,
|
||||
}
|
||||
|
||||
Choice :: struct {
|
||||
message: Message,
|
||||
}
|
||||
|
||||
Chat_Response :: struct {
|
||||
choices: []Choice,
|
||||
}
|
||||
|
||||
NPC_Line :: struct {
|
||||
dialogue: string,
|
||||
}
|
||||
|
||||
write_response :: proc "c" (
|
||||
data: [^]byte,
|
||||
size, count: c.size_t,
|
||||
user_data: rawptr,
|
||||
) -> c.size_t {
|
||||
context = runtime.default_context()
|
||||
n := int(size * count)
|
||||
response := cast(^[dynamic]byte)user_data
|
||||
append(response, ..data[:n])
|
||||
return c.size_t(n)
|
||||
}
|
||||
|
||||
request_dialogue :: proc(headers: ^curl.slist, beat: string) -> (string, bool) {
|
||||
body, allocated := strings.replace_all(REQUEST, "$BEAT", beat)
|
||||
defer if allocated {
|
||||
delete(body)
|
||||
}
|
||||
body_cstr := strings.clone_to_cstring(body)
|
||||
defer delete(body_cstr)
|
||||
|
||||
response := make([dynamic]byte, 0, 1024)
|
||||
defer delete(response)
|
||||
|
||||
handle := curl.easy_init()
|
||||
if handle == nil {
|
||||
return "", false
|
||||
}
|
||||
defer curl.easy_cleanup(handle)
|
||||
|
||||
curl.easy_setopt(handle, .URL, API_URL)
|
||||
curl.easy_setopt(handle, .HTTPHEADER, headers)
|
||||
curl.easy_setopt(handle, .POSTFIELDS, body_cstr)
|
||||
curl.easy_setopt(handle, .POSTFIELDSIZE, c.long(len(body)))
|
||||
curl.easy_setopt(handle, .WRITEFUNCTION, write_response)
|
||||
curl.easy_setopt(handle, .WRITEDATA, &response)
|
||||
curl.easy_setopt(handle, .TIMEOUT, c.long(30))
|
||||
|
||||
result := curl.easy_perform(handle)
|
||||
if result != .E_OK {
|
||||
fmt.eprintfln("curl: %s", curl.easy_strerror(result))
|
||||
return "", false
|
||||
}
|
||||
|
||||
chat: Chat_Response
|
||||
if json.unmarshal(response[:], &chat) != nil || len(chat.choices) == 0 {
|
||||
fmt.eprintfln("server: %s", string(response[:]))
|
||||
return "", false
|
||||
}
|
||||
defer delete(chat.choices)
|
||||
defer delete(chat.choices[0].message.content)
|
||||
|
||||
line: NPC_Line
|
||||
if json.unmarshal_string(chat.choices[0].message.content, &line) != nil {
|
||||
return "", false
|
||||
}
|
||||
return line.dialogue, true
|
||||
}
|
||||
|
||||
print_menu :: proc() {
|
||||
fmt.println("\n1. Demand passage")
|
||||
fmt.println("2. Offer 20 gold")
|
||||
fmt.println("3. Pay 50 gold")
|
||||
fmt.println("4. Invoke Lady Marwyn")
|
||||
fmt.print("> ")
|
||||
}
|
||||
|
||||
main :: proc() {
|
||||
if curl.global_init(c.long(curl.GLOBAL_DEFAULT)) != .E_OK {
|
||||
fmt.eprintln("Could not initialize libcurl")
|
||||
return
|
||||
}
|
||||
defer curl.global_cleanup()
|
||||
|
||||
headers := curl.slist_append(nil, "Content-Type: application/json")
|
||||
if headers == nil {
|
||||
fmt.eprintln("Could not create HTTP headers")
|
||||
return
|
||||
}
|
||||
defer curl.slist_free_all(headers)
|
||||
|
||||
reader: bufio.Reader
|
||||
bufio.reader_init(&reader, os.to_reader(os.stdin))
|
||||
defer bufio.reader_destroy(&reader)
|
||||
|
||||
fmt.println("Oakhaven, after dark. Captain Ronald blocks the gate.")
|
||||
if line, ok := request_dialogue(headers, "Keep the gate closed; demand exactly fifty gold before allowing passage."); ok {
|
||||
defer delete(line)
|
||||
fmt.printfln("\nRonald: %s", line)
|
||||
} else {
|
||||
fmt.eprintln("LLM request failed. Is llama-server running on port 8080?")
|
||||
return
|
||||
}
|
||||
|
||||
actions := [4]string{
|
||||
"Open the gate. I don't have time for this.",
|
||||
"Take twenty gold and let me through.",
|
||||
"Fine. Here's your fifty gold.",
|
||||
"Lady Marwyn will hear how you delayed her envoy.",
|
||||
}
|
||||
beats := [4]string{
|
||||
"Keep the gate closed; demand exactly fifty gold before allowing passage.",
|
||||
"Keep the gate closed; reject twenty gold and insist on exactly fifty gold.",
|
||||
"Accept exactly fifty gold, open the gate, and permit passage.",
|
||||
"Permit passage without payment. Claim a misunderstanding because Ronald fears Lady Marwyn.",
|
||||
}
|
||||
|
||||
for {
|
||||
print_menu()
|
||||
input, err := bufio.reader_read_string(&reader, '\n')
|
||||
if err != nil && len(input) == 0 {
|
||||
return
|
||||
}
|
||||
defer delete(input)
|
||||
|
||||
if len(input) == 0 || input[0] < '1' || input[0] > '4' {
|
||||
fmt.println("Choose 1-4.")
|
||||
continue
|
||||
}
|
||||
|
||||
choice := int(input[0] - '1')
|
||||
fmt.printfln("\nYou: %s", actions[choice])
|
||||
line, ok := request_dialogue(headers, beats[choice])
|
||||
if !ok {
|
||||
fmt.eprintln("LLM request failed")
|
||||
continue
|
||||
}
|
||||
fmt.printfln("\nRonald: %s", line)
|
||||
delete(line)
|
||||
|
||||
if choice == 2 || choice == 3 {
|
||||
fmt.println("\nThe gate opens.")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user