Skip to content

Commit 1c56f97

Browse files
author
David Motsonashvili
committed
revert changes in vertexai dir
1 parent f2dc583 commit 1c56f97

File tree

76 files changed

+9274
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

76 files changed

+9274
-0
lines changed
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
/*
2+
* Copyright 2024 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.firebase.vertexai
18+
19+
import android.graphics.Bitmap
20+
import com.google.firebase.vertexai.type.Content
21+
import com.google.firebase.vertexai.type.GenerateContentResponse
22+
import com.google.firebase.vertexai.type.ImagePart
23+
import com.google.firebase.vertexai.type.InlineDataPart
24+
import com.google.firebase.vertexai.type.InvalidStateException
25+
import com.google.firebase.vertexai.type.TextPart
26+
import com.google.firebase.vertexai.type.content
27+
import java.util.LinkedList
28+
import java.util.concurrent.Semaphore
29+
import kotlinx.coroutines.flow.Flow
30+
import kotlinx.coroutines.flow.onCompletion
31+
import kotlinx.coroutines.flow.onEach
32+
33+
/**
34+
* Representation of a multi-turn interaction with a model.
35+
*
36+
* Captures and stores the history of communication in memory, and provides it as context with each
37+
* new message.
38+
*
39+
* **Note:** This object is not thread-safe, and calling [sendMessage] multiple times without
40+
* waiting for a response will throw an [InvalidStateException].
41+
*
42+
* @param model The model to use for the interaction.
43+
* @property history The previous content from the chat that has been successfully sent and received
44+
* from the model. This will be provided to the model for each message sent (as context for the
45+
* discussion).
46+
*/
47+
public class Chat(
48+
private val model: GenerativeModel,
49+
public val history: MutableList<Content> = ArrayList()
50+
) {
51+
private var lock = Semaphore(1)
52+
53+
/**
54+
* Sends a message using the provided [prompt]; automatically providing the existing [history] as
55+
* context.
56+
*
57+
* If successful, the message and response will be added to the [history]. If unsuccessful,
58+
* [history] will remain unchanged.
59+
*
60+
* @param prompt The input that, together with the history, will be given to the model as the
61+
* prompt.
62+
* @throws InvalidStateException if [prompt] is not coming from the 'user' role.
63+
* @throws InvalidStateException if the [Chat] instance has an active request.
64+
*/
65+
public suspend fun sendMessage(prompt: Content): GenerateContentResponse {
66+
prompt.assertComesFromUser()
67+
attemptLock()
68+
try {
69+
val response = model.generateContent(*history.toTypedArray(), prompt)
70+
history.add(prompt)
71+
history.add(response.candidates.first().content)
72+
return response
73+
} finally {
74+
lock.release()
75+
}
76+
}
77+
78+
/**
79+
* Sends a message using the provided [text prompt][prompt]; automatically providing the existing
80+
* [history] as context.
81+
*
82+
* If successful, the message and response will be added to the [history]. If unsuccessful,
83+
* [history] will remain unchanged.
84+
*
85+
* @param prompt The input that, together with the history, will be given to the model as the
86+
* prompt.
87+
* @throws InvalidStateException if [prompt] is not coming from the 'user' role.
88+
* @throws InvalidStateException if the [Chat] instance has an active request.
89+
*/
90+
public suspend fun sendMessage(prompt: String): GenerateContentResponse {
91+
val content = content { text(prompt) }
92+
return sendMessage(content)
93+
}
94+
95+
/**
96+
* Sends a message using the existing history of this chat as context and the provided image
97+
* prompt.
98+
*
99+
* If successful, the message and response will be added to the history. If unsuccessful, history
100+
* will remain unchanged.
101+
*
102+
* @param prompt The input that, together with the history, will be given to the model as the
103+
* prompt.
104+
* @throws InvalidStateException if [prompt] is not coming from the 'user' role.
105+
* @throws InvalidStateException if the [Chat] instance has an active request.
106+
*/
107+
public suspend fun sendMessage(prompt: Bitmap): GenerateContentResponse {
108+
val content = content { image(prompt) }
109+
return sendMessage(content)
110+
}
111+
112+
/**
113+
* Sends a message using the existing history of this chat as context and the provided [Content]
114+
* prompt.
115+
*
116+
* The response from the model is returned as a stream.
117+
*
118+
* If successful, the message and response will be added to the history. If unsuccessful, history
119+
* will remain unchanged.
120+
*
121+
* @param prompt The input that, together with the history, will be given to the model as the
122+
* prompt.
123+
* @throws InvalidStateException if [prompt] is not coming from the 'user' role.
124+
* @throws InvalidStateException if the [Chat] instance has an active request.
125+
*/
126+
public fun sendMessageStream(prompt: Content): Flow<GenerateContentResponse> {
127+
prompt.assertComesFromUser()
128+
attemptLock()
129+
130+
val flow = model.generateContentStream(*history.toTypedArray(), prompt)
131+
val bitmaps = LinkedList<Bitmap>()
132+
val inlineDataParts = LinkedList<InlineDataPart>()
133+
val text = StringBuilder()
134+
135+
/**
136+
* TODO: revisit when images and inline data are returned. This will cause issues with how
137+
* things are structured in the response. eg; a text/image/text response will be (incorrectly)
138+
* represented as image/text
139+
*/
140+
return flow
141+
.onEach {
142+
for (part in it.candidates.first().content.parts) {
143+
when (part) {
144+
is TextPart -> text.append(part.text)
145+
is ImagePart -> bitmaps.add(part.image)
146+
is InlineDataPart -> inlineDataParts.add(part)
147+
}
148+
}
149+
}
150+
.onCompletion {
151+
lock.release()
152+
if (it == null) {
153+
val content =
154+
content("model") {
155+
for (bitmap in bitmaps) {
156+
image(bitmap)
157+
}
158+
for (inlineDataPart in inlineDataParts) {
159+
inlineData(inlineDataPart.inlineData, inlineDataPart.mimeType)
160+
}
161+
if (text.isNotBlank()) {
162+
text(text.toString())
163+
}
164+
}
165+
166+
history.add(prompt)
167+
history.add(content)
168+
}
169+
}
170+
}
171+
172+
/**
173+
* Sends a message using the existing history of this chat as context and the provided text
174+
* prompt.
175+
*
176+
* The response from the model is returned as a stream.
177+
*
178+
* If successful, the message and response will be added to the history. If unsuccessful, history
179+
* will remain unchanged.
180+
*
181+
* @param prompt The input(s) that, together with the history, will be given to the model as the
182+
* prompt.
183+
* @throws InvalidStateException if [prompt] is not coming from the 'user' role.
184+
* @throws InvalidStateException if the [Chat] instance has an active request.
185+
*/
186+
public fun sendMessageStream(prompt: String): Flow<GenerateContentResponse> {
187+
val content = content { text(prompt) }
188+
return sendMessageStream(content)
189+
}
190+
191+
/**
192+
* Sends a message using the existing history of this chat as context and the provided image
193+
* prompt.
194+
*
195+
* The response from the model is returned as a stream.
196+
*
197+
* If successful, the message and response will be added to the history. If unsuccessful, history
198+
* will remain unchanged.
199+
*
200+
* @param prompt The input that, together with the history, will be given to the model as the
201+
* prompt.
202+
* @throws InvalidStateException if [prompt] is not coming from the 'user' role.
203+
* @throws InvalidStateException if the [Chat] instance has an active request.
204+
*/
205+
public fun sendMessageStream(prompt: Bitmap): Flow<GenerateContentResponse> {
206+
val content = content { image(prompt) }
207+
return sendMessageStream(content)
208+
}
209+
210+
private fun Content.assertComesFromUser() {
211+
if (role !in listOf("user", "function")) {
212+
throw InvalidStateException("Chat prompts should come from the 'user' or 'function' role.")
213+
}
214+
}
215+
216+
private fun attemptLock() {
217+
if (!lock.tryAcquire()) {
218+
throw InvalidStateException(
219+
"This chat instance currently has an ongoing request, please wait for it to complete " +
220+
"before sending more messages"
221+
)
222+
}
223+
}
224+
}

0 commit comments

Comments
 (0)