feat:初始化工程
This commit is contained in:
12
.env
Normal file
12
.env
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
RUSTFS_ENDPOINT=http://114.66.28.183:42736
|
||||||
|
RUSTFS_ACCESS_KEY=B4K79q5HGhPJlnXfLs20
|
||||||
|
RUSTFS_SECRET_KEY=j9b3M2LRU80okVcZnBOxePvd7NXF5TSrqf6gImA1
|
||||||
|
RUSTFS_BUCKET=study
|
||||||
|
|
||||||
|
OCR_API_URL=https://paddleocr.aistudio-app.com/api/v2/ocr/jobs
|
||||||
|
OCR_API_TOKEN=df7dcc85a5c3c9d64e421f353d11d13ec45512f6
|
||||||
|
OCR_MODEL=PaddleOCR-VL-1.6
|
||||||
|
|
||||||
|
MODEL_NAME=deepseek-v4-flash
|
||||||
|
MODEL_BASE_URL=https://api.deepseek.com
|
||||||
|
MODEL_API_KEY=sk-0b237d41f6bc44fc9732ea66bd7eade0
|
||||||
68
.gitignore
vendored
Normal file
68
.gitignore
vendored
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
# Python 字节码文件
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C 扩展
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# 分发/打包
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
|
||||||
|
# 虚拟环境
|
||||||
|
venv/
|
||||||
|
env/
|
||||||
|
ENV/
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
|
||||||
|
# 测试
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
.hypothesis/
|
||||||
|
|
||||||
|
# Django 相关
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
db.sqlite3
|
||||||
|
db.sqlite3-journal
|
||||||
|
media/
|
||||||
|
|
||||||
|
# PyCharm IDE
|
||||||
|
.idea/
|
||||||
|
*.iml
|
||||||
|
*.iws
|
||||||
|
*.ipr
|
||||||
|
|
||||||
|
# VS Code
|
||||||
|
.vscode/
|
||||||
|
*.code-workspace
|
||||||
|
.history/
|
||||||
|
|
||||||
|
# 其他
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
logs/
|
||||||
|
packages/
|
||||||
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<settings>
|
||||||
|
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||||
|
<version value="1.0" />
|
||||||
|
</settings>
|
||||||
|
</component>
|
||||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/study-agent-service.iml" filepath="$PROJECT_DIR$/.idea/study-agent-service.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
10
.idea/study-agent-service.iml
generated
Normal file
10
.idea/study-agent-service.iml
generated
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="PYTHON_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="jdk" jdkName="Python 3.10 (study-agent-service)" jdkType="Python SDK" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
114
.idea/workspace.xml
generated
Normal file
114
.idea/workspace.xml
generated
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="AutoImportSettings">
|
||||||
|
<option name="autoReloadType" value="SELECTIVE" />
|
||||||
|
</component>
|
||||||
|
<component name="ChangeListManager">
|
||||||
|
<list default="true" id="c72023a9-a934-49fc-81d0-53a83489c638" name="Changes" comment="">
|
||||||
|
<change afterPath="$PROJECT_DIR$/.env" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/.idea/inspectionProfiles/profiles_settings.xml" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/.idea/modules.xml" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/.idea/study-agent-service.iml" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/.idea/vcs.xml" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/agent/model.py" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/agent/prompt.py" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/agent/study.py" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/main.py" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/models/agent.py" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/ocr.py" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/requirements.txt" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/service.py" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/storage.py" afterDir="false" />
|
||||||
|
</list>
|
||||||
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
|
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||||
|
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||||
|
</component>
|
||||||
|
<component name="FileTemplateManagerImpl">
|
||||||
|
<option name="RECENT_TEMPLATES">
|
||||||
|
<list>
|
||||||
|
<option value="FastAPI main" />
|
||||||
|
<option value="FastAPI test_main" />
|
||||||
|
<option value="Python Script" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="Git.Settings">
|
||||||
|
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectColorInfo"><![CDATA[{
|
||||||
|
"associatedIndex": 4
|
||||||
|
}]]></component>
|
||||||
|
<component name="ProjectId" id="3H4xOetugdBhU5dfrCgc0HWwHSq" />
|
||||||
|
<component name="ProjectLevelVcsManager" settingsEditedManually="true" />
|
||||||
|
<component name="ProjectViewState">
|
||||||
|
<option name="hideEmptyMiddlePackages" value="true" />
|
||||||
|
<option name="showLibraryContents" value="true" />
|
||||||
|
</component>
|
||||||
|
<component name="PropertiesComponent"><![CDATA[{
|
||||||
|
"keyToString": {
|
||||||
|
"FastAPI.study-agent-service.executor": "Run",
|
||||||
|
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||||
|
"git-widget-placeholder": "master",
|
||||||
|
"last_opened_file_path": "D:/Cxx/PythonProjects/study-agent-service",
|
||||||
|
"node.js.detected.package.eslint": "true",
|
||||||
|
"node.js.detected.package.tslint": "true",
|
||||||
|
"node.js.selected.package.eslint": "(autodetect)",
|
||||||
|
"node.js.selected.package.tslint": "(autodetect)",
|
||||||
|
"nodejs_package_manager_path": "npm",
|
||||||
|
"vue.rearranger.settings.migration": "true"
|
||||||
|
}
|
||||||
|
}]]></component>
|
||||||
|
<component name="RecentsManager">
|
||||||
|
<key name="CopyFile.RECENT_KEYS">
|
||||||
|
<recent name="D:\Cxx\PythonProjects\study-agent-service" />
|
||||||
|
<recent name="D:\Cxx\PythonProjects\study-agent-service\models" />
|
||||||
|
<recent name="D:\Cxx\PythonProjects\study-agent-service\agent" />
|
||||||
|
</key>
|
||||||
|
</component>
|
||||||
|
<component name="RunManager">
|
||||||
|
<configuration name="study-agent-service" type="Python.FastAPI">
|
||||||
|
<option name="file" value="$PROJECT_DIR$/main.py" />
|
||||||
|
<module name="study-agent-service" />
|
||||||
|
<option name="ENV_FILES" value="" />
|
||||||
|
<option name="INTERPRETER_OPTIONS" value="" />
|
||||||
|
<option name="PARENT_ENVS" value="true" />
|
||||||
|
<option name="SDK_HOME" value="" />
|
||||||
|
<option name="WORKING_DIRECTORY" value="" />
|
||||||
|
<option name="IS_MODULE_SDK" value="false" />
|
||||||
|
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||||
|
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||||
|
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
|
||||||
|
<option name="launchJavascriptDebuger" value="false" />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
|
<component name="SharedIndexes">
|
||||||
|
<attachedChunks>
|
||||||
|
<set>
|
||||||
|
<option value="bundled-js-predefined-d6986cc7102b-1632447f56bf-JavaScript-PY-243.26053.29" />
|
||||||
|
<option value="bundled-python-sdk-b1dbf8ef85a6-4df51de95216-com.jetbrains.pycharm.pro.sharedIndexes.bundled-PY-243.26053.29" />
|
||||||
|
</set>
|
||||||
|
</attachedChunks>
|
||||||
|
</component>
|
||||||
|
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
|
||||||
|
<component name="TaskManager">
|
||||||
|
<task active="true" id="Default" summary="Default task">
|
||||||
|
<changelist id="c72023a9-a934-49fc-81d0-53a83489c638" name="Changes" comment="" />
|
||||||
|
<created>1785141112222</created>
|
||||||
|
<option name="number" value="Default" />
|
||||||
|
<option name="presentableId" value="Default" />
|
||||||
|
<updated>1785141112222</updated>
|
||||||
|
<workItem from="1785141113303" duration="9082000" />
|
||||||
|
</task>
|
||||||
|
<servers />
|
||||||
|
</component>
|
||||||
|
<component name="TypeScriptGeneratedFilesManager">
|
||||||
|
<option name="version" value="3" />
|
||||||
|
</component>
|
||||||
|
<component name="com.intellij.coverage.CoverageDataManagerImpl">
|
||||||
|
<SUITE FILE_PATH="coverage/study_agent_service$study_agent_service.coverage" NAME="study-agent-service Coverage Results" MODIFIED="1785153562933" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
13
agent/model.py
Normal file
13
agent/model.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
from langchain.chat_models import init_chat_model
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
model = init_chat_model(
|
||||||
|
model=os.getenv('MODEL_NAME'),
|
||||||
|
model_provider="openai",
|
||||||
|
base_url=os.getenv('MODEL_BASE_URL'),
|
||||||
|
api_key=os.getenv('MODEL_API_KEY')
|
||||||
|
)
|
||||||
27
agent/prompt.py
Normal file
27
agent/prompt.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
study_prompt = """
|
||||||
|
你是一位专业的出题专家,擅长根据学习材料生成高质量选择题。
|
||||||
|
|
||||||
|
要求:
|
||||||
|
1. 题目必须严格基于学习内容,不得编造知识点。
|
||||||
|
2. 每道题只有 1 个正确答案。
|
||||||
|
3. 干扰项要有迷惑性。
|
||||||
|
4. 输出必须是纯 JSON,禁止 Markdown。
|
||||||
|
5. 不要修改专业术语。
|
||||||
|
|
||||||
|
返回格式:
|
||||||
|
{
|
||||||
|
"questions": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"question": "题目内容",
|
||||||
|
"options": {
|
||||||
|
"A": "选项A内容",
|
||||||
|
"B": "选项B内容",
|
||||||
|
"C": "选项C内容",
|
||||||
|
"D": "选项D内容"
|
||||||
|
},
|
||||||
|
"answer": "A"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
"""
|
||||||
9
agent/study.py
Normal file
9
agent/study.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
from langchain.agents import create_agent
|
||||||
|
|
||||||
|
from agent.model import model
|
||||||
|
from agent.prompt import study_prompt
|
||||||
|
|
||||||
|
study_agent = create_agent(
|
||||||
|
model=model,
|
||||||
|
system_prompt=study_prompt
|
||||||
|
)
|
||||||
28
main.py
Normal file
28
main.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
from fastapi import FastAPI, UploadFile, File
|
||||||
|
from starlette.responses import StreamingResponse
|
||||||
|
|
||||||
|
from models.agent import QueryRequest
|
||||||
|
from service import query_agent
|
||||||
|
from storage import upload_rustfs
|
||||||
|
from ocr import ocr_from_url
|
||||||
|
|
||||||
|
app = FastAPI(title="AI Study Service")
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/upload")
|
||||||
|
def upload(file: UploadFile = File(...)):
|
||||||
|
url = upload_rustfs(file.file, file.filename)
|
||||||
|
text = ocr_from_url(url)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"text": text,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/generate")
|
||||||
|
def generate(query: QueryRequest):
|
||||||
|
"""流式对话"""
|
||||||
|
return StreamingResponse(
|
||||||
|
query_agent(query),
|
||||||
|
media_type="text/event-stream"
|
||||||
|
)
|
||||||
5
models/agent.py
Normal file
5
models/agent.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class QueryRequest(BaseModel):
|
||||||
|
message: str
|
||||||
53
ocr.py
Normal file
53
ocr.py
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import os
|
||||||
|
import time
|
||||||
|
import requests
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
API_URL = os.getenv('OCR_API_URL')
|
||||||
|
HEADERS = {
|
||||||
|
"Authorization": f"bearer {os.getenv('OCR_API_TOKEN')}",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
}
|
||||||
|
MODEL = os.getenv("OCR_MODEL")
|
||||||
|
|
||||||
|
|
||||||
|
def ocr_from_url(image_url: str) -> str:
|
||||||
|
r = requests.post(
|
||||||
|
API_URL,
|
||||||
|
json={
|
||||||
|
"fileUrl": image_url,
|
||||||
|
"model": MODEL,
|
||||||
|
"optionalPayload": {
|
||||||
|
"useDocOrientationClassify": False,
|
||||||
|
"useDocUnwarping": False,
|
||||||
|
"useChartRecognition": False,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
headers=HEADERS,
|
||||||
|
)
|
||||||
|
r.raise_for_status()
|
||||||
|
job_id = r.json()["data"]["jobId"]
|
||||||
|
|
||||||
|
while True:
|
||||||
|
r = requests.get(f"{API_URL}/{job_id}", headers=HEADERS)
|
||||||
|
r.raise_for_status()
|
||||||
|
data = r.json()["data"]
|
||||||
|
|
||||||
|
if data["state"] == "done":
|
||||||
|
jsonl_url = data["resultUrl"]["jsonUrl"]
|
||||||
|
break
|
||||||
|
if data["state"] == "failed":
|
||||||
|
raise RuntimeError("OCR failed")
|
||||||
|
|
||||||
|
time.sleep(3)
|
||||||
|
|
||||||
|
lines = requests.get(jsonl_url).text.strip().splitlines()
|
||||||
|
text = ""
|
||||||
|
for line in lines:
|
||||||
|
item = __import__("json").loads(line)
|
||||||
|
for res in item["result"]["layoutParsingResults"]:
|
||||||
|
text += res["markdown"]["text"]
|
||||||
|
|
||||||
|
return text
|
||||||
10
requirements.txt
Normal file
10
requirements.txt
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
fastapi~=0.140.0
|
||||||
|
python-dotenv~=1.2.2
|
||||||
|
requests~=2.34.2
|
||||||
|
boto3~=1.43.56
|
||||||
|
botocore~=1.43.56
|
||||||
|
langchain~=1.3.14
|
||||||
|
langchain-core~=1.5.1
|
||||||
|
langchain-openai~=1.4.1
|
||||||
|
starlette~=1.3.1
|
||||||
|
pydantic~=2.13.4
|
||||||
21
service.py
Normal file
21
service.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
from langchain_core.messages import AIMessageChunk, HumanMessage
|
||||||
|
|
||||||
|
from agent.study import study_agent
|
||||||
|
from models.agent import QueryRequest
|
||||||
|
|
||||||
|
|
||||||
|
def query_agent(query: QueryRequest):
|
||||||
|
try:
|
||||||
|
user_msg = f"学习内容:\n{query.message}"
|
||||||
|
|
||||||
|
# 流式调用Agent
|
||||||
|
for chunk, metadata in study_agent.stream(
|
||||||
|
{"messages": [HumanMessage(content=user_msg)]},
|
||||||
|
stream_mode="messages"
|
||||||
|
):
|
||||||
|
if isinstance(chunk, AIMessageChunk):
|
||||||
|
if isinstance(chunk, AIMessageChunk) and chunk.content:
|
||||||
|
yield chunk.content
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\n[错误]: {str(e)}")
|
||||||
|
yield "信息检索失败,请重新输入问题提问"
|
||||||
24
storage.py
Normal file
24
storage.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import os
|
||||||
|
import boto3
|
||||||
|
from botocore.config import Config
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
ENDPOINT = os.getenv("RUSTFS_ENDPOINT")
|
||||||
|
BUCKET = os.getenv("RUSTFS_BUCKET")
|
||||||
|
|
||||||
|
_s3 = boto3.client(
|
||||||
|
"s3",
|
||||||
|
endpoint_url=ENDPOINT,
|
||||||
|
aws_access_key_id=os.getenv("RUSTFS_ACCESS_KEY"),
|
||||||
|
aws_secret_access_key=os.getenv("RUSTFS_SECRET_KEY"),
|
||||||
|
config=Config(signature_version="s3v4"),
|
||||||
|
region_name="us-east-1",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def upload_rustfs(file_obj, filename: str) -> str:
|
||||||
|
_s3.upload_fileobj(file_obj, BUCKET, filename)
|
||||||
|
|
||||||
|
return f"{ENDPOINT}/{BUCKET}/{filename}"
|
||||||
Reference in New Issue
Block a user