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