-
Notifications
You must be signed in to change notification settings - Fork 3
feat:add ai client #61
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
4f6fd2d
4649c2c
89744de
eb009f9
2d7fab3
1c373d7
e748e92
da7e94e
1276e19
f945069
0c56bb0
a62669e
9925cb7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| # ── LLM ── | ||
| LLM_API_KEY=your-llm-key | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P1] Align the documented environment names with |
||
| LLM_MODEL_ID=your-model-id | ||
| LLM_BASE_URL=https://api.example.com/v1 | ||
| LLM_IMAGE_MODEL_ID=your-image-model-id | ||
| SERPAPI_API_KEY=your-serpapi-key | ||
|
|
||
| # ── Postgres ── | ||
| # 本地开发默认对应 Docker 容器 root/admin123@localhost:4000 | ||
| POSTGRES_HOST=localhost | ||
| POSTGRES_PORT=4000 | ||
| POSTGRES_USER=root | ||
| POSTGRES_PASSWORD=admin123 | ||
| POSTGRES_DB=windup | ||
|
|
||
| # ── Qiniu Kodo 对象存储 ── | ||
| QINIU_ACCESS_KEY=your-qiniu-access-key | ||
| QINIU_SECRET_KEY=your-qiniu-secret-key | ||
| QINIU_BUCKET_NAME=your-bucket-name | ||
| QINIU_BUCKET_DOMAIN=https://your-cdn-or-test-domain.com | ||
| QINIU_PRIVATE_SPACE=false | ||
| # QINIU_REGION=z0 # 可选:z0=华东 z1=华北 z2=华南 na0=北美 as0=东南亚;不填 SDK 自动查询 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,138 @@ | ||
| name: Backend CI | ||
|
|
||
| on: | ||
| push: | ||
| pull_request: | ||
|
|
||
| # 同一 ref 新 run 取消旧的,省额度 | ||
| concurrency: | ||
| group: ci-${{ github.ref }} | ||
| cancel-in-progress: true | ||
|
|
||
| # 开源项目最小权限 | ||
| permissions: | ||
| contents: read | ||
|
|
||
| jobs: | ||
| lint-and-test: | ||
| runs-on: ubuntu-latest | ||
| defaults: | ||
| run: | ||
| working-directory: backend # uv workspace 在 backend/,不是 repo 根 | ||
| steps: | ||
| - uses: actions/checkout@v7 | ||
|
|
||
| - name: Install uv | ||
| # setup-uv 自 v8.0.0 起停止发布 floating major tag(@v7/@v8), | ||
| # 出于安全只发布 immutable tag,故用具体版本号 | ||
| uses: astral-sh/setup-uv@v8.3.2 | ||
| with: | ||
| enable-cache: true | ||
|
|
||
| - name: Set up Python | ||
| run: uv python install 3.12 | ||
|
|
||
| - name: Install dependencies | ||
| run: uv sync --frozen # 用提交的 uv.lock 锁定版本,不偷偷升级 | ||
|
|
||
| - name: Ruff | ||
| run: uv run ruff check . | ||
|
|
||
| - name: Import-linter (分层契约) | ||
| run: uv run lint-imports | ||
|
|
||
| - name: Pytest | ||
| run: uv run pytest -q | ||
|
|
||
| # ── 命名规范门禁 ───────────────────────────────────────────── | ||
| # 分支名:<type>/<description>,如 feat/login、fix/redirect、docs/api | ||
| # 提交信息:Conventional Commits,<type>(<scope>)?: <描述> | ||
| # 长期分支(main/develop/release/*)与无斜杠的扁平分支名豁免; | ||
| # 上游 squash-merge 提交(结尾 (#NN))豁免--贡献者无法改写上游历史。 | ||
| validate-branch: | ||
| name: Branch name | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Check branch name | ||
| env: | ||
| # 用 env 传参,避免 ${{ }} 直接插值进 shell 造成命令注入 | ||
| EVENT_NAME: ${{ github.event_name }} | ||
| HEAD_REF: ${{ github.head_ref }} | ||
| REF_NAME: ${{ github.ref_name }} | ||
| run: | | ||
| set -uo pipefail | ||
| if [ "$EVENT_NAME" = "pull_request" ]; then | ||
| branch="$HEAD_REF" | ||
| else | ||
| branch="$REF_NAME" | ||
| fi | ||
| echo "Branch: $branch" | ||
| # 长期分支豁免 | ||
| case "$branch" in | ||
| main|master|develop) echo "豁免(长期分支): $branch"; exit 0 ;; | ||
| release/*|hotfix/*) echo "豁免(release/hotfix): $branch"; exit 0 ;; | ||
| esac | ||
| # 无斜杠的扁平分支名豁免(如 backend-architecture、upstream-sync) | ||
| case "$branch" in | ||
| */*) ;; | ||
| *) echo "豁免(扁平名): $branch"; exit 0 ;; | ||
| esac | ||
| PATTERN='^(feat|fix|docs|doc|chore|refactor|test|style|perf|ci|build|revert|explore|wip)/.+' | ||
| if printf '%s' "$branch" | grep -Eq "$PATTERN"; then | ||
| echo "OK: '$branch'" | ||
| exit 0 | ||
| fi | ||
| echo "::error::分支 '$branch' 不符合 <type>/<description> 规范。" | ||
| echo "允许的 type: feat fix docs doc chore refactor test style perf ci build revert explore wip" | ||
| echo "示例: feat/backend-architecture、fix/login-redirect、docs/api-reference" | ||
| exit 1 | ||
|
|
||
| validate-commits: | ||
| name: Commit messages | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v7 | ||
| with: | ||
| fetch-depth: 0 # 需要完整历史来算 merge-base | ||
| - name: Check commit messages | ||
| env: | ||
| EVENT_NAME: ${{ github.event_name }} | ||
| BASE_REF_PR: ${{ github.base_ref }} | ||
| DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} | ||
| run: | | ||
| set -euo pipefail | ||
| if [ "$EVENT_NAME" = "pull_request" ]; then | ||
| BASE_REF="$BASE_REF_PR" | ||
| else | ||
| BASE_REF="$DEFAULT_BRANCH" | ||
| fi | ||
| echo "Base ref: $BASE_REF" | ||
| git fetch --no-tags origin "$BASE_REF" | ||
| BASE=$(git merge-base "origin/$BASE_REF" HEAD) | ||
| echo "检查范围: $BASE..HEAD(merge 提交与上游 squash (#NN) 豁免)" | ||
| TYPE='(feat|fix|docs|chore|refactor|test|style|perf|ci|build|revert)' | ||
| SCOPE='(\([^)]+\))?' | ||
| PATTERN="^${TYPE}${SCOPE}!?: .+" | ||
| fail=0; total=0 | ||
| for sha in $(git rev-list --no-merges "$BASE..HEAD"); do | ||
| total=$((total + 1)) | ||
| subject=$(git log -1 --format=%s "$sha") | ||
| if printf '%s' "$subject" | grep -Eq '\(#[0-9]+\)$'; then | ||
| echo "skip(上游 squash): $sha '$subject'" | ||
| continue | ||
| fi | ||
| if printf '%s' "$subject" | grep -Eq "$PATTERN"; then | ||
| echo "ok: $sha '$subject'" | ||
| else | ||
| echo "::error::commit $sha 不符合 Conventional Commits: '$subject'" | ||
| fail=1 | ||
| fi | ||
| done | ||
| echo "共检查 $total 个非 merge 提交" | ||
| if [ "$fail" -ne 0 ]; then | ||
| echo "" | ||
| echo "格式: <type>(<scope>)?: <简要描述>" | ||
| echo "type: feat fix docs chore refactor test style perf ci build revert" | ||
| echo "示例: feat: 添加健康检查路由 fix(parser): 修复空指针" | ||
| exit 1 | ||
| fi |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| # Python | ||
| __pycache__/ | ||
| *.py[cod] | ||
| *.egg-info/ | ||
|
|
||
| # 虚拟环境 | ||
| .venv/ | ||
| venv/ | ||
|
|
||
| # IDE | ||
| .idea/ | ||
| .vscode/ | ||
|
|
||
| # 本地 agent / 工具产物(个人本地,不入库) | ||
| .agents/ | ||
| .claude/ | ||
| .evolver/ | ||
| skills-lock.json | ||
| docs/ | ||
|
|
||
| # 系统文件 | ||
| .DS_Store | ||
|
|
||
| # 敏感配置(切勿提交) | ||
| .env | ||
| .env.* | ||
| # .env.example 是模板,需入库供参考 | ||
| !.env.example | ||
|
|
||
| # 运行产物 | ||
| output/ | ||
|
|
||
| # 构建缓存 | ||
| .ruff_cache/ | ||
| .pytest_cache/ | ||
| .import_linter_cache/ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| [project] | ||
| name = "windup-ai-engine" | ||
| version = "0.1.0" | ||
| description = "windup 资产生产引擎:langgraph 图 / 策略 / 切片 / 后处理 / prompt" | ||
| requires-python = ">=3.12" | ||
| dependencies = [ | ||
| "windup-common", | ||
| "windup-framework", | ||
| "langgraph>=0.2", | ||
| "langchain-core>=0.3", | ||
| "pillow>=10.4", | ||
| "numpy>=1.26", | ||
| # "rembg", # 抠图(按需启用) | ||
| ] | ||
|
|
||
| [tool.uv.sources] | ||
| windup-common = { workspace = true } | ||
| windup-framework = { workspace = true } | ||
|
|
||
| [build-system] | ||
| requires = ["hatchling"] | ||
| build-backend = "hatchling.build" | ||
|
|
||
| [tool.hatch.build.targets.wheel] | ||
| packages = ["src/windup_ai_engine"] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| [project] | ||
| name = "windup-app" | ||
| version = "0.1.0" | ||
| description = "windup 应用:server 领域 + web API + worker MQ 适配 + bootstrap 装配" | ||
| requires-python = ">=3.12" | ||
| dependencies = [ | ||
| "windup-common", | ||
| "windup-framework", | ||
| "windup-ai-engine", | ||
| "fastapi>=0.115", | ||
| "uvicorn[standard]>=0.30", | ||
| "pydantic>=2.7", | ||
| "sqlalchemy>=2.0", | ||
| "python-multipart>=0.0.9", | ||
| ] | ||
|
|
||
| [project.scripts] | ||
| windup = "windup_app.bootstrap.app:main" | ||
|
|
||
| [tool.uv.sources] | ||
| windup-common = { workspace = true } | ||
| windup-framework = { workspace = true } | ||
| windup-ai-engine = { workspace = true } | ||
|
|
||
| [build-system] | ||
| requires = ["hatchling"] | ||
| build-backend = "hatchling.build" | ||
|
|
||
| [tool.hatch.build.targets.wheel] | ||
| packages = ["src/windup_app"] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| """``python -m windup_app`` 入口:委托给 bootstrap.app:main。""" | ||
|
|
||
| from windup_app.bootstrap.app import main | ||
|
|
||
| if __name__ == "__main__": | ||
| main() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| """FastAPI 应用工厂与装配入口。 | ||
|
|
||
| ``create_app`` 负责创建 FastAPI 实例并挂载路由 / 中间件 / 异常处理, | ||
| 是整个 web 服务的唯一装配点(composition root)。 | ||
|
|
||
| ``main`` 是开发启动入口:``python -m windup_app`` 或 ``windup`` 命令。 | ||
| """ | ||
|
|
||
| import os | ||
| from contextlib import asynccontextmanager | ||
|
|
||
| import windup_framework.db # noqa: F401 组装时显式触发 DB engine/session 初始化 | ||
| from fastapi import FastAPI | ||
|
|
||
| from windup_app.bootstrap.banner import print_banner | ||
| from windup_app.web.api.health import router as health_router | ||
| from windup_app.web.api.project import router as project_router | ||
| from windup_app.web.api.upload import router as upload_router | ||
| from windup_app.web.handler.exception_handlers import register_exception_handlers | ||
|
|
||
|
|
||
| def _env_flag(name: str) -> bool: | ||
| """把环境变量解析为真正的布尔值:仅 1/true/yes/on(忽略大小写与空白)视为 True。""" | ||
| return os.getenv(name, "").strip().lower() in {"1", "true", "yes", "on"} | ||
|
|
||
|
|
||
| @asynccontextmanager | ||
| async def _lifespan(app: FastAPI): | ||
| """应用启动时打印 banner,关闭时无特殊处理。""" | ||
| print_banner() | ||
| yield | ||
|
|
||
|
|
||
| def create_app() -> FastAPI: | ||
| app = FastAPI(title="windup", version="0.1.0", lifespan=_lifespan) | ||
| app.include_router(health_router) | ||
| app.include_router(project_router) | ||
| app.include_router(upload_router) | ||
| register_exception_handlers(app) | ||
| return app | ||
|
|
||
|
|
||
| def main() -> None: | ||
| """开发启动入口:用 uvicorn 跑 ``create_app``。 | ||
|
|
||
| host/port/reload 可用 ``WINDUP_HOST`` / ``WINDUP_PORT`` / ``WINDUP_RELOAD`` 覆盖。 | ||
| """ | ||
| import uvicorn | ||
|
|
||
| uvicorn.run( | ||
| "windup_app.bootstrap.app:create_app", | ||
| factory=True, | ||
| host=os.getenv("WINDUP_HOST", "127.0.0.1"), | ||
| port=int(os.getenv("WINDUP_PORT", "8000")), | ||
| reload=_env_flag("WINDUP_RELOAD"), | ||
| ) | ||
|
|
||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| """启动 banner:终端打印 WINDUP 横幅 + 旁边提灯笼的小人。 | ||
|
|
||
| 说明: | ||
| - 我把横幅改为更清晰的像素风格标题,并加入了一个简约的小人图案作为项目标识。 | ||
| """ | ||
|
|
||
| _BANNER = r""" | ||
| __ __ _ __ ____ _ _ ____ | ||
| \ \ / /| |\ \ | _ \ | | | || _ \ | ||
| \ \ /\ / / | | \ \| | | || | | || |_) | | ||
| \ V V / | | \ \ |_| || |_| || __/ | ||
| \_/\_/ |_| \_\____| \___/ |_| | ||
|
|
||
| .----. _ | ||
| / /\ \ _|=|__ | ||
| /__/ \__\ / .--\ | ||
| (•_•) | / | | ||
| /|_|\ | | () | | ||
| / \ | \__ / | ||
| /___\ \____/ <- pixel 小人举着灯笼 | ||
|
|
||
| windup 启动成功 · 接口文档: /docs | ||
| """ | ||
|
|
||
|
|
||
| def print_banner() -> None: | ||
| """启动时打印 banner 到终端(flush 确保重定向/管道下也立即输出)。""" | ||
| print(_BANNER, flush=True) | ||
| print(" windup 启动成功 · 接口文档: /docs", flush=True) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| """项目领域。""" | ||
|
|
||
| from windup_app.server.project.model import Project | ||
|
|
||
| __all__ = ["Project"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P1] Align the documented environment names with
AIProviderSettings. The new settings class readsAI_API_KEY,AI_MODEL, andAI_BASE_URL, while this example instructs users to setLLM_API_KEY,LLM_MODEL_ID, andLLM_BASE_URL. Following the checked-in configuration leaves the key/model empty and keeps the OpenAI default URL, so the new factories cannot use the documented provider.