diff --git a/app.py b/app.py index 0926b0e..2d86cc2 100644 --- a/app.py +++ b/app.py @@ -1,156 +1,195 @@ """ 农技智能问答 -基于 Qwen3.5 大模型的农业技术知识问答应用 +基于大模型的农业技术知识问答应用 """ +import json + import httpx import streamlit as st from config import CHAT_API_URL, CHAT_MODEL, HEADERS # ─── Page Config ──────────────────────────────────────────────────────────── st.set_page_config( - page_title="农技智能问答", + page_title="农技问答", page_icon="🌾", - layout="wide", - initial_sidebar_state="expanded", + layout="centered", + initial_sidebar_state="collapsed", ) # ─── Custom CSS ────────────────────────────────────────────────────────────── st.markdown(""" """, unsafe_allow_html=True) @@ -158,108 +197,140 @@ html, body, [class*="css"] { # ─── Quick Questions ───────────────────────────────────────────────────────── QUICK_QUESTIONS = [ - "水稻稻瘟病怎么防治?什么时候打药效果最好?", - "小麦叶片上出现铁锈色的粉状物是什么病?怎么处理?", - "如何判断玉米是否需要灌溉?有哪些判断方法?", - "如何种榴莲?怎样成熟快?", + "水稻稻瘟病怎么防治?", + "小麦锈病怎么处理?", + "玉米什么时候浇水最合适?", + "种榴莲需要注意什么?", ] -# ─── Sidebar ───────────────────────────────────────────────────────────────── -with st.sidebar: - st.markdown('
🌾 农技问答
', unsafe_allow_html=True) - st.markdown('
AGRICULTURE QA SYSTEM v1.0
', unsafe_allow_html=True) - st.markdown("---") - - st.markdown('
⚙️ 模型参数
', unsafe_allow_html=True) - temperature = st.slider("Temperature", 0.0, 1.0, 0.7, 0.1) - top_p = st.slider("Top P", 0.0, 1.0, 0.8, 0.1) - enable_thinking = st.checkbox("开启思维链", value=True) - - st.markdown('
💡 快捷提问
', unsafe_allow_html=True) - for q in QUICK_QUESTIONS: - if st.button(q, key=q): - st.session_state.user_input = q - - -# ─── Main Layout ───────────────────────────────────────────────────────────── +# ─── Header ────────────────────────────────────────────────────────────────── st.markdown(""" -
-
农技智能问答
+
+

🌾 农技问答

+

有农业问题,随时来问

-
Powered by Qwen3.5  |  支持思维链推理的农业技术知识问答
""", unsafe_allow_html=True) -st.markdown("
", unsafe_allow_html=True) +# ─── Quick Questions ───────────────────────────────────────────────────────── +chips_html = '
' +for q in QUICK_QUESTIONS: + chips_html += f'{q}' +chips_html += '
' +st.markdown(chips_html, unsafe_allow_html=True) -# 输入区域 +# Handle chip clicks via native buttons (invisible, placed neatly) +cols = st.columns(len(QUICK_QUESTIONS)) +for i, q in enumerate(QUICK_QUESTIONS): + with cols[i]: + st.markdown("
" + " " + "
", unsafe_allow_html=True) + if st.button(q, key=f"chip_{q}", use_container_width=True): + st.session_state.user_input = q + st.rerun() + +# ─── Input ─────────────────────────────────────────────────────────────────── +st.markdown('
', unsafe_allow_html=True) user_input = st.text_area( - "请输入您的农业问题:", + "请输入您的问题", value=st.session_state.get("user_input", ""), - height=120, + height=110, placeholder="例如:水稻稻瘟病怎么防治?", + label_visibility="collapsed", ) +col1, col2 = st.columns([1, 6]) +with col1: + submitted = st.button("发送", use_container_width=True) +st.markdown('
', unsafe_allow_html=True) -if st.button("提交", type="primary") and user_input.strip(): - with st.spinner("模型正在思考中..."): - try: - payload = { - "model": CHAT_MODEL, - "messages": [{"role": "user", "content": user_input.strip()}], - "temperature": temperature, - "top_p": top_p, - "presence_penalty": 1.5, - "chat_template_kwargs": {"enable_thinking": enable_thinking}, - } - resp = httpx.post( - CHAT_API_URL, headers=HEADERS, json=payload, timeout=120 - ) +# ─── Settings Panel ────────────────────────────────────────────────────────── +with st.expander("⚙️ 模型设置"): + col_t, col_p, col_c = st.columns(3) + with col_t: + temperature = st.slider("Temperature", 0.0, 1.0, 0.7, 0.1) + with col_p: + top_p = st.slider("Top P", 0.0, 1.0, 0.8, 0.1) + with col_c: + enable_thinking = st.checkbox("显示推理过程", value=True) + +# ─── Request & Stream ──────────────────────────────────────────────────────── +if submitted and user_input.strip(): + try: + payload = { + "model": CHAT_MODEL, + "messages": [{"role": "user", "content": user_input.strip()}], + "temperature": temperature, + "top_p": top_p, + "presence_penalty": 1.5, + "chat_template_kwargs": {"enable_thinking": enable_thinking}, + "stream": True, + } + + thinking_placeholder = st.empty() + answer_placeholder = st.empty() + + full_reasoning = "" + full_content = "" + + with httpx.stream( + "POST", CHAT_API_URL, headers=HEADERS, json=payload, timeout=120 + ) as resp: resp.raise_for_status() - data = resp.json() + for line in resp.iter_lines(): + if not line.startswith("data: "): + continue + data_str = line[6:] + if data_str == "[DONE]": + break + try: + chunk = json.loads(data_str) + except json.JSONDecodeError: + continue - choice = data["choices"][0]["message"] - reasoning = choice.get("reasoning_content", "") - content = choice["content"] - usage = data["usage"] + delta = chunk.get("choices", [{}])[0].get("delta", {}) - # 显示回答 - st.markdown(f""" -
-

💬 模型回答

-
{content}
-
- """, unsafe_allow_html=True) + reasoning_piece = delta.get("reasoning_content", "") + if reasoning_piece: + full_reasoning += reasoning_piece + if enable_thinking: + thinking_placeholder.markdown( + f'
' + f'正在思考...
' + f'
{full_reasoning}
' + f'
', + unsafe_allow_html=True, + ) - # 显示思考过程(可折叠) - if reasoning and enable_thinking: - with st.expander("🧠 查看思考过程"): - st.markdown(reasoning) + content_piece = delta.get("content", "") + if content_piece: + full_content += content_piece + answer_placeholder.markdown( + f'
' + f'

🌱 回答

' + f'
' + f'{full_content}
' + f'
', + unsafe_allow_html=True, + ) - # Token 用量 - st.markdown(f""" -
- Token 用量 — 输入: {usage['prompt_tokens']} | \ - 输出: {usage['completion_tokens']} | \ - 合计: {usage['total_tokens']} -
- """, unsafe_allow_html=True) + if full_reasoning and enable_thinking: + thinking_placeholder.empty() + with st.expander("查看推理过程"): + st.markdown(full_reasoning) - except httpx.HTTPStatusError as e: - st.markdown( - f'
请求失败 (HTTP {e.response.status_code}): {e.response.text}
', - unsafe_allow_html=True, - ) - except Exception as e: - st.markdown( - f'
请求异常: {e}
', - unsafe_allow_html=True, - ) + except httpx.HTTPStatusError as e: + st.markdown( + f'
请求失败 (HTTP {e.response.status_code}): {e.response.text}
', + unsafe_allow_html=True, + ) + except Exception as e: + st.markdown( + f'
请求异常: {e}
', + unsafe_allow_html=True, + ) # ─── Footer ─────────────────────────────────────────────────────────────────── -st.markdown("
", unsafe_allow_html=True) st.markdown(""" -
- Qwen3.5 + Thinking Chain  |  农技智能问答系统 + """, unsafe_allow_html=True)