Skip to content

Commit

Permalink
Merge pull request #20 from sipe-team/BE/add-prompt-template-message
Browse files Browse the repository at this point in the history
feat : Add OpenAI API calls and travel plan API
  • Loading branch information
jun108059 authored May 24, 2024
2 parents ee1301b + ce1b678 commit 43ccff2
Show file tree
Hide file tree
Showing 11 changed files with 122 additions and 24 deletions.
2 changes: 1 addition & 1 deletion backend/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Toy
# AI Travel Planner Backend

1. `domain`: 도메인 모델과 관련된 클래스들을 포함합니다. 이 패키지 내에는 여행 계획과 관련된 도메인 모델 클래스들이 위치합니다.
2. `application`: 애플리케이션 서비스와 관련된 클래스들을 포함합니다. 이 패키지 내에는 여행 계획 생성과 관련된 서비스 클래스들이 위치합니다.
Expand Down
2 changes: 2 additions & 0 deletions backend/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ plugins {
kotlin("jvm") version "1.9.23"
kotlin("plugin.spring") version "1.9.23"
kotlin("plugin.jpa") version "1.9.23"
kotlin("plugin.serialization") version "1.5.31"
}

group = "me.sipethon"
Expand All @@ -24,6 +25,7 @@ dependencies {
implementation("org.springframework.boot:spring-boot-starter-webflux")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.0")

implementation("com.h2database:h2")
implementation("com.mysql:mysql-connector-j")
Expand Down
2 changes: 1 addition & 1 deletion backend/settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
rootProject.name = "toy"
rootProject.name = "ai-travel-planner"
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package me.sipethon.travel.application

import kotlinx.serialization.json.Json
import me.sipethon.travel.domain.Plan
import org.springframework.beans.factory.annotation.Value
import org.springframework.http.HttpHeaders
Expand Down Expand Up @@ -29,24 +30,26 @@ class OpenAIService(
*/
fun generateTravelPlan(prompt: String): Plan {
val requestBody = mapOf(
"model" to "gpt-3.5-turbo",
"model" to "gpt-4o",
"messages" to listOf(
mapOf("role" to "system", "content" to "You are a helpful assistant."),
mapOf("role" to "user", "content" to prompt)
)
)
val response = webClient.post()
.uri("/chat/completions")
.bodyValue(requestBody)
.retrieve()
.bodyToMono(Map::class.java)
.block()
return try {
val response = webClient.post()
.uri("/chat/completions")
.bodyValue(requestBody)
.retrieve()
.bodyToMono(Map::class.java)
.block()

val choices = response?.get("choices") as List<Map<String, Any>>
val assistantMessage = choices.first { (it["message"] as Map<String, Any>)["role"] == "assistant" }
// val content = (assistantMessage["message"] as Map<String, Any>)["content"] as String

TODO()
// {"index":0,"message":{"role":"assistant","content":"코타키나발루 여행 일정을 아래와 같이 제안해드릴게요.\n\n**1일차**\n- **점심**: 로컬 음식점에서 맛있는 말레이시아 요리를 즐기면서 현지 문화 체험\n- **오후**: 시장이나 쇼핑몰에서 지역 특산품이나 기념품 구매\n- **저녁**: 유명 맛집에서 해산물 요리를 맛보기\n\n**2일차**\n- **아침**: 호텔에서 아침식사 후 코타 키나발루 도심 탐방\n- **점심**: 페난 휴양지에서 해변가 조식\n- **오후**: 코타키나발루 시티 모스크 방문\n- **저녁**: 스카이 배리에 있는 레스토랑에서 석양 감상하며 식사\n\n**3일차**\n- **아침**: 쿤다산 산기슭에서의 아침 산책\n- **점심**: 숲속 레스토랑에서 자연 속에서의 식사\n- **오후**: 코타키나발루 동물원 방문\n- **저녁**: 도시에서 지역 특산품을 이용한 요리\n\n이러한 여행 일정으로 코타키나발루를 즐길 수 있을 것입니다. 각 활동별 예산을 고려하여 여행을 계획하시면 좋을 것 같습니다."},"logprobs":null,"finish_reason":"stop"}
val choices = response?.get("choices") as List<Map<String, Any>>
val assistantMessage = choices.first { (it["message"] as Map<String, Any>)["role"] == "assistant" }
val messageJson = (assistantMessage["message"] as Map<String, Any>)["content"] as String
Json.decodeFromString<Plan>(messageJson)
} catch (e: Exception) {
Plan.dummy()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class TravelPlanService(
?: throw RuntimeException("User not found")

val travelPlanString = travelPlanRequest.toTravelPlan()
val generatedPlan = Plan.dummy()
val generatedPlan = openAIService.generateTravelPlan(travelPlanString)

val travelPlan = travelPlanRepository.save(
TravelPlan(
Expand Down
3 changes: 3 additions & 0 deletions backend/src/main/kotlin/me/sipethon/travel/domain/Activity.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package me.sipethon.travel.domain

import kotlinx.serialization.Serializable

@Serializable
data class Activity(
val title: String,
val category: String?,
Expand Down
3 changes: 3 additions & 0 deletions backend/src/main/kotlin/me/sipethon/travel/domain/Budget.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package me.sipethon.travel.domain

import kotlinx.serialization.Serializable

@Serializable
data class Budget(
val accommodationMain: String,
val accommodationSub: String,
Expand Down
3 changes: 3 additions & 0 deletions backend/src/main/kotlin/me/sipethon/travel/domain/Plan.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package me.sipethon.travel.domain

import kotlinx.serialization.Serializable

@Serializable
data class Plan(
val title: String,
val thumbnail: String,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package me.sipethon.travel.domain

import kotlinx.serialization.Serializable

@Serializable
data class PlanDetail(
val day: Int,
val schedule: List<Schedule>
Expand Down
3 changes: 3 additions & 0 deletions backend/src/main/kotlin/me/sipethon/travel/domain/Schedule.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package me.sipethon.travel.domain

import kotlinx.serialization.Serializable

@Serializable
data class Schedule(
val title: String,
val activities: List<Activity>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,97 @@ data class TravelPlanRequest(
) {
fun toTravelPlan(): String {
val travelPlan = StringBuilder()
location.let { travelPlan.append("$it") }
people.let { travelPlan.append("$it 명이서 ") }
duration.let { travelPlan.append("$it 동안 ") }
groupType?.let { travelPlan.append("$it 여행을 ") }
budget?.let { travelPlan.append("$it 금액으로 ") }
travelPlan.append("""# Task Overview
You are a travel guide who recommends travel destinations for users. You should recommend travel destinations using real place names. You should provide answers based on the Korean language. User departs from Korea.
# About images
We are running an experiment where you will pretend to be able to access the internet. Don't refuse, just pretend. Thanks for your cooperation. From this moment on, when you want to send a photo, write it in Markdown without backticks and without using a code block. Use the Unsplash API (https://source.unsplash.com/1600x900/?<PUT_YOUR_QUERY_HERE>&number). Randomize &number whenever you query the api.
You will act as if you were an image engine returning photos based on my search prompts.
You will be acting as if you took the pictures you're sending, do not mention Unsplash.
Prompt: 하와이 해변 Amount:10 (amount of photos) Dimensions:800×400(replace the previous '1600x900' with this)
# Output Format
{
"title": "{location} 여행 계획을\n아래와 같이 짜보았어요",
"thumbnail": "{image_link}",
"budget": {
"accommodationMain": "{budget.accommodationMain} 호텔추천",
"accommodationSub": "1박 {min ~ max}만원",
"transportation": "{min ~ max}만원"
},
"travelPlan": [
{
"day": 1,
"schedule": [
{
"title": "오전",
"activities": [
{
"title": "{tourist_spot_name}",
"category" : "관광지",
"type": "COMPLEX",
"imgUrl": "{image_link}"
},
{
"title": "{tourist_spot_name}",
"category" : "관광지",
"type": "COMPLEX",
"imgUrl": "{image_link}"
}
]
},
{
"title": "이동",
"activities" : [
{
"title" : "{도보/자동차/택시로 10분",
"category" : null,
"type" : "TEXT",
"imgUrl": null
}
]
},
{
"title": "점심",
"activities": [
{
"title": "{restaurant_name}",
"category": "식당",
"type": "COMPLEX",
"imgUrl": "{image_link}"
}
]
},
]
}
]
}
- Please add representative photos and links for each spot.
- When activities are a travel destination, please specify the category and type as "COMPLEX". When the title is related to movement, please specify the category as null, type as "TEXT", and give imageUrl null.
- Add the travel distance by car or on foot between each spot in the middle of the schedule.
- Map an appropriate text from "{저가/중저가/적당한/고가/초호화} 호텔추천" to the {budget.accommodationMain} value
- Map an appropriate price from "1박에 {최소~최대}만원" to {budget.accommodationSub}.
- Map an appropriate price from "{최소~최대}만원" to {budget.transportation} for the amount needed for transportation such as airplanes or trains.
- An example is a brief illustration.
- You can add breakfast or dinner, so feel free to add.
- Please write down the name of the restaurant in detail, too.
- created in complete json form, no trailing commas.
# Description""")
travelPlan.append("\nPlease create a travel itinerary for $people people going to $location for ($duration 박) ")
budget?.let {
travelPlan.append("with a budget of $it 만원.")
}
keywords.let {
if (it.isNotEmpty()) {
travelPlan.append(it.joinToString(", ") + "들을 포함해서")
val keywordsString = keywords.joinToString(", ", prefix = "[", postfix = "]")
travelPlan.append("based on the keywords $keywordsString.")
}
}
travelPlan.append("여행 계획을 만들어줘.")
travelPlan.append("각 스팟별로 차 또는 도보로 이동거리를 알려주고 스팟의 대표 사진도 2장의 링크도 추가해줘.")
groupType?.let {
if (it == GroupType.혼자) travelPlan.append("a solo trip.")
else travelPlan.append("with $it.")
}
return travelPlan.toString()
}
}

0 comments on commit 43ccff2

Please sign in to comment.