使用apifox调用百度api的时候报错“error_code“: 282002, “error_msg“: “input encoding error“

本文介绍了如何在与百度API交互时,由于默认请求体采用GBK编码,需在HTTP头部添加charset= UTF-8 的解决方法,强调了字符集转换的重要性。

原因是百度默认的请求体是以GBK编码的,所以需要在请求头中加入charset,设置为UTF-8(区分大小写)

 

import pandas as pd import requests import os from PIL import Image from io import BytesIO import time def batch_fetch_street_view(input_xls, output_dir, api_key, max_retries=3): """ 批量获取高德地图街景数据 参数: input_xls: 输入XLS文件路径(需包含FID, X, Y列) output_dir: 图片输出目录 api_key: 高德地图API密钥 max_retries: 失败重试次数 """ # 创建输出目录 os.makedirs(output_dir, exist_ok=True) # 读取Excel文件 try: df = pd.read_excel(input_xls, engine='openpyxl') print(f"成功读取Excel文件,共{len(df)}条记录") except Exception as e: print(f"读取Excel文件失败: {e}") return # 检查必需列是否存在 required_columns = ['FID', 'X', 'Y'] if not all(col in df.columns for col in required_columns): missing = [col for col in required_columns if col not in df.columns] print(f"缺少必需列: {', '.join(missing)}") return # 创建结果日志文件 log_file = os.path.join(output_dir, "streetview_log.csv") with open(log_file, 'w', encoding='utf-8') as f: f.write("FID,X,Y,Status,Error_Message\n") # 高德地图API参数配置 base_url = "https://restapi.amap.com/v3/staticmap" params = { 'zoom': 17, # 缩放级别(街景固定为17) 'size': '1024*512', # 图片尺寸 'fov': 180, # 视野范围(180度) 'view': '360', # 全景模式 'key': api_key # API密钥 } # 遍历所有位置点 success_count = 0 for index, row in df.iterrows(): fid = row['FID'] location = f"{row['X']},{row['Y']}" params['location'] = location retry = 0 status = "Success" error_msg = "" while retry <= max_retries: try: # 发送API请求 response = requests.get(base_url, params=params) # 检查响应状态 if response.status_code == 200: # 检查返回内容是否为图片 if 'image' in response.headers.get('Content-Type', ''): # 保存图片 img = Image.open(BytesIO(response.content)) img_path = os.path.join(output_dir, f"{fid}.jpg") img.save(img_path) print(f"成功获取 FID={fid} 的街景图") success_count += 1 break else: # 尝试解析错误信息 error_data = response.json() error_msg = f"API返回错误: {error_data.get('info', '未知错误')}" status = "Failed" else: error_msg = f"HTTP错误: {response.status_code}" status = "Failed" except Exception as e: error_msg = f"请求异常: {str(e)}" status = "Failed" retry += 1 if retry <= max_retries: print(f"重试 {retry}/{max_retries} 对于 FID={fid}") time.sleep(1) # 重试前等待1秒 # 记录日志 with open(log_file, 'a', encoding='utf-8') as f: f.write(f"{fid},{row['X']},{row['Y']},{status},\"{error_msg}\"\n") print(f"\n处理完成!成功获取 {success_count}/{len(df)} 个位置的街景图") print(f"日志文件已保存至: {log_file}") # 使用示例 if __name__ == "__main__": # 配置参数 INPUT_XLS = "locations.xls" # 替换为您的XLS文件路径 OUTPUT_DIR = "streetview_images" # 街景图片输出目录 API_KEY = "0096b81db6aebe1c1a3e6f157aea3ad1" # 您的高德地图API密钥 # 执行批量获取 batch_fetch_street_view(INPUT_XLS, OUTPUT_DIR, API_KEY) 在此代码上改
最新发布
11-29
我用命令行用voxtral转写音频,运行代码后报错/pytorch/aten/src/ATen/native/cuda/IndexKernel.cu:382: masked_scatter_size_check: block: [0,0,0], thread: [0,0,0] Assertion `totalElements <= srcSize` failed. ❌ [0.00-15.00分钟] 转写失败: CUDA error: device-side assert triggered CUDA kernel errors might be asynchronously reported at some other API call, so the stacktrace below might be incorrect. For debugging consider passing CUDA_LAUNCH_BLOCKING=1 Compile with `TORCH_USE_CUDA_DSA` to enable device-side assertions. 下面是代码#!/usr/bin/env python3 import os import sys import torch import librosa import numpy as np from transformers import AutoProcessor, VoxtralForConditionalGeneration import gc def transcribe_long_audio_chinese_fixed(model_path, audio_path, output_path, chunk_minutes=15): print(f"🎵 加载音频: {audio_path}") audio_data, sr = librosa.load(audio_path, sr=16000) total_duration = len(audio_data) / sr print(f"📊 采样率: {sr}Hz") print(f"📊 总采样点数: {len(audio_data):,}") print(f"⏱️ 总时长: {total_duration/60:.2f}分钟 ({total_duration:.2f}秒)") if torch.cuda.is_available(): gpu_name = torch.cuda.get_device_name(0) gpu_memory = torch.cuda.get_device_properties(0).total_memory / 1024**3 print(f"🚀 使用GPU: {gpu_name} ({gpu_memory:.1f}GB)") else: print("⚠️ 使用CPU模式") print(f"📦 加载模型: {model_path}") processor = AutoProcessor.from_pretrained(model_path, trust_remote_code=True) dtype = torch.float16 if torch.cuda.is_available() else torch.float32 device = "cuda" if torch.cuda.is_available() else "cpu" model = VoxtralForConditionalGeneration.from_pretrained( model_path, torch_dtype=dtype, trust_remote_code=True, device_map="auto" if torch.cuda.is_available() else None ) model.eval() print(f"✅ 模型已加载到 {device}") # 🔥 关键修复:确保模型配置中有audio_token_id if not hasattr(model.config, 'audio_token_id') or model.config.audio_token_id is None: # 从tokenizer词汇表推断audio_token_id vocab = processor.tokenizer.get_vocab() # 通常audio_token在词汇表末尾或者是一个特殊的大数值 vocab_size = len(vocab) # 尝试几个可能的值 possible_audio_token_ids = [128256, vocab_size, vocab_size + 1, 51865, 51866] # 检查config中是否有其他提示 for attr in dir(model.config): if 'audio' in attr.lower() and not attr.startswith('_'): val = getattr(model.config, attr) if isinstance(val, int): possible_audio_token_ids.insert(0, val) # 使用第一个候选值 audio_token_id = possible_audio_token_ids[0] model.config.audio_token_id = audio_token_id print(f"⚠️ 模型配置中audio_token_id为None,已设置为: {audio_token_id}") print(f" 词汇表大小: {vocab_size}") else: audio_token_id = model.config.audio_token_id print(f"✅ 使用配置中的audio_token_id: {audio_token_id}") chunk_seconds = chunk_minutes * 60 chunk_size = int(chunk_seconds * sr) total_len = len(audio_data) chunks = [] for i in range(0, total_len, chunk_size): start = i end = min(i + chunk_size, total_len) chunks.append((start, end)) print(f"🔄 将音频分为 {len(chunks)} 块,每块约 {chunk_minutes} 分钟") print("=" * 60) transcripts = [] for idx, (start, end) in enumerate(chunks): inputs = None input_features = None input_ids = None attention_mask = None outputs = None start_time = start / sr end_time = end / sr chunk_duration = (end - start) / sr print(f"🎯 处理块 {idx + 1}/{len(chunks)}") print(f" 时间段: {start_time/60:.2f}-{end_time/60:.2f}分钟") print(f" 时长: {chunk_duration/60:.2f}分钟 ({chunk_duration:.1f}秒)") chunk_audio = audio_data[start:end].astype(np.float32) print(f" 音频块大小: {len(chunk_audio):,} 采样点") try: print(" 🔄 预处理音频...") audio_inputs = processor.feature_extractor( chunk_audio, sampling_rate=sr, return_tensors="pt" ) input_features = audio_inputs['input_features'].to(device) # 获取token IDs bos_token_id = processor.tokenizer.bos_token_id if processor.tokenizer.bos_token_id is not None else 1 eos_token_id = processor.tokenizer.eos_token_id if processor.tokenizer.eos_token_id is not None else 2 # 🔥 修复:获取正确的音频时间步数(第3维,不是第2维) # input_features shape: [batch, n_mels, time_steps] num_audio_time_steps = input_features.shape[2] # 注意是shape[2]不是shape[1] print(f" 音频特征形状: {input_features.shape} (batch, n_mels, time_steps)") print(f" 音频时间步数: {num_audio_time_steps}") # 中文提示文本 chinese_prompt = "以下是普通话的句子。" text_encoded = processor.tokenizer.encode(chinese_prompt, add_special_tokens=False) print(f" 中文提示token数: {len(text_encoded)}") print(f" 使用audio_token_id: {model.config.audio_token_id}") # 🔥 关键:构造input_ids,使用音频时间步数 # 格式:[BOS] + [文本提示tokens] + [audio_token * 音频时间步数] + [EOS] input_ids_list = ( [bos_token_id] + text_encoded + [model.config.audio_token_id] * num_audio_time_steps + [eos_token_id] ) # 确保没有None值 if None in input_ids_list: raise ValueError(f"input_ids_list包含None值,请检查token IDs") input_ids = torch.tensor([input_ids_list], dtype=torch.long, device=device) attention_mask = torch.ones_like(input_ids, device=device) # Debug输出 print(" 📊 输入详情:") print(f" input_features: {input_features.shape} dtype={input_features.dtype}") print(f" input_ids: {input_ids.shape} dtype={input_ids.dtype}") print(f" attention_mask: {attention_mask.shape}") print(f" input_ids长度: {input_ids.shape[1]} = 1(bos) + {len(text_encoded)}(prompt) + {num_audio_time_steps}(audio) + 1(eos)") # 验证input_ids中audio_token的数量 audio_token_count = (input_ids == model.config.audio_token_id).sum().item() print(f" input_ids中audio_token数量: {audio_token_count}") inputs = { "input_features": input_features, "input_ids": input_ids, "attention_mask": attention_mask, } print(" 🤖 开始转写(中文模式)...") with torch.no_grad(): outputs = model.generate( **inputs, max_new_tokens=512, min_new_tokens=10, do_sample=False, num_beams=1, repetition_penalty=1.2, no_repeat_ngram_size=3, temperature=1.0, pad_token_id=eos_token_id, eos_token_id=eos_token_id, ) text = processor.batch_decode(outputs, skip_special_tokens=True)[0].strip() # 清理提示文本 text = text.replace(chinese_prompt, "").strip() # 后处理去重 if text: lines = text.split('。') unique_lines = [] prev_line = "" for line in lines: line = line.strip() if line and line != prev_line: unique_lines.append(line) prev_line = line text = '。'.join(unique_lines) chinese_chars = sum(1 for c in text if '\u4e00' <= c <= '\u9fff') total_chars = len([c for c in text if c.strip()]) chinese_ratio = chinese_chars / total_chars if total_chars > 0 else 0.0 print(f" 📊 中文字符: {chinese_chars}/{total_chars} ({chinese_ratio*100:.1f}%)") timestamp = f"[{start_time/60:.2f}-{end_time/60:.2f}分钟]" transcripts.append(f"{timestamp} {text}") print(f" ✅ 转写成功: {len(text)} 字符") print(f" 📝 预览: {text[:100]}...") else: print(" ⚠️ 转写结果为空") transcripts.append(f"[{start_time/60:.2f}-{end_time/60:.2f}分钟] [无内容]") except Exception as e: error_msg = f"[{start_time/60:.2f}-{end_time/60:.2f}分钟] 转写失败: {str(e)}" print(f" ❌ {error_msg}") transcripts.append(error_msg) import traceback print(" 详细错误:") traceback.print_exc() finally: # 清理内存 for var in [inputs, input_features, input_ids, attention_mask, outputs]: if var is not None: del var torch.cuda.empty_cache() gc.collect() if torch.cuda.is_available(): memory_used = torch.cuda.memory_allocated(0) / 1024**3 memory_total = torch.cuda.get_device_properties(0).total_memory / 1024**3 print(f" 💾 GPU内存: {memory_used:.2f}GB / {memory_total:.2f}GB") print("-" * 40) # 保存结果 print(f"💾 保存转写结果到: {output_path}") output_dir = os.path.dirname(output_path) if output_dir: os.makedirs(output_dir, exist_ok=True) with open(output_path, "w", encoding="utf-8") as f: f.write("# Voxtral 3B 中文音频转写结果\n") f.write(f"# 原始音频: {audio_path}\n") f.write(f"# 总时长: {total_duration/60:.2f}分钟\n") f.write(f"# 处理块数: {len(chunks)}\n") f.write(f"# 每块时长: {chunk_minutes}分钟\n") f.write("# " + "="*50 + "\n\n") for transcript in transcripts: f.write(transcript + "\n\n") print("🎉 转写完成!") print(f"📄 结果文件: {output_path}") successful = len([t for t in transcripts if '转写失败' not in t and '无内容' not in t]) print(f"📝 成功转写: {successful}/{len(transcripts)} 块") del model del processor torch.cuda.empty_cache() gc.collect() if __name__ == "__main__": if len(sys.argv) < 3: print("用法: python3 voxtral_chinese_transcribe.py <音频文件> <输出文件> [块分钟数]") print("示例: python3 voxtral_chinese_transcribe.py input.wav output.txt 15") sys.exit(1) audio_file = sys.argv[1] output_file = sys.argv[2] chunk_minutes = int(sys.argv[3]) if len(sys.argv) > 3 else 15 model_dir = "/home/ubuntu/Voxtral-Mini-3B-2507" if not os.path.exists(audio_file): print(f"❌ 错误: 音频文件不存在: {audio_file}") sys.exit(1) if not os.path.exists(model_dir): print(f"❌ 错误: 模型目录不存在: {model_dir}") sys.exit(1) print("🚀 Voxtral 3B 中文长音频转写工具") print(f"🎵 输入音频: {audio_file}") print(f"📄 输出文件: {output_file}") print(f"⏱️ 块大小: {chunk_minutes}分钟") print(f"🇨🇳 语言: 中文") print("=" * 60) transcribe_long_audio_chinese_fixed(model_dir, audio_file, output_file, chunk_minutes)
11-10
import asyncio from datetime import timedelta import httpx import json import os import torch import uvicorn from fastapi import Form from fastapi import Depends, FastAPI, Request, Response from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates from jinja2 import Environment, FileSystemLoader from dotenv import load_dotenv from pathlib import Path from transformers import AutoModelForCausalLM, AutoTokenizer from jwt_handler import ( decode_access_token, get_current_user_id, verify_password, get_password_hash, create_access_token ) import database import logging from typing import Callable # --- 日志配置 --- LOG_DIR = Path(__file__).parent / "logs" LOG_DIR.mkdir(exist_ok=True) # 确保 logs 目录存在 LOG_FILE = LOG_DIR / "app.log" # 配置 logger logging.basicConfig( level=logging.INFO, format='%(asctime)s | %(levelname)s | %(message)s', handlers=[ logging.FileHandler(LOG_FILE, encoding='utf-8', mode='a'), # 写入文件 logging.StreamHandler() # 同时输出到控制台 ] ) logger = logging.getLogger(__name__) BASE_DIR = Path(__file__).resolve().parent.parent # 项目根目录 FRONTEND_DIR = BASE_DIR / "frontend" app = FastAPI() load_dotenv(dotenv_path=BASE_DIR / "backend" / ".env") # 挂载静态资源 app.mount("/static", StaticFiles(directory=str(FRONTEND_DIR / "static")), name="static") templates = Jinja2Templates(directory=str(FRONTEND_DIR)) template_env = Environment(loader=FileSystemLoader(str(FRONTEND_DIR))) model, tokenizer = None, None conversation_history = {} # 内存中缓存最近对话(用于实时推理) max_history_turns = 5 def load_model(): model_name = str(BASE_DIR / "model/deepseek-coder-1.3b-instruct") print("Loading tokenizer...") tok = AutoTokenizer.from_pretrained(model_name) print("Loading model...") m = AutoModelForCausalLM.from_pretrained( model_name, torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32, device_map="auto", low_cpu_mem_usage=True ).eval() return m, tok async def start_load(): global model, tokenizer loop = asyncio.get_event_loop() model, tokenizer = await loop.run_in_executor(None, load_model) print("✅ Model loaded during startup!") # --- HTTP 中间件用于调试日志 --- @app.middleware("http") async def debug_request_middleware(request: Request, call_next: Callable): # 记录客户端 IP client_host = request.client.host if request.client else "unknown" logger.info(f"➡️ {request.method} {request.url.path} from {client_host}") # 尝试读取 cookie 中的 token 并解析用户 ID(不中断流程) try: access_token = request.cookies.get("access_token") user_id = None if access_token: user_id = decode_access_token(access_token) # 直接调用,不加 await logger.info(f"👤 User ID: {user_id or 'Not logged in'}") except Exception as e: logger.warning(f"⚠️ Failed to decode user from token: {e}") user_id = None # 记录请求体(仅 POST/PUT 等有 body 的) if request.method in ("POST", "PUT", "PATCH"): try: body = await request.json() logger.debug(f"📥 Request Body: {body}") except Exception as e: logger.debug(f"❌ Could not parse request body: {e}") # 执行请求 response: Response = await call_next(request) # 记录响应状态 logger.info(f"⬅️ Response status: {response.status_code}") return response @app.get("/", response_class=HTMLResponse) async def home(): template = template_env.get_template("home.html") content = template.render(debug_user=None) return HTMLResponse(content=content) @app.get("/login", response_class=HTMLResponse) async def login_page(): template = template_env.get_template("login.html") content = template.render() return HTMLResponse(content=content) @app.post("/login") async def login(request: Request): data = await request.json() account = data.get("account") password = data.get("password") hash_password = get_password_hash(password) if not account or not password: logger.warning(f"Login failed: missing credentials from {request.client.host}") return JSONResponse( {"success": False, "message": "请输入用户名和密码"}, status_code=400 ) result = database.check_users(account, hash_password) if not result: logger.warning(f"Login failed: user not found - {account}") return JSONResponse( {"success": False, "message": "用户名或密码错误"}, status_code=401 ) user_id, hashed_password_from_db = result if not verify_password(password, hashed_password_from_db): logger.warning(f"Login failed: wrong password for user {account}") return JSONResponse( {"success": False, "message": "用户名或密码错误"}, status_code=401 ) access_token_expires = timedelta(minutes=int(os.getenv("ACCESS_TOKEN_EXPIRE_MINUTES", 30))) access_token = create_access_token(data={"sub": str(user_id)}, expires_delta=access_token_expires) logger.info(f"🔐 User {user_id} logged in successfully") response = JSONResponse({"success": True, "account": account}) response.set_cookie( key="access_token", value=access_token, httponly=True, secure=False, samesite="lax", max_age=access_token_expires.total_seconds() ) return response @app.post("/save_role_setting") async def save_role_setting( request: Request, current_user_id: str = Depends(get_current_user_id), payload: dict = None ): if not payload: payload = await request.json() role_setting = payload.get("role_setting", "").strip() if not role_setting: return JSONResponse({"error": "角色设定不能为空"}, status_code=400) try: database.save_role_profile(current_user_id,role_setting) return JSONResponse({"message": "角色设定已保存!", "role": role_setting}) except Exception as e: logger.error(f"[DB] Save role failed: {e}") return JSONResponse({"error": "保存失败"}, status_code=500) @app.get("/user1", response_class=HTMLResponse) async def chat_page(request: Request, current_user_id: str = Depends(get_current_user_id)): client_ip = request.client.host logger.info(f"📋 User {current_user_id} accessing /user1 from IP: {client_ip}") if not current_user_id: logger.warning(f"🚫 Unauthorized access to /user1 from IP: {client_ip}") return RedirectResponse(url="/login") # 查询用户当前的角色设定 custom_role = "你是一个乐于助人的助手。" try: custom_role = database.get_role_profile(current_user_id) except Exception as e: logger.error(f"[DB Error] Failed to fetch role setting: {e}") template = template_env.get_template("myAI1.html") content = template.render( debug_user=current_user_id, custom_role=custom_role # 传给前端 ) return HTMLResponse(content=content) @app.post("/user1/chat")# 主聊天接口 async def chat_handler(request: Request, current_user_id: str): client_ip = request.client.host if not current_user_id: logger.warning(f"🚫 Chat attempt without auth from IP: {client_ip}") return JSONResponse({"error": "未授权访问"}, status_code=401) logger.info(f"💬 User {current_user_id} sending message from {client_ip}") data = await request.json() user_message = data.get("message") if not user_message: logger.warning(f"User {current_user_id}: Missing params in chat request - {data}") return JSONResponse({"error": "缺少必要参数"}, status_code=400) global model, tokenizer if model is None or tokenizer is None: return JSONResponse({"error": "模型尚未加载,请先启动模型"}, status_code=500) # 初始化用户对话历史(内存中) if current_user_id not in conversation_history: conversation_history[current_user_id] = [] user_history = conversation_history[current_user_id] # ✅ 动态获取用户专属角色设定 user_role_setting = "你是一个乐于助人的助手。" # 默认值 try: user_role_setting = database.get_role_profile(current_user_id) except Exception as e: logger.warning(f"⚠️ Failed to load custom role for {current_user_id}: {e}") # 【保存到数据库】 try: database.save_conversation(current_user_id, user_message, "") # 先存用户消息 except Exception as e: logger.error(f"💾 DB save failed: {e}") # 构建输入消息(带角色设定) recent_ai_reflection = database.get_latest_ai_reflection(current_user_id) # 查询 ai_character_log 最新一条 messages = [{"role": "system", "content": user_role_setting}] if recent_ai_reflection: messages.append({ "role": "system", "content": f"📌 注意:你在之前与该用户的对话中被观察到:{recent_ai_reflection['exhibited_behavior']}。\n" "请根据这一反馈适当调整语气,保持一致性或进行修复。" }) # 添加近期对话(最多 max_history_turns 轮) start_idx = max(0, len(user_history) - max_history_turns) for hist in user_history[start_idx:]: messages.append({"role": "user", "content": hist["user"]}) messages.append({"role": "assistant", "content": hist["bot"]}) messages.append({"role": "user", "content": user_message}) # 使用 tokenizer 构造输入 input_text = tokenizer.apply_chat_template( messages, tokenize=False, add_generation_prompt=True ) inputs = tokenizer(input_text, return_tensors="pt").to(model.device) # 推理 with torch.no_grad(): outputs = model.generate( **inputs, max_new_tokens=512, temperature=0.8, top_p=0.9, do_sample=True, repetition_penalty=1.1, eos_token_id=tokenizer.eos_token_id, pad_token_id=tokenizer.eos_token_id ) full_response = tokenizer.decode(outputs[0], skip_special_tokens=False) reply = "" if "<|assistant|>" in full_response: reply = full_response.split("<|assistant|>")[-1].strip() else: reply = full_response[len(input_text):].strip() eot_token = "<|EOT|>" if eot_token in reply: reply = reply.split(eot_token)[0].strip() # 更新本地历史 user_history.append({ "user": user_message, "bot": reply }) # 控制长度 while len(user_history) > max_history_turns * 2: user_history.pop(0) # 【保存 AI 回复到数据库】 try: # 更新之前插入的记录(或新增一条)—— 这里建议改为直接插入完整 pair # 更合理的做法是上面只记 user,这里再补一条完整记录 database.save_conversation(current_user_id, user_message, reply) except Exception as e: logger.error(f"💾 Save AI response failed: {e}") # ================================ # 🔥 触发记忆总结(每满 6 条对话触发一次) # ================================ SHOULD_SUMMARIZE = len(user_history) > 0 and len(user_history) % 6 == 0 # 在 SHOULD_SUMMARIZE 分支内添加: if SHOULD_SUMMARIZE: # === 1. 总结用户事件 + 性格情感 === events, personality_emotion = await summarize_conversation_and_personality( current_user_id, user_history ) for evt in events: database.save_memory(current_user_id, evt, "event") if personality_emotion.strip(): lines = [ln.strip() for ln in personality_emotion.split('\n') if ln.strip()] personality = "" emotion = "" for line in lines: if "性格:" in line: personality = line.replace("性格:", "").strip(";;") elif "情绪:" in line or "情感:" in line: emotion = line.replace("情绪:", "").replace("情感:", "").strip(";;") database.update_user_profile(current_user_id, personality, emotion) # === 2. 总结 AI 自身的表现(新增)=== exhibited_behavior, deviation_analysis = await summarize_ai_character( current_user_id, user_history, user_role_setting # 原始角色设定传入 ) database.save_ai_character_log(current_user_id,user_role_setting,exhibited_behavior,user_history) database.save_user_to_ai(exhibited_behavior,deviation_analysis,current_user_id) logger.info(f"🤖 AI self-reflection saved for user {current_user_id}: {exhibited_behavior}") # === 4. 清理本地历史 === database.clear_local_and_db_conversations(current_user_id) logger.info(f"🧹 Cleared conversation history for user {current_user_id} after summarization.") return JSONResponse({"reply": reply}) @app.get("/user2", response_class=HTMLResponse) async def chat_page(request: Request, current_user_id: str = Depends(get_current_user_id)): client_ip = request.client.host logger.info(f"📋 User {current_user_id} accessing /user2 from IP: {client_ip}") if not current_user_id: logger.warning(f"🚫 Unauthorized access to /user2 from IP: {client_ip}") return RedirectResponse(url="/login") template = template_env.get_template("myAI2.html") characters = database.get_all_characters() characters_json = json.dumps([ {"id": c["id"], "name": c["name"], "trait": c["trait"]} for c in characters ], ensure_ascii=False) content = template.render(characters=characters, characters_json=characters_json, debug_user=current_user_id) return HTMLResponse(content=content) @app.post("/user2/chat") async def dashscope_chat( request: Request, current_user_id: str = Depends(get_current_user_id) ): client_ip = request.client.host if not current_user_id: logger.warning(f"🚫 Chat attempt without auth from IP: {client_ip}") return JSONResponse({"error": "未授权访问"}, status_code=401) logger.info(f"💬 User {current_user_id} sending message from {client_ip}") data = await request.json() character_id = data.get("character_id") user_message = data.get("message") if not character_id or not user_message: logger.warning(f"User {current_user_id}: Missing params in chat request - {data}") return JSONResponse({"error": "缺少必要参数"}, status_code=400) character = database.get_character_by_id(character_id) if not character: logger.warning(f"User {current_user_id}: Invalid character ID {character_id}") return JSONResponse({"error": "角色不存在"}, status_code=404) system_prompt = f""" 你正在扮演 {character['name']}。 人物设定:{character['trait']} 请始终以这个角色的身份、语气和思维方式回答问题。 不要暴露你是 AI 的事实。 """.strip() try: API_KEY = os.getenv("DASHSCOPE_API_KEY") if not API_KEY: logger.error("❗ DASHSCOPE_API_KEY is not set") return JSONResponse({"error": "API密钥未配置"}, status_code=500) headers = { "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json", "Accept": "application/json" } payload = { "model": "qwen-plus", "messages": [ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_message} ], "temperature": 0.85, "top_p": 0.95, "max_tokens": 512, "stream": False } async with httpx.AsyncClient(timeout=30.0) as client: resp = await client.post( "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions", json=payload, headers=headers ) if resp.status_code != 200: error_detail = resp.text logger.error(f"☁️ Remote API error [{resp.status_code}]: {error_detail}") return JSONResponse( {"error": f"远程API错误 [{resp.status_code}]", "detail": error_detail}, status_code=resp.status_code ) result = resp.json() reply = result["choices"][0]["message"]["content"].strip() database.save_conversation(int(current_user_id), user_message, reply, character_id) logger.info(f"🤖 Reply generated for user {current_user_id}, length: {len(reply)} chars") return JSONResponse({"reply": reply}) except Exception as e: import traceback error_msg = traceback.format_exc() logger.critical(f"💥 Unexpected error in /user2/chat:\n{error_msg}") return JSONResponse( {"error": f"请求失败: {str(e)}", "detail": str(e)}, status_code=500 ) #对话历史总结 async def summarize_conversation_and_personality(user_id: str, history: list): """ 同时生成: 1. 结构化事件记忆(某年某月某日...) 2. 用户性格与情感总结 """ if not history: return [], "" prompt = f""" 请根据以下用户与AI的对话内容,完成两项任务: ### 任务一:提取重要生活事件 格式要求: - 每条以「YYYY年MM月DD日」开头(无法确定则写“近日”) - 格式:「YYYY年MM月DD日,和[人物]在[地点]做了[事情]」 - 每条独立一行 ### 任务二:总结用户性格特征与近期情绪状态 回答格式如下: --- 事件总结: 2025年4月1日,和朋友小李在杭州西湖边散步并拍照 2025年4月3日,独自在家完成了项目报告撰写 性格与情感: 性格:外向、富有同情心、喜欢计划; 情绪:近期表现出轻度焦虑,但整体趋于稳定。 --- 现在开始分析对话记录: """ for h in history: prompt += f"\n用户: {h['user']}\nAI: {h['bot']}\n" prompt += "\n请开始输出:\n" try: inputs = tokenizer(prompt, return_tensors="pt").to(model.device) with torch.no_grad(): outputs = model.generate( **inputs, max_new_tokens=512, temperature=0.7, top_p=0.9, do_sample=True, repetition_penalty=1.2, eos_token_id=tokenizer.eos_token_id, pad_token_id=tokenizer.eos_token_id ) full_output = tokenizer.decode(outputs[0], skip_special_tokens=True) # 解析输出 if "事件总结:" not in full_output or "性格与情感:" not in full_output: return [], "" # 格式错误,跳过 part1 = full_output.split("事件总结:")[1] event_section = part1.split("性格与情感:")[0].strip() profile_section = part1.split("性格与情感:")[1].strip() # 提取事件条目 events = [] for line in event_section.split('\n'): line = line.strip() if ("年" in line and "月" in line and "日" in line) or "近日" in line: if "做了" in line: events.append(line) # 返回 (事件列表, 性格情绪字符串) return events, profile_section except Exception as e: print(f"[Error] Summarization failed: {e}") return [], "" async def summarize_ai_character(user_id: str, history: list, original_role: str): """ 让模型总结:AI 在本次对话中实际表现出的性格与情绪 输入: - user_id: 用户ID - history: 对话历史 [{"user": "", "bot": ""}, ...] - original_role: 初始角色设定(如"你是一个温柔耐心的助手") 输出: - exhibited: 实际表现 - deviation: 是否偏离 + 解释 """ prompt = f""" 你是一个元认知分析系统,任务是评估AI助手在与用户对话中的实际行为表现。 原始角色设定:{original_role} 以下是AI与用户的对话记录: """ for h in history: prompt += f"\n用户: {h['user']}\nAI: {h['bot']}\n" prompt += f""" 请回答以下问题: 1. 根据上述对话,AI实际表现出的性格特征有哪些?(例如:急躁、幽默、冷漠、鼓励性等) 2. AI的情绪倾向如何?(如积极、防御性、疲惫感、热情下降等) 3. 相较于原始角色设定,是否有明显偏离?如果有,请说明原因(比如用户情绪影响、话题压力等) 请以如下格式输出: --- 实际表现: 性格:幽默、反应迅速、偶尔打断; 情绪:前期热情高涨,后期略显疲惫。 与原设对比: 存在轻微偏离。原设定为“冷静理性”,但在用户多次追问下表现出一定防御性,可能因上下文过长导致响应紧迫。 --- """ try: inputs = tokenizer(prompt, return_tensors="pt").to(model.device) with torch.no_grad(): outputs = model.generate( **inputs, max_new_tokens=300, temperature=0.7, top_p=0.9, do_sample=True, repetition_penalty=1.2, eos_token_id=tokenizer.eos_token_id, pad_token_id=tokenizer.eos_token_id ) response = tokenizer.decode(outputs[0], skip_special_tokens=True) # 提取内容 if "实际表现:" not in response or "与原设对比:" not in response: return "未知", "无显著变化" exhibited = response.split("实际表现:")[1].split("与原设对比:")[0].strip() deviation = response.split("与原设对比:")[1].strip() return exhibited, deviation except Exception as e: print(f"[Error] AI self-reflection failed: {e}") return "分析失败", "无法评估" if __name__ == "__main__": uvicorn.run("myapp:app", host="127.0.0.1", port=8000, reload=True, log_level="info") 主程序内容过多,将一部分抽离作为资源调用
09-30
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值