Skip to content

Update Gemini models to official release in InputForm.tsx #136

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

Draft
wants to merge 33 commits into
base: fixes
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
aa4fb9d
更新 Vite 配置,修正路径解析以支持文件 URL
LeaderOnePro Jun 3, 2025
e538603
Update README.md
CharlesCNorton Jun 3, 2025
8dde8c6
Merge branch 'google-gemini:main' into main
LeaderOnePro Jun 4, 2025
c429cb2
Update the Vite configuration to correct the path resolution to use _…
LeaderOnePro Jun 4, 2025
70c348c
Merge branch 'main' of github.com:LeaderOnePro/deepresearch-fullstack…
LeaderOnePro Jun 4, 2025
418c729
Refactor layout and scrolling for chat and welcome screens
vietnamesekid Jun 4, 2025
b0dd02b
fix: improve IME compatibility by changing form submission to Ctrl/Cm…
kahirokunn Jun 4, 2025
3da4c4e
fix(docs): Correct typos and grammatical inconsistencies
dkqjrm Jun 5, 2025
3bf5d97
Add CLI example
nisaharan Jun 5, 2025
211c23f
Merge pull request #1 from nisaharan/Research_agent/cli_research
nisaharan Jun 5, 2025
dff6596
Update Python version requirement to match pyproject.toml
nandsha Jun 5, 2025
26c0b47
Update README.md
smell-of-curry Jun 5, 2025
1c4d5a7
Remove duplicate imports in state.py
tigermlt Jun 10, 2025
fdb34df
Improve image scaling on desktop view
7Gamil Jun 10, 2025
8e6f3f5
Update prompts.py
cscandore Jun 11, 2025
6efccf7
Merge pull request #109 from google-gemini/fixes
philschmid Jun 18, 2025
c4969e4
Merge pull request #44 from nandsha/docs/fix-python-version-requirement
philschmid Jun 18, 2025
8304e3f
Merge branch 'main' into main
philschmid Jun 18, 2025
3092013
Merge pull request #15 from vietnamesekid/main
philschmid Jun 18, 2025
1607958
Merge pull request #90 from cscandore/query-writer-typo
philschmid Jun 18, 2025
d0abbad
Merge pull request #86 from 7Gamil/Improve-image-scaling-on-desktop-view
philschmid Jun 18, 2025
7f95976
Merge pull request #76 from tigermlt/patch-1
philschmid Jun 18, 2025
972392d
Merge pull request #5 from LeaderOnePro/main
philschmid Jun 18, 2025
d3fd999
Merge pull request #7 from CharlesCNorton/patch-1
philschmid Jun 18, 2025
3214a7e
Merge pull request #17 from kahirokunn/fix/ime-input-form-submission
philschmid Jun 18, 2025
23f6aa5
Merge pull request #35 from dkqjrm/chore/typo
philschmid Jun 18, 2025
d9c44fe
Merge pull request #43 from nisaharan/main
philschmid Jun 18, 2025
35d7f02
Merge branch 'main' into patch-1
philschmid Jun 18, 2025
f586b75
Merge pull request #45 from smell-of-curry/patch-1
philschmid Jun 18, 2025
e34e569
updates to prompt
philschmid Jun 18, 2025
22d8280
Update models to official release in InputForm.tsx
zbruceli Jul 17, 2025
d54825e
Merge branch 'google-gemini:main' into patch-1
zbruceli Jul 18, 2025
718e937
Update default display model version in InputForm.tsx
zbruceli Jul 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 18 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

This project demonstrates a fullstack application using a React frontend and a LangGraph-powered backend agent. The agent is designed to perform comprehensive research on a user's query by dynamically generating search terms, querying the web using Google Search, reflecting on the results to identify knowledge gaps, and iteratively refining its search until it can provide a well-supported answer with citations. This application serves as an example of building research-augmented conversational AI using LangGraph and Google's Gemini models.

![Gemini Fullstack LangGraph](./app.png)
<img src="./app.png" title="Gemini Fullstack LangGraph" alt="Gemini Fullstack LangGraph" width="90%">

## Features

Expand All @@ -12,7 +12,7 @@ This project demonstrates a fullstack application using a React frontend and a L
- 🌐 Integrated web research via Google Search API.
- 🤔 Reflective reasoning to identify knowledge gaps and refine searches.
- 📄 Generates answers with citations from gathered sources.
- 🔄 Hot-reloading for both frontend and backend development during development.
- 🔄 Hot-reloading for both frontend and backend during development.

## Project Structure

Expand All @@ -28,7 +28,7 @@ Follow these steps to get the application running locally for development and te
**1. Prerequisites:**

- Node.js and npm (or yarn/pnpm)
- Python 3.8+
- Python 3.11+
- **`GEMINI_API_KEY`**: The backend agent requires a Google Gemini API key.
1. Navigate to the `backend/` directory.
2. Create a file named `.env` by copying the `backend/.env.example` file.
Expand Down Expand Up @@ -65,21 +65,33 @@ _Alternatively, you can run the backend and frontend development servers separat

The core of the backend is a LangGraph agent defined in `backend/src/agent/graph.py`. It follows these steps:

![Agent Flow](./agent.png)
<img src="./agent.png" title="Agent Flow" alt="Agent Flow" width="50%">

1. **Generate Initial Queries:** Based on your input, it generates a set of initial search queries using a Gemini model.
2. **Web Research:** For each query, it uses the Gemini model with the Google Search API to find relevant web pages.
3. **Reflection & Knowledge Gap Analysis:** The agent analyzes the search results to determine if the information is sufficient or if there are knowledge gaps. It uses a Gemini model for this reflection process.
4. **Iterative Refinement:** If gaps are found or the information is insufficient, it generates follow-up queries and repeats the web research and reflection steps (up to a configured maximum number of loops).
5. **Finalize Answer:** Once the research is deemed sufficient, the agent synthesizes the gathered information into a coherent answer, including citations from the web sources, using a Gemini model.

## CLI Example

For quick one-off questions you can execute the agent from the command line. The
script `backend/examples/cli_research.py` runs the LangGraph agent and prints the
final answer:

```bash
cd backend
python examples/cli_research.py "What are the latest trends in renewable energy?"
```


## Deployment

In production, the backend server serves the optimized static frontend build. LangGraph requires a Redis instance and a Postgres database. Redis is used as a pub-sub broker to enable streaming real time output from background runs. Postgres is used to store assistants, threads, runs, persist thread state and long term memory, and to manage the state of the background task queue with 'exactly once' semantics. For more details on how to deploy the backend server, take a look at the [LangGraph Documentation](https://langchain-ai.github.io/langgraph/concepts/deployment_options/). Below is an example of how to build a Docker image that includes the optimized frontend build and the backend server and run it via `docker-compose`.

_Note: For the docker-compose.yml example you need a LangSmith API key, you can get one from [LangSmith](https://smith.langchain.com/settings)._

_Note: If you are not running the docker-compose.yml example or exposing the backend server to the public internet, you update the `apiUrl` in the `frontend/src/App.tsx` file your host. Currently the `apiUrl` is set to `http://localhost:8123` for docker-compose or `http://localhost:2024` for development._
_Note: If you are not running the docker-compose.yml example or exposing the backend server to the public internet, you should update the `apiUrl` in the `frontend/src/App.tsx` file to your host. Currently the `apiUrl` is set to `http://localhost:8123` for docker-compose or `http://localhost:2024` for development._

**1. Build the Docker Image:**

Expand All @@ -105,4 +117,4 @@ Open your browser and navigate to `http://localhost:8123/app/` to see the applic

## License

This project is licensed under the Apache License 2.0. See the [LICENSE](LICENSE) file for details.
This project is licensed under the Apache License 2.0. See the [LICENSE](LICENSE) file for details.
43 changes: 43 additions & 0 deletions backend/examples/cli_research.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import argparse
from langchain_core.messages import HumanMessage
from agent.graph import graph


def main() -> None:
"""Run the research agent from the command line."""
parser = argparse.ArgumentParser(description="Run the LangGraph research agent")
parser.add_argument("question", help="Research question")
parser.add_argument(
"--initial-queries",
type=int,
default=3,
help="Number of initial search queries",
)
parser.add_argument(
"--max-loops",
type=int,
default=2,
help="Maximum number of research loops",
)
parser.add_argument(
"--reasoning-model",
default="gemini-2.5-pro-preview-05-06",
help="Model for the final answer",
)
args = parser.parse_args()

state = {
"messages": [HumanMessage(content=args.question)],
"initial_search_query_count": args.initial_queries,
"max_research_loops": args.max_loops,
"reasoning_model": args.reasoning_model,
}

result = graph.invoke(state)
messages = result.get("messages", [])
if messages:
print(messages[-1].content)


if __name__ == "__main__":
main()
6 changes: 3 additions & 3 deletions backend/src/agent/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,17 @@

# Nodes
def generate_query(state: OverallState, config: RunnableConfig) -> QueryGenerationState:
"""LangGraph node that generates a search queries based on the User's question.
"""LangGraph node that generates search queries based on the User's question.

Uses Gemini 2.0 Flash to create an optimized search query for web research based on
Uses Gemini 2.0 Flash to create an optimized search queries for web research based on
the User's question.

Args:
state: Current graph state containing the User's question
config: Configuration for the runnable, including LLM provider settings

Returns:
Dictionary with state update, including search_query key containing the generated query
Dictionary with state update, including search_query key containing the generated queries
"""
configurable = Configuration.from_runnable_config(config)

Expand Down
4 changes: 2 additions & 2 deletions backend/src/agent/prompts.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def get_current_date():
- Query should ensure that the most current information is gathered. The current date is {current_date}.

Format:
- Format your response as a JSON object with ALL three of these exact keys:
- Format your response as a JSON object with ALL two of these exact keys:
- "rationale": Brief explanation of why these queries are relevant
- "query": A list of search queries

Expand Down Expand Up @@ -87,7 +87,7 @@ def get_current_date():
- You have access to all the information gathered from the previous steps.
- You have access to the user's question.
- Generate a high-quality answer to the user's question based on the provided summaries and the user's question.
- you MUST include all the citations from the summaries in the answer correctly.
- Include the sources you used from the Summaries in the answer correctly, use markdown format (e.g. [apnews](https://vertexaisearch.cloud.google.com/id/1-0)). THIS IS A MUST.

User Context:
- {research_topic}
Expand Down
2 changes: 0 additions & 2 deletions backend/src/agent/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@


import operator
from dataclasses import dataclass, field
from typing_extensions import Annotated


class OverallState(TypedDict):
Expand Down
8 changes: 1 addition & 7 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -151,12 +151,7 @@ export default function App() {

return (
<div className="flex h-screen bg-neutral-800 text-neutral-100 font-sans antialiased">
<main className="flex-1 flex flex-col overflow-hidden max-w-4xl mx-auto w-full">
<div
className={`flex-1 overflow-y-auto ${
thread.messages.length === 0 ? "flex" : ""
}`}
>
<main className="h-full w-full max-w-4xl mx-auto">
{thread.messages.length === 0 ? (
<WelcomeScreen
handleSubmit={handleSubmit}
Expand Down Expand Up @@ -188,7 +183,6 @@ export default function App() {
historicalActivities={historicalActivities}
/>
)}
</div>
</main>
</div>
);
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/components/ChatMessagesView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -253,9 +253,9 @@ export function ChatMessagesView({
}
};
return (
<div className="flex flex-col h-full overflow-hidden">
<ScrollArea className="flex-1 min-h-0" ref={scrollAreaRef}>
<div className="p-4 md:p-6 pb-2 space-y-2 max-w-4xl mx-auto">
<div className="flex flex-col h-full">
<ScrollArea className="flex-1 overflow-y-auto" ref={scrollAreaRef}>
<div className="p-4 md:p-6 space-y-2 max-w-4xl mx-auto pt-16">
{messages.map((message, index) => {
const isLast = index === messages.length - 1;
return (
Expand Down
17 changes: 8 additions & 9 deletions frontend/src/components/InputForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const InputForm: React.FC<InputFormProps> = ({
}) => {
const [internalInputValue, setInternalInputValue] = useState("");
const [effort, setEffort] = useState("medium");
const [model, setModel] = useState("gemini-2.5-flash-preview-04-17");
const [model, setModel] = useState("gemini-2.5-flash");

const handleInternalSubmit = (e?: React.FormEvent) => {
if (e) e.preventDefault();
Expand All @@ -35,10 +35,9 @@ export const InputForm: React.FC<InputFormProps> = ({
setInternalInputValue("");
};

const handleInternalKeyDown = (
e: React.KeyboardEvent<HTMLTextAreaElement>
) => {
if (e.key === "Enter" && !e.shiftKey) {
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
// Submit with Ctrl+Enter (Windows/Linux) or Cmd+Enter (Mac)
if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) {
e.preventDefault();
handleInternalSubmit();
}
Expand All @@ -59,9 +58,9 @@ export const InputForm: React.FC<InputFormProps> = ({
<Textarea
value={internalInputValue}
onChange={(e) => setInternalInputValue(e.target.value)}
onKeyDown={handleInternalKeyDown}
onKeyDown={handleKeyDown}
placeholder="Who won the Euro 2024 and scored the most goals?"
className={`w-full text-neutral-100 placeholder-neutral-500 resize-none border-0 focus:outline-none focus:ring-0 outline-none focus-visible:ring-0 shadow-none
className={`w-full text-neutral-100 placeholder-neutral-500 resize-none border-0 focus:outline-none focus:ring-0 outline-none focus-visible:ring-0 shadow-none
md:text-base min-h-[56px] max-h-[200px]`}
rows={1}
/>
Expand Down Expand Up @@ -145,15 +144,15 @@ export const InputForm: React.FC<InputFormProps> = ({
</div>
</SelectItem>
<SelectItem
value="gemini-2.5-flash-preview-04-17"
value="gemini-2.5-flash"
className="hover:bg-neutral-600 focus:bg-neutral-600 cursor-pointer"
>
<div className="flex items-center">
<Zap className="h-4 w-4 mr-2 text-orange-400" /> 2.5 Flash
</div>
</SelectItem>
<SelectItem
value="gemini-2.5-pro-preview-05-06"
value="gemini-2.5-pro"
className="hover:bg-neutral-600 focus:bg-neutral-600 cursor-pointer"
>
<div className="flex items-center">
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/WelcomeScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const WelcomeScreen: React.FC<WelcomeScreenProps> = ({
onCancel,
isLoading,
}) => (
<div className="flex flex-col items-center justify-center text-center px-4 flex-1 w-full max-w-3xl mx-auto gap-4">
<div className="h-full flex flex-col items-center justify-center text-center px-4 flex-1 w-full max-w-3xl mx-auto gap-4">
<div>
<h1 className="text-5xl md:text-6xl font-semibold text-neutral-100 mb-3">
Welcome.
Expand Down
2 changes: 1 addition & 1 deletion frontend/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export default defineConfig({
base: "/app/",
resolve: {
alias: {
"@": path.resolve(new URL(".", import.meta.url).pathname, "./src"),
"@": path.resolve(__dirname, "./src"),
},
},
server: {
Expand Down