chansung commited on
Commit
4f5f090
·
verified ·
1 Parent(s): c4dd456

Upload folder using huggingface_hub

Browse files
.github/workflows/sync_to_spaces.yml ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Sync to Hugging Face Spaces
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ workflow_dispatch:
8
+
9
+ jobs:
10
+ sync:
11
+ runs-on: ubuntu-latest
12
+
13
+ steps:
14
+ - name: Checkout GitHub Repository
15
+ uses: actions/checkout@v3
16
+
17
+ - name: Set up Python
18
+ uses: actions/setup-python@v4
19
+ with:
20
+ python-version: "3.10"
21
+
22
+ - name: Install Hugging Face Hub CLI
23
+ run: |
24
+ python -m pip install --upgrade pip
25
+ pip install huggingface_hub
26
+
27
+ - name: Clone Hugging Face Spaces Repository
28
+ run: |
29
+ huggingface-cli login --token $HF_TOKEN --add-to-git-credential
30
+ git clone https://huggingface.co/spaces/adaptsum/demo hf_space
31
+ cd hf_space
32
+ git checkout main
33
+ env:
34
+ HF_TOKEN: ${{ secrets.HUGGINGFACE_TOKEN }}
35
+
36
+ - name: Copy Files to Hugging Face Repo
37
+ run: |
38
+ rsync -av --exclude='.git' --exclude='README.md' ./ hf_space/
39
+
40
+ - name: Merge README.md Files
41
+ run: |
42
+ cat hf_space/README.md README.md > hf_space/README_combined.md
43
+ mv hf_space/README_combined.md hf_space/README.md
44
+ rm -rf hf_space/README_combined.md
45
+
46
+ - name: Commit and Push Changes
47
+ run: |
48
+ cd hf_space
49
+ git add .
50
+ if git diff --cached --quiet; then
51
+ echo "No changes to commit"
52
+ else
53
+ huggingface-cli upload adaptsum/demo . --repo-type=space
54
+ echo "Changes have been pushed."
55
+ fi
56
+ env:
57
+ HUGGINGFACE_TOKEN: ${{ secrets.HUGGINGFACE_TOKEN }}
README.md CHANGED
@@ -10,4 +10,27 @@ pinned: false
10
  license: apache-2.0
11
  ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  license: apache-2.0
11
  ---
12
 
13
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference# AdaptSum
14
+
15
+ AdaptSum stands for Adaptive Summarization. This project focuses on developing an LLM-powered system for dynamic summarization. Instead of generating entirely new summaries with each update, the system intelligently identifies and modifies only the necessary parts of the existing summary. This approach aims to create a more efficient and fluid summarization process within a continuous chat interaction with an LLM.
16
+
17
+ # Instructions
18
+
19
+ 1. Install dependencies
20
+ ```shell
21
+ $ pip install requirements.txt
22
+ ```
23
+
24
+ 2. Setup Gemini API Key
25
+ ```shell
26
+ $ export GEMINI_API_KEY=xxxxx
27
+ ```
28
+ > note that GEMINI API KEY should be obtained from Google AI Studio. Vertex AI is not supported at the moment (this is because Gemini SDK does not provide file uploading functionality for Vertex AI usage now).
29
+
30
+ 3. Run Gradio app
31
+ ```shell
32
+ $ python main.py # or gradio main.py
33
+ ```
34
+
35
+ # Acknowledgments
36
+ This is a project built during the Vertex sprints held by Google's ML Developer Programs team. We are thankful to be granted good amount of GCP credits to do this project.
app.py CHANGED
@@ -1,10 +1,12 @@
1
  import os
2
  import argparse
 
3
  import gradio as gr
4
  from difflib import Differ
5
  from string import Template
6
  from utils import load_prompt, setup_gemini_client
7
  from configs.responses import SummaryResponses
 
8
 
9
  def parse_args():
10
  parser = argparse.ArgumentParser()
@@ -12,8 +14,8 @@ def parse_args():
12
  parser.add_argument("--vertexai", action="store_true", default=False)
13
  parser.add_argument("--vertexai-project", type=str, default="gcp-ml-172005")
14
  parser.add_argument("--vertexai-location", type=str, default="us-central1")
15
- parser.add_argument("--model", type=str, default="gemini-1.5-flash")
16
-
17
  parser.add_argument("--prompt-tmpl-path", type=str, default="configs/prompts.toml")
18
  parser.add_argument("--css-path", type=str, default="statics/styles.css")
19
  args = parser.parse_args()
@@ -25,8 +27,9 @@ def find_attached_file(filename, attached_files):
25
  return file
26
  return None
27
 
28
- def echo(message, history, state):
29
  attached_file = None
 
30
 
31
  if message['files']:
32
  path_local = message['files'][0]
@@ -34,7 +37,7 @@ def echo(message, history, state):
34
 
35
  attached_file = find_attached_file(filename, state["attached_files"])
36
  if attached_file is None:
37
- path_gcp = client.files.upload(path=path_local)
38
  state["attached_files"].append({
39
  "name": filename,
40
  "path_local": path_local,
@@ -52,35 +55,50 @@ def echo(message, history, state):
52
  chat_history = chat_history + user_message
53
  state['messages'] = chat_history
54
 
55
- response = client.models.generate_content(
56
- model="gemini-1.5-flash",
57
- contents=state['messages'],
58
- )
59
- model_response = response.text
60
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  # make summary
62
- if state['summary'] != "":
63
- response = client.models.generate_content(
64
- model="gemini-1.5-flash",
65
- contents=[
66
- Template(
67
- prompt_tmpl['summarization']['prompt']
68
- ).safe_substitute(
69
- previous_summary=state['summary'],
70
- latest_conversation=str({"user": message['text'], "assistant": model_response})
71
- )
72
- ],
73
- config={'response_mime_type': 'application/json',
74
- 'response_schema': SummaryResponses,
75
- },
 
76
  )
 
77
 
78
- if state['summary'] != "":
79
- prev_summary = state['summary_history'][-1]
80
- else:
81
- prev_summary = ""
82
 
83
- d = Differ()
84
  state['summary'] = (
85
  response.parsed.summary
86
  if getattr(response.parsed, "summary", None) is not None
@@ -94,14 +112,13 @@ def echo(message, history, state):
94
  state['summary_diff_history'].append(
95
  [
96
  (token[2:], token[0] if token[0] != " " else None)
97
- for token in d.compare(prev_summary, state['summary'])
98
  ]
99
  )
100
 
101
- return (
102
- model_response,
103
  state,
104
- # state['summary'],
105
  state['summary_diff_history'][-1],
106
  state['summary_history'][-1],
107
  gr.Slider(
@@ -132,7 +149,7 @@ def navigate_to_summary(summary_num, state):
132
  def main(args):
133
  style_css = open(args.css_path, "r").read()
134
 
135
- global client, prompt_tmpl
136
  client = setup_gemini_client(args)
137
  prompt_tmpl = load_prompt(args)
138
 
@@ -166,7 +183,7 @@ def main(args):
166
  # value="No summary yet. As you chat with the assistant, the summary will be updated automatically.",
167
  combine_adjacent=True,
168
  show_legend=True,
169
- color_map={"+": "red", "-": "green"},
170
  elem_classes=["summary-window"],
171
  visible=False
172
  )
@@ -183,12 +200,20 @@ def main(args):
183
  view_toggle_btn.change(change_view_toggle, inputs=[view_toggle_btn], outputs=[summary_diff, summary_md])
184
  summary_num.release(navigate_to_summary, inputs=[summary_num, state], outputs=[summary_diff, summary_md])
185
 
 
 
 
 
 
 
 
 
186
  with gr.Column("chat-window", elem_id="chat-window"):
187
  gr.ChatInterface(
188
  multimodal=True,
189
  type="messages",
190
  fn=echo,
191
- additional_inputs=[state],
192
  additional_outputs=[state, summary_diff, summary_md, summary_num],
193
  )
194
 
@@ -197,4 +222,4 @@ def main(args):
197
  if __name__ == "__main__":
198
  args = parse_args()
199
  demo = main(args)
200
- demo.launch()
 
1
  import os
2
  import argparse
3
+ import asyncio
4
  import gradio as gr
5
  from difflib import Differ
6
  from string import Template
7
  from utils import load_prompt, setup_gemini_client
8
  from configs.responses import SummaryResponses
9
+ from google.genai import types
10
 
11
  def parse_args():
12
  parser = argparse.ArgumentParser()
 
14
  parser.add_argument("--vertexai", action="store_true", default=False)
15
  parser.add_argument("--vertexai-project", type=str, default="gcp-ml-172005")
16
  parser.add_argument("--vertexai-location", type=str, default="us-central1")
17
+ parser.add_argument("--model", type=str, default="gemini-2.0-flash", choices=["gemini-1.5-flash", "gemini-2.0-flash", "gemini-2.0-flash-001"])
18
+ parser.add_argument("--seed", type=int, default=2025)
19
  parser.add_argument("--prompt-tmpl-path", type=str, default="configs/prompts.toml")
20
  parser.add_argument("--css-path", type=str, default="statics/styles.css")
21
  args = parser.parse_args()
 
27
  return file
28
  return None
29
 
30
+ async def echo(message, history, state, persona):
31
  attached_file = None
32
+ system_instruction = Template(prompt_tmpl['summarization']['system_prompt']).safe_substitute(persona=persona)
33
 
34
  if message['files']:
35
  path_local = message['files'][0]
 
37
 
38
  attached_file = find_attached_file(filename, state["attached_files"])
39
  if attached_file is None:
40
+ path_gcp = await client.files.upload(path=path_local)
41
  state["attached_files"].append({
42
  "name": filename,
43
  "path_local": path_local,
 
55
  chat_history = chat_history + user_message
56
  state['messages'] = chat_history
57
 
58
+ response_chunks = ""
59
+ model_content_stream = await client.models.generate_content_stream(
60
+ model=args.model,
61
+ contents=state['messages'],
62
+ config=types.GenerateContentConfig(
63
+ system_instruction=system_instruction, seed=args.seed
64
+ ),
65
+ )
66
+ async for chunk in model_content_stream:
67
+ response_chunks += chunk.text
68
+ # when model generates too fast, Gradio does not respond that in real-time.
69
+ await asyncio.sleep(0.1)
70
+ yield (
71
+ response_chunks,
72
+ state,
73
+ state['summary_diff_history'][-1] if len(state['summary_diff_history']) > 1 else "",
74
+ state['summary_history'][-1] if len(state['summary_history']) > 1 else "",
75
+ gr.Slider(
76
+ visible=False if len(state['summary_history']) <= 1 else True,
77
+ interactive=False if len(state['summary_history']) <= 1 else True,
78
+ ),
79
+ )
80
+
81
  # make summary
82
+ response = await client.models.generate_content(
83
+ model=args.model,
84
+ contents=[
85
+ Template(
86
+ prompt_tmpl['summarization']['prompt']
87
+ ).safe_substitute(
88
+ previous_summary=state['summary'],
89
+ latest_conversation=str({"user": message['text'], "assistant": response_chunks})
90
+ )
91
+ ],
92
+ config=types.GenerateContentConfig(
93
+ system_instruction=system_instruction,
94
+ seed=args.seed,
95
+ response_mime_type='application/json',
96
+ response_schema=SummaryResponses
97
  )
98
+ )
99
 
100
+ prev_summary = state['summary_history'][-1] if len(state['summary_history']) >= 1 else ""
 
 
 
101
 
 
102
  state['summary'] = (
103
  response.parsed.summary
104
  if getattr(response.parsed, "summary", None) is not None
 
112
  state['summary_diff_history'].append(
113
  [
114
  (token[2:], token[0] if token[0] != " " else None)
115
+ for token in Differ().compare(prev_summary, state['summary'])
116
  ]
117
  )
118
 
119
+ yield (
120
+ response_chunks,
121
  state,
 
122
  state['summary_diff_history'][-1],
123
  state['summary_history'][-1],
124
  gr.Slider(
 
149
  def main(args):
150
  style_css = open(args.css_path, "r").read()
151
 
152
+ global client, prompt_tmpl, system_instruction
153
  client = setup_gemini_client(args)
154
  prompt_tmpl = load_prompt(args)
155
 
 
183
  # value="No summary yet. As you chat with the assistant, the summary will be updated automatically.",
184
  combine_adjacent=True,
185
  show_legend=True,
186
+ color_map={"-": "red", "+": "green"},
187
  elem_classes=["summary-window"],
188
  visible=False
189
  )
 
200
  view_toggle_btn.change(change_view_toggle, inputs=[view_toggle_btn], outputs=[summary_diff, summary_md])
201
  summary_num.release(navigate_to_summary, inputs=[summary_num, state], outputs=[summary_diff, summary_md])
202
 
203
+ with gr.Column("persona-dropdown-container", elem_id="persona-dropdown-container"):
204
+ persona = gr.Dropdown(
205
+ ["expert", "novice", "regular practitioner", "high schooler"],
206
+ label="Summary Persona",
207
+ info="Control the tonality of the conversation.",
208
+ min_width="auto",
209
+ )
210
+
211
  with gr.Column("chat-window", elem_id="chat-window"):
212
  gr.ChatInterface(
213
  multimodal=True,
214
  type="messages",
215
  fn=echo,
216
+ additional_inputs=[state, persona],
217
  additional_outputs=[state, summary_diff, summary_md, summary_num],
218
  )
219
 
 
222
  if __name__ == "__main__":
223
  args = parse_args()
224
  demo = main(args)
225
+ demo.launch()
configs/prompts.toml CHANGED
@@ -1,7 +1,7 @@
1
  [summarization]
2
  prompt = """
3
  Below is the initial summary of our conversation.
4
- Based on the summary and the last conversation between you(assistant) and me(user), I want to update the summary.
5
 
6
  **Initial Summary:**
7
  $previous_summary
@@ -23,5 +23,12 @@ By following these guidelines, you will maintain an evolving summary that accura
23
  * **Clearer Instructions:** More explicit instructions on how to update the summary (i.e., updating specific portions instead of rewriting).
24
  * **Emphasis on Accuracy:** Stronger emphasis on factual accuracy and reflecting nuances.
25
  * **Conciseness:** Added a direction to balance detail with conciseness.
26
- * **Structure:** Improved organization and formatting for better readability. Bullet points with Markdown would be preferred.
 
 
 
 
 
 
 
27
  """
 
1
  [summarization]
2
  prompt = """
3
  Below is the initial summary of our conversation.
4
+ Based on the summary and the last conversation between you (assistant) and me (user), I want to update the summary.
5
 
6
  **Initial Summary:**
7
  $previous_summary
 
23
  * **Clearer Instructions:** More explicit instructions on how to update the summary (i.e., updating specific portions instead of rewriting).
24
  * **Emphasis on Accuracy:** Stronger emphasis on factual accuracy and reflecting nuances.
25
  * **Conciseness:** Added a direction to balance detail with conciseness.
26
+ * **Structure:** Improved organization and formatting for better readability.
27
+ """
28
+
29
+ system_prompt = """
30
+ Consider yourself an expert at summarizing content with a high bar
31
+ for scientific rigor. However, when generating the summaries you
32
+ must follow the persona of a $persona. This persona will help set
33
+ the tone of the conversation.
34
  """
hf_space/.gitattributes ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
hf_space/.gitignore ADDED
@@ -0,0 +1,171 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py,cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ #Pipfile.lock
96
+
97
+ # UV
98
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ #uv.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ #poetry.lock
109
+
110
+ # pdm
111
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
112
+ #pdm.lock
113
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
114
+ # in version control.
115
+ # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
116
+ .pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
121
+ __pypackages__/
122
+
123
+ # Celery stuff
124
+ celerybeat-schedule
125
+ celerybeat.pid
126
+
127
+ # SageMath parsed files
128
+ *.sage.py
129
+
130
+ # Environments
131
+ .env
132
+ .venv
133
+ env/
134
+ venv/
135
+ ENV/
136
+ env.bak/
137
+ venv.bak/
138
+
139
+ # Spyder project settings
140
+ .spyderproject
141
+ .spyproject
142
+
143
+ # Rope project settings
144
+ .ropeproject
145
+
146
+ # mkdocs documentation
147
+ /site
148
+
149
+ # mypy
150
+ .mypy_cache/
151
+ .dmypy.json
152
+ dmypy.json
153
+
154
+ # Pyre type checker
155
+ .pyre/
156
+
157
+ # pytype static type analyzer
158
+ .pytype/
159
+
160
+ # Cython debug symbols
161
+ cython_debug/
162
+
163
+ # PyCharm
164
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
165
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
166
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
167
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
168
+ #.idea/
169
+
170
+ # PyPI configuration file
171
+ .pypirc
hf_space/app.py ADDED
@@ -0,0 +1,225 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import argparse
3
+ import asyncio
4
+ import gradio as gr
5
+ from difflib import Differ
6
+ from string import Template
7
+ from utils import load_prompt, setup_gemini_client
8
+ from configs.responses import SummaryResponses
9
+ from google.genai import types
10
+
11
+ def parse_args():
12
+ parser = argparse.ArgumentParser()
13
+ parser.add_argument("--ai-studio-api-key", type=str, default=os.getenv("GEMINI_API_KEY"))
14
+ parser.add_argument("--vertexai", action="store_true", default=False)
15
+ parser.add_argument("--vertexai-project", type=str, default="gcp-ml-172005")
16
+ parser.add_argument("--vertexai-location", type=str, default="us-central1")
17
+ parser.add_argument("--model", type=str, default="gemini-2.0-flash", choices=["gemini-1.5-flash", "gemini-2.0-flash", "gemini-2.0-flash-001"])
18
+ parser.add_argument("--seed", type=int, default=2025)
19
+ parser.add_argument("--prompt-tmpl-path", type=str, default="configs/prompts.toml")
20
+ parser.add_argument("--css-path", type=str, default="statics/styles.css")
21
+ args = parser.parse_args()
22
+ return args
23
+
24
+ def find_attached_file(filename, attached_files):
25
+ for file in attached_files:
26
+ if file['name'] == filename:
27
+ return file
28
+ return None
29
+
30
+ async def echo(message, history, state, persona):
31
+ attached_file = None
32
+ system_instruction = Template(prompt_tmpl['summarization']['system_prompt']).safe_substitute(persona=persona)
33
+
34
+ if message['files']:
35
+ path_local = message['files'][0]
36
+ filename = os.path.basename(path_local)
37
+
38
+ attached_file = find_attached_file(filename, state["attached_files"])
39
+ if attached_file is None:
40
+ path_gcp = await client.files.upload(path=path_local)
41
+ state["attached_files"].append({
42
+ "name": filename,
43
+ "path_local": path_local,
44
+ "gcp_entity": path_gcp,
45
+ "path_gcp": path_gcp.name,
46
+ "mime_type=": path_gcp.mime_type,
47
+ "expiration_time": path_gcp.expiration_time,
48
+ })
49
+ attached_file = path_gcp
50
+
51
+ user_message = [message['text']]
52
+ if attached_file: user_message.append(attached_file)
53
+
54
+ chat_history = state['messages']
55
+ chat_history = chat_history + user_message
56
+ state['messages'] = chat_history
57
+
58
+ response_chunks = ""
59
+ model_content_stream = await client.models.generate_content_stream(
60
+ model=args.model,
61
+ contents=state['messages'],
62
+ config=types.GenerateContentConfig(
63
+ system_instruction=system_instruction, seed=args.seed
64
+ ),
65
+ )
66
+ async for chunk in model_content_stream:
67
+ response_chunks += chunk.text
68
+ # when model generates too fast, Gradio does not respond that in real-time.
69
+ await asyncio.sleep(0.1)
70
+ yield (
71
+ response_chunks,
72
+ state,
73
+ state['summary_diff_history'][-1] if len(state['summary_diff_history']) > 1 else "",
74
+ state['summary_history'][-1] if len(state['summary_history']) > 1 else "",
75
+ gr.Slider(
76
+ visible=False if len(state['summary_history']) <= 1 else True,
77
+ interactive=False if len(state['summary_history']) <= 1 else True,
78
+ ),
79
+ )
80
+
81
+ # make summary
82
+ response = await client.models.generate_content(
83
+ model=args.model,
84
+ contents=[
85
+ Template(
86
+ prompt_tmpl['summarization']['prompt']
87
+ ).safe_substitute(
88
+ previous_summary=state['summary'],
89
+ latest_conversation=str({"user": message['text'], "assistant": response_chunks})
90
+ )
91
+ ],
92
+ config=types.GenerateContentConfig(
93
+ system_instruction=system_instruction,
94
+ seed=args.seed,
95
+ response_mime_type='application/json',
96
+ response_schema=SummaryResponses
97
+ )
98
+ )
99
+
100
+ prev_summary = state['summary_history'][-1] if len(state['summary_history']) >= 1 else ""
101
+
102
+ state['summary'] = (
103
+ response.parsed.summary
104
+ if getattr(response.parsed, "summary", None) is not None
105
+ else response.text
106
+ )
107
+ state['summary_history'].append(
108
+ response.parsed.summary
109
+ if getattr(response.parsed, "summary", None) is not None
110
+ else response.text
111
+ )
112
+ state['summary_diff_history'].append(
113
+ [
114
+ (token[2:], token[0] if token[0] != " " else None)
115
+ for token in Differ().compare(prev_summary, state['summary'])
116
+ ]
117
+ )
118
+
119
+ yield (
120
+ response_chunks,
121
+ state,
122
+ state['summary_diff_history'][-1],
123
+ state['summary_history'][-1],
124
+ gr.Slider(
125
+ maximum=len(state['summary_history']),
126
+ value=len(state['summary_history']),
127
+ visible=False if len(state['summary_history']) == 1 else True, interactive=True
128
+ ),
129
+ )
130
+
131
+ def change_view_toggle(view_toggle):
132
+ if view_toggle == "Diff":
133
+ return (
134
+ gr.HighlightedText(visible=True),
135
+ gr.Markdown(visible=False)
136
+ )
137
+ else:
138
+ return (
139
+ gr.HighlightedText(visible=False),
140
+ gr.Markdown(visible=True)
141
+ )
142
+
143
+ def navigate_to_summary(summary_num, state):
144
+ return (
145
+ state['summary_diff_history'][summary_num-1],
146
+ state['summary_history'][summary_num-1]
147
+ )
148
+
149
+ def main(args):
150
+ style_css = open(args.css_path, "r").read()
151
+
152
+ global client, prompt_tmpl, system_instruction
153
+ client = setup_gemini_client(args)
154
+ prompt_tmpl = load_prompt(args)
155
+
156
+ ## Gradio Blocks
157
+ with gr.Blocks(css=style_css) as demo:
158
+ # State per session
159
+ state = gr.State({
160
+ "messages": [],
161
+ "attached_files": [],
162
+ "summary": "",
163
+ "summary_history": [],
164
+ "summary_diff_history": []
165
+ })
166
+
167
+ with gr.Column():
168
+ gr.Markdown("# Adaptive Summarization")
169
+ gr.Markdown("AdaptSum stands for Adaptive Summarization. This project focuses on developing an LLM-powered system for dynamic summarization. Instead of generating entirely new summaries with each update, the system intelligently identifies and modifies only the necessary parts of the existing summary. This approach aims to create a more efficient and fluid summarization process within a continuous chat interaction with an LLM.")
170
+
171
+ with gr.Column():
172
+ with gr.Accordion("Adaptively Summarized Conversation", elem_id="adaptive-summary-accordion", open=False):
173
+ with gr.Row(elem_id="view-toggle-btn-container"):
174
+ view_toggle_btn = gr.Radio(
175
+ choices=["Diff", "Markdown"],
176
+ value="Markdown",
177
+ interactive=True,
178
+ elem_id="view-toggle-btn"
179
+ )
180
+
181
+ summary_diff = gr.HighlightedText(
182
+ label="Summary so far",
183
+ # value="No summary yet. As you chat with the assistant, the summary will be updated automatically.",
184
+ combine_adjacent=True,
185
+ show_legend=True,
186
+ color_map={"-": "red", "+": "green"},
187
+ elem_classes=["summary-window"],
188
+ visible=False
189
+ )
190
+
191
+ summary_md = gr.Markdown(
192
+ label="Summary so far",
193
+ value="No summary yet. As you chat with the assistant, the summary will be updated automatically.",
194
+ elem_classes=["summary-window"],
195
+ visible=True
196
+ )
197
+
198
+ summary_num = gr.Slider(label="summary history", minimum=1, maximum=1, step=1, show_reset_button=False, visible=False)
199
+
200
+ view_toggle_btn.change(change_view_toggle, inputs=[view_toggle_btn], outputs=[summary_diff, summary_md])
201
+ summary_num.release(navigate_to_summary, inputs=[summary_num, state], outputs=[summary_diff, summary_md])
202
+
203
+ with gr.Column("persona-dropdown-container", elem_id="persona-dropdown-container"):
204
+ persona = gr.Dropdown(
205
+ ["expert", "novice", "regular practitioner", "high schooler"],
206
+ label="Summary Persona",
207
+ info="Control the tonality of the conversation.",
208
+ min_width="auto",
209
+ )
210
+
211
+ with gr.Column("chat-window", elem_id="chat-window"):
212
+ gr.ChatInterface(
213
+ multimodal=True,
214
+ type="messages",
215
+ fn=echo,
216
+ additional_inputs=[state, persona],
217
+ additional_outputs=[state, summary_diff, summary_md, summary_num],
218
+ )
219
+
220
+ return demo
221
+
222
+ if __name__ == "__main__":
223
+ args = parse_args()
224
+ demo = main(args)
225
+ demo.launch()
hf_space/configs/prompts.toml ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [summarization]
2
+ prompt = """
3
+ Below is the initial summary of our conversation.
4
+ Based on the summary and the last conversation between you(assistant) and me(user), I want to update the summary.
5
+
6
+ **Initial Summary:**
7
+ $previous_summary
8
+
9
+ **Last Conversation:**
10
+ $latest_conversation
11
+
12
+ When updating the summary:
13
+ * **Focus:** Only include information we have explicitly discussed in this session. Do not introduce any new information or topics, even if you have prior knowledge.
14
+ * **Accuracy:** Ensure the summary is factually accurate and reflects the nuances of our discussion.
15
+ * **Completeness:** Strive to be as comprehensive and detailed as possible, capturing all key points and insights.
16
+ * **Conciseness:** While being detailed, also aim for conciseness and clarity in the summary.
17
+ * **Update Strategy:** Instead of rewriting the entire summary each time, update only the specific portions necessary to reflect new information or changes in understanding.
18
+
19
+ By following these guidelines, you will maintain an evolving summary that accurately reflects my learning and the key takeaways from our conversation."
20
+
21
+ **Key improvements:**
22
+
23
+ * **Clearer Instructions:** More explicit instructions on how to update the summary (i.e., updating specific portions instead of rewriting).
24
+ * **Emphasis on Accuracy:** Stronger emphasis on factual accuracy and reflecting nuances.
25
+ * **Conciseness:** Added a direction to balance detail with conciseness.
26
+ * **Structure:** Improved organization and formatting for better readability. Bullet points with Markdown would be preferred.
27
+ """
hf_space/configs/responses.py ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ from pydantic import BaseModel
2
+
3
+ class SummaryResponses(BaseModel):
4
+ summary: str
hf_space/requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ google-genai==0.7.0
2
+ toml
3
+ gradio
hf_space/statics/styles.css ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .summary-window {
2
+ height: 550px !important;
3
+ /* border: dashed 1px #e0e0e0 !important; */
4
+ border-radius: 10px !important;
5
+ padding: 4px;
6
+ }
7
+
8
+ .summary-window > label {
9
+ display: none !important;
10
+ }
11
+
12
+ #view-toggle-btn-container > div {
13
+ border: none !important;
14
+ }
15
+
16
+ #view-toggle-btn > span {
17
+ display: none !important;
18
+ }
19
+
20
+ #view-toggle-btn > div:nth-child(3) {
21
+ margin: auto !important;
22
+ width: fit-content !important;
23
+ }
24
+
25
+ #adaptive-summary-accordion {
26
+ position: absolute !important;
27
+ z-index: 100 !important;
28
+ box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.2);
29
+ }
30
+
31
+ #chat-window {
32
+ margin-top: 40px !important;
33
+ }
34
+
35
+ #chat-window > div > div:nth-child(1) {
36
+ height: 600px !important;
37
+ }
38
+
39
+ .textfield {
40
+ line-height: 1.7 !important;
41
+ }
42
+
43
+ .textspan {
44
+ padding: 0px !important;
45
+ margin: 0px !important;
46
+ line-height: 1.7 !important;
47
+ }
48
+
49
+ @media (prefers-color-scheme: dark) {
50
+ #adaptive-summary-accordion {
51
+ /* White-ish shadow for dark themes */
52
+ box-shadow: 5px 5px 10px rgba(245, 245, 245, 0.2); /* Or any other white-ish color you prefer */
53
+ }
54
+ }
hf_space/utils.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import toml
2
+ from google import genai
3
+
4
+ def load_prompt(args):
5
+ with open(args.prompt_tmpl_path, 'r') as f:
6
+ prompts = toml.load(f)
7
+
8
+ return prompts
9
+
10
+ def setup_gemini_client(args):
11
+ if args.vertexai:
12
+ client = genai.Client(
13
+ vertexai=args.vertexai,
14
+ project=args.vertexai_project,
15
+ location=args.vertexai_location
16
+ )
17
+ else:
18
+ client = genai.Client(
19
+ api_key=args.ai_studio_api_key,
20
+ )
21
+
22
+ return client
requirements.txt CHANGED
@@ -1,3 +1,3 @@
1
  google-genai==0.7.0
2
  toml
3
- gradio
 
1
  google-genai==0.7.0
2
  toml
3
+ gradio
statics/styles.css CHANGED
@@ -1,5 +1,5 @@
1
  .summary-window {
2
- height: 550px !important;
3
  /* border: dashed 1px #e0e0e0 !important; */
4
  border-radius: 10px !important;
5
  padding: 4px;
@@ -28,9 +28,9 @@
28
  box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.2);
29
  }
30
 
31
- #chat-window {
32
  margin-top: 40px !important;
33
- }
34
 
35
  #chat-window > div > div:nth-child(1) {
36
  height: 600px !important;
@@ -46,9 +46,6 @@
46
  line-height: 1.7 !important;
47
  }
48
 
49
- @media (prefers-color-scheme: dark) {
50
- #adaptive-summary-accordion {
51
- /* White-ish shadow for dark themes */
52
- box-shadow: 5px 5px 10px rgba(245, 245, 245, 0.2); /* Or any other white-ish color you prefer */
53
- }
54
  }
 
1
  .summary-window {
2
+ height: 600px !important;
3
  /* border: dashed 1px #e0e0e0 !important; */
4
  border-radius: 10px !important;
5
  padding: 4px;
 
28
  box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.2);
29
  }
30
 
31
+ /* #chat-window {
32
  margin-top: 40px !important;
33
+ } */
34
 
35
  #chat-window > div > div:nth-child(1) {
36
  height: 600px !important;
 
46
  line-height: 1.7 !important;
47
  }
48
 
49
+ #persona-dropdown-container {
50
+ margin-top: 40px !important;
 
 
 
51
  }
utils.py CHANGED
@@ -9,14 +9,18 @@ def load_prompt(args):
9
 
10
  def setup_gemini_client(args):
11
  if args.vertexai:
12
- client = genai.Client(
13
- vertexai=args.vertexai,
14
- project=args.vertexai_project,
15
- location=args.vertexai_location
 
 
16
  )
17
  else:
18
- client = genai.Client(
19
- api_key=args.ai_studio_api_key,
 
 
20
  )
21
 
22
  return client
 
9
 
10
  def setup_gemini_client(args):
11
  if args.vertexai:
12
+ client = genai.client.AsyncClient(
13
+ genai.client.ApiClient(
14
+ vertexai=args.vertexai,
15
+ project=args.vertexai_project,
16
+ location=args.vertexai_location
17
+ )
18
  )
19
  else:
20
+ client = genai.client.AsyncClient(
21
+ genai.client.ApiClient(
22
+ api_key=args.ai_studio_api_key
23
+ )
24
  )
25
 
26
  return client