添加推理交互和模型配置:创建推理控制台UI和API,扩展config模型添加LLM模型字段(model_name/provider/api_key/temperature等)
This commit is contained in:
parent
4882ba0290
commit
3dfd935dfe
@ -81,6 +81,65 @@
|
||||
"nullable": "no",
|
||||
"default": "3"
|
||||
},
|
||||
{
|
||||
"name": "model_name",
|
||||
"title": "LLM model name (e.g. qwen3-max, claude-sonnet-4)",
|
||||
"type": "str",
|
||||
"length": 64,
|
||||
"nullable": "yes",
|
||||
"default": "qwen3-max"
|
||||
},
|
||||
{
|
||||
"name": "model_provider",
|
||||
"title": "Model provider (e.g. openrouter, anthropic, dashscope)",
|
||||
"type": "str",
|
||||
"length": 32,
|
||||
"nullable": "yes",
|
||||
"default": "openrouter"
|
||||
},
|
||||
{
|
||||
"name": "api_key",
|
||||
"title": "API key for LLM provider",
|
||||
"type": "text",
|
||||
"nullable": "yes",
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"name": "api_endpoint",
|
||||
"title": "API endpoint URL (optional, uses default if empty)",
|
||||
"type": "str",
|
||||
"length": 255,
|
||||
"nullable": "yes",
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"name": "temperature",
|
||||
"title": "LLM temperature (0.0-1.0)",
|
||||
"type": "float",
|
||||
"nullable": "no",
|
||||
"default": "0.7"
|
||||
},
|
||||
{
|
||||
"name": "max_output_tokens",
|
||||
"title": "Maximum output tokens",
|
||||
"type": "int",
|
||||
"nullable": "no",
|
||||
"default": "4096"
|
||||
},
|
||||
{
|
||||
"name": "top_p",
|
||||
"title": "Top-p sampling parameter",
|
||||
"type": "float",
|
||||
"nullable": "no",
|
||||
"default": "0.9"
|
||||
},
|
||||
{
|
||||
"name": "system_prompt",
|
||||
"title": "System prompt template for reasoning",
|
||||
"type": "text",
|
||||
"nullable": "yes",
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"name": "created_at",
|
||||
"title": "Creation timestamp",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Get reasoning configuration for current user"""
|
||||
"""Get reasoning configuration for current user (includes model config)"""
|
||||
import json
|
||||
|
||||
result = {'success': False, 'config': {}}
|
||||
@ -18,7 +18,6 @@ try:
|
||||
rows = await sor.sqlExe(sql, {'user_id': user_id})
|
||||
if rows and len(rows) > 0:
|
||||
config = dict(rows[0])
|
||||
# Convert string booleans to actual booleans for UI
|
||||
config['enable_cross_session_search'] = config.get('enable_cross_session_search', '1') == '1'
|
||||
config['enable_skill_auto_loading'] = config.get('enable_skill_auto_loading', '1') == '1'
|
||||
config['enable_error_recovery'] = config.get('enable_error_recovery', '1') == '1'
|
||||
@ -32,7 +31,15 @@ try:
|
||||
'safety_mode': 'strict',
|
||||
'max_context_tokens': 4000,
|
||||
'enable_error_recovery': True,
|
||||
'max_recovery_attempts': 3
|
||||
'max_recovery_attempts': 3,
|
||||
'model_name': 'qwen3-max',
|
||||
'model_provider': 'openrouter',
|
||||
'api_key': '',
|
||||
'api_endpoint': '',
|
||||
'temperature': 0.7,
|
||||
'max_output_tokens': 4096,
|
||||
'top_p': 0.9,
|
||||
'system_prompt': ''
|
||||
}
|
||||
result['success'] = True
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Save reasoning configuration for current user"""
|
||||
"""Save reasoning configuration for current user (includes model config)"""
|
||||
import json, uuid, time
|
||||
|
||||
result = {'widgettype': 'Message', 'options': {'title': 'Error', 'message': 'Invalid request', 'type': 'error'}}
|
||||
@ -10,6 +10,7 @@ try:
|
||||
user_id = await get_user()
|
||||
now = time.strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
# Reasoning engine params
|
||||
max_reasoning_steps = int(params_kw.get('max_reasoning_steps', 10))
|
||||
max_tool_calls_per_step = int(params_kw.get('max_tool_calls_per_step', 5))
|
||||
enable_cross_session_search = '1' if params_kw.get('enable_cross_session_search') == '1' else '0'
|
||||
@ -19,8 +20,17 @@ try:
|
||||
enable_error_recovery = '1' if params_kw.get('enable_error_recovery') == '1' else '0'
|
||||
max_recovery_attempts = int(params_kw.get('max_recovery_attempts', 3))
|
||||
|
||||
# Model configuration
|
||||
model_name = params_kw.get('model_name', 'qwen3-max').strip()
|
||||
model_provider = params_kw.get('model_provider', 'openrouter').strip()
|
||||
api_key = params_kw.get('api_key', '').strip()
|
||||
api_endpoint = params_kw.get('api_endpoint', '').strip()
|
||||
temperature = float(params_kw.get('temperature', 0.7))
|
||||
max_output_tokens = int(params_kw.get('max_output_tokens', 4096))
|
||||
top_p = float(params_kw.get('top_p', 0.9))
|
||||
system_prompt = params_kw.get('system_prompt', '').strip()
|
||||
|
||||
async with DBPools().sqlorContext(dbname) as sor:
|
||||
# Check if config exists
|
||||
rows = await sor.sqlExe("SELECT id FROM harnessed_reasoning_config WHERE user_id = ${user_id}$", {'user_id': user_id})
|
||||
|
||||
if rows and len(rows) > 0:
|
||||
@ -34,6 +44,14 @@ try:
|
||||
max_context_tokens = ${max_context_tokens}$,
|
||||
enable_error_recovery = ${enable_error_recovery}$,
|
||||
max_recovery_attempts = ${max_recovery_attempts}$,
|
||||
model_name = ${model_name}$,
|
||||
model_provider = ${model_provider}$,
|
||||
api_key = ${api_key}$,
|
||||
api_endpoint = ${api_endpoint}$,
|
||||
temperature = ${temperature}$,
|
||||
max_output_tokens = ${max_output_tokens}$,
|
||||
top_p = ${top_p}$,
|
||||
system_prompt = ${system_prompt}$,
|
||||
updated_at = ${updated_at}$
|
||||
WHERE id = ${id}$""", {
|
||||
'id': config_id,
|
||||
@ -45,6 +63,14 @@ try:
|
||||
'max_context_tokens': max_context_tokens,
|
||||
'enable_error_recovery': enable_error_recovery,
|
||||
'max_recovery_attempts': max_recovery_attempts,
|
||||
'model_name': model_name,
|
||||
'model_provider': model_provider,
|
||||
'api_key': api_key,
|
||||
'api_endpoint': api_endpoint,
|
||||
'temperature': temperature,
|
||||
'max_output_tokens': max_output_tokens,
|
||||
'top_p': top_p,
|
||||
'system_prompt': system_prompt,
|
||||
'updated_at': now
|
||||
})
|
||||
else:
|
||||
@ -53,10 +79,14 @@ try:
|
||||
(id, user_id, max_reasoning_steps, max_tool_calls_per_step,
|
||||
enable_cross_session_search, enable_skill_auto_loading, safety_mode,
|
||||
max_context_tokens, enable_error_recovery, max_recovery_attempts,
|
||||
model_name, model_provider, api_key, api_endpoint,
|
||||
temperature, max_output_tokens, top_p, system_prompt,
|
||||
created_at, updated_at)
|
||||
VALUES (${id}$, ${user_id}$, ${max_reasoning_steps}$, ${max_tool_calls_per_step}$,
|
||||
${enable_cross_session_search}$, ${enable_skill_auto_loading}$, ${safety_mode}$,
|
||||
${max_context_tokens}$, ${enable_error_recovery}$, ${max_recovery_attempts}$,
|
||||
${model_name}$, ${model_provider}$, ${api_key}$, ${api_endpoint}$,
|
||||
${temperature}$, ${max_output_tokens}$, ${top_p}$, ${system_prompt}$,
|
||||
${created_at}$, ${updated_at}$)""", {
|
||||
'id': config_id,
|
||||
'user_id': user_id,
|
||||
@ -68,6 +98,14 @@ try:
|
||||
'max_context_tokens': max_context_tokens,
|
||||
'enable_error_recovery': enable_error_recovery,
|
||||
'max_recovery_attempts': max_recovery_attempts,
|
||||
'model_name': model_name,
|
||||
'model_provider': model_provider,
|
||||
'api_key': api_key,
|
||||
'api_endpoint': api_endpoint,
|
||||
'temperature': temperature,
|
||||
'max_output_tokens': max_output_tokens,
|
||||
'top_p': top_p,
|
||||
'system_prompt': system_prompt,
|
||||
'created_at': now,
|
||||
'updated_at': now
|
||||
})
|
||||
|
||||
61
wwwroot/api/reasoning_submit.dspy
Normal file
61
wwwroot/api/reasoning_submit.dspy
Normal file
@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Submit reasoning request and return results"""
|
||||
import json, time, uuid
|
||||
|
||||
result = {'widgettype': 'Message', 'options': {'title': 'Error', 'message': '推理请求失败', 'type': 'error'}}
|
||||
|
||||
try:
|
||||
request_text = params_kw.get('request', '').strip()
|
||||
if not request_text:
|
||||
result['options'] = {'title': 'Error', 'message': '请输入推理请求', 'type': 'error'}
|
||||
else:
|
||||
user_id = await get_user()
|
||||
execute_immediately = params_kw.get('execute_immediately', '1') == '1'
|
||||
|
||||
# Call the reasoning engine from ServerEnv
|
||||
reasoning_result = await hermes_reason_and_execute(
|
||||
request=request_text,
|
||||
execute_immediately=execute_immediately
|
||||
)
|
||||
|
||||
if reasoning_result.get('success'):
|
||||
# Build result widget showing reasoning output
|
||||
plan_items = []
|
||||
for step in reasoning_result.get('execution_plan', []):
|
||||
plan_items.append(f"步骤{step.get('step_number', '?')}: {step.get('description', '')}")
|
||||
|
||||
plan_text = '\n'.join(plan_items) if plan_items else '无执行计划'
|
||||
safety_text = '\n'.join(reasoning_result.get('safety_violations', [])) or '无安全风险'
|
||||
|
||||
summary = (
|
||||
f"请求: {request_text}\n\n"
|
||||
f"上下文: {reasoning_result.get('context_summary', '无')}\n\n"
|
||||
f"置信度: {reasoning_result.get('confidence_score', 0):.0%}\n\n"
|
||||
f"执行计划:\n{plan_text}\n\n"
|
||||
f"安全检查:\n{safety_text}\n\n"
|
||||
f"状态: {reasoning_result.get('status', 'unknown')}"
|
||||
)
|
||||
|
||||
if reasoning_result.get('execution_results'):
|
||||
summary += f"\n\n执行结果:\n{json.dumps(reasoning_result['execution_results'], ensure_ascii=False, indent=2)}"
|
||||
|
||||
result = {
|
||||
'widgettype': 'Message',
|
||||
'options': {
|
||||
'title': '推理完成',
|
||||
'message': summary,
|
||||
'type': 'success'
|
||||
}
|
||||
}
|
||||
else:
|
||||
result['options'] = {
|
||||
'title': '推理失败',
|
||||
'message': reasoning_result.get('error', '未知错误'),
|
||||
'type': 'error'
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
result['options'] = {'title': 'Error', 'message': '推理异常: ' + str(e), 'type': 'error'}
|
||||
|
||||
return json.dumps(result, ensure_ascii=False)
|
||||
@ -1,14 +1,21 @@
|
||||
{
|
||||
"widgettype": "TabPanel",
|
||||
"options": {
|
||||
"tab_pos": "top",
|
||||
"items": [
|
||||
{
|
||||
"name": "reasoning_config",
|
||||
"label": "推理引擎",
|
||||
"icon": "settings",
|
||||
"content": {
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"width": "100%",
|
||||
"height": "100%",
|
||||
"padding": "16px"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Form",
|
||||
"id": "config_form",
|
||||
"id": "engine_config_form",
|
||||
"options": {
|
||||
"data_url": "{{entire_url('api/config_get.dspy')}}",
|
||||
"data_method": "GET",
|
||||
@ -29,14 +36,10 @@
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "enable_cross_session_search",
|
||||
"label": "启用跨会话搜索",
|
||||
"uitype": "check"
|
||||
},
|
||||
{
|
||||
"name": "enable_skill_auto_loading",
|
||||
"label": "启用技能自动加载",
|
||||
"uitype": "check"
|
||||
"name": "max_context_tokens",
|
||||
"label": "最大上下文 token 数",
|
||||
"uitype": "int",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "safety_mode",
|
||||
@ -50,10 +53,14 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "max_context_tokens",
|
||||
"label": "最大上下文 token 数",
|
||||
"uitype": "int",
|
||||
"required": true
|
||||
"name": "enable_cross_session_search",
|
||||
"label": "启用跨会话搜索",
|
||||
"uitype": "check"
|
||||
},
|
||||
{
|
||||
"name": "enable_skill_auto_loading",
|
||||
"label": "启用技能自动加载",
|
||||
"uitype": "check"
|
||||
},
|
||||
{
|
||||
"name": "enable_error_recovery",
|
||||
@ -70,7 +77,7 @@
|
||||
"buttons": [
|
||||
{
|
||||
"type": "submit",
|
||||
"label": "保存配置",
|
||||
"label": "保存引擎配置",
|
||||
"variant": "primary"
|
||||
}
|
||||
],
|
||||
@ -87,3 +94,107 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "model_config",
|
||||
"label": "模型配置",
|
||||
"icon": "code",
|
||||
"content": {
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"padding": "16px"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Form",
|
||||
"id": "model_config_form",
|
||||
"options": {
|
||||
"data_url": "{{entire_url('api/config_get.dspy')}}",
|
||||
"data_method": "GET",
|
||||
"submit_url": "{{entire_url('api/config_save.dspy')}}",
|
||||
"method": "POST",
|
||||
"layout": "vertical",
|
||||
"fields": [
|
||||
{
|
||||
"name": "model_name",
|
||||
"label": "模型名称",
|
||||
"uitype": "text",
|
||||
"required": true,
|
||||
"placeholder": "qwen3-max, claude-sonnet-4, gpt-4o..."
|
||||
},
|
||||
{
|
||||
"name": "model_provider",
|
||||
"label": "模型提供商",
|
||||
"uitype": "code",
|
||||
"required": true,
|
||||
"data": [
|
||||
{"value": "openrouter", "text": "OpenRouter"},
|
||||
{"value": "anthropic", "text": "Anthropic"},
|
||||
{"value": "dashscope", "text": "Dashscope (阿里云)"},
|
||||
{"value": "openai", "text": "OpenAI"},
|
||||
{"value": "custom", "text": "自定义"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "api_key",
|
||||
"label": "API Key",
|
||||
"uitype": "password",
|
||||
"required": false,
|
||||
"placeholder": "输入 API Key"
|
||||
},
|
||||
{
|
||||
"name": "api_endpoint",
|
||||
"label": "API Endpoint (可选)",
|
||||
"uitype": "text",
|
||||
"placeholder": "留空使用默认端点"
|
||||
},
|
||||
{
|
||||
"name": "temperature",
|
||||
"label": "Temperature (0.0-1.0)",
|
||||
"uitype": "float",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "max_output_tokens",
|
||||
"label": "最大输出 token 数",
|
||||
"uitype": "int",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "top_p",
|
||||
"label": "Top-p (0.0-1.0)",
|
||||
"uitype": "float",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "system_prompt",
|
||||
"label": "系统提示词模板",
|
||||
"uitype": "textarea",
|
||||
"rows": 4,
|
||||
"placeholder": "留空使用默认系统提示词"
|
||||
}
|
||||
],
|
||||
"buttons": [
|
||||
{
|
||||
"type": "submit",
|
||||
"label": "保存模型配置",
|
||||
"variant": "primary"
|
||||
}
|
||||
],
|
||||
"maxWidth": "500px"
|
||||
},
|
||||
"binds": [
|
||||
{
|
||||
"wid": "self",
|
||||
"event": "submited",
|
||||
"actiontype": "script",
|
||||
"script": "await bricks.show_resp_message_or_error(event.params)"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,7 +15,6 @@
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"text": "Reasoning Engine",
|
||||
"css": "ios-navbar-title",
|
||||
"fontSize": "18px",
|
||||
"fontWeight": "bold",
|
||||
"color": "#1E40AF"
|
||||
@ -24,8 +23,7 @@
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"text": "Manage sessions and system configuration",
|
||||
"css": "ios-navbar-subtitle",
|
||||
"text": "AI reasoning and execution planning",
|
||||
"fontSize": "12px",
|
||||
"color": "#6B7280"
|
||||
}
|
||||
@ -39,6 +37,17 @@
|
||||
"tab_pos": "top",
|
||||
"css": "tabpanel",
|
||||
"items": [
|
||||
{
|
||||
"name": "console",
|
||||
"label": "推理控制台",
|
||||
"icon": "terminal",
|
||||
"content": {
|
||||
"widgettype": "urlwidget",
|
||||
"options": {
|
||||
"url": "{{entire_url('reasoning_console.ui')}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "sessions",
|
||||
"label": "推理会话",
|
||||
@ -52,7 +61,7 @@
|
||||
},
|
||||
{
|
||||
"name": "config",
|
||||
"label": "推理配置",
|
||||
"label": "配置管理",
|
||||
"icon": "settings",
|
||||
"content": {
|
||||
"widgettype": "urlwidget",
|
||||
|
||||
@ -14,6 +14,21 @@
|
||||
"name":"hermes_reasoning",
|
||||
"label":"推理控制台",
|
||||
"url":"{{entire_url('hermes_reasoning.ui')}}"
|
||||
},
|
||||
{
|
||||
"name":"reasoning_console",
|
||||
"label":"推理交互",
|
||||
"url":"{{entire_url('reasoning_console.ui')}}"
|
||||
},
|
||||
{
|
||||
"name":"reasoning_sessions",
|
||||
"label":"推理会话",
|
||||
"url":"{{entire_url('harnessed_reasoning_sessions_crud')}}"
|
||||
},
|
||||
{
|
||||
"name":"reasoning_config",
|
||||
"label":"推理配置",
|
||||
"url":"{{entire_url('harnessed_reasoning_config_view')}}"
|
||||
}
|
||||
{% endif %}
|
||||
]
|
||||
|
||||
86
wwwroot/reasoning_console.ui
Normal file
86
wwwroot/reasoning_console.ui
Normal file
@ -0,0 +1,86 @@
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"width": "100%",
|
||||
"height": "100%",
|
||||
"padding": "16px",
|
||||
"spacing": "16px"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"text": "推理控制台",
|
||||
"fontSize": "20px",
|
||||
"fontWeight": "bold",
|
||||
"color": "#1E40AF"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Form",
|
||||
"id": "reasoning_form",
|
||||
"options": {
|
||||
"submit_url": "{{entire_url('api/reasoning_submit.dspy')}}",
|
||||
"method": "POST",
|
||||
"layout": "vertical",
|
||||
"fields": [
|
||||
{
|
||||
"name": "request",
|
||||
"label": "推理请求",
|
||||
"uitype": "textarea",
|
||||
"required": true,
|
||||
"rows": 4,
|
||||
"placeholder": "请输入您的请求,例如:分析当前项目的安全问题并生成修复方案"
|
||||
},
|
||||
{
|
||||
"name": "execute_immediately",
|
||||
"label": "提交后立即执行(否则仅生成计划)",
|
||||
"uitype": "check",
|
||||
"checked": true
|
||||
}
|
||||
],
|
||||
"buttons": [
|
||||
{
|
||||
"type": "submit",
|
||||
"label": "开始推理",
|
||||
"variant": "primary"
|
||||
}
|
||||
],
|
||||
"maxWidth": "700px"
|
||||
},
|
||||
"binds": [
|
||||
{
|
||||
"wid": "self",
|
||||
"event": "submited",
|
||||
"actiontype": "script",
|
||||
"script": "await bricks.show_resp_message_or_error(event.params)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"width": "100%",
|
||||
"padding": "16px"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"text": "历史推理记录",
|
||||
"fontSize": "16px",
|
||||
"fontWeight": "bold",
|
||||
"color": "#374151",
|
||||
"marginBottom": "8px"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "urlwidget",
|
||||
"options": {
|
||||
"url": "{{entire_url('harnessed_reasoning_sessions_crud')}}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user