Spaces:
Sleeping
Sleeping
| # ChatBIA ์๋๋ก์ด๋ ์คํธ๋ฆฌ๋ฐ ์ฐ๋ ๊ฐ์ด๋ | |
| ## ๐ก API ์๋ํฌ์ธํธ | |
| ### 1. ์ผ๋ฐ ์ฑํ (๋น์คํธ๋ฆฌ๋ฐ) | |
| ``` | |
| POST /chat | |
| ``` | |
| - **ํ์์์ ์ํ**: ๊ธด ์๋ต ์ ํ์์์ ๋ฐ์ ๊ฐ๋ฅ | |
| - **์๋๋ก์ด๋์์ ๊ถ์ฅํ์ง ์์** | |
| ### 2. ์คํธ๋ฆฌ๋ฐ ์ฑํ โ **๊ถ์ฅ** | |
| ``` | |
| POST /chat/stream | |
| ``` | |
| - **ํ์์์ ๋ฐฉ์ง**: ํ ํฐ ๋จ์๋ก ์ค์๊ฐ ์์ | |
| - **์๋๋ก์ด๋์ ์ต์ ํ** | |
| - **SSE (Server-Sent Events)** ๋ฐฉ์ | |
| --- | |
| ## ๐ง ์๋๋ก์ด๋ ๊ตฌํ (Kotlin) | |
| ### 1. build.gradle ์์กด์ฑ ์ถ๊ฐ | |
| ```gradle | |
| dependencies { | |
| // OkHttp for SSE streaming | |
| implementation("com.squareup.okhttp3:okhttp:4.12.0") | |
| implementation("com.squareup.okhttp3:okhttp-sse:4.12.0") | |
| // JSON ํ์ฑ | |
| implementation("com.google.code.gson:gson:2.10.1") | |
| // Coroutines | |
| implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3") | |
| } | |
| ``` | |
| ### 2. ๋ฐ์ดํฐ ๋ชจ๋ธ | |
| ```kotlin | |
| // ChatRequest.kt | |
| data class ChatRequest( | |
| val message: String, | |
| val mode: String = "bsl", // "bsl" or "general" | |
| val max_tokens: Int = 1024, | |
| val temperature: Float = 0.7f | |
| ) | |
| // StreamingResponse.kt | |
| data class StreamingResponse( | |
| val token: String = "", | |
| val done: Boolean = false, | |
| val token_count: Int = 0, | |
| val mode: String = "", | |
| val error: String? = null | |
| ) | |
| ``` | |
| ### 3. ChatBIA API ํด๋ผ์ด์ธํธ | |
| ```kotlin | |
| // ChatBiaApiClient.kt | |
| import com.google.gson.Gson | |
| import kotlinx.coroutines.flow.Flow | |
| import kotlinx.coroutines.flow.flow | |
| import okhttp3.* | |
| import okhttp3.MediaType.Companion.toMediaType | |
| import okhttp3.RequestBody.Companion.toRequestBody | |
| import okhttp3.sse.EventSource | |
| import okhttp3.sse.EventSourceListener | |
| import okhttp3.sse.EventSources | |
| import kotlin.coroutines.resume | |
| import kotlin.coroutines.resumeWithException | |
| import kotlin.coroutines.suspendCoroutine | |
| class ChatBiaApiClient(private val baseUrl: String) { | |
| private val client = OkHttpClient.Builder() | |
| .connectTimeout(30, TimeUnit.SECONDS) | |
| .readTimeout(60, TimeUnit.SECONDS) // ์คํธ๋ฆฌ๋ฐ์ ๊ธด ํ์์์ | |
| .writeTimeout(30, TimeUnit.SECONDS) | |
| .build() | |
| private val gson = Gson() | |
| /** | |
| * ์คํธ๋ฆฌ๋ฐ ์ฑํ (๊ถ์ฅ) | |
| * Flow๋ฅผ ํตํด ํ ํฐ ๋จ์๋ก ์ค์๊ฐ ์์ | |
| */ | |
| fun chatStream(request: ChatRequest): Flow<StreamingResponse> = flow { | |
| suspendCoroutine<Unit> { continuation -> | |
| val url = "$baseUrl/chat/stream" | |
| // JSON ์์ฒญ body | |
| val jsonBody = gson.toJson(request) | |
| val requestBody = jsonBody.toRequestBody("application/json".toMediaType()) | |
| val httpRequest = Request.Builder() | |
| .url(url) | |
| .post(requestBody) | |
| .addHeader("Accept", "text/event-stream") | |
| .build() | |
| // SSE EventSource ์์ฑ | |
| val eventSource = EventSources.createFactory(client) | |
| .newEventSource(httpRequest, object : EventSourceListener() { | |
| override fun onOpen(eventSource: EventSource, response: Response) { | |
| // ์ฐ๊ฒฐ ์ฑ๊ณต | |
| } | |
| override fun onEvent( | |
| eventSource: EventSource, | |
| id: String?, | |
| type: String?, | |
| data: String | |
| ) { | |
| try { | |
| val response = gson.fromJson(data, StreamingResponse::class.java) | |
| // Flow๋ก emit | |
| trySend(response) | |
| // ์๋ฃ ์ ์ฐ๊ฒฐ ์ข ๋ฃ | |
| if (response.done) { | |
| eventSource.cancel() | |
| continuation.resume(Unit) | |
| } | |
| } catch (e: Exception) { | |
| eventSource.cancel() | |
| continuation.resumeWithException(e) | |
| } | |
| } | |
| override fun onFailure( | |
| eventSource: EventSource, | |
| t: Throwable?, | |
| response: Response? | |
| ) { | |
| continuation.resumeWithException( | |
| t ?: Exception("SSE ์ฐ๊ฒฐ ์คํจ: ${response?.code}") | |
| ) | |
| } | |
| override fun onClosed(eventSource: EventSource) { | |
| if (!continuation.isCompleted) { | |
| continuation.resume(Unit) | |
| } | |
| } | |
| }) | |
| } | |
| } | |
| /** | |
| * ์ผ๋ฐ ์ฑํ (๋น์คํธ๋ฆฌ๋ฐ) | |
| * ๊ธด ์๋ต ์ ํ์์์ ์ํ ์์ | |
| */ | |
| suspend fun chat(request: ChatRequest): ChatResponse = suspendCoroutine { continuation -> | |
| val url = "$baseUrl/chat" | |
| val jsonBody = gson.toJson(request) | |
| val requestBody = jsonBody.toRequestBody("application/json".toMediaType()) | |
| val httpRequest = Request.Builder() | |
| .url(url) | |
| .post(requestBody) | |
| .build() | |
| client.newCall(httpRequest).enqueue(object : Callback { | |
| override fun onFailure(call: Call, e: IOException) { | |
| continuation.resumeWithException(e) | |
| } | |
| override fun onResponse(call: Call, response: Response) { | |
| if (response.isSuccessful) { | |
| val body = response.body?.string() | |
| val chatResponse = gson.fromJson(body, ChatResponse::class.java) | |
| continuation.resume(chatResponse) | |
| } else { | |
| continuation.resumeWithException( | |
| Exception("HTTP ${response.code}: ${response.message}") | |
| ) | |
| } | |
| } | |
| }) | |
| } | |
| data class ChatResponse( | |
| val response: String, | |
| val mode: String, | |
| val tokens: Int | |
| ) | |
| } | |
| ``` | |
| ### 4. ViewModel ์ฌ์ฉ ์์ | |
| ```kotlin | |
| // ChatViewModel.kt | |
| import androidx.lifecycle.ViewModel | |
| import androidx.lifecycle.viewModelScope | |
| import kotlinx.coroutines.flow.MutableStateFlow | |
| import kotlinx.coroutines.flow.StateFlow | |
| import kotlinx.coroutines.flow.catch | |
| import kotlinx.coroutines.launch | |
| class ChatViewModel : ViewModel() { | |
| private val apiClient = ChatBiaApiClient("https://your-hf-space.hf.space") | |
| private val _chatState = MutableStateFlow("") | |
| val chatState: StateFlow<String> = _chatState | |
| private val _isLoading = MutableStateFlow(false) | |
| val isLoading: StateFlow<Boolean> = _isLoading | |
| /** | |
| * ์คํธ๋ฆฌ๋ฐ ์ฑํ ์ ์ก | |
| */ | |
| fun sendStreamingMessage(message: String, mode: String = "bsl") { | |
| viewModelScope.launch { | |
| _isLoading.value = true | |
| _chatState.value = "" // ์ด๊ธฐํ | |
| val request = ChatRequest( | |
| message = message, | |
| mode = mode, | |
| max_tokens = 1024, | |
| temperature = 0.7f | |
| ) | |
| apiClient.chatStream(request) | |
| .catch { e -> | |
| _chatState.value = "์ค๋ฅ: ${e.message}" | |
| _isLoading.value = false | |
| } | |
| .collect { response -> | |
| if (response.error != null) { | |
| _chatState.value = "์๋ฒ ์ค๋ฅ: ${response.error}" | |
| _isLoading.value = false | |
| } else if (response.done) { | |
| // ์๋ฃ | |
| _isLoading.value = false | |
| } else { | |
| // ํ ํฐ ์ถ๊ฐ | |
| _chatState.value += response.token | |
| } | |
| } | |
| } | |
| } | |
| } | |
| ``` | |
| ### 5. Compose UI ์์ | |
| ```kotlin | |
| // ChatScreen.kt | |
| import androidx.compose.foundation.layout.* | |
| import androidx.compose.material3.* | |
| import androidx.compose.runtime.* | |
| import androidx.compose.ui.Modifier | |
| import androidx.compose.ui.unit.dp | |
| @Composable | |
| fun ChatScreen(viewModel: ChatViewModel = viewModel()) { | |
| val chatState by viewModel.chatState.collectAsState() | |
| val isLoading by viewModel.isLoading.collectAsState() | |
| var inputText by remember { mutableStateOf("") } | |
| Column( | |
| modifier = Modifier | |
| .fillMaxSize() | |
| .padding(16.dp) | |
| ) { | |
| // ์ฑํ ์ถ๋ ฅ | |
| Card( | |
| modifier = Modifier | |
| .fillMaxWidth() | |
| .weight(1f) | |
| ) { | |
| Text( | |
| text = chatState, | |
| modifier = Modifier.padding(16.dp) | |
| ) | |
| } | |
| Spacer(modifier = Modifier.height(16.dp)) | |
| // ์ ๋ ฅ ํ๋ | |
| Row( | |
| modifier = Modifier.fillMaxWidth() | |
| ) { | |
| OutlinedTextField( | |
| value = inputText, | |
| onValueChange = { inputText = it }, | |
| modifier = Modifier.weight(1f), | |
| placeholder = { Text("๋ฉ์์ง ์ ๋ ฅ...") }, | |
| enabled = !isLoading | |
| ) | |
| Spacer(modifier = Modifier.width(8.dp)) | |
| Button( | |
| onClick = { | |
| if (inputText.isNotBlank()) { | |
| viewModel.sendStreamingMessage(inputText) | |
| inputText = "" | |
| } | |
| }, | |
| enabled = !isLoading | |
| ) { | |
| Text(if (isLoading) "์ ์ก ์ค..." else "์ ์ก") | |
| } | |
| } | |
| } | |
| } | |
| ``` | |
| --- | |
| ## ๐งช ํ ์คํธ ๋ฐฉ๋ฒ | |
| ### 1. ๋ก์ปฌ ์๋ฒ ์คํ | |
| ```bash | |
| cd ChatBIA-Server | |
| uvicorn main:app --host 0.0.0.0 --port 8000 | |
| ``` | |
| ### 2. Python ํ ์คํธ | |
| ```bash | |
| python test_streaming.py | |
| ``` | |
| ### 3. ์๋๋ก์ด๋ ์ฑ์์ ์ฐ๊ฒฐ | |
| ```kotlin | |
| // ๋ก์ปฌ ํ ์คํธ (์๋ฎฌ๋ ์ดํฐ) | |
| val apiClient = ChatBiaApiClient("http://10.0.2.2:8000") | |
| // ์ค์ ๋๋ฐ์ด์ค (๊ฐ์ ๋คํธ์ํฌ) | |
| val apiClient = ChatBiaApiClient("http://YOUR_IP:8000") | |
| // Hugging Face Spaces (๋ฐฐํฌ ํ) | |
| val apiClient = ChatBiaApiClient("https://your-space.hf.space") | |
| ``` | |
| --- | |
| ## ๐ ์๋ต ํ์ | |
| ### ์คํธ๋ฆฌ๋ฐ ์๋ต (SSE) | |
| ``` | |
| data: {"token":"์๋ ","done":false,"token_count":1} | |
| data: {"token":"ํ์ธ์","done":false,"token_count":2} | |
| data: {"token":"!","done":false,"token_count":3} | |
| data: {"token":"","done":true,"token_count":3,"mode":"bsl"} | |
| ``` | |
| ### ์ต์ข ์๋ต | |
| ```json | |
| { | |
| "token": "", | |
| "done": true, | |
| "token_count": 150, | |
| "mode": "bsl" | |
| } | |
| ``` | |
| ### ์ค๋ฅ ์๋ต | |
| ```json | |
| { | |
| "error": "์ค๋ฅ ๋ฉ์์ง", | |
| "done": true | |
| } | |
| ``` | |
| --- | |
| ## โก ์ฑ๋ฅ ์ต์ ํ ํ | |
| 1. **ํ์์์ ์ค์ ** | |
| - Connect: 30์ด | |
| - Read: 60์ด (์คํธ๋ฆฌ๋ฐ) | |
| - Write: 30์ด | |
| 2. **์ฌ์ฐ๊ฒฐ ๋ก์ง** | |
| ```kotlin | |
| fun retryOnFailure(maxRetries: Int = 3) { | |
| var attempts = 0 | |
| while (attempts < maxRetries) { | |
| try { | |
| chatStream(request).collect { } | |
| break | |
| } catch (e: Exception) { | |
| attempts++ | |
| delay(2000 * attempts) // ์ง์ ๋ฐฑ์คํ | |
| } | |
| } | |
| } | |
| ``` | |
| 3. **๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ** | |
| - Flow๋ฅผ ์ฌ์ฉํ์ฌ ๋ฉ๋ชจ๋ฆฌ ํจ์จ์ ์ผ๋ก ์ฒ๋ฆฌ | |
| - UI ์ ๋ฐ์ดํธ๋ StateFlow๋ก ์ต์ ํ | |
| --- | |
| ## ๐ ๋ฐฐํฌ ํ ์ฌ์ฉ | |
| Hugging Face Spaces์ ๋ฐฐํฌ ํ: | |
| ```kotlin | |
| val BASE_URL = "https://your-username-chatbia-server.hf.space" | |
| val apiClient = ChatBiaApiClient(BASE_URL) | |
| ``` | |
| **์ฃผ์**: Hugging Face Spaces ๋ฌด๋ฃ ํ๋์ CPU๋ง ์ ๊ณต๋๋ฏ๋ก ์๋ต ์๋๊ฐ ๋๋ฆด ์ ์์ต๋๋ค. ์คํธ๋ฆฌ๋ฐ ๋ฐฉ์์ด ๋์ฑ ์ค์ํฉ๋๋ค! | |
| --- | |
| ## ๐ ๊ด๋ จ ๋ฌธ์ | |
| - [FastAPI Streaming](https://fastapi.tiangolo.com/advanced/custom-response/#streamingresponse) | |
| - [OkHttp SSE](https://square.github.io/okhttp/recipes/#server-sent-events) | |
| - [Kotlin Flow](https://kotlinlang.org/docs/flow.html) | |