Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Spring Boot Project Starter for Google Gemini API model - ChatLangauge, Streaming model and Embedding Model #74

Open
wants to merge 55 commits into
base: main
Choose a base branch
from

Conversation

Suhas-Koheda
Copy link

@Suhas-Koheda Suhas-Koheda commented Nov 13, 2024

@Suhas-Koheda
Copy link
Author

@langchain4j hey can you please have a look at this

Copy link
Collaborator

@langchain4j langchain4j left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Suhas-Koheda thanks a lot!

langchain4j-google-ai-gemini/pom.xml Outdated Show resolved Hide resolved
langchain4j-google-ai-gemini/pom.xml Outdated Show resolved Hide resolved
.topP(chatModelProperties.getTopP())
.topK(chatModelProperties.getTopK())
.maxOutputTokens(chatModelProperties.getMaxOutputTokens())
.responseFormat(ResponseFormat.JSON)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is it json?

Copy link

@franglopez franglopez Nov 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi. Related to your question. AutoConfig it's not injecting logging properties and json format from the properties -> https://docs.langchain4j.dev/tutorials/logging/ is not working when this AutoConfig is used. I will wait until this PR is merged to create a PR to avoid conflicts or If you want to add this configuration in this PR. Whatever you consider better.
Thank you!

Copy link
Author

@Suhas-Koheda Suhas-Koheda Nov 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey! can you provide me with some references
As i did not find the logging used in other starters
Or maybe i overlooked
Thank you!
@franglopez

@Suhas-Koheda
Copy link
Author

@langchain4j also in the timeout values i had doubt
Will it be set by the user? Or should i keep the condition if its not set then set to 0

At present i have hardcoded used 60 secs

@langchain4j
Copy link
Collaborator

@Suhas-Koheda you can see how timeouts are handled in OpenAI starter here, the same for .logRequests(chatModelProperties.logRequests()).

@ddobrin
Copy link

ddobrin commented Nov 14, 2024

@Suhas-Koheda
when you are looking at logging requests and responses, please be aware that the GogleAiGeminiChatModel in the module langchain4j-google-ai-gemini does not make a distinction in the constructor between logRequests() and log Responses() and uses a single Boolean to enable/disable the logging functionality link

@Suhas-Koheda
Copy link
Author

Suhas-Koheda commented Nov 14, 2024

Yeah sure I'll take care of it!
Thank you!
@ddobrin

@Suhas-Koheda
Copy link
Author

@ddobrin hey for the response format i have kept the type of prop as ResponseFormat only which is user defined - not a primitive type?
is this ok ? or does it cause any problem?

@Suhas-Koheda
Copy link
Author

@langchain4j
Hey! Hi!
I've completed writing tests and both of the tests passed!
Also review the code once and let me know if any changes are to be made :)

@ddobrin
Copy link

ddobrin commented Nov 16, 2024

@Suhas-Koheda on a 📱 let me come back to it at the beginning of the week

@langchain4j
Copy link
Collaborator

@Suhas-Koheda I would love to include your PR in this release (today), but there are still some issues left (please see my comments above). Since this is an independent module, we can release it a bit later separately, once this PR is ready.

@Suhas-Koheda
Copy link
Author

@langchain4j
Yeah yeha sure!
I'll be working on it
I was a bit busy with my exams
Now I'll be completely on it!

@Suhas-Koheda
Copy link
Author

@langchain4j hey i have written the NPE checks can you please have a look over this
Thank you!

return defaultMap;
}
return Map.of(
safetySetting.geminiHarmCategory(),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these settings supposed to have only a single key-value pair?

Copy link
Author

@Suhas-Koheda Suhas-Koheda Dec 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The safety settings builder takes the map of geminiharmcategory as key and geminiharmblockthreshold as value

No it can take any number of values
It changes them into list

But for a single model thre would be only one harmcategory and blockthreshold right?

If that's not the case we can write such that the harmcategory and blockthreshold takes comma separated values and we manually convert then into map in the autoconfig?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@langchain4j

private Map<GeminiHarmCategory,GeminiHarmBlockThreshold> checkSafetySettingForNull(GeminiSafetySetting safetySetting) {
        if(safetySetting==null){
            Map<GeminiHarmCategory,GeminiHarmBlockThreshold> defaultMap= new HashMap<>();
            defaultMap.put(HARM_CATEGORY_CIVIC_INTEGRITY,HARM_BLOCK_THRESHOLD_UNSPECIFIED);
            return defaultMap;
        }
        Map<GeminiHarmCategory,GeminiHarmBlockThreshold> userMap= new HashMap<>();
        safetySetting.geminiHarmCategory().forEach(category -> userMap.put(category,safetySetting.geminiHarmBlockThreshold().get(safetySetting.geminiHarmCategory().indexOf(category))));
        return userMap;
    }

this can be done if there are multiple GeminiHarmCategory and GeminiHarmBlockThreshold

and the test cases run properly too

the code is ready to be commited
thank you!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @ddobrin, could you please help with the review?

It seems to me that current logic in checkSafetySettingForNull is not correct. If settings are not specified, defaultMap.put(HARM_CATEGORY_CIVIC_INTEGRITY,HARM_BLOCK_THRESHOLD_UNSPECIFIED); is set (is it correct?). Also, chatModelProperties can have only a single setting...

@Suhas-Koheda I guess ChatModelProperties should have a Map<GeminiHarmCategory, GeminiHarmBlockThreshold> safetySettings instead of GeminiSafetySetting safetySetting.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or List<GeminiSafetySetting> safetySettings, the same way as it is in the BaseGeminiChatModel

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @Suhas-Koheda

The safety filters settings, with their defaults, are available at his link

For flash and pro 1.5 the "default" block method is "SEVERITY" with the threshold by default at "BLOCK_MEDIUM_AND_ABOVE".

The HARM_CATEGORY_CIVIC_INTEGRITY filter is off by default.

Can I suggest that you do not set a value at all in
private GeminiMode checkGeminiModeForNull(GeminiFunctionCallingConfig geminiFunctionCallingConfig)?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To simplify, you can use the GeminiSafetySetting class which has a pair of <category, threshold?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just in case: I would not duplicate the default settings for Gemini in LC4j code. If user did not configure anything explicitly, we should not set the defaults, but let Gemini backend do it

Copy link
Author

@Suhas-Koheda Suhas-Koheda Jan 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey i have a doubt
Like we are writing the safety settings in builder right
How can we leave it empty or such?
Probably there isn't any function in the backend with withSafetySettings() as such!
I am not sure of what to do
Like we should include safetysettings in builder pattern but pass null value since the user did not configure any in settings

@langchain4j
@ddobrin

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, agreed.
If the user does not specify any settings, then none are to be set.

Gemini has a default set which it will use if no other setting is modifying those defaults.
Link above just shows you the settings and why civic integrity should not be set

You can just change the condition when safetySettings is null or empty

@Suhas-Koheda
Copy link
Author

@langchain4j hey! :-)

@Suhas-Koheda
Copy link
Author

@langchain4j this logic looks perfect to me now!
jus that default values have to be sorted

@ddobrin
Copy link

ddobrin commented Jan 4, 2025

Let me have a look tomorrow

@Suhas-Koheda
Copy link
Author

@ddobrin
@langchain4j
hey can you please check this
i have solved the issue
all the tests run
and there are no defautl values too!

@ddobrin
Copy link

ddobrin commented Jan 8, 2025

Hi @Suhas-Koheda
thanks for making the change.

The test code runs as is, and, if safetySettings are not set, it passes, so that part is solved.

While looking at the code and running it, you can see that there is no test which would address the use case when a user wishes to actually set in the configuration the safetySettings, which would close the testing for this area.

Do you want to add a test as well for setting actual values?

@ddobrin
Copy link

ddobrin commented Jan 8, 2025

Hi @Suhas-Koheda
Setting the safety settings has to be tested out, as the values are not plain Strings, but enum values.
There are multiple ways to resolve this, from conversion to fully qualified enum values in the config.

I'll share some code here, as non-intrusive as possible, and you can consider merging it to your code, as you see fit:

Added a new test:

    @Test
    void provide_chat_mode_and_safety_settings() {
        contextRunner.withPropertyValues(
            "langchain4j.google-ai-gemini.chat-model.api-key=" + API_KEY,
                "langchain4j.google-ai-gemini.chat-model.model-name=gemini-1.5-flash",
                "langchain4j.google-ai-gemini.chat-model.temperature=1.0",
                "logging.level.org.springframework.boot.context.properties=DEBUG",
                "langchain4j.google-ai-gemini.chat-model.safety-settings.HARM_CATEGORY_DANGEROUS_CONTENT=BLOCK_LOW_AND_ABOVE"
            )
            .run(context -> {
                ChatLanguageModel chatLanguageModel = context.getBean(ChatLanguageModel.class);
                assertThat(context.getBean(GoogleAiGeminiChatModel.class)).isSameAs(chatLanguageModel);

                String response = chatLanguageModel.generate("What is the capital of India");
                assertThat(response).contains("Delhi");

                String newResponse = chatLanguageModel.generate("Calculate the Fibonacci of 22 and give me the result as an integer value along with the code. ");
                assertThat(newResponse).contains("17711");
            });
    }

Changed in ChatModelProperties the settings to be plain Strings:
Map<String, String> safetySettings

Last, in AutoConfig:

...
        if (chatModelProperties.safetySettings() != null
            && !chatModelProperties.safetySettings().isEmpty()) {
            builder.safetySettings(convertSafetySettings(chatModelProperties.safetySettings()));
        }
...

The small conversion method maps the Strings to the actual enums:

    private Map<GeminiHarmCategory, GeminiHarmBlockThreshold> convertSafetySettings(Map<String, String> map) {
        return map.entrySet().stream()
            .collect(Collectors.toMap(
                e -> GeminiHarmCategory.valueOf(e.getKey()),
                e -> GeminiHarmBlockThreshold.valueOf(e.getValue())
            ));
    }

@Suhas-Koheda
Copy link
Author

@ddobrin yeah sure the map conversion would help
The other tests for the safetysettings and toolconfig
I have tests written before
I would be adding it by evening

@Suhas-Koheda
Copy link
Author

@ddobrin
hey i have written the detailed tests!
and made the changes required

@ddobrin
Copy link

ddobrin commented Jan 8, 2025

Thank you for making the changes @Suhas-Koheda. Nice to add the individual properties to the tests.

The only small thing you might wish to adjust is L.58 in the AutoConfigIT test, as the test is not running correctly due to:
"langchain4j.google-ai-gemini.chat-model.safety-settings.HARM_CATEGORY_DANGEROUS_CONTENT=BLOCK_LOW_AND_ABOVE",

should be
"langchain4j.google-ai-gemini.chat-model.safety-setting.HARM_CATEGORY_DANGEROUS_CONTENT=BLOCK_LOW_AND_ABOVE",

"setting" instead of "settings"

@Suhas-Koheda
Copy link
Author

@ddobrin
Done! :-)

@ddobrin
Copy link

ddobrin commented Jan 8, 2025

Just small things:

  • streamingChatModel is using lower-case C in a test and thus fails
  • flash returns sometimes 17,711 and sometimes 17711 - to avoid that search only for 711 - this is non-deterministic by the model
  • same for using the gemini-2.0-flash-exp model - sometimes the streamingChatTest fails, and 2.0 seems consistent
    Thanks @Suhas-Koheda

@Suhas-Koheda
Copy link
Author

Suhas-Koheda commented Jan 8, 2025

@ddobrin

  • solved
  • Is that okay? Like just checking for 711? Actually i would change the prompt in a way to return just the integer along with code to eliminate any commas in between
  • I did not get this? What does fail? The 17711 one? It can be solved by requesting just the integer
    If it is any other you can specify

I have run all the tests with gemini 2 flash exp model and works for me fine!

Please mention if there are any other things!
Thank you!

@ddobrin
Copy link

ddobrin commented Jan 8, 2025

@Suhas-Koheda - yes, any change to get a deterministic text works for 17,711 vs 17711

The tests failing on the model are the streamingChatModel() tests

@Suhas-Koheda
Copy link
Author

@ddobrin
Can you check the latest commit?
All works well for me!

@ddobrin
Copy link

ddobrin commented Jan 8, 2025

Looks good @Suhas-Koheda

@Suhas-Koheda
Copy link
Author

@langchain4j can you have a look over this!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Google AI Gemini: implement Spring Boot starter
4 participants