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('
-
农技智能问答
+
-
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"""
-
- """, 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)