ソースを参照

Create route to setting up a session

Lee Morgan 1 ヶ月 前
コミット
abf4579541

+ 1 - 2
sql/characters.sql

@@ -1,7 +1,6 @@
 CREATE TABLE IF NOT EXISTS characters(
     id UUID PRIMARY KEY,
-
-    game_id UUID NOT NULL REFERENCES games(id),
+    game_id UUID NOT NULL REFERENCES games(id) ON DELETE CASCADE,
     name TEXT NOT NULL,
     description TEXT NOT NULL
 );

+ 1 - 1
sql/sessions.sql

@@ -1,6 +1,6 @@
 CREATE TABLE IF NOT EXISTS sessions(
     id UUID PRIMARY KEY,
-    game_id UUID NOT NULL REFERENCES games(id),
+    game_id UUID NOT NULL REFERENCES games(id) ON DELETE CASCADE,
     summary TEXT NULL,
     started_at TIMESTAMPTZ NOT NULL,
     ended_at TIMESTAMPTZ NULL

+ 3 - 3
sql/turns.sql

@@ -1,8 +1,8 @@
 CREATE TABLE IF NOT EXISTS turns(
     id UUID PRIMARY KEY,
-    session_id UUID NOT NULL REFERENCES sessions(id),
+    session_id UUID NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
     user_text TEXT NOT NULL,
-    llm_text TEXT NULL,
-    sequence_number INTEGER NOT NULL,
+    llm_text TEXT NOT NULL,
+    tokens_used INTEGER NOT NULL,
     created_at TIMESTAMPTZ NOT NULL
 );

+ 43 - 8
src/controllers/game/session/create.rs

@@ -1,16 +1,30 @@
 use actix_web::{HttpResponse, HttpRequest, web, post};
 use sqlx::PgPool;
 use uuid::Uuid;
+use serde::Serialize;
 use crate::{
     http_error::HttpError,
     auth::user_auth,
     models::{
+        user::User,
         game::Game,
         character::Character,
-        session::Session
+        session::Session,
+        turn::Turn
+    },
+    logic::prompt::{
+        PromptMessage,
+        PromptRole,
+        send_message
     }
 };
 
+#[derive(Serialize)]
+struct Response {
+    session: Session,
+    turn: Turn
+}
+
 #[post("/game/{game_id}/session")]
 pub async fn route(
     db: web::Data<PgPool>,
@@ -19,17 +33,32 @@ pub async fn route(
 ) -> Result<HttpResponse, HttpError> {
     let user = user_auth(&db, &req, true).await?;
     let game_id = Uuid::parse_str(&path.into_inner())?;
-    let (game, mut characters, sessions) = Game::get_full_game(&db, game_id, Some(user.id)).await?;
-    let text = new_session_text(game.game_context.clone(), characters.remove(0), sessions);
+    let (game, mut characters, sessions) = Game::get_full_game(&db, game_id.clone(), Some(user.id)).await?;
+    let prompt = new_session_prompt(game.game_context.clone(), characters.remove(0), sessions);
+    let response = send_message(prompt).await?;
+    let session = Session::new(game_id);
+    let turn = Turn::new(
+        session.id,
+        response.input_tokens,
+        response.output_tokens,
+        "".to_string(),
+        response.message
+    );
+    session.insert(&db).await?;
+    turn.insert(&db).await?;
+    User::add_tokens(&db, user.id, -turn.tokens_used).await?;
 
-    Ok(HttpResponse::Ok().json(game))
+    Ok(HttpResponse::Ok().json(Response {
+        session: session,
+        turn: turn
+    }))
 }
 
-fn new_session_text(
+fn new_session_prompt(
     game_context: String,
     character: Character,
     sessions: Vec<Session>
-) -> String {
+) -> Vec<PromptMessage> {
     let mut text = "".to_string();
 
     text += "You are acting as a game master for an RPG and running the game.\n";
@@ -44,11 +73,17 @@ fn new_session_text(
         text += "This is the first session in this game, there have been no previous sessions\n";
     } else {
         for (i, session) in sessions.iter().enumerate() {
-            text += &format!("Summary of session {}: {}\n", i, session.summary);
+            match &session.summary {
+                Some(s) => {text += &format!("Summary of session {}: {}\n", i, s)},
+                None => ()
+            }
         }
     }
 
     text += "Start off a new session for the player";
 
-    text
+    vec![PromptMessage {
+        role: PromptRole::System,
+        content: text
+    }]
 }

+ 15 - 17
src/logic/prompt.rs

@@ -8,8 +8,8 @@ use crate::{
 
 #[derive(Serialize)]
 pub struct PromptMessage {
-    role: PromptRole,
-    content: String
+    pub role: PromptRole,
+    pub content: String
 }
 
 #[derive(Serialize)]
@@ -20,27 +20,24 @@ pub enum PromptRole {
     Assistant
 }
 
-impl PromptRole {
-    fn to_string(&self) -> String {
-        match self {
-            PromptRole::System => "system".to_string(),
-            PromptRole::User => "user".to_string(),
-            PromptRole::Assistant => "assistant".to_string()
-        }
-    }
+pub struct PromptResponse {
+    pub message: String,
+    pub input_tokens: i32,
+    pub output_tokens: i32
 }
 
-pub async fn send_message(prompt: Vec<PromptMessage>) -> Result<String, HttpError> {
+pub async fn send_message(prompt: Vec<PromptMessage>) -> Result<PromptResponse, HttpError> {
     let client = Client::new();
     let key = DEEPINFRA_TOKEN.get().unwrap();
 
     let body = json!({
         "model": "Qwen/Qwen3-32B",
-        "messages": prompt
+        "messages": prompt,
+        "reasoning_effort": "none"
     });
 
     let response = client
-        .post("https://api.deepinfra.com/v1/openai/chat/completions")
+        .post("https://api.deepinfra.com/v1/chat/completions")
         .header("Authorization", format!("Bearer {}", key))
         .header("Content-Type", "application/json")
         .json(&body)
@@ -53,8 +50,9 @@ pub async fn send_message(prompt: Vec<PromptMessage>) -> Result<String, HttpErro
         .await
         .map_err(|e| HttpError::InternalError(e.to_string()))?;
 
-    let content = &data["choices"][0]["message"]["content"];
-    println!("{}", content);
-
-    Ok(content.to_string())
+    Ok(PromptResponse {
+        message: (&data["choices"][0]["message"]["content"]).to_string(),
+        input_tokens: data["usage"]["prompt_tokens"].as_i64().unwrap_or(0) as i32,
+        output_tokens: data["usage"]["completion_tokens"].as_i64().unwrap_or(0) as i32
+    })
 }

+ 34 - 4
src/models/session.rs

@@ -1,12 +1,42 @@
 use uuid::Uuid;
 use chrono::{DateTime, Utc};
-use sqlx::FromRow;
+use sqlx::{PgPool, FromRow};
+use serde::Serialize;
+use crate::http_error::HttpError;
 
-#[derive(FromRow)]
+#[derive(Serialize, FromRow)]
 pub struct Session {
     pub id: Uuid,
     pub game_id: Uuid,
-    pub summary: String,
+    pub summary: Option<String>,
     pub started_at: DateTime<Utc>,
-    pub ended_at: DateTime<Utc>
+    pub ended_at: Option<DateTime<Utc>>,
+}
+
+impl Session {
+    pub fn new(game_id: Uuid) -> Self {
+        Self {
+            id: Uuid::now_v7(),
+            game_id: game_id,
+            summary: None,
+            started_at: Utc::now(),
+            ended_at: None
+        }
+    }
+
+    pub async fn insert(&self, db: &PgPool) -> Result<(), HttpError> {
+        sqlx::query(
+            "INSERT INTO sessions(id, game_id, summary, started_at, ended_at)
+            VALUES($1, $2, $3, $4, $5)"
+        )
+            .bind(&self.id)
+            .bind(&self.game_id)
+            .bind(&self.summary)
+            .bind(&self.started_at)
+            .bind(&self.ended_at)
+            .execute(db)
+            .await?;
+
+        Ok(())
+    }
 }

+ 42 - 2
src/models/turn.rs

@@ -1,11 +1,51 @@
 use uuid::Uuid;
 use chrono::{DateTime, Utc};
+use sqlx::PgPool;
+use serde::Serialize;
+use crate::http_error::HttpError;
 
+#[derive(Serialize)]
 pub struct Turn {
     pub id: Uuid,
     pub session_id: Uuid,
+    pub tokens_used: i32,
     pub user_text: String,
-    pub llm_text: Option<String>,
-    pub sequence_number: i32,
+    pub llm_text: String,
     pub created_at: DateTime<Utc>
 }
+
+impl Turn {
+    pub fn new(
+        session_id: Uuid,
+        input_tokens: i32,
+        output_tokens: i32,
+        user_text: String,
+        llm_text: String
+    ) -> Self {
+        Self {
+            id: Uuid::now_v7(),
+            session_id: session_id,
+            tokens_used: input_tokens + (output_tokens * 2),
+            user_text: user_text,
+            llm_text: llm_text,
+            created_at: Utc::now()
+        }
+    }
+
+    pub async fn insert(&self, db: &PgPool) -> Result<(), HttpError> {
+        sqlx::query(
+            "INSERT INTO turns(id, session_id, tokens_used, user_text, llm_text, created_at)
+            VALUES($1, $2, $3, $4, $5, $6)"
+        )
+            .bind(&self.id)
+            .bind(&self.session_id)
+            .bind(&self.tokens_used)
+            .bind(&self.user_text)
+            .bind(&self.llm_text)
+            .bind(&self.created_at)
+            .execute(db)
+            .await?;
+
+        Ok(())
+    }
+}