dev : 合并提交 #41
219
.gitea/workflows/deploy.master.yml
Normal file
219
.gitea/workflows/deploy.master.yml
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
name: CI/CD Pipeline
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master # 触发构建的分支
|
||||||
|
|
||||||
|
env:
|
||||||
|
# ========== 环境变量配置 ==========
|
||||||
|
DOCKER_REGISTRY_URL: ${{vars.DOCKER_REGISTRY_URL}} # 私有Docker镜像仓库地址
|
||||||
|
INTERNAL_DOCKER_REGISTRY_URL: ${{vars.INTERNAL_DOCKER_REGISTRY_URL}}
|
||||||
|
PROJECT_NAME: llmx # 项目名称
|
||||||
|
MAIN_COMPOSE_FILE: docker/docker-compose.master.main.yml
|
||||||
|
AGENT_COMPOSE_FILE: docker/docker-compose.master.agent.yml
|
||||||
|
SERVER_PASSWORD: ${{ secrets.SERVER_PASSWORD }} # 仓库密码
|
||||||
|
JCNC_GITEA_URL: ${{vars.SERVER_GITEA_URL}} # Gitea地址
|
||||||
|
RUNNER_TOOL_CACHE: /opt/tools-cache # 工具缓存目录
|
||||||
|
GRADLE_CACHE_KEY: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
|
||||||
|
SPRING_PROFILES_ACTIVE: master
|
||||||
|
INTERNAL_SERVER_HOST: ${{ vars.INTERNAL_SERVER_HOST }}
|
||||||
|
INTERNAL_SERVER_PROT: ${{ vars.INTERNAL_SERVER_PROT }}
|
||||||
|
SINGAPORE_SERVER_HOST: ${{ vars.SINGAPORE_SERVER_HOST }}
|
||||||
|
SSH_PROT: ${{ vars.SSH_PROT }}
|
||||||
|
jobs:
|
||||||
|
build-and-deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: jcnc/act-runner:latest # 使用自定义Runner镜像
|
||||||
|
options: --user root # 以root用户运行(需要docker权限)
|
||||||
|
|
||||||
|
steps:
|
||||||
|
# ========== 1. 代码检出 ==========
|
||||||
|
- name: 🛒 Checkout source code
|
||||||
|
uses: ${{env.JCNC_GITEA_URL}}/actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0 # 获取完整git历史(某些插件需要)
|
||||||
|
|
||||||
|
# ========== 2. Docker环境准备 ==========
|
||||||
|
- name: 🐳 Install Docker Environment
|
||||||
|
run: |
|
||||||
|
echo "=== 检查Docker安装状态 ==="
|
||||||
|
if ! command -v docker >/dev/null; then
|
||||||
|
echo "❌ Docker未安装,开始安装..."
|
||||||
|
curl -fsSL https://get.docker.com | sh | tee docker-install.log
|
||||||
|
echo "✅ Docker安装完成"
|
||||||
|
echo "✅ Docker Compose安装完成"
|
||||||
|
else
|
||||||
|
echo "ℹ️ Docker已安装,版本: $(docker -v)"
|
||||||
|
echo "ℹ️ Docker Compose已安装,版本: $(docker compose version)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ========== 3. Gradle环境准备 ==========
|
||||||
|
- name: 🔧 Prepare Gradle Environment
|
||||||
|
run: |
|
||||||
|
echo "赋予gradlew执行权限..."
|
||||||
|
chmod +x gradlew
|
||||||
|
echo "当前目录结构:"
|
||||||
|
ls -al
|
||||||
|
# ========== 4. 恢复缓存 ==========
|
||||||
|
- name: 📦 Use Cache
|
||||||
|
id: cache
|
||||||
|
uses: ${{env.JCNC_GITEA_URL}}/actions/cache/restore@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.gradle/caches
|
||||||
|
~/.gradle/wrapper
|
||||||
|
~/.cache
|
||||||
|
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-gradle-
|
||||||
|
${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
|
||||||
|
env:
|
||||||
|
ACTIONS_RUNNER_DEBUG: true # 启用缓存调试输出
|
||||||
|
|
||||||
|
- name: ⚙️ Setup Gradle
|
||||||
|
uses: ${{env.JCNC_GITEA_URL}}/gradle/actions/setup-gradle@v4
|
||||||
|
with:
|
||||||
|
gradle-version: wrapper # 使用项目自带的gradle-wrapper
|
||||||
|
- name: 📦 Copy Compose File to Internal Server
|
||||||
|
uses: ${{env.JCNC_GITEA_URL}}/appleboy/scp-action@v1
|
||||||
|
with:
|
||||||
|
host: ${{ env.INTERNAL_SERVER_HOST }}
|
||||||
|
port: $INTERNAL_SERVER_PROT
|
||||||
|
username: root
|
||||||
|
password: ${{ secrets.INTERNAL_SERVER_PASSWORD }}
|
||||||
|
source: $MAIN_COMPOSE_FILE
|
||||||
|
target: "/home/luke/deploy/llmx/docker-compose.master.yml"
|
||||||
|
- name: 📦 Copy Compose File to Singapore Server
|
||||||
|
uses: ${{env.JCNC_GITEA_URL}}/appleboy/scp-action@v1
|
||||||
|
with:
|
||||||
|
host: $SINGAPORE_SERVER_HOST
|
||||||
|
port: $SSH_PROT
|
||||||
|
username: root
|
||||||
|
password: ${{ secrets.SINGAPORE_SERVER_PASSWORD }}
|
||||||
|
source: $AGENT_COMPOSE_FILE
|
||||||
|
target: "/home/deploy/llmx/docker-compose.master.yml"
|
||||||
|
# ========== 5. 构建阶段 ==========
|
||||||
|
- name: 🏗️ Build with Jib
|
||||||
|
run: |
|
||||||
|
echo "开始构建Docker镜像..."
|
||||||
|
./gradlew jib --stacktrace --build-cache --info -Dorg.gradle.caching=true -Dorg.gradle.jvmargs="-Xmx2g -Xms2g -XX:MaxMetaspaceSize=1g" | tee build.log
|
||||||
|
|
||||||
|
echo "=== 镜像构建结果 ==="
|
||||||
|
docker images | grep ${{ env.PROJECT_NAME }} || true
|
||||||
|
- name: 🛑 Stop Gradle Daemon
|
||||||
|
run: |
|
||||||
|
echo "停止Gradle守护进程..."
|
||||||
|
./gradlew --stop
|
||||||
|
echo "剩余Java进程:"
|
||||||
|
ps aux | grep java || true
|
||||||
|
- name: 🛰️ Tag & Push to Internal Registry
|
||||||
|
run: |
|
||||||
|
echo "标记并推送镜像到内部服务器..."
|
||||||
|
docker tag ${{env.DOCKER_REGISTRY_URL}}/llmx-core-service:latest ${{env.INTERNAL_DOCKER_REGISTRY_URL}}/llmx-core-service:latest
|
||||||
|
docker tag ${{env.DOCKER_REGISTRY_URL}}/llmx-impl-bailian:latest ${{env.INTERNAL_DOCKER_REGISTRY_URL}}/llmx-impl-bailian:latest
|
||||||
|
|
||||||
|
echo "${{ secrets.INTERNAL_DOCKER_REGISTRY_PASSWORD }}" | docker login ${{env.INTERNAL_DOCKER_REGISTRY_URL}} -u root --password-stdin
|
||||||
|
docker push ${{env.INTERNAL_DOCKER_REGISTRY_URL}}/llmx-core-service:latest
|
||||||
|
docker push ${{env.INTERNAL_DOCKER_REGISTRY_URL}}/llmx-impl-bailian:latest
|
||||||
|
docker logout ${{env.INTERNAL_DOCKER_REGISTRY_URL}}
|
||||||
|
- name: 🛰️ Tag & Push to Singapore Registry
|
||||||
|
run: |
|
||||||
|
echo "标记并推送镜像到内部服务器..."
|
||||||
|
docker tag ${{env.DOCKER_REGISTRY_URL}}/llmx-core-service:latest ${{env.SINGAPORE_DOCKER_REGISTRY_URL}}/llmx-core-service:latest
|
||||||
|
docker tag ${{env.DOCKER_REGISTRY_URL}}/llmx-impl-bailian:latest ${{env.SINGAPORE_DOCKER_REGISTRY_URL}}/llmx-impl-bailian:latest
|
||||||
|
|
||||||
|
echo "${{ secrets.INTERNAL_DOCKER_REGISTRY_PASSWORD }}" | docker login ${{env.SINGAPORE_DOCKER_REGISTRY_URL}} -u root --password-stdin
|
||||||
|
docker push ${{env.SINGAPORE_DOCKER_REGISTRY_URL}}/llmx-core-service:latest
|
||||||
|
docker push ${{env.SINGAPORE_DOCKER_REGISTRY_URL}}/llmx-impl-bailian:latest
|
||||||
|
docker logout ${{env.SINGAPORE_DOCKER_REGISTRY_URL}}
|
||||||
|
# ========== 6. 保存缓存 ==========
|
||||||
|
- name: 📦 Save Cache
|
||||||
|
id: cache
|
||||||
|
uses: ${{env.JCNC_GITEA_URL}}/actions/cache/save@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.gradle/caches
|
||||||
|
~/.gradle/wrapper
|
||||||
|
~/.cache
|
||||||
|
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-gradle-
|
||||||
|
${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
|
||||||
|
env:
|
||||||
|
ACTIONS_RUNNER_DEBUG: true # 启用缓存调试输出
|
||||||
|
- name: 🧼 Cleanup Dangling Images
|
||||||
|
run: |
|
||||||
|
echo "开始清理无标签镜像..."
|
||||||
|
docker image prune -f
|
||||||
|
remote-internal-deploy:
|
||||||
|
needs: build-and-deploy
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: jcnc/act-runner:latest # 使用自定义Runner镜像
|
||||||
|
options: --user root # 以root用户运行(需要docker权限)
|
||||||
|
steps:
|
||||||
|
- name: ✈️ Deploy on Internal Server
|
||||||
|
uses: ${{env.JCNC_GITEA_URL}}/appleboy/ssh-action@v1
|
||||||
|
with:
|
||||||
|
host: $INTERNAL_SERVER_HOST
|
||||||
|
port: $INTERNAL_SERVER_PROT
|
||||||
|
username: root
|
||||||
|
password: ${{ secrets.INTERNAL_SERVER_PASSWORD }}
|
||||||
|
script: |
|
||||||
|
cd /home/luke/deploy/llmx
|
||||||
|
echo "准备部署环境..."
|
||||||
|
chmod +x docker-compose.master.yml
|
||||||
|
echo "当前Docker状态:"
|
||||||
|
docker ps -a
|
||||||
|
echo "清理旧容器..."
|
||||||
|
docker compose -f docker-compose.master.yml down --remove-orphans
|
||||||
|
echo "清理后Docker状态:"
|
||||||
|
docker ps -a
|
||||||
|
echo "拉取最新镜像..."
|
||||||
|
docker compose -f docker-compose.master.yml pull
|
||||||
|
echo "启动新服务..."
|
||||||
|
docker compose -f docker-compose.master.yml up -d
|
||||||
|
docker compose ps
|
||||||
|
echo "=== 服务状态检查 ==="
|
||||||
|
docker compose -f docker-compose.master.yml ps
|
||||||
|
echo "开始清理无标签镜像..."
|
||||||
|
docker image prune -f
|
||||||
|
echo "清理docker-compose.master.yml"
|
||||||
|
rm -rf docker-compose.master.yml
|
||||||
|
remote-singapore-deploy:
|
||||||
|
needs: build-and-deploy
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: jcnc/act-runner:latest # 使用自定义Runner镜像
|
||||||
|
options: --user root # 以root用户运行(需要docker权限)
|
||||||
|
steps:
|
||||||
|
- name: ✈️ Deploy on Internal Server
|
||||||
|
uses: ${{env.JCNC_GITEA_URL}}/appleboy/ssh-action@v1
|
||||||
|
with:
|
||||||
|
host: $SINGAPORE_SERVER_HOST
|
||||||
|
port: $SSH_PROT
|
||||||
|
username: root
|
||||||
|
password: ${{ secrets.SINGAPORE_SERVER_PASSWORD }}
|
||||||
|
script: |
|
||||||
|
cd /home/deploy/llmx
|
||||||
|
echo "准备部署环境..."
|
||||||
|
chmod +x docker-compose.master.yml
|
||||||
|
echo "当前Docker状态:"
|
||||||
|
docker ps -a
|
||||||
|
echo "清理旧容器..."
|
||||||
|
docker compose -f docker-compose.master.yml down --remove-orphans
|
||||||
|
echo "清理后Docker状态:"
|
||||||
|
docker ps -a
|
||||||
|
echo "拉取最新镜像..."
|
||||||
|
docker compose -f docker-compose.master.yml pull
|
||||||
|
echo "启动新服务..."
|
||||||
|
docker compose -f docker-compose.master.yml up -d
|
||||||
|
docker compose ps
|
||||||
|
echo "=== 服务状态检查 ==="
|
||||||
|
docker compose -f docker-compose.master.yml ps
|
||||||
|
echo "开始清理无标签镜像..."
|
||||||
|
docker image prune -f
|
||||||
|
echo "清理docker-compose.master.yml"
|
||||||
|
rm -rf docker-compose.master.yml
|
||||||
138
.gitea/workflows/deploy.test.yml
Normal file
138
.gitea/workflows/deploy.test.yml
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
name: CI/CD Pipeline
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- test # 触发构建的分支
|
||||||
|
|
||||||
|
env:
|
||||||
|
# ========== 环境变量配置 ==========
|
||||||
|
DOCKER_REGISTRY_URL: ${{vars.DOCKER_REGISTRY_URL}} # 私有Docker镜像仓库地址
|
||||||
|
PROJECT_NAME: llmx # 项目名称
|
||||||
|
COMPOSE_FILE: docker/docker-compose.test.yml # Docker compose文件路径
|
||||||
|
SERVER_PASSWORD: ${{ secrets.SERVER_PASSWORD }} # 仓库密码
|
||||||
|
JCNC_GITEA_URL: ${{vars.SERVER_GITEA_URL}} # Gitea地址
|
||||||
|
RUNNER_TOOL_CACHE: /opt/tools-cache # 工具缓存目录
|
||||||
|
GRADLE_CACHE_KEY: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
|
||||||
|
SPRING_PROFILES_ACTIVE: test
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: jcnc/act-runner:latest # 使用自定义Runner镜像
|
||||||
|
options: --user root # 以root用户运行(需要docker权限)
|
||||||
|
|
||||||
|
steps:
|
||||||
|
# ========== 1. 代码检出 ==========
|
||||||
|
- name: 🛒 Checkout source code
|
||||||
|
uses: ${{env.JCNC_GITEA_URL}}/actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0 # 获取完整git历史(某些插件需要)
|
||||||
|
|
||||||
|
# ========== 2. Docker环境准备 ==========
|
||||||
|
- name: 🐳 Install Docker Environment
|
||||||
|
run: |
|
||||||
|
echo "=== 检查Docker安装状态 ==="
|
||||||
|
if ! command -v docker >/dev/null; then
|
||||||
|
echo "❌ Docker未安装,开始安装..."
|
||||||
|
curl -fsSL https://get.docker.com | sh | tee docker-install.log
|
||||||
|
echo "✅ Docker安装完成"
|
||||||
|
echo "✅ Docker Compose安装完成"
|
||||||
|
else
|
||||||
|
echo "ℹ️ Docker已安装,版本: $(docker -v)"
|
||||||
|
echo "ℹ️ Docker Compose已安装,版本: $(docker compose version)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ========== 3. Gradle环境准备 ==========
|
||||||
|
- name: 🔧 Prepare Gradle Environment
|
||||||
|
run: |
|
||||||
|
echo "赋予gradlew执行权限..."
|
||||||
|
chmod +x gradlew
|
||||||
|
echo "当前目录结构:"
|
||||||
|
ls -al
|
||||||
|
# ========== 4. 恢复缓存 ==========
|
||||||
|
- name: 📦 Use Cache
|
||||||
|
id: cache
|
||||||
|
uses: ${{env.JCNC_GITEA_URL}}/actions/cache/restore@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.gradle/caches
|
||||||
|
~/.gradle/wrapper
|
||||||
|
~/.cache
|
||||||
|
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-gradle-
|
||||||
|
${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
|
||||||
|
env:
|
||||||
|
ACTIONS_RUNNER_DEBUG: true # 启用缓存调试输出
|
||||||
|
|
||||||
|
- name: ⚙️ Setup Gradle
|
||||||
|
uses: ${{env.JCNC_GITEA_URL}}/gradle/actions/setup-gradle@v4
|
||||||
|
with:
|
||||||
|
gradle-version: wrapper # 使用项目自带的gradle-wrapper
|
||||||
|
|
||||||
|
# ========== 5. 构建阶段 ==========
|
||||||
|
- name: 🏗️ Build with Jib
|
||||||
|
run: |
|
||||||
|
echo "开始构建Docker镜像..."
|
||||||
|
./gradlew jib --stacktrace --build-cache --info -Dorg.gradle.caching=true -Dorg.gradle.jvmargs="-Xmx2g -Xms2g -XX:MaxMetaspaceSize=1g" | tee build.log
|
||||||
|
|
||||||
|
echo "=== 镜像构建结果 ==="
|
||||||
|
docker images | grep ${{ env.PROJECT_NAME }} || true
|
||||||
|
- name: 🛑 Stop Gradle Daemon
|
||||||
|
run: |
|
||||||
|
echo "停止Gradle守护进程..."
|
||||||
|
./gradlew --stop
|
||||||
|
echo "剩余Java进程:"
|
||||||
|
ps aux | grep java || true
|
||||||
|
# ========== 6. 保存缓存 ==========
|
||||||
|
- name: 📦 Save Cache
|
||||||
|
id: cache
|
||||||
|
uses: ${{env.JCNC_GITEA_URL}}/actions/cache/save@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.gradle/caches
|
||||||
|
~/.gradle/wrapper
|
||||||
|
~/.cache
|
||||||
|
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-gradle-
|
||||||
|
${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
|
||||||
|
env:
|
||||||
|
ACTIONS_RUNNER_DEBUG: true # 启用缓存调试输出
|
||||||
|
# ========== 7. 部署阶段 ==========
|
||||||
|
- name: 🔧 Prepare Deployment
|
||||||
|
run: |
|
||||||
|
echo "当前路径..."
|
||||||
|
pwd && ls -al
|
||||||
|
echo "准备部署环境..."
|
||||||
|
chmod +x ${COMPOSE_FILE}
|
||||||
|
echo "当前Docker状态:"
|
||||||
|
docker ps -a
|
||||||
|
|
||||||
|
- name: 🧹 Clean Old Containers
|
||||||
|
run: |
|
||||||
|
echo "当前路径..."
|
||||||
|
pwd && ls -al
|
||||||
|
echo "清理旧容器..."
|
||||||
|
docker compose -f ${COMPOSE_FILE} down --remove-orphans
|
||||||
|
echo "清理后Docker状态:"
|
||||||
|
docker ps -a
|
||||||
|
|
||||||
|
- name: 🚀 Deploy New Version
|
||||||
|
run: |
|
||||||
|
echo "当前路径..."
|
||||||
|
pwd && ls -al
|
||||||
|
echo "拉取最新镜像..."
|
||||||
|
docker compose -f ${COMPOSE_FILE} pull
|
||||||
|
|
||||||
|
echo "启动新服务..."
|
||||||
|
docker compose -f ${COMPOSE_FILE} up -d
|
||||||
|
|
||||||
|
echo "=== 服务状态检查 ==="
|
||||||
|
docker compose -f ${COMPOSE_FILE} ps
|
||||||
|
- name: 🧼 Cleanup Dangling Images
|
||||||
|
run: |
|
||||||
|
echo "开始清理无标签镜像..."
|
||||||
|
docker image prune -f
|
||||||
102
build.gradle.kts
102
build.gradle.kts
@ -3,7 +3,6 @@ plugins {
|
|||||||
alias(libs.plugins.kotlin.jvm)
|
alias(libs.plugins.kotlin.jvm)
|
||||||
alias(libs.plugins.kotlin.plugin.spring)
|
alias(libs.plugins.kotlin.plugin.spring)
|
||||||
alias(libs.plugins.spring.boot)
|
alias(libs.plugins.spring.boot)
|
||||||
alias(libs.plugins.spring.dependency.management)
|
|
||||||
alias(libs.plugins.jibLocalPlugin)
|
alias(libs.plugins.jibLocalPlugin)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -19,10 +18,14 @@ configurations.implementation {
|
|||||||
allprojects {
|
allprojects {
|
||||||
// 设置全局属性
|
// 设置全局属性
|
||||||
ext {
|
ext {
|
||||||
set(ProjectFlags.USE_SPRING_BOOT, false)
|
set(ProjectFlags.USE_SPRING_BOOT_WEB, false)
|
||||||
set(ProjectFlags.USE_LLM_CORE_SPI, false)
|
set(ProjectFlags.USE_LLM_KT_IMPL_DEPENDENCE, false)
|
||||||
set(ProjectFlags.USE_SPRING_CLOUD_BOM, false)
|
set(ProjectFlags.USE_SPRING_CLOUD_BOM, false)
|
||||||
set(ProjectFlags.IS_ROOT_MODULE, false)
|
set(ProjectFlags.IS_ROOT_MODULE, false)
|
||||||
|
set(ProjectFlags.USE_SPRING_BOOT_BOM, false)
|
||||||
|
set(ProjectFlags.USE_LLM_IMPL_PLATFORM_DEPENDENCE, false)
|
||||||
|
set(ProjectFlags.USE_NACOS_DEPENDENCE, false)
|
||||||
|
set(ProjectFlags.USE_DAO_DEPENDENCE, false)
|
||||||
}
|
}
|
||||||
repositories {
|
repositories {
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
@ -33,12 +36,10 @@ allprojects {
|
|||||||
maven {
|
maven {
|
||||||
url = uri("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev")
|
url = uri("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev")
|
||||||
}
|
}
|
||||||
maven {
|
|
||||||
url = uri("https://raw.githubusercontent.com/eurotech/kura_addons/mvn-repo/")
|
|
||||||
}
|
|
||||||
maven {
|
maven {
|
||||||
url = uri("https://maven.aliyun.com/repository/public/")
|
url = uri("https://maven.aliyun.com/repository/public/")
|
||||||
}
|
}
|
||||||
|
maven { url = uri("https://maven.aliyun.com/repository/central") }
|
||||||
maven {
|
maven {
|
||||||
url = uri("https://maven.aliyun.com/repository/spring/")
|
url = uri("https://maven.aliyun.com/repository/spring/")
|
||||||
}
|
}
|
||||||
@ -66,24 +67,38 @@ allprojects {
|
|||||||
|
|
||||||
subprojects {
|
subprojects {
|
||||||
afterEvaluate {
|
afterEvaluate {
|
||||||
// springbootWeb
|
if (project.getPropertyByBoolean(ProjectFlags.USE_LLM_IMPL_PLATFORM_DEPENDENCE)) {
|
||||||
if (project.getPropertyByBoolean(ProjectFlags.USE_SPRING_BOOT)) {
|
|
||||||
apply {
|
apply {
|
||||||
plugin(libs.plugins.spring.dependency.management.get().pluginId)
|
|
||||||
plugin(libs.plugins.spring.boot.get().pluginId)
|
|
||||||
plugin(libs.plugins.kotlin.plugin.spring.get().pluginId)
|
plugin(libs.plugins.kotlin.plugin.spring.get().pluginId)
|
||||||
}
|
}
|
||||||
dependencies {
|
ext {
|
||||||
implementation(libs.springBootStarter.web)
|
setProperty(ProjectFlags.USE_SPRING_BOOT_WEB, true)
|
||||||
|
setProperty(ProjectFlags.USE_SPRING_CLOUD_BOM, true)
|
||||||
testImplementation(libs.springBootStarter.test)
|
setProperty(ProjectFlags.USE_LLM_KT_IMPL_DEPENDENCE, true)
|
||||||
testRuntimeOnly(libs.junitPlatform.launcher)
|
setProperty(ProjectFlags.USE_NACOS_DEPENDENCE, true)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// llmx-core-spi
|
|
||||||
if (project.getPropertyByBoolean(ProjectFlags.USE_LLM_CORE_SPI)) {
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
// 核心spi依赖
|
||||||
implementation(project(Modules.Core.SPI))
|
implementation(project(Modules.Core.SPI))
|
||||||
|
// okHttp依赖
|
||||||
|
implementation(libs.okHttp)
|
||||||
|
// forgeBoot依赖
|
||||||
|
implementation(libs.forgeBoot.core.extension)
|
||||||
|
implementation(libs.forgeBoot.core.extension)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(project.getPropertyByBoolean(ProjectFlags.USE_DAO_DEPENDENCE)){
|
||||||
|
dependencies{
|
||||||
|
runtimeOnly(libs.postgresql)
|
||||||
|
implementation(libs.springBootStarter.data.jpa)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// nacos dependence
|
||||||
|
if(project.getPropertyByBoolean(ProjectFlags.USE_NACOS_DEPENDENCE)){
|
||||||
|
dependencies {
|
||||||
|
implementation(libs.springCloudStarter.alibaba.nacos.discovery)
|
||||||
|
implementation(libs.springCloudStarter.alibaba.nacos.config)
|
||||||
|
implementation(libs.springCloudStarter.bootstrap)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// springCloudBom
|
// springCloudBom
|
||||||
@ -92,6 +107,34 @@ subprojects {
|
|||||||
implementation(platform(libs.springCloudDependencies.bom))
|
implementation(platform(libs.springCloudDependencies.bom))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// springBootBom
|
||||||
|
if (project.getPropertyByBoolean(ProjectFlags.USE_SPRING_BOOT_BOM)) {
|
||||||
|
dependencies {
|
||||||
|
implementation(platform(libs.springBootDependencies.bom))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// springbootWeb
|
||||||
|
if (project.getPropertyByBoolean(ProjectFlags.USE_SPRING_BOOT_WEB)) {
|
||||||
|
apply {
|
||||||
|
plugin(libs.plugins.spring.boot.get().pluginId)
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
implementation(libs.springBootStarter.web)
|
||||||
|
implementation(platform(libs.springBootDependencies.bom))
|
||||||
|
testImplementation(libs.springBootStarter.test)
|
||||||
|
testRuntimeOnly(libs.junitPlatform.launcher)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 使用kt实现impl服务依赖
|
||||||
|
if (project.getPropertyByBoolean(ProjectFlags.USE_LLM_KT_IMPL_DEPENDENCE)) {
|
||||||
|
dependencies {
|
||||||
|
// jackson kt模块依赖
|
||||||
|
implementation(libs.jackson.module.kotlin)
|
||||||
|
// kt协程依赖
|
||||||
|
implementation(libs.kotlinx.coruntes.reactor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
val libs = rootProject.libs
|
val libs = rootProject.libs
|
||||||
apply {
|
apply {
|
||||||
@ -99,7 +142,6 @@ subprojects {
|
|||||||
plugin(libs.plugins.kotlin.jvm.get().pluginId)
|
plugin(libs.plugins.kotlin.jvm.get().pluginId)
|
||||||
plugin(libs.plugins.jibLocalPlugin.get().pluginId)
|
plugin(libs.plugins.jibLocalPlugin.get().pluginId)
|
||||||
}
|
}
|
||||||
println(project.name + ":" + project.getPropertyByBoolean(ProjectFlags.USE_SPRING_BOOT))
|
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
compilerOptions {
|
compilerOptions {
|
||||||
@ -107,10 +149,30 @@ subprojects {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
jibConfig {
|
jibConfig {
|
||||||
|
val env = System.getenv("SPRING_PROFILES_ACTIVE")
|
||||||
project {
|
project {
|
||||||
projectName = "llmx-core-service"
|
projectName = "llmx-core-service"
|
||||||
ports = listOf("9002")
|
ports = listOf("9002")
|
||||||
environment = mapOf("SPRING_PROFILES_ACTIVE" to "prod")
|
environment = mapOf("SPRING_PROFILES_ACTIVE" to env)
|
||||||
|
imageName = "llmx-core-service"
|
||||||
|
paths = listOf(File(rootProject.projectDir, "scripts").absolutePath)
|
||||||
|
entrypoint = listOf(
|
||||||
|
"/bin/sh", "-c",
|
||||||
|
"/entrypoint.sh -d llmx-nacos:8848,llmx-database:5432 -c " +
|
||||||
|
"'java -cp $( cat /app/jib-classpath-file ) $( cat /app/jib-main-class-file )'"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
project {
|
||||||
|
projectName = "llmx-impl-bailian"
|
||||||
|
ports = listOf("9003")
|
||||||
|
environment = mapOf("SPRING_PROFILES_ACTIVE" to env)
|
||||||
|
imageName = "llmx-impl-bailian"
|
||||||
|
paths = listOf(File(rootProject.projectDir, "scripts").absolutePath)
|
||||||
|
entrypoint = listOf(
|
||||||
|
"/bin/sh", "-c",
|
||||||
|
"/entrypoint.sh -d llmx-nacos:8848,llmx-database:5432 -c " +
|
||||||
|
"'java -cp $( cat /app/jib-classpath-file ) $( cat /app/jib-main-class-file )'"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,8 +12,8 @@ dependencies {
|
|||||||
gradlePlugin {
|
gradlePlugin {
|
||||||
plugins {
|
plugins {
|
||||||
register("jib-plugin") {
|
register("jib-plugin") {
|
||||||
id = "org.jcnc.llmhub.plugin.jib"
|
id = "org.jcnc.llmx.plugin.jib"
|
||||||
implementationClass = "org.jcnc.llmhub.plugin.jib.JibPlugin"
|
implementationClass = "org.jcnc.llmx.plugin.jib.JibPlugin"
|
||||||
description =
|
description =
|
||||||
"提供简单的配置构建镜像"
|
"提供简单的配置构建镜像"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,6 +14,8 @@ object Modules {
|
|||||||
object Core{
|
object Core{
|
||||||
// llmx-core-spi模块的路径,用于定义核心功能的SPI
|
// llmx-core-spi模块的路径,用于定义核心功能的SPI
|
||||||
const val SPI = ":llmx-core:llmx-core-spi"
|
const val SPI = ":llmx-core:llmx-core-spi"
|
||||||
|
const val API = ":llmx-core:llmx-core-api"
|
||||||
|
const val COMMON = ":llmx-core:llmx-core-common"
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,14 @@
|
|||||||
object ProjectFlags {
|
object ProjectFlags {
|
||||||
const val USE_SPRING_BOOT = "useSpringBoot"
|
const val USE_SPRING_BOOT_WEB = "useSpringBootWeb"
|
||||||
|
const val USE_SPRING_BOOT_BOM = "useSpringBootBom"
|
||||||
const val USE_SPRING_CLOUD_BOM = "useSpringCloudBom"
|
const val USE_SPRING_CLOUD_BOM = "useSpringCloudBom"
|
||||||
const val USE_LLM_CORE_SPI = "useLLMCoreSPI"
|
const val USE_LLM_KT_IMPL_DEPENDENCE = "useLLMKtImplDependence"
|
||||||
const val IS_ROOT_MODULE = "isRootModule"
|
const val IS_ROOT_MODULE = "isRootModule"
|
||||||
|
const val USE_NACOS_DEPENDENCE = "useNacosDependence"
|
||||||
|
const val USE_DAO_DEPENDENCE = "useDaoDependence"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用实现服务第三方平台依赖
|
||||||
|
*/
|
||||||
|
const val USE_LLM_IMPL_PLATFORM_DEPENDENCE = "useLLMImplPlatformDependence"
|
||||||
}
|
}
|
||||||
@ -52,12 +52,13 @@ class JibPlugin : Plugin<Project> {
|
|||||||
}
|
}
|
||||||
to {
|
to {
|
||||||
image =
|
image =
|
||||||
"${System.getenv("LUKE_SERVER_DOCKER_REGISTRY_URL")}/${jibProject.imageName}:${jibProject.version}"
|
"${System.getenv("DOCKER_REGISTRY_URL")}/${jibProject.imageName}:${jibProject.version}"
|
||||||
auth {
|
auth {
|
||||||
username = "root"
|
username = "root"
|
||||||
password = System.getenv("LUKE_SERVER_DOCKER_REGISTRY_PASSWORD")
|
password = System.getenv("SERVER_PASSWORD")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
setAllowInsecureRegistries(true)
|
||||||
// 动态配置容器参数
|
// 动态配置容器参数
|
||||||
container {
|
container {
|
||||||
ports = jibProject.ports
|
ports = jibProject.ports
|
||||||
@ -69,7 +70,9 @@ class JibPlugin : Plugin<Project> {
|
|||||||
|
|
||||||
// 动态配置额外目录
|
// 动态配置额外目录
|
||||||
extraDirectories {
|
extraDirectories {
|
||||||
|
if(jibProject.paths.isNotEmpty()){
|
||||||
setPaths(jibProject.paths)
|
setPaths(jibProject.paths)
|
||||||
|
}
|
||||||
permissions.putAll(jibProject.permissions)
|
permissions.putAll(jibProject.permissions)
|
||||||
}
|
}
|
||||||
// 将动态部分移到任务配置中
|
// 将动态部分移到任务配置中
|
||||||
|
|||||||
@ -19,14 +19,14 @@ data class JibProject(
|
|||||||
var ports: List<String> = listOf("8080"),
|
var ports: List<String> = listOf("8080"),
|
||||||
var environment: Map<String, String> = mapOf("SPRING_PROFILES_ACTIVE" to "prod"),
|
var environment: Map<String, String> = mapOf("SPRING_PROFILES_ACTIVE" to "prod"),
|
||||||
var entrypoint: List<String> = emptyList(),
|
var entrypoint: List<String> = emptyList(),
|
||||||
var paths: List<String> = listOf("llmhub-base/scripts/entrypoint.sh"),
|
var paths: List<String> = listOf(),
|
||||||
var imageName: String = "",
|
var imageName: String = "",
|
||||||
var version: String = "latest",
|
var version: String = "latest",
|
||||||
var permissions: Map<String, String> = mapOf("/scripts/entrypoint.sh" to "755"),
|
var permissions: Map<String, String> = mapOf("/entrypoint.sh" to "755"),
|
||||||
var baseImage: String = "docker://bellsoft/liberica-openjdk-debian:21"
|
var baseImage: String = "docker://gewuyou/liberica-openjdk-debian-nc:21"
|
||||||
) {
|
) {
|
||||||
init {
|
init {
|
||||||
if (imageName.isEmpty()) {
|
if (imageName.isBlank()) {
|
||||||
imageName = projectName
|
imageName = projectName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
42
docker/docker-compose.dev.yml
Normal file
42
docker/docker-compose.dev.yml
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
services:
|
||||||
|
llmx-core-database:
|
||||||
|
image: postgres:16-alpine # 长期支持版本推荐用 16
|
||||||
|
container_name: llmx-core-database
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- "5432:9052"
|
||||||
|
networks:
|
||||||
|
- llmx-net
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: llmx_db
|
||||||
|
POSTGRES_USER: llmx
|
||||||
|
POSTGRES_PASSWORD: L4s6f9y3,
|
||||||
|
volumes:
|
||||||
|
- llmx-core-db-volume:/var/lib/postgresql/data
|
||||||
|
llmx-core-service:
|
||||||
|
image: ${Docker_REGISTRY_URL}/llmx-core-service
|
||||||
|
container_name: llmx-core-service
|
||||||
|
ports:
|
||||||
|
- "9002:9002"
|
||||||
|
networks:
|
||||||
|
- llmx-net
|
||||||
|
volumes:
|
||||||
|
- llmx-core-service-volume:/app/volume
|
||||||
|
restart: always
|
||||||
|
llmx-impl-baiLian:
|
||||||
|
image: ${Docker_REGISTRY_URL}/llmx-impl-bailian
|
||||||
|
container_name: llmx-impl-bailian
|
||||||
|
ports:
|
||||||
|
- "9003:9003"
|
||||||
|
networks:
|
||||||
|
- llmx-net
|
||||||
|
volumes:
|
||||||
|
- llmx-impl-baiLian-volume:/app/volume
|
||||||
|
restart: always
|
||||||
|
networks:
|
||||||
|
llmx-net:
|
||||||
|
driver: bridge
|
||||||
|
volumes:
|
||||||
|
llmx-core-service-volume:
|
||||||
|
llmx-impl-baiLian-volume:
|
||||||
|
llmx-core-db-volume:
|
||||||
32
docker/docker-compose.master.agent.yml
Normal file
32
docker/docker-compose.master.agent.yml
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
services:
|
||||||
|
llmx-core-service-banana:
|
||||||
|
image: ${DOCKER_REGISTRY_URL}/llmx-core-service
|
||||||
|
container_name: llmx-core-service
|
||||||
|
ports:
|
||||||
|
- "9002:9002"
|
||||||
|
networks:
|
||||||
|
- llmx-net-master
|
||||||
|
environment:
|
||||||
|
SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE}
|
||||||
|
volumes:
|
||||||
|
- llmx-core-service-volume:/app/volume
|
||||||
|
restart: always
|
||||||
|
llmx-impl-bailian-banana:
|
||||||
|
image: ${DOCKER_REGISTRY_URL}/llmx-impl-bailian
|
||||||
|
container_name: llmx-impl-bailian
|
||||||
|
ports:
|
||||||
|
- "9003:9003"
|
||||||
|
networks:
|
||||||
|
- llmx-net-master
|
||||||
|
environment:
|
||||||
|
SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE}
|
||||||
|
volumes:
|
||||||
|
- llmx-impl-bailian-volume:/app/volume
|
||||||
|
restart: always
|
||||||
|
|
||||||
|
networks:
|
||||||
|
llmx-net-master:
|
||||||
|
driver: bridge
|
||||||
|
volumes:
|
||||||
|
llmx-core-service-volume:
|
||||||
|
llmx-impl-bailian-volume:
|
||||||
48
docker/docker-compose.master.main.yml
Normal file
48
docker/docker-compose.master.main.yml
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
services:
|
||||||
|
llmx-core-service-apple:
|
||||||
|
image: ${DOCKER_REGISTRY_URL}/llmx-core-service
|
||||||
|
container_name: llmx-core-service
|
||||||
|
ports:
|
||||||
|
- "9002:9002"
|
||||||
|
networks:
|
||||||
|
- llmx-net-master
|
||||||
|
environment:
|
||||||
|
SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE}
|
||||||
|
volumes:
|
||||||
|
- llmx-core-service-volume:/app/volume
|
||||||
|
restart: always
|
||||||
|
llmx-database:
|
||||||
|
image: postgres:16-alpine # 长期支持版本推荐用 16
|
||||||
|
container_name: llmx-database
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- "9052:5432"
|
||||||
|
networks:
|
||||||
|
- llmx-net-master
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: llmx_db
|
||||||
|
POSTGRES_USER: llmx
|
||||||
|
POSTGRES_PASSWORD: L4s6f9y3,
|
||||||
|
volumes:
|
||||||
|
- llmx-db-volume
|
||||||
|
llmx-impl-bailian-apple:
|
||||||
|
image: ${DOCKER_REGISTRY_URL}/llmx-impl-bailian
|
||||||
|
container_name: llmx-impl-bailian
|
||||||
|
ports:
|
||||||
|
- "9003:9003"
|
||||||
|
networks:
|
||||||
|
- llmx-net-master
|
||||||
|
environment:
|
||||||
|
SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE}
|
||||||
|
volumes:
|
||||||
|
- llmx-impl-bailian-volume:/app/volume
|
||||||
|
restart: always
|
||||||
|
|
||||||
|
networks:
|
||||||
|
llmx-net-master:
|
||||||
|
driver: bridge
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
llmx-core-service-volume:
|
||||||
|
llmx-impl-bailian-volume:
|
||||||
|
llmx-db-volume:
|
||||||
67
docker/docker-compose.test.yml
Normal file
67
docker/docker-compose.test.yml
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
services:
|
||||||
|
llmx-nacos:
|
||||||
|
image: nacos/nacos-server:v2.3.2
|
||||||
|
container_name: llmx-nacos
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- "8848:8848"
|
||||||
|
- "9848:9848"
|
||||||
|
- "9849:9849"
|
||||||
|
networks:
|
||||||
|
- llmx-net-test
|
||||||
|
environment:
|
||||||
|
MODE: standalone # 显式指定为单体模式
|
||||||
|
NACOS_AUTH_ENABLE: "true" # ✅ 开启鉴权
|
||||||
|
NACOS_AUTH_CACHE_ENABLE: "false"
|
||||||
|
NACOS_AUTH_IDENTITY_KEY: "nacos" # 可选,默认是 nacos
|
||||||
|
NACOS_AUTH_IDENTITY_VALUE: "L4s6f9y3," # 可选
|
||||||
|
NACOS_AUTH_TOKEN: "h61bUSqvp0npCNHIZ0VzqBFz2U59UKrECE6TvBt58DQ=" # ✅ JWT 密钥
|
||||||
|
volumes:
|
||||||
|
- llmx-nacos-volume:/home/nacos
|
||||||
|
llmx-core-service:
|
||||||
|
image: ${DOCKER_REGISTRY_URL}/llmx-core-service
|
||||||
|
container_name: llmx-core-service
|
||||||
|
ports:
|
||||||
|
- "9002:9002"
|
||||||
|
networks:
|
||||||
|
- llmx-net-test
|
||||||
|
environment:
|
||||||
|
SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE}
|
||||||
|
volumes:
|
||||||
|
- llmx-core-service-volume:/app/volume
|
||||||
|
restart: always
|
||||||
|
llmx-impl-bailian:
|
||||||
|
image: ${DOCKER_REGISTRY_URL}/llmx-impl-bailian
|
||||||
|
container_name: llmx-impl-bailian
|
||||||
|
ports:
|
||||||
|
- "9003:9003"
|
||||||
|
networks:
|
||||||
|
- llmx-net-test
|
||||||
|
environment:
|
||||||
|
SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE}
|
||||||
|
volumes:
|
||||||
|
- llmx-impl-bailian-volume:/app/volume
|
||||||
|
restart: always
|
||||||
|
llmx-core-database:
|
||||||
|
image: postgres:16-alpine # 长期支持版本推荐用 16
|
||||||
|
container_name: llmx-database
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- "9052:5432"
|
||||||
|
networks:
|
||||||
|
- llmx-net-test
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: llmx_db
|
||||||
|
POSTGRES_USER: llmx
|
||||||
|
POSTGRES_PASSWORD: L4s6f9y3,
|
||||||
|
volumes:
|
||||||
|
- llmx-core-db-volume:/var/lib/postgresql/data
|
||||||
|
|
||||||
|
networks:
|
||||||
|
llmx-net-test:
|
||||||
|
driver: bridge
|
||||||
|
volumes:
|
||||||
|
llmx-core-service-volume:
|
||||||
|
llmx-impl-bailian-volume:
|
||||||
|
llmx-core-db-volume:
|
||||||
|
llmx-nacos-volume:
|
||||||
@ -5,9 +5,11 @@ spring-boot-version = "3.2.4"
|
|||||||
spring-dependency-management-version = "1.1.7"
|
spring-dependency-management-version = "1.1.7"
|
||||||
aliyun-bailian-version = "2.0.0"
|
aliyun-bailian-version = "2.0.0"
|
||||||
spring-cloud-starter-alibaba-nacos-discovery-version = "2023.0.1.0"
|
spring-cloud-starter-alibaba-nacos-discovery-version = "2023.0.1.0"
|
||||||
forgeBoot-version = "1.1.0-SNAPSHOT"
|
forgeBoot-version = "1.3.0-SNAPSHOT"
|
||||||
okHttp-version = "4.12.0"
|
okHttp-version = "4.12.0"
|
||||||
jib-version = "3.4.2"
|
jib-version = "3.4.2"
|
||||||
|
org-reactivestreams-reactiveStreams-version = "1.0.4"
|
||||||
|
postgresql-version = "42.7.4"
|
||||||
[plugins]
|
[plugins]
|
||||||
# 应用 Java 插件,提供基本的 Java 代码编译和构建能力
|
# 应用 Java 插件,提供基本的 Java 代码编译和构建能力
|
||||||
java = { id = "java" }
|
java = { id = "java" }
|
||||||
@ -26,11 +28,12 @@ spring-dependency-management = { id = "io.spring.dependency-management", version
|
|||||||
spring-boot = { id = "org.springframework.boot", version.ref = "spring-boot-version" }
|
spring-boot = { id = "org.springframework.boot", version.ref = "spring-boot-version" }
|
||||||
|
|
||||||
jib = { id = "com.google.cloud.tools.jib", version.ref = "jib-version" }
|
jib = { id = "com.google.cloud.tools.jib", version.ref = "jib-version" }
|
||||||
jibLocalPlugin = { id = "org.jcnc.llmhub.plugin.jib" }
|
jibLocalPlugin = { id = "org.jcnc.llmx.plugin.jib" }
|
||||||
[libraries]
|
[libraries]
|
||||||
jib-gradlePlugin = { module = "com.google.cloud.tools.jib:com.google.cloud.tools.jib.gradle.plugin", version.ref = "jib-version" }
|
jib-gradlePlugin = { module = "com.google.cloud.tools.jib:com.google.cloud.tools.jib.gradle.plugin", version.ref = "jib-version" }
|
||||||
# bom
|
# bom
|
||||||
springCloudDependencies-bom = { group ="org.springframework.cloud",name = "spring-cloud-dependencies", version.ref = "spring-cloud-version" }
|
springCloudDependencies-bom = { group ="org.springframework.cloud",name = "spring-cloud-dependencies", version.ref = "spring-cloud-version" }
|
||||||
|
springBootDependencies-bom = { module = "org.springframework.boot:spring-boot-dependencies", version.ref = "spring-boot-version" }
|
||||||
# kotlinx
|
# kotlinx
|
||||||
# 响应式协程库
|
# 响应式协程库
|
||||||
kotlinx-coruntes-reactor = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-reactor" }
|
kotlinx-coruntes-reactor = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-reactor" }
|
||||||
@ -40,15 +43,20 @@ aliyun-bailian = { group = "com.aliyun", name = "bailian20231229", version.ref =
|
|||||||
|
|
||||||
# SrpingCloud
|
# SrpingCloud
|
||||||
springCloudStarter-alibaba-nacos-discovery = { group = "com.alibaba.cloud", name = "spring-cloud-starter-alibaba-nacos-discovery", version.ref = "spring-cloud-starter-alibaba-nacos-discovery-version" }
|
springCloudStarter-alibaba-nacos-discovery = { group = "com.alibaba.cloud", name = "spring-cloud-starter-alibaba-nacos-discovery", version.ref = "spring-cloud-starter-alibaba-nacos-discovery-version" }
|
||||||
|
springCloudStarter-alibaba-nacos-config = { group = "com.alibaba.cloud", name = "spring-cloud-starter-alibaba-nacos-config", version.ref = "spring-cloud-starter-alibaba-nacos-discovery-version" }
|
||||||
springCloudStarter-loadbalancer = { group = "org.springframework.cloud", name = "spring-cloud-starter-loadbalancer" }
|
springCloudStarter-loadbalancer = { group = "org.springframework.cloud", name = "spring-cloud-starter-loadbalancer" }
|
||||||
|
springCloudStarter-openfeign = { group = "org.springframework.cloud", name = "spring-cloud-starter-openfeign" }
|
||||||
|
springCloudStarter-bootstrap = { group = "org.springframework.cloud", name = "spring-cloud-starter-bootstrap" }
|
||||||
# SpringBootStarter
|
# SpringBootStarter
|
||||||
springBootStarter-webflux = { group = "org.springframework.boot", name = "spring-boot-starter-webflux" }
|
springBootStarter-webflux = { group = "org.springframework.boot", name = "spring-boot-starter-webflux" }
|
||||||
springBootStarter-web = { group = "org.springframework.boot", name = "spring-boot-starter-web" }
|
springBootStarter-web = { group = "org.springframework.boot", name = "spring-boot-starter-web" }
|
||||||
springBootStarter-test = { group = "org.springframework.boot", name = "spring-boot-starter-test" }
|
springBootStarter-test = { group = "org.springframework.boot", name = "spring-boot-starter-test" }
|
||||||
|
springBootStarter-data-jpa = { group = "org.springframework.boot", name = "spring-boot-starter-data-jpa" }
|
||||||
|
|
||||||
junitPlatform-launcher = { group = "org.junit.platform", name = "junit-platform-launcher" }
|
junitPlatform-launcher = { group = "org.junit.platform", name = "junit-platform-launcher" }
|
||||||
|
|
||||||
|
# org-reactivestreams
|
||||||
|
org-reactivestreams-reactiveStreams = { group = "org.reactivestreams", name = "reactive-streams" ,version.ref="org-reactivestreams-reactiveStreams-version"}
|
||||||
# OkHttp
|
# OkHttp
|
||||||
okHttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okHttp-version" }
|
okHttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okHttp-version" }
|
||||||
# forgeBoot
|
# forgeBoot
|
||||||
@ -61,4 +69,6 @@ jackson-databind={group="com.fasterxml.jackson.core", name="jackson-databind"}
|
|||||||
jackson-annotations={group="com.fasterxml.jackson.core", name="jackson-annotations"}
|
jackson-annotations={group="com.fasterxml.jackson.core", name="jackson-annotations"}
|
||||||
jackson-datatype-jsr310={group="com.fasterxml.jackson.datatype", name="jackson-datatype-jsr310"}
|
jackson-datatype-jsr310={group="com.fasterxml.jackson.datatype", name="jackson-datatype-jsr310"}
|
||||||
jackson-module-kotlin={group="com.fasterxml.jackson.module", name="jackson-module-kotlin"}
|
jackson-module-kotlin={group="com.fasterxml.jackson.module", name="jackson-module-kotlin"}
|
||||||
|
|
||||||
|
postgresql = { module = "org.postgresql:postgresql", version.ref = "postgresql-version" }
|
||||||
[bundles]
|
[bundles]
|
||||||
|
|||||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,6 +1,6 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip
|
distributionUrl=https://mirrors.cloud.tencent.com/gradle/gradle-8.12-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
validateDistributionUrl=true
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
|||||||
2
gradlew
vendored
2
gradlew
vendored
@ -207,7 +207,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
|||||||
# Collect all arguments for the java command:
|
# Collect all arguments for the java command:
|
||||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||||
# and any embedded shellness will be escaped.
|
# and any embedded shellness will be escaped.
|
||||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
# * For gewuyou: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||||
# treated as '${Hostname}' itself on the command line.
|
# treated as '${Hostname}' itself on the command line.
|
||||||
|
|
||||||
set -- \
|
set -- \
|
||||||
|
|||||||
9
llmx-app/build.gradle.kts
Normal file
9
llmx-app/build.gradle.kts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
dependencies {
|
||||||
|
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 由于 Kotlin 插件引入时会自动添加依赖,但根项目不需要 Kotlin 依赖,因此需要排除 Kotlin 依赖
|
||||||
|
*/
|
||||||
|
configurations.implementation {
|
||||||
|
exclude(group = "org.jetbrains.kotlin", module = "kotlin-stdlib")
|
||||||
|
}
|
||||||
3
llmx-app/llmx-app-multimodality/.gitattributes
vendored
Normal file
3
llmx-app/llmx-app-multimodality/.gitattributes
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
/gradlew text eol=lf
|
||||||
|
*.bat text eol=crlf
|
||||||
|
*.jar binary
|
||||||
40
llmx-app/llmx-app-multimodality/.gitignore
vendored
Normal file
40
llmx-app/llmx-app-multimodality/.gitignore
vendored
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
HELP.md
|
||||||
|
.gradle
|
||||||
|
build/
|
||||||
|
!gradle/wrapper/gradle-wrapper.jar
|
||||||
|
!**/src/main/**/build/
|
||||||
|
!**/src/test/**/build/
|
||||||
|
|
||||||
|
### STS ###
|
||||||
|
.apt_generated
|
||||||
|
.classpath
|
||||||
|
.factorypath
|
||||||
|
.project
|
||||||
|
.settings
|
||||||
|
.springBeans
|
||||||
|
.sts4-cache
|
||||||
|
bin/
|
||||||
|
!**/src/main/**/bin/
|
||||||
|
!**/src/test/**/bin/
|
||||||
|
|
||||||
|
### IntelliJ IDEA ###
|
||||||
|
.idea
|
||||||
|
*.iws
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
out/
|
||||||
|
!**/src/main/**/out/
|
||||||
|
!**/src/test/**/out/
|
||||||
|
|
||||||
|
### NetBeans ###
|
||||||
|
/nbproject/private/
|
||||||
|
/nbbuild/
|
||||||
|
/dist/
|
||||||
|
/nbdist/
|
||||||
|
/.nb-gradle/
|
||||||
|
|
||||||
|
### VS Code ###
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
### Kotlin ###
|
||||||
|
.kotlin
|
||||||
13
llmx-app/llmx-app-multimodality/build.gradle.kts
Normal file
13
llmx-app/llmx-app-multimodality/build.gradle.kts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
extra {
|
||||||
|
// 开启springboot
|
||||||
|
setProperty(ProjectFlags.USE_SPRING_BOOT_WEB, true)
|
||||||
|
setProperty(ProjectFlags.USE_SPRING_CLOUD_BOM, true)
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
// Nacos 服务发现和配置
|
||||||
|
implementation(libs.springCloudStarter.alibaba.nacos.discovery)
|
||||||
|
implementation(libs.forgeBoot.core.extension)
|
||||||
|
implementation(libs.forgeBoot.webmvc.version.springBootStarter)
|
||||||
|
implementation(libs.jackson.module.kotlin)
|
||||||
|
implementation(project(Modules.Core.API))
|
||||||
|
}
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
package org.jcnc.llmx.app.multimodality
|
||||||
|
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication
|
||||||
|
import org.springframework.boot.runApplication
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
class LlmxAppMultimodalityApplication
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
runApplication<LlmxAppMultimodalityApplication>(*args)
|
||||||
|
}
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
server:
|
||||||
|
port: 8081
|
||||||
|
spring:
|
||||||
|
config:
|
||||||
|
import: classpath:bootstrap-dev.yml
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
server:
|
||||||
|
port: 9003
|
||||||
|
spring:
|
||||||
|
config:
|
||||||
|
import: classpath:bootstrap-test.yml
|
||||||
|
|
||||||
|
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
spring:
|
||||||
|
application:
|
||||||
|
name: llmx-app-multimodality
|
||||||
|
profiles:
|
||||||
|
active: dev
|
||||||
@ -4,7 +4,7 @@ spring:
|
|||||||
username: nacos
|
username: nacos
|
||||||
password: L4s6f9y3
|
password: L4s6f9y3
|
||||||
server-addr: 49.235.96.75:8848
|
server-addr: 49.235.96.75:8848
|
||||||
ip: 192.168.1.6
|
ip: 192.168.1.100
|
||||||
discovery:
|
discovery:
|
||||||
server-addr: ${spring.cloud.nacos.server-addr}
|
server-addr: ${spring.cloud.nacos.server-addr}
|
||||||
username: ${spring.cloud.nacos.username}
|
username: ${spring.cloud.nacos.username}
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
spring:
|
||||||
|
cloud:
|
||||||
|
nacos:
|
||||||
|
username: nacos
|
||||||
|
password: L4s6f9y3
|
||||||
|
server-addr: 49.235.96.75:9001
|
||||||
|
discovery:
|
||||||
|
server-addr: ${spring.cloud.nacos.server-addr}
|
||||||
|
username: ${spring.cloud.nacos.username}
|
||||||
|
password: ${spring.cloud.nacos.password}
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
package org.jcnc.llmx.app.multimodality
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest
|
||||||
|
|
||||||
|
@SpringBootTest
|
||||||
|
class LlmxAppMultimodalityApplicationTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun contextLoads() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -2,7 +2,7 @@ dependencies {
|
|||||||
|
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* 由于 Kotlin 插件被引入时会自动添加依赖,但根项目不需要 Kotlin 依赖,因此需要排除 Kotlin 依赖
|
* 由于 Kotlin 插件引入时会自动添加依赖,但根项目不需要 Kotlin 依赖,因此需要排除 Kotlin 依赖
|
||||||
*/
|
*/
|
||||||
configurations.implementation {
|
configurations.implementation {
|
||||||
exclude(group = "org.jetbrains.kotlin", module = "kotlin-stdlib")
|
exclude(group = "org.jetbrains.kotlin", module = "kotlin-stdlib")
|
||||||
|
|||||||
3
llmx-core/llmx-core-api/.gitattributes
vendored
Normal file
3
llmx-core/llmx-core-api/.gitattributes
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
/gradlew text eol=lf
|
||||||
|
*.bat text eol=crlf
|
||||||
|
*.jar binary
|
||||||
40
llmx-core/llmx-core-api/.gitignore
vendored
Normal file
40
llmx-core/llmx-core-api/.gitignore
vendored
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
HELP.md
|
||||||
|
.gradle
|
||||||
|
build/
|
||||||
|
!gradle/wrapper/gradle-wrapper.jar
|
||||||
|
!**/src/main/**/build/
|
||||||
|
!**/src/test/**/build/
|
||||||
|
|
||||||
|
### STS ###
|
||||||
|
.apt_generated
|
||||||
|
.classpath
|
||||||
|
.factorypath
|
||||||
|
.project
|
||||||
|
.settings
|
||||||
|
.springBeans
|
||||||
|
.sts4-cache
|
||||||
|
bin/
|
||||||
|
!**/src/main/**/bin/
|
||||||
|
!**/src/test/**/bin/
|
||||||
|
|
||||||
|
### IntelliJ IDEA ###
|
||||||
|
.idea
|
||||||
|
*.iws
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
out/
|
||||||
|
!**/src/main/**/out/
|
||||||
|
!**/src/test/**/out/
|
||||||
|
|
||||||
|
### NetBeans ###
|
||||||
|
/nbproject/private/
|
||||||
|
/nbbuild/
|
||||||
|
/dist/
|
||||||
|
/nbdist/
|
||||||
|
/.nb-gradle/
|
||||||
|
|
||||||
|
### VS Code ###
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
### Kotlin ###
|
||||||
|
.kotlin
|
||||||
10
llmx-core/llmx-core-api/build.gradle.kts
Normal file
10
llmx-core/llmx-core-api/build.gradle.kts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
ext{
|
||||||
|
set(ProjectFlags.USE_SPRING_CLOUD_BOM,true)
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
api(project(Modules.Core.COMMON))
|
||||||
|
api(libs.org.reactivestreams.reactiveStreams)
|
||||||
|
// Spring Cloud OpenFeign
|
||||||
|
implementation(libs.springCloudStarter.openfeign)
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
package org.jcnc.llmx.core.api
|
||||||
|
|
||||||
|
import org.springframework.cloud.openfeign.EnableFeignClients
|
||||||
|
import org.springframework.context.annotation.Configuration
|
||||||
|
|
||||||
|
/**
|
||||||
|
*LLM 核心Feign自动配置
|
||||||
|
*
|
||||||
|
* @since 2025-05-08 16:48:23
|
||||||
|
* @author gewuyou
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@EnableFeignClients(basePackages = ["org.jcnc.llmx.core.api"])
|
||||||
|
open class LLMCoreFeignAutoConfiguration
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
package org.jcnc.llmx.core.api
|
||||||
|
|
||||||
|
import org.jcnc.llmx.core.common.entities.request.ChatRequest
|
||||||
|
import org.jcnc.llmx.core.common.entities.response.ChatResponsePart
|
||||||
|
import org.reactivestreams.Publisher
|
||||||
|
import org.springframework.cloud.openfeign.FeignClient
|
||||||
|
import org.springframework.http.MediaType
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody
|
||||||
|
|
||||||
|
/**
|
||||||
|
*LLM 核心客户端
|
||||||
|
*
|
||||||
|
* @since 2025-05-08 16:22:22
|
||||||
|
* @author gewuyou
|
||||||
|
*/
|
||||||
|
@FeignClient(name = "llmx-core-service") // name 对应 Nacos 中注册的服务名
|
||||||
|
fun interface LLMCoreFeignClient {
|
||||||
|
@PostMapping("/api/v1/chat/stream", consumes = [MediaType.APPLICATION_JSON_VALUE])
|
||||||
|
fun chat(@RequestBody request: ChatRequest): Publisher<ChatResponsePart>
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
org.jcnc.llmx.core.api.LLMCoreFeignAutoConfiguration
|
||||||
3
llmx-core/llmx-core-common/.gitattributes
vendored
Normal file
3
llmx-core/llmx-core-common/.gitattributes
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
/gradlew text eol=lf
|
||||||
|
*.bat text eol=crlf
|
||||||
|
*.jar binary
|
||||||
40
llmx-core/llmx-core-common/.gitignore
vendored
Normal file
40
llmx-core/llmx-core-common/.gitignore
vendored
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
HELP.md
|
||||||
|
.gradle
|
||||||
|
build/
|
||||||
|
!gradle/wrapper/gradle-wrapper.jar
|
||||||
|
!**/src/main/**/build/
|
||||||
|
!**/src/test/**/build/
|
||||||
|
|
||||||
|
### STS ###
|
||||||
|
.apt_generated
|
||||||
|
.classpath
|
||||||
|
.factorypath
|
||||||
|
.project
|
||||||
|
.settings
|
||||||
|
.springBeans
|
||||||
|
.sts4-cache
|
||||||
|
bin/
|
||||||
|
!**/src/main/**/bin/
|
||||||
|
!**/src/test/**/bin/
|
||||||
|
|
||||||
|
### IntelliJ IDEA ###
|
||||||
|
.idea
|
||||||
|
*.iws
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
out/
|
||||||
|
!**/src/main/**/out/
|
||||||
|
!**/src/test/**/out/
|
||||||
|
|
||||||
|
### NetBeans ###
|
||||||
|
/nbproject/private/
|
||||||
|
/nbbuild/
|
||||||
|
/dist/
|
||||||
|
/nbdist/
|
||||||
|
/.nb-gradle/
|
||||||
|
|
||||||
|
### VS Code ###
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
### Kotlin ###
|
||||||
|
.kotlin
|
||||||
7
llmx-core/llmx-core-common/build.gradle.kts
Normal file
7
llmx-core/llmx-core-common/build.gradle.kts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
ext{
|
||||||
|
set(ProjectFlags.USE_SPRING_BOOT_BOM,true)
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
implementation(libs.jackson.core)
|
||||||
|
implementation(libs.jackson.module.kotlin)
|
||||||
|
}
|
||||||
@ -0,0 +1,42 @@
|
|||||||
|
package org.jcnc.llmx.core.common.deserializer
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonParser
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationContext
|
||||||
|
import com.fasterxml.jackson.databind.JsonDeserializer
|
||||||
|
import com.fasterxml.jackson.databind.node.ObjectNode
|
||||||
|
import org.jcnc.llmx.core.common.entities.request.MultiModalContent
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 多模态内容反序列化器
|
||||||
|
*
|
||||||
|
* 该类用于将JSON表示的多模态内容反序列化为MultiModalContent数据结构
|
||||||
|
* 主要处理两种情况:文本内容和图像内容,并相应地创建Text或Image实例
|
||||||
|
* 如果JSON中不包含已知的内容类型,则抛出IllegalArgumentException
|
||||||
|
*
|
||||||
|
* @since 2025-05-08 20:41:15
|
||||||
|
* @author gewuyou
|
||||||
|
*/
|
||||||
|
class MultiModalContentDeserializer : JsonDeserializer<MultiModalContent>() {
|
||||||
|
/**
|
||||||
|
* 反序列化方法
|
||||||
|
*
|
||||||
|
* 根据JSON节点中的内容类型,创建相应的MultiModalContent实例
|
||||||
|
* 如果内容类型未知,则抛出异常
|
||||||
|
*
|
||||||
|
* @param p JsonParser对象,用于解析JSON输入
|
||||||
|
* @param ctxt DeserializationContext对象,提供反序列化上下文
|
||||||
|
* @return MultiModalContent实例,表示反序列化的多模态内容
|
||||||
|
* @throws IllegalArgumentException 如果JSON节点中的内容类型未知
|
||||||
|
*/
|
||||||
|
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): MultiModalContent {
|
||||||
|
// 解析JSON输入并将其读取为ObjectNode
|
||||||
|
val node: ObjectNode = p.codec.readTree(p)
|
||||||
|
|
||||||
|
// 根据JSON节点中的内容类型,返回相应的MultiModalContent实例
|
||||||
|
return when {
|
||||||
|
node.has("text") -> MultiModalContent.Text(node["text"].asText())
|
||||||
|
node.has("image") -> MultiModalContent.Image(node["image"].asText())
|
||||||
|
else -> throw IllegalArgumentException("Unknown content type in: $node")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,52 @@
|
|||||||
|
package org.jcnc.llmx.core.common.entities.request
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
|
||||||
|
import org.jcnc.llmx.core.common.deserializer.MultiModalContentDeserializer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MultiModalContent密封类,用于定义消息内容的多种模态
|
||||||
|
* 这是一个密封类,意味着所有的子类都需要在这个文件中定义
|
||||||
|
*/
|
||||||
|
@JsonDeserialize(using = MultiModalContentDeserializer::class)
|
||||||
|
sealed class MultiModalContent {
|
||||||
|
/**
|
||||||
|
* Text数据类,表示文本内容的模态
|
||||||
|
* @param text 具体的文本内容
|
||||||
|
*/
|
||||||
|
data class Text(val text: String) : MultiModalContent()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Image数据类,表示图像内容的模态
|
||||||
|
* @param image 图像的base64
|
||||||
|
*/
|
||||||
|
data class Image(val image: String) : MultiModalContent()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ChatRequest数据类,用于封装聊天请求的参数
|
||||||
|
* @since 2025-04-25 17:07:02
|
||||||
|
* @author gewuyou
|
||||||
|
* @param prompt 用户的聊天提示或消息,是聊天请求的主要输入
|
||||||
|
* @param model 使用的聊天模型名称,决定了解析和响应的方式
|
||||||
|
* @param messages 包含多个模态消息的列表,每个消息有特定的角色和内容
|
||||||
|
* @param options 可选的额外参数集合,用于定制聊天请求的行为和输出
|
||||||
|
* 可以包括如最大回复长度、温度(随机性)等
|
||||||
|
*/
|
||||||
|
data class ChatRequest(
|
||||||
|
val prompt: String? = "",
|
||||||
|
val model: String,
|
||||||
|
val messages: List<MultiModalMessage> =listOf(),
|
||||||
|
val options: Map<String, String> = mapOf()
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MultiModalMessage数据类,用于定义聊天请求中的单个消息
|
||||||
|
* @param role 消息的角色,可以是"system", "user", "assistant"等
|
||||||
|
* @param content 消息的内容,可以是文本、图像等多种模态的组合
|
||||||
|
*/
|
||||||
|
data class MultiModalMessage(
|
||||||
|
val role: String, // "system", "user", "assistant"
|
||||||
|
val content: List<MultiModalContent>
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package org.jcnc.llmx.core.spi.entities.request
|
package org.jcnc.llmx.core.common.entities.request
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 嵌入请求类
|
* 嵌入请求类
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package org.jcnc.llmx.core.spi.entities.response
|
package org.jcnc.llmx.core.common.entities.response
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 聊天响应类
|
* 聊天响应类
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package org.jcnc.llmx.core.spi.entities.response
|
package org.jcnc.llmx.core.common.entities.response
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 嵌入响应类
|
* 嵌入响应类
|
||||||
@ -1,19 +1,17 @@
|
|||||||
extra {
|
extra {
|
||||||
// 开启springboot
|
// 开启springboot
|
||||||
setProperty(ProjectFlags.USE_SPRING_BOOT, true)
|
setProperty(ProjectFlags.USE_SPRING_BOOT_WEB, true)
|
||||||
setProperty(ProjectFlags.USE_SPRING_CLOUD_BOM,true)
|
setProperty(ProjectFlags.USE_SPRING_CLOUD_BOM,true)
|
||||||
|
setProperty(ProjectFlags.USE_NACOS_DEPENDENCE, true)
|
||||||
|
setProperty(ProjectFlags.USE_DAO_DEPENDENCE, true)
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
val libs = rootProject.libs
|
|
||||||
// Nacos 服务发现和配置
|
|
||||||
implementation(libs.springCloudStarter.alibaba.nacos.discovery)
|
|
||||||
|
|
||||||
// WebClient 和 Spring Cloud LoadBalancer
|
// WebClient 和 Spring Cloud LoadBalancer
|
||||||
implementation(libs.springBootStarter.webflux)
|
implementation(libs.springBootStarter.webflux)
|
||||||
implementation(libs.springCloudStarter.loadbalancer)
|
implementation(libs.springCloudStarter.loadbalancer)
|
||||||
|
|
||||||
|
|
||||||
implementation(project(Modules.Core.SPI))
|
implementation(project(Modules.Core.COMMON))
|
||||||
|
|
||||||
// Kotlin Coroutines
|
// Kotlin Coroutines
|
||||||
implementation(libs.kotlinx.coruntes.reactor)
|
implementation(libs.kotlinx.coruntes.reactor)
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import org.springframework.cloud.client.discovery.EnableDiscoveryClient
|
|||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
@EnableDiscoveryClient
|
@EnableDiscoveryClient
|
||||||
class LlmxCoreServiceApplication
|
open class LlmxCoreServiceApplication
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 程序的入口点。
|
* 程序的入口点。
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
package org.jcnc.llmx.core.service.config
|
package org.jcnc.llmx.core.service.config
|
||||||
|
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties
|
|
||||||
import org.springframework.cloud.client.loadbalancer.LoadBalanced
|
import org.springframework.cloud.client.loadbalancer.LoadBalanced
|
||||||
import org.springframework.context.annotation.Bean
|
import org.springframework.context.annotation.Bean
|
||||||
import org.springframework.context.annotation.Configuration
|
import org.springframework.context.annotation.Configuration
|
||||||
@ -13,8 +12,7 @@ import org.springframework.web.reactive.function.client.WebClient
|
|||||||
* @author gewuyou
|
* @author gewuyou
|
||||||
*/
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableConfigurationProperties(ModelProperties::class)
|
open class AppConfiguration {
|
||||||
class AppConfiguration {
|
|
||||||
/**
|
/**
|
||||||
* 创建一个配置了负载均衡的WebClient构建器
|
* 创建一个配置了负载均衡的WebClient构建器
|
||||||
*
|
*
|
||||||
@ -25,7 +23,7 @@ class AppConfiguration {
|
|||||||
*/
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
@LoadBalanced
|
@LoadBalanced
|
||||||
fun webClientBuilder(): WebClient.Builder {
|
open fun webClientBuilder(): WebClient.Builder {
|
||||||
return WebClient.builder()
|
return WebClient.builder()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
package org.jcnc.llmx.core.service.config
|
||||||
|
|
||||||
|
import com.gewuyou.forgeboot.core.extension.log
|
||||||
|
import org.springframework.context.annotation.Configuration
|
||||||
|
import org.springframework.web.servlet.config.annotation.CorsRegistry
|
||||||
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CORS配置类
|
||||||
|
*
|
||||||
|
* 该类用于全局配置跨域请求设置,以确保前端应用可以与后端服务进行通信
|
||||||
|
* 主要通过重写addCorsMappings方法来配置跨域请求映射,以及通过corsWebFilter方法来提供更细粒度的跨域支持
|
||||||
|
*
|
||||||
|
* @since 2025-04-02 17:03:41
|
||||||
|
* @author gewuyou
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
open class CorsConfig : WebMvcConfigurer {
|
||||||
|
/**
|
||||||
|
* 添加跨域请求映射
|
||||||
|
*
|
||||||
|
* 该方法重写了父接口中的addCorsMappings方法,用于配置全局的跨域请求规则
|
||||||
|
* 主要配置了允许所有路径、所有来源、常见HTTP方法、所有请求头的跨域请求,并设置了不携带Cookie以及预检请求缓存时间
|
||||||
|
*
|
||||||
|
* @param registry 跨域请求注册表,用于添加跨域请求映射
|
||||||
|
*/
|
||||||
|
override fun addCorsMappings(registry: CorsRegistry) {
|
||||||
|
log.info("Web CORS配置生效")
|
||||||
|
registry.addMapping("/**") // 匹配所有路径
|
||||||
|
.allowedOrigins("*") // 允许所有来源(生产环境建议指定具体域名)
|
||||||
|
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") // 允许的HTTP方法
|
||||||
|
.allowedHeaders("*") // 允许所有请求头
|
||||||
|
.allowCredentials(false) // 是否允许携带Cookie(true时需要明确指定allowedOrigins,不能为*)
|
||||||
|
.maxAge(3600) // 预检请求缓存时间(秒)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,21 +0,0 @@
|
|||||||
package org.jcnc.llmx.core.service.config
|
|
||||||
|
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties
|
|
||||||
import org.springframework.cloud.context.config.annotation.RefreshScope
|
|
||||||
|
|
||||||
/**
|
|
||||||
*模型属性
|
|
||||||
*
|
|
||||||
* @since 2025-04-26 18:01:48
|
|
||||||
* @author gewuyou
|
|
||||||
*/
|
|
||||||
@ConfigurationProperties(prefix = "llmhub.model-route")
|
|
||||||
@RefreshScope
|
|
||||||
open class ModelProperties {
|
|
||||||
/**
|
|
||||||
* 模型名前缀 -> 服务名映射
|
|
||||||
* 该映射表存储了模型名前缀与服务名的对应关系,用于快速查找模型对应的服务
|
|
||||||
* 例:openai -> llmhub-impl-openai
|
|
||||||
*/
|
|
||||||
var modelServiceMap: Map<String, String> = emptyMap()
|
|
||||||
}
|
|
||||||
@ -1,11 +1,12 @@
|
|||||||
package org.jcnc.llmx.core.service.controller
|
package org.jcnc.llmx.core.service.controller
|
||||||
|
|
||||||
|
import com.gewuyou.forgeboot.core.extension.log
|
||||||
import com.gewuyou.forgeboot.webmvc.version.annotation.ApiVersion
|
import com.gewuyou.forgeboot.webmvc.version.annotation.ApiVersion
|
||||||
import kotlinx.coroutines.flow.Flow
|
import org.jcnc.llmx.core.common.entities.request.ChatRequest
|
||||||
|
import org.jcnc.llmx.core.common.entities.response.ChatResponsePart
|
||||||
import org.jcnc.llmx.core.service.service.impl.LLMServiceImpl
|
import org.jcnc.llmx.core.service.service.impl.LLMServiceImpl
|
||||||
import org.jcnc.llmx.core.spi.entities.request.ChatRequest
|
import org.reactivestreams.Publisher
|
||||||
import org.jcnc.llmx.core.spi.entities.response.ChatResponsePart
|
import org.springframework.http.MediaType
|
||||||
|
|
||||||
import org.springframework.web.bind.annotation.PostMapping
|
import org.springframework.web.bind.annotation.PostMapping
|
||||||
import org.springframework.web.bind.annotation.RequestBody
|
import org.springframework.web.bind.annotation.RequestBody
|
||||||
import org.springframework.web.bind.annotation.RequestMapping
|
import org.springframework.web.bind.annotation.RequestMapping
|
||||||
@ -33,8 +34,23 @@ class ChatController(
|
|||||||
* @param request 聊天请求对象,包含了发起聊天所需的各种参数和用户信息
|
* @param request 聊天请求对象,包含了发起聊天所需的各种参数和用户信息
|
||||||
* @return 返回一个Flow流,流中依次提供了聊天响应的部分数据,允许异步处理和逐步消费响应内容
|
* @return 返回一个Flow流,流中依次提供了聊天响应的部分数据,允许异步处理和逐步消费响应内容
|
||||||
*/
|
*/
|
||||||
@PostMapping("/stream")
|
@PostMapping("/stream", produces = [MediaType.APPLICATION_NDJSON_VALUE])
|
||||||
fun chat(@RequestBody request: ChatRequest): Flow<ChatResponsePart> {
|
fun chat(@RequestBody request: ChatRequest): Publisher<ChatResponsePart> {
|
||||||
return llmServiceImpl.chat(request)
|
return llmServiceImpl.chat(request)
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* 处理多模态聊天请求的函数
|
||||||
|
* 该函数通过 POST 方法接收一个聊天请求,并以 NDJSON 的形式返回聊天响应的部分
|
||||||
|
*
|
||||||
|
* @param request 包含聊天请求信息的数据类
|
||||||
|
* @return 返回一个发布者,用于异步地发送聊天响应的部分
|
||||||
|
*
|
||||||
|
* 注意:该函数被设计为异步处理,以提高性能和响应性
|
||||||
|
* 它使用了响应式编程模型,适合处理高并发和大数据量的响应
|
||||||
|
*/
|
||||||
|
@PostMapping("/streamMultimodality", produces = [MediaType.APPLICATION_NDJSON_VALUE])
|
||||||
|
fun multimodalityChat(@RequestBody request: ChatRequest): Publisher<ChatResponsePart>{
|
||||||
|
log.info("request: {}", request)
|
||||||
|
return llmServiceImpl.multimodalityChat(request)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -0,0 +1,74 @@
|
|||||||
|
package org.jcnc.llmx.core.service.domain.model
|
||||||
|
|
||||||
|
import jakarta.persistence.*
|
||||||
|
import org.hibernate.annotations.ColumnDefault
|
||||||
|
import java.time.OffsetDateTime
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表示模型与其路由信息之间的映射关系。
|
||||||
|
* 该类用于定义模型与对应服务之间的关联,
|
||||||
|
* 包括相关的描述信息和状态信息。
|
||||||
|
*
|
||||||
|
* 使用注解来定义表名、主键生成策略以及字段与数据库列的映射关系。
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "model_route_mapping", schema = "core")
|
||||||
|
open class ModelRouteMapping {
|
||||||
|
/**
|
||||||
|
* 映射关系的唯一标识符。
|
||||||
|
* 使用序列生成器来自动生成ID。
|
||||||
|
*/
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "model_route_mapping_id_gen")
|
||||||
|
@SequenceGenerator(
|
||||||
|
name = "model_route_mapping_id_gen",
|
||||||
|
sequenceName = "model_route_mapping_id_seq",
|
||||||
|
allocationSize = 1
|
||||||
|
)
|
||||||
|
@Column(name = "id", nullable = false)
|
||||||
|
open var id: Long? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模型名称。
|
||||||
|
* 用于标识与路由信息相关联的模型。
|
||||||
|
*/
|
||||||
|
@Column(name = "model", nullable = false, length = 50)
|
||||||
|
open lateinit var model: String
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对应的服务名称。
|
||||||
|
* 表示该模型所关联的具体服务实例。
|
||||||
|
*/
|
||||||
|
@Column(name = "service_name", nullable = false, length = 50)
|
||||||
|
open lateinit var serviceName: String
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 映射描述。
|
||||||
|
* 提供有关映射的额外信息,便于理解和维护。
|
||||||
|
*/
|
||||||
|
@Column(name = "description", length = 150)
|
||||||
|
open var description: String? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 映射状态。
|
||||||
|
* 表示当前映射是否启用。默认情况下,新映射是启用的。
|
||||||
|
*/
|
||||||
|
@ColumnDefault("true")
|
||||||
|
@Column(name = "enabled")
|
||||||
|
open var enabled: Boolean=true
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间。
|
||||||
|
* 默认为创建新映射时的当前时间戳。
|
||||||
|
*/
|
||||||
|
@ColumnDefault("CURRENT_TIMESTAMP")
|
||||||
|
@Column(name = "create_at", nullable = false)
|
||||||
|
open var createAt: OffsetDateTime? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最后更新时间。
|
||||||
|
* 记录映射信息的最后修改时间。
|
||||||
|
*/
|
||||||
|
@Column(name = "update_at")
|
||||||
|
open var updateAt: OffsetDateTime? = null
|
||||||
|
}
|
||||||
@ -1,7 +1,8 @@
|
|||||||
package org.jcnc.llmx.core.service.manager
|
package org.jcnc.llmx.core.service.manager
|
||||||
|
|
||||||
|
|
||||||
import org.jcnc.llmx.core.service.config.ModelProperties
|
import com.gewuyou.forgeboot.core.extension.log
|
||||||
|
import org.jcnc.llmx.core.service.repositories.ModelRouteMappingRepository
|
||||||
import org.springframework.stereotype.Component
|
import org.springframework.stereotype.Component
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -15,7 +16,7 @@ import org.springframework.stereotype.Component
|
|||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
class ModelRouteManager(
|
class ModelRouteManager(
|
||||||
private val modelProperties: ModelProperties
|
private val modelRouteMappingRepository: ModelRouteMappingRepository,
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
* 根据模型名查找对应服务
|
* 根据模型名查找对应服务
|
||||||
@ -28,8 +29,11 @@ class ModelRouteManager(
|
|||||||
* @throws IllegalArgumentException 如果模型名不匹配任何已知前缀,抛出此异常
|
* @throws IllegalArgumentException 如果模型名不匹配任何已知前缀,抛出此异常
|
||||||
*/
|
*/
|
||||||
fun resolveServiceName(model: String): String {
|
fun resolveServiceName(model: String): String {
|
||||||
for ((prefix, serviceName) in modelProperties.modelServiceMap) {
|
val modelServiceMap = modelRouteMappingRepository.findAllByEnabled(true)
|
||||||
if (model.startsWith(prefix)) {
|
.associate { it.model to it.serviceName }
|
||||||
|
log.info("modelServiceMap: $modelServiceMap")
|
||||||
|
for ((model, serviceName) in modelServiceMap) {
|
||||||
|
if (model.startsWith(model)) {
|
||||||
return serviceName
|
return serviceName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,10 @@
|
|||||||
|
package org.jcnc.llmx.core.service.repositories
|
||||||
|
|
||||||
|
import org.jcnc.llmx.core.service.domain.model.ModelRouteMapping
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository
|
||||||
|
import org.springframework.data.jpa.repository.JpaSpecificationExecutor
|
||||||
|
|
||||||
|
interface ModelRouteMappingRepository : JpaRepository<ModelRouteMapping, Long>,
|
||||||
|
JpaSpecificationExecutor<ModelRouteMapping> {
|
||||||
|
fun findAllByEnabled(isEnabled: Boolean): List<ModelRouteMapping>
|
||||||
|
}
|
||||||
@ -1,8 +1,9 @@
|
|||||||
package org.jcnc.llmx.core.service.service
|
package org.jcnc.llmx.core.service.service
|
||||||
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import org.jcnc.llmx.core.spi.entities.request.ChatRequest
|
import org.jcnc.llmx.core.common.entities.request.ChatRequest
|
||||||
import org.jcnc.llmx.core.spi.entities.response.ChatResponsePart
|
import org.jcnc.llmx.core.common.entities.response.ChatResponsePart
|
||||||
|
import org.reactivestreams.Publisher
|
||||||
|
|
||||||
import org.springframework.web.bind.annotation.PostMapping
|
import org.springframework.web.bind.annotation.PostMapping
|
||||||
|
|
||||||
@ -12,7 +13,7 @@ import org.springframework.web.bind.annotation.PostMapping
|
|||||||
* @since 2025-04-26 17:38:18
|
* @since 2025-04-26 17:38:18
|
||||||
* @author gewuyou
|
* @author gewuyou
|
||||||
*/
|
*/
|
||||||
fun interface LLMService {
|
interface LLMService {
|
||||||
/**
|
/**
|
||||||
* 初始化与聊天服务的连接,以处理聊天请求
|
* 初始化与聊天服务的连接,以处理聊天请求
|
||||||
*
|
*
|
||||||
@ -23,5 +24,6 @@ fun interface LLMService {
|
|||||||
* @return 返回一个Flow流,通过该流可以接收到聊天响应的部分数据,如消息、状态更新等
|
* @return 返回一个Flow流,通过该流可以接收到聊天响应的部分数据,如消息、状态更新等
|
||||||
*/
|
*/
|
||||||
@PostMapping("/chat")
|
@PostMapping("/chat")
|
||||||
fun chat(request: ChatRequest): Flow<ChatResponsePart>
|
fun chat(request: ChatRequest): Publisher<ChatResponsePart>
|
||||||
|
fun multimodalityChat(request: ChatRequest): Publisher<ChatResponsePart>
|
||||||
}
|
}
|
||||||
@ -1,11 +1,11 @@
|
|||||||
package org.jcnc.llmx.core.service.service.impl
|
package org.jcnc.llmx.core.service.service.impl
|
||||||
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.reactive.asFlow
|
import org.jcnc.llmx.core.common.entities.request.ChatRequest
|
||||||
|
import org.jcnc.llmx.core.common.entities.response.ChatResponsePart
|
||||||
import org.jcnc.llmx.core.service.manager.ModelRouteManager
|
import org.jcnc.llmx.core.service.manager.ModelRouteManager
|
||||||
import org.jcnc.llmx.core.service.service.LLMService
|
import org.jcnc.llmx.core.service.service.LLMService
|
||||||
import org.jcnc.llmx.core.spi.entities.request.ChatRequest
|
import org.reactivestreams.Publisher
|
||||||
import org.jcnc.llmx.core.spi.entities.response.ChatResponsePart
|
|
||||||
|
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import org.springframework.web.reactive.function.client.WebClient
|
import org.springframework.web.reactive.function.client.WebClient
|
||||||
@ -30,7 +30,7 @@ class LLMServiceImpl(
|
|||||||
* @param request 聊天请求对象,包含建立聊天所需的信息,如用户标识、会话标识等
|
* @param request 聊天请求对象,包含建立聊天所需的信息,如用户标识、会话标识等
|
||||||
* @return 返回一个Flow流,通过该流可以接收到聊天响应的部分数据,如消息、状态更新等
|
* @return 返回一个Flow流,通过该流可以接收到聊天响应的部分数据,如消息、状态更新等
|
||||||
*/
|
*/
|
||||||
override fun chat(request: ChatRequest): Flow<ChatResponsePart> {
|
override fun chat(request: ChatRequest): Publisher<ChatResponsePart> {
|
||||||
val serviceName = modelRouteManager.resolveServiceName(request.model)
|
val serviceName = modelRouteManager.resolveServiceName(request.model)
|
||||||
val webClient = webClientBuilder.build()
|
val webClient = webClientBuilder.build()
|
||||||
return webClient.post()
|
return webClient.post()
|
||||||
@ -38,6 +38,15 @@ class LLMServiceImpl(
|
|||||||
.bodyValue(request)
|
.bodyValue(request)
|
||||||
.retrieve()
|
.retrieve()
|
||||||
.bodyToFlux(ChatResponsePart::class.java)
|
.bodyToFlux(ChatResponsePart::class.java)
|
||||||
.asFlow()
|
}
|
||||||
|
|
||||||
|
override fun multimodalityChat(request: ChatRequest): Publisher<ChatResponsePart> {
|
||||||
|
val serviceName = modelRouteManager.resolveServiceName(request.model)
|
||||||
|
val webClient = webClientBuilder.build()
|
||||||
|
return webClient.post()
|
||||||
|
.uri("http://$serviceName/provider/multimodalityChat")
|
||||||
|
.bodyValue(request)
|
||||||
|
.retrieve()
|
||||||
|
.bodyToFlux(ChatResponsePart::class.java)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,13 +1,2 @@
|
|||||||
server:
|
server:
|
||||||
port: 8081
|
port: 8081
|
||||||
spring:
|
|
||||||
config:
|
|
||||||
import: classpath:bootstrap-dev.yml
|
|
||||||
llmhub:
|
|
||||||
model-route:
|
|
||||||
modelServiceMap:
|
|
||||||
qwen-turbo: llmhub-impl-baiLian
|
|
||||||
qwen-max: llmhub-impl-baiLian
|
|
||||||
qwen-plus: llmhub-impl-baiLian
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,2 @@
|
|||||||
|
server:
|
||||||
|
port: 9002
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
server:
|
||||||
|
port: 9002
|
||||||
|
|
||||||
|
|
||||||
@ -1,5 +1,3 @@
|
|||||||
spring:
|
spring:
|
||||||
application:
|
|
||||||
name: llmhub-core-service
|
|
||||||
profiles:
|
profiles:
|
||||||
active: dev
|
active: dev
|
||||||
|
|||||||
@ -1,12 +1,25 @@
|
|||||||
spring:
|
spring:
|
||||||
cloud:
|
cloud:
|
||||||
nacos:
|
nacos:
|
||||||
|
ip: 127.0.0.1
|
||||||
username: nacos
|
username: nacos
|
||||||
password: L4s6f9y3
|
password: L4s6f9y3,
|
||||||
server-addr: 49.235.96.75:8848
|
server-addr: 49.235.96.75:8848
|
||||||
ip: 192.168.1.6
|
|
||||||
discovery:
|
discovery:
|
||||||
server-addr: ${spring.cloud.nacos.server-addr}
|
ip: ${spring.cloud.nacos.ip}
|
||||||
username: ${spring.cloud.nacos.username}
|
username: ${spring.cloud.nacos.username}
|
||||||
password: ${spring.cloud.nacos.password}
|
password: ${spring.cloud.nacos.password}
|
||||||
ip: ${spring.cloud.nacos.ip}
|
server-addr: ${spring.cloud.nacos.server-addr}
|
||||||
|
group: llmx-${spring.profiles.active}
|
||||||
|
namespace: a17d57ec-4fd9-44c7-a617-7f6003a0b332
|
||||||
|
config:
|
||||||
|
file-extension: yaml
|
||||||
|
namespace: a17d57ec-4fd9-44c7-a617-7f6003a0b332
|
||||||
|
refresh-enabled: true
|
||||||
|
extension-configs:
|
||||||
|
- data-id: ${spring.application.name}-${spring.profiles.active}.yaml
|
||||||
|
refresh: true
|
||||||
|
group: ${spring.application.name}
|
||||||
|
- data-id: common-db.yaml
|
||||||
|
refresh: true
|
||||||
|
group: infra
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
spring:
|
||||||
|
cloud:
|
||||||
|
nacos:
|
||||||
|
username: nacos
|
||||||
|
password: L4s6f9y3,
|
||||||
|
server-addr: 49.235.96.75:8848
|
||||||
|
discovery:
|
||||||
|
server-addr: ${spring.cloud.nacos.server-addr}
|
||||||
|
username: ${spring.cloud.nacos.username}
|
||||||
|
password: ${spring.cloud.nacos.password}
|
||||||
|
group: llmx-${spring.profiles.active}
|
||||||
|
namespace: ab34d859-6f1a-4f28-ac6b-27a7410ab27b
|
||||||
|
config:
|
||||||
|
file-extension: yaml
|
||||||
|
namespace: ab34d859-6f1a-4f28-ac6b-27a7410ab27b
|
||||||
|
refresh-enabled: true
|
||||||
|
extension-configs:
|
||||||
|
- data-id: ${spring.application.name}-${spring.profiles.active}.yaml
|
||||||
|
refresh: true
|
||||||
|
group: ${spring.application.name}
|
||||||
|
- data-id: common-db.yaml
|
||||||
|
refresh: true
|
||||||
|
group: infra
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
spring:
|
||||||
|
cloud:
|
||||||
|
nacos:
|
||||||
|
server-addr: llmx-nacos:8848
|
||||||
|
username: nacos
|
||||||
|
password: L4s6f9y3,
|
||||||
|
discovery:
|
||||||
|
server-addr: ${spring.cloud.nacos.server-addr}
|
||||||
|
username: ${spring.cloud.nacos.username}
|
||||||
|
password: ${spring.cloud.nacos.password}
|
||||||
|
group: llmx-${spring.profiles.active}
|
||||||
|
namespace: 54a289f7-5f4a-4c83-8a0a-199defa35458
|
||||||
|
config:
|
||||||
|
file-extension: yaml
|
||||||
|
namespace: 54a289f7-5f4a-4c83-8a0a-199defa35458
|
||||||
|
refresh-enabled: true
|
||||||
|
extension-configs:
|
||||||
|
- data-id: ${spring.application.name}-${spring.profiles.active}.yaml
|
||||||
|
refresh: true
|
||||||
|
group: ${spring.application.name}
|
||||||
|
- data-id: common-db.yaml
|
||||||
|
refresh: true
|
||||||
|
group: infra
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
spring:
|
||||||
|
application:
|
||||||
|
name: llmx-core-service
|
||||||
|
profiles:
|
||||||
|
active: dev
|
||||||
@ -4,6 +4,7 @@ apply {
|
|||||||
plugin(libs.plugins.kotlin.plugin.spring.get().pluginId)
|
plugin(libs.plugins.kotlin.plugin.spring.get().pluginId)
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
compileOnly(libs.kotlinx.coruntes.reactor)
|
|
||||||
compileOnly(libs.springBootStarter.web)
|
compileOnly(libs.springBootStarter.web)
|
||||||
|
api(libs.org.reactivestreams.reactiveStreams)
|
||||||
|
api(project(Modules.Core.COMMON))
|
||||||
}
|
}
|
||||||
@ -1,17 +0,0 @@
|
|||||||
package org.jcnc.llmx.core.spi.entities.request
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ChatRequest数据类,用于封装聊天请求的参数
|
|
||||||
* @since 2025-04-25 17:07:02
|
|
||||||
* @author gewuyou
|
|
||||||
* @param prompt 用户的聊天提示或消息,是聊天请求的主要输入
|
|
||||||
* @param model 使用的聊天模型名称,决定了解析和响应的方式
|
|
||||||
* @param options 可选的额外参数集合,用于定制聊天请求的行为和输出
|
|
||||||
* 可以包括如最大回复长度、温度(随机性)等
|
|
||||||
*/
|
|
||||||
data class ChatRequest(
|
|
||||||
val prompt: String,
|
|
||||||
val model: String,
|
|
||||||
val options: Map<String, String> = mapOf()
|
|
||||||
)
|
|
||||||
@ -1,11 +1,12 @@
|
|||||||
package org.jcnc.llmx.core.spi.provider
|
package org.jcnc.llmx.core.spi.provider
|
||||||
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import org.jcnc.llmx.core.spi.entities.request.ChatRequest
|
|
||||||
import org.jcnc.llmx.core.spi.entities.request.EmbeddingRequest
|
|
||||||
import org.jcnc.llmx.core.spi.entities.response.ChatResponsePart
|
|
||||||
import org.jcnc.llmx.core.spi.entities.response.EmbeddingResponse
|
|
||||||
|
|
||||||
|
import org.jcnc.llmx.core.common.entities.request.ChatRequest
|
||||||
|
import org.jcnc.llmx.core.common.entities.request.EmbeddingRequest
|
||||||
|
import org.jcnc.llmx.core.common.entities.response.ChatResponsePart
|
||||||
|
import org.jcnc.llmx.core.common.entities.response.EmbeddingResponse
|
||||||
|
import org.reactivestreams.Publisher
|
||||||
|
import org.springframework.http.MediaType
|
||||||
import org.springframework.web.bind.annotation.PostMapping
|
import org.springframework.web.bind.annotation.PostMapping
|
||||||
import org.springframework.web.bind.annotation.RequestMapping
|
import org.springframework.web.bind.annotation.RequestMapping
|
||||||
|
|
||||||
@ -29,9 +30,20 @@ interface LLMProvider {
|
|||||||
* @param request 聊天请求对象,包含建立聊天所需的信息,如用户标识、会话标识等
|
* @param request 聊天请求对象,包含建立聊天所需的信息,如用户标识、会话标识等
|
||||||
* @return 返回一个Flow流,通过该流可以接收到聊天响应的部分数据,如消息、状态更新等
|
* @return 返回一个Flow流,通过该流可以接收到聊天响应的部分数据,如消息、状态更新等
|
||||||
*/
|
*/
|
||||||
@PostMapping("/chat")
|
@PostMapping("/chat", produces = [MediaType.APPLICATION_NDJSON_VALUE])
|
||||||
fun chat(request: ChatRequest): Flow<ChatResponsePart>
|
fun chat(request: ChatRequest): Publisher<ChatResponsePart>
|
||||||
|
/**
|
||||||
|
* 处理多模态聊天请求的函数
|
||||||
|
* 该函数通过 POST 方法接收一个聊天请求,并以 NDJSON 的形式返回聊天响应的部分
|
||||||
|
*
|
||||||
|
* @param request 包含聊天请求信息的数据类
|
||||||
|
* @return 返回一个发布者,用于异步地发送聊天响应的部分
|
||||||
|
*
|
||||||
|
* 注意:该函数被设计为异步处理,以提高性能和响应性
|
||||||
|
* 它使用了响应式编程模型,适合处理高并发和大数据量的响应
|
||||||
|
*/
|
||||||
|
@PostMapping("/multimodalityChat", produces = [MediaType.APPLICATION_NDJSON_VALUE])
|
||||||
|
fun multimodalityChat(request: ChatRequest): Publisher<ChatResponsePart>
|
||||||
/**
|
/**
|
||||||
* 嵌入功能方法
|
* 嵌入功能方法
|
||||||
* 该方法允许用户发送嵌入请求,以获取LLM生成的嵌入向量
|
* 该方法允许用户发送嵌入请求,以获取LLM生成的嵌入向量
|
||||||
|
|||||||
@ -1,23 +0,0 @@
|
|||||||
|
|
||||||
// 开启springboot
|
|
||||||
extra[ProjectFlags.USE_SPRING_BOOT] = true
|
|
||||||
setProperty(ProjectFlags.USE_SPRING_CLOUD_BOM,true)
|
|
||||||
dependencies {
|
|
||||||
// Nacos 服务发现和配置
|
|
||||||
implementation(libs.springCloudStarter.alibaba.nacos.discovery)
|
|
||||||
|
|
||||||
implementation(project(Modules.Core.SPI))
|
|
||||||
|
|
||||||
implementation(libs.kotlinx.coruntes.reactor)
|
|
||||||
|
|
||||||
implementation(libs.aliyun.bailian)
|
|
||||||
|
|
||||||
implementation(libs.okHttp)
|
|
||||||
|
|
||||||
implementation(libs.forgeBoot.core.extension)
|
|
||||||
|
|
||||||
implementation(libs.jackson.module.kotlin)
|
|
||||||
|
|
||||||
implementation(libs.forgeBoot.core.extension)
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1,105 +0,0 @@
|
|||||||
package org.jcnc.llmx.impl.baiLian.adapter
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
|
||||||
import com.gewuyou.forgeboot.core.extension.log
|
|
||||||
import kotlinx.coroutines.*
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.flow
|
|
||||||
import okhttp3.Headers.Companion.toHeaders
|
|
||||||
import okhttp3.MediaType.Companion.toMediaType
|
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
import okhttp3.Request
|
|
||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
|
||||||
import org.jcnc.llmx.core.spi.entities.response.ChatResponsePart
|
|
||||||
|
|
||||||
import org.springframework.stereotype.Component
|
|
||||||
import java.io.BufferedReader
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 百炼适配器
|
|
||||||
*
|
|
||||||
* 该类负责与百炼API进行交互,提供流式聊天功能。
|
|
||||||
* 它使用OkHttpClient发送请求,并通过Jackson库处理JSON数据。
|
|
||||||
*
|
|
||||||
* @param okHttpClient 用于发送HTTP请求的客户端
|
|
||||||
* @param objectMapper 用于序列化和反序列化JSON数据的映射器
|
|
||||||
* @since 2025-04-27 10:31:47
|
|
||||||
* @author gewuyou
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
class DashScopeAdapter(
|
|
||||||
private val okHttpClient: OkHttpClient,
|
|
||||||
private val objectMapper: ObjectMapper
|
|
||||||
) {
|
|
||||||
/**
|
|
||||||
* 发送流式聊天请求
|
|
||||||
*
|
|
||||||
* 本函数构建并发送一个聊天请求,然后以流的形式接收和处理响应。
|
|
||||||
* 它主要用于与DashScope API进行交互,提取并发布聊天响应的部分内容。
|
|
||||||
*
|
|
||||||
* @param url 请求的URL
|
|
||||||
* @param headers 请求的头部信息
|
|
||||||
* @param requestBody 请求的主体内容
|
|
||||||
* @param extractContent 一个函数,用于从JSON响应中提取内容
|
|
||||||
* @param dispatcher 协程调度器,默认为IO调度器
|
|
||||||
* @return 返回一个Flow,发布聊天响应的部分内容
|
|
||||||
*/
|
|
||||||
fun sendStreamChat(
|
|
||||||
url: String,
|
|
||||||
headers: Map<String, String>,
|
|
||||||
requestBody: Any,
|
|
||||||
extractContent: (String) -> ChatResponsePart,
|
|
||||||
dispatcher: CoroutineDispatcher = Dispatchers.IO
|
|
||||||
): Flow<ChatResponsePart> = flow {
|
|
||||||
val requestJson = objectMapper.writeValueAsString(requestBody)
|
|
||||||
log.info("📤 请求参数: {}", requestJson)
|
|
||||||
|
|
||||||
val request = Request.Builder()
|
|
||||||
.url(url)
|
|
||||||
.headers(headers.toHeaders())
|
|
||||||
.post(requestJson.toRequestBody("application/json".toMediaType()))
|
|
||||||
.build()
|
|
||||||
|
|
||||||
val call = okHttpClient.newCall(request)
|
|
||||||
val response = call.execute()
|
|
||||||
|
|
||||||
if (!response.isSuccessful) {
|
|
||||||
throw RuntimeException("❌ DashScope 请求失败: HTTP ${response.code}")
|
|
||||||
}
|
|
||||||
|
|
||||||
val responseBody = response.body ?: throw RuntimeException("❌ DashScope 响应体为空")
|
|
||||||
|
|
||||||
val bufferedReader: BufferedReader = responseBody.charStream().buffered()
|
|
||||||
val allContent = StringBuilder()
|
|
||||||
|
|
||||||
try {
|
|
||||||
while (currentCoroutineContext().isActive) {
|
|
||||||
val line = withContext(dispatcher) {
|
|
||||||
bufferedReader.readLine()
|
|
||||||
} ?: break
|
|
||||||
log.info("📥 接收到行: {}", line)
|
|
||||||
|
|
||||||
if (line.startsWith("data:")) {
|
|
||||||
val jsonPart = line.removePrefix("data:").trim()
|
|
||||||
try {
|
|
||||||
val part = extractContent(jsonPart)
|
|
||||||
allContent.append(part.content)
|
|
||||||
log.info("✅ 提取内容: {}", part)
|
|
||||||
emit(part)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
log.warn("⚠️ 无法解析 JSON: {}", jsonPart, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.info("📦 完整内容: {}", allContent)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
log.error("🚨 读取 DashScope 响应流失败: {}", e.message, e)
|
|
||||||
throw e
|
|
||||||
} finally {
|
|
||||||
withContext(dispatcher) {
|
|
||||||
bufferedReader.close()
|
|
||||||
}
|
|
||||||
response.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
server:
|
|
||||||
port: 8082
|
|
||||||
spring:
|
|
||||||
config:
|
|
||||||
import: classpath:bootstrap-dev.yml
|
|
||||||
application:
|
|
||||||
name: llmhub-impl-baiLian
|
|
||||||
cloud:
|
|
||||||
nacos:
|
|
||||||
discovery:
|
|
||||||
server-addr: 49.235.96.75:8848 # Nacos 服务地址
|
|
||||||
# 阿里云配置
|
|
||||||
aliyun:
|
|
||||||
# DashScope服务配置
|
|
||||||
dash:
|
|
||||||
# 访问凭证配置
|
|
||||||
scope:
|
|
||||||
access-key-id: LTAI5tHiA2Ry3XTAfoSEJW6z # 阿里云访问密钥ID
|
|
||||||
access-key-secret: K5sf4FxZZuUgLEFnyfepBfMqFGmDcD # 阿里云访问密钥密钥
|
|
||||||
endpoint: bailian.cn-beijing.aliyuncs.com # 阿里云服务端点
|
|
||||||
workspace-id: llm-axfkuqft05uzbjpi # 工作区ID
|
|
||||||
api-key: sk-78af4dd964a94f4cb373851064dbdc12 # API密钥
|
|
||||||
app-id: 3fae0bbab2e54a90a37aa02cd12dd62c # 应用ID
|
|
||||||
base-url: https://dashscope.aliyuncs.com/api/v1/apps/ # 基础API URL
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
server:
|
|
||||||
port: 9003
|
|
||||||
spring:
|
|
||||||
application:
|
|
||||||
name: llmhub-impl-baiLian
|
|
||||||
cloud:
|
|
||||||
nacos:
|
|
||||||
discovery:
|
|
||||||
server-addr: 49.235.96.75:9001 # Nacos 服务地址
|
|
||||||
# 阿里云配置
|
|
||||||
aliyun:
|
|
||||||
# DashScope服务配置
|
|
||||||
dash:
|
|
||||||
# 访问凭证配置
|
|
||||||
scope:
|
|
||||||
access-key-id: LTAI5tHiA2Ry3XTAfoSEJW6z # 阿里云访问密钥ID
|
|
||||||
access-key-secret: K5sf4FxZZuUgLEFnyfepBfMqFGmDcD # 阿里云访问密钥密钥
|
|
||||||
endpoint: bailian.cn-beijing.aliyuncs.com # 阿里云服务端点
|
|
||||||
workspace-id: llm-axfkuqft05uzbjpi # 工作区ID
|
|
||||||
api-key: sk-78af4dd964a94f4cb373851064dbdc12 # API密钥
|
|
||||||
app-id: 3fae0bbab2e54a90a37aa02cd12dd62c # 应用ID
|
|
||||||
base-url: https://dashscope.aliyuncs.com/api/v1/apps/ # 基础API URL
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
spring:
|
|
||||||
application:
|
|
||||||
name: llmhub-impl-baiLian
|
|
||||||
profiles:
|
|
||||||
active: dev
|
|
||||||
# 阿里云配置
|
|
||||||
aliyun:
|
|
||||||
# DashScope服务配置
|
|
||||||
dash:
|
|
||||||
# 访问凭证配置
|
|
||||||
scope:
|
|
||||||
access-key-id: LTAI5tHiA2Ry3XTAfoSEJW6z # 阿里云访问密钥ID
|
|
||||||
access-key-secret: K5sf4FxZZuUgLEFnyfepBfMqFGmDcD # 阿里云访问密钥密钥
|
|
||||||
endpoint: bailian.cn-beijing.aliyuncs.com # 阿里云服务端点
|
|
||||||
workspace-id: llm-axfkuqft05uzbjpi # 工作区ID
|
|
||||||
api-key: sk-78af4dd964a94f4cb373851064dbdc12 # API密钥
|
|
||||||
app-id: 3fae0bbab2e54a90a37aa02cd12dd62c # 应用ID
|
|
||||||
base-url: https://dashscope.aliyuncs.com/api/v1/apps/ # 基础API URL
|
|
||||||
3
llmx-impl/llmx-impl-bailian/.gitattributes
vendored
Normal file
3
llmx-impl/llmx-impl-bailian/.gitattributes
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
/gradlew text eol=lf
|
||||||
|
*.bat text eol=crlf
|
||||||
|
*.jar binary
|
||||||
40
llmx-impl/llmx-impl-bailian/.gitignore
vendored
Normal file
40
llmx-impl/llmx-impl-bailian/.gitignore
vendored
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
HELP.md
|
||||||
|
.gradle
|
||||||
|
build/
|
||||||
|
!gradle/wrapper/gradle-wrapper.jar
|
||||||
|
!**/src/main/**/build/
|
||||||
|
!**/src/test/**/build/
|
||||||
|
|
||||||
|
### STS ###
|
||||||
|
.apt_generated
|
||||||
|
.classpath
|
||||||
|
.factorypath
|
||||||
|
.project
|
||||||
|
.settings
|
||||||
|
.springBeans
|
||||||
|
.sts4-cache
|
||||||
|
bin/
|
||||||
|
!**/src/main/**/bin/
|
||||||
|
!**/src/test/**/bin/
|
||||||
|
|
||||||
|
### IntelliJ IDEA ###
|
||||||
|
.idea
|
||||||
|
*.iws
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
out/
|
||||||
|
!**/src/main/**/out/
|
||||||
|
!**/src/test/**/out/
|
||||||
|
|
||||||
|
### NetBeans ###
|
||||||
|
/nbproject/private/
|
||||||
|
/nbbuild/
|
||||||
|
/dist/
|
||||||
|
/nbdist/
|
||||||
|
/.nb-gradle/
|
||||||
|
|
||||||
|
### VS Code ###
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
### Kotlin ###
|
||||||
|
.kotlin
|
||||||
6
llmx-impl/llmx-impl-bailian/build.gradle.kts
Normal file
6
llmx-impl/llmx-impl-bailian/build.gradle.kts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
// 开启LLM实现平台依赖
|
||||||
|
setProperty(ProjectFlags.USE_LLM_IMPL_PLATFORM_DEPENDENCE, true)
|
||||||
|
dependencies {
|
||||||
|
implementation(libs.aliyun.bailian)
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,137 @@
|
|||||||
|
package org.jcnc.llmx.impl.baiLian.adapter
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import com.gewuyou.forgeboot.core.extension.log
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.FlowCollector
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
|
import okhttp3.Headers.Companion.toHeaders
|
||||||
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
|
import org.jcnc.llmx.core.common.entities.response.ChatResponsePart
|
||||||
|
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Component
|
||||||
|
import java.io.BufferedReader
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 百炼适配器
|
||||||
|
*
|
||||||
|
* 该类负责与百炼API进行交互,提供流式聊天功能。
|
||||||
|
* 它使用OkHttpClient发送请求,并通过Jackson库处理JSON数据。
|
||||||
|
*
|
||||||
|
* @param okHttpClient 用于发送HTTP请求的客户端
|
||||||
|
* @param objectMapper 用于序列化和反序列化JSON数据的映射器
|
||||||
|
* @since 2025-04-27 10:31:47
|
||||||
|
* @author gewuyou
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
class DashScopeAdapter(
|
||||||
|
private val okHttpClient: OkHttpClient,
|
||||||
|
private val objectMapper: ObjectMapper,
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* 发送流式聊天请求
|
||||||
|
*
|
||||||
|
* 本函数构建并发送一个聊天请求,然后以流的形式接收和处理响应。
|
||||||
|
* 它主要用于与DashScope API进行交互,提取并发布聊天响应的部分内容。
|
||||||
|
*
|
||||||
|
* @param url 请求的URL
|
||||||
|
* @param headers 请求的头部信息
|
||||||
|
* @param requestBody 请求的主体内容
|
||||||
|
* @param extractContent 一个函数,用于从JSON响应中提取内容
|
||||||
|
* @param dispatcher 协程调度器,默认为IO调度器
|
||||||
|
* @return 返回一个Flow,发布聊天响应的部分内容
|
||||||
|
*/
|
||||||
|
fun sendStreamChat(
|
||||||
|
url: String,
|
||||||
|
headers: Map<String, String>,
|
||||||
|
requestBody: Any,
|
||||||
|
extractContent: (String) -> ChatResponsePart,
|
||||||
|
dispatcher: CoroutineDispatcher = Dispatchers.IO,
|
||||||
|
): Flow<ChatResponsePart> = flow {
|
||||||
|
val requestJson = objectMapper.writeValueAsString(requestBody)
|
||||||
|
log.info("📤 请求参数: {}", requestJson)
|
||||||
|
|
||||||
|
val request = buildRequest(url, headers, requestJson)
|
||||||
|
|
||||||
|
okHttpClient.newCall(request).execute().use { response ->
|
||||||
|
if (!response.isSuccessful) {
|
||||||
|
log.error("🚨 DashScope 请求失败: code ${response.code} message: ${response.message} body: ${response.body}")
|
||||||
|
throw RuntimeException("❌ DashScope 请求失败: HTTP ${response.code}")
|
||||||
|
}
|
||||||
|
|
||||||
|
val responseBody = response.body ?: throw RuntimeException("❌ DashScope 响应体为空")
|
||||||
|
|
||||||
|
responseBody.charStream().buffered().use { reader ->
|
||||||
|
val allContent = StringBuilder()
|
||||||
|
try {
|
||||||
|
processResponse(dispatcher, reader, extractContent, allContent)
|
||||||
|
log.info("📦 完整内容: {}", allContent)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
log.error("🚨 读取 DashScope 响应流失败: {}", e.message, e)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理响应流
|
||||||
|
*
|
||||||
|
* 本函数读取HTTP响应中的数据行,解析并提取内容。
|
||||||
|
* 它在循环中读取每一行,并使用提供的函数提取内容。
|
||||||
|
*
|
||||||
|
* @param dispatcher 协程调度器
|
||||||
|
* @param reader 响应体的BufferedReader
|
||||||
|
* @param extractContent 一个函数,用于从JSON响应中提取内容
|
||||||
|
* @param allContent 保存所有提取内容的StringBuilder
|
||||||
|
*/
|
||||||
|
private suspend fun FlowCollector<ChatResponsePart>.processResponse(
|
||||||
|
dispatcher: CoroutineDispatcher,
|
||||||
|
reader: BufferedReader,
|
||||||
|
extractContent: (String) -> ChatResponsePart,
|
||||||
|
allContent: StringBuilder,
|
||||||
|
) {
|
||||||
|
while (currentCoroutineContext().isActive) {
|
||||||
|
val line = withContext(dispatcher) {
|
||||||
|
reader.readLine()
|
||||||
|
} ?: break
|
||||||
|
|
||||||
|
log.debug("📥 接收到行: {}", line)
|
||||||
|
|
||||||
|
if (line.startsWith("data:")) {
|
||||||
|
val jsonPart = line.removePrefix("data:").trim()
|
||||||
|
try {
|
||||||
|
val part = extractContent(jsonPart)
|
||||||
|
allContent.append(part.content)
|
||||||
|
log.debug("✅ 提取内容: {}", part)
|
||||||
|
emit(part)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
log.warn("⚠️ 无法解析 JSON: {}", jsonPart, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建请求
|
||||||
|
*
|
||||||
|
* 本函数构建一个OkHttp请求对象,用于发送聊天请求。
|
||||||
|
*
|
||||||
|
* @param url 请求的URL
|
||||||
|
* @param headers 请求的头部信息
|
||||||
|
* @param json 请求的主体内容的JSON字符串
|
||||||
|
* @return 返回构建好的Request对象
|
||||||
|
*/
|
||||||
|
private fun buildRequest(url: String, headers: Map<String, String>, json: String): Request {
|
||||||
|
return Request.Builder()
|
||||||
|
.url(url)
|
||||||
|
.headers(headers.toHeaders())
|
||||||
|
.post(json.toRequestBody("application/json".toMediaType()))
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -44,5 +44,9 @@ class DashScopeProperties {
|
|||||||
* 应用调用基路径,例如 /api/v1。
|
* 应用调用基路径,例如 /api/v1。
|
||||||
*/
|
*/
|
||||||
var baseUrl: String = ""
|
var baseUrl: String = ""
|
||||||
|
/**
|
||||||
|
* 多模态对话调用路径
|
||||||
|
*/
|
||||||
|
var multimodalityUrl: String = ""
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -1,16 +1,20 @@
|
|||||||
package org.jcnc.llmx.impl.baiLian.controller
|
package org.jcnc.llmx.impl.baiLian.controller
|
||||||
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import org.jcnc.llmx.core.spi.entities.request.ChatRequest
|
import kotlinx.coroutines.reactive.asPublisher
|
||||||
import org.jcnc.llmx.core.spi.entities.request.EmbeddingRequest
|
import org.jcnc.llmx.core.common.entities.request.ChatRequest
|
||||||
import org.jcnc.llmx.core.spi.entities.response.ChatResponsePart
|
import org.jcnc.llmx.core.common.entities.request.EmbeddingRequest
|
||||||
import org.jcnc.llmx.core.spi.entities.response.EmbeddingResponse
|
import org.jcnc.llmx.core.common.entities.response.ChatResponsePart
|
||||||
|
import org.jcnc.llmx.core.common.entities.response.EmbeddingResponse
|
||||||
|
|
||||||
import org.jcnc.llmx.core.spi.provider.LLMProvider
|
import org.jcnc.llmx.core.spi.provider.LLMProvider
|
||||||
import org.jcnc.llmx.impl.baiLian.service.BaiLianModelService
|
import org.jcnc.llmx.impl.baiLian.service.BaiLianModelService
|
||||||
|
import org.reactivestreams.Publisher
|
||||||
|
|
||||||
import org.springframework.web.bind.annotation.RequestBody
|
import org.springframework.web.bind.annotation.RequestBody
|
||||||
import org.springframework.web.bind.annotation.RestController
|
import org.springframework.web.bind.annotation.RestController
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*百炼提供商
|
*百炼提供商
|
||||||
*
|
*
|
||||||
@ -18,7 +22,6 @@ import org.springframework.web.bind.annotation.RestController
|
|||||||
* @author gewuyou
|
* @author gewuyou
|
||||||
*/
|
*/
|
||||||
@RestController
|
@RestController
|
||||||
//@RequestMapping("/provider")
|
|
||||||
class BaiLianProvider(
|
class BaiLianProvider(
|
||||||
private val baiLianModelService: BaiLianModelService
|
private val baiLianModelService: BaiLianModelService
|
||||||
) : LLMProvider {
|
) : LLMProvider {
|
||||||
@ -31,9 +34,12 @@ class BaiLianProvider(
|
|||||||
* @param request 聊天请求对象,包含建立聊天所需的信息,如用户标识、会话标识等
|
* @param request 聊天请求对象,包含建立聊天所需的信息,如用户标识、会话标识等
|
||||||
* @return 返回一个Flow流,通过该流可以接收到聊天响应的部分数据,如消息、状态更新等
|
* @return 返回一个Flow流,通过该流可以接收到聊天响应的部分数据,如消息、状态更新等
|
||||||
*/
|
*/
|
||||||
// @PostMapping("/chat")
|
override fun chat(@RequestBody request: ChatRequest): Publisher<ChatResponsePart> {
|
||||||
override fun chat(@RequestBody request: ChatRequest): Flow<ChatResponsePart> {
|
return baiLianModelService.streamChat(request).asPublisher()
|
||||||
return baiLianModelService.streamChat(request)
|
}
|
||||||
|
|
||||||
|
override fun multimodalityChat(@RequestBody request: ChatRequest): Publisher<ChatResponsePart> {
|
||||||
|
return baiLianModelService.streamMultimodalityChat(request).asPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -0,0 +1,117 @@
|
|||||||
|
package org.jcnc.llmx.impl.baiLian.entities.response
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dash范围响应类
|
||||||
|
*
|
||||||
|
* 该类用于表示从Dash服务接收到的响应数据结构它包含了响应的输出信息、使用情况和请求ID
|
||||||
|
* 主要用于JSON序列化和反序列化
|
||||||
|
*
|
||||||
|
* @param output 输出信息,包括生成的内容和完成原因
|
||||||
|
* @param usage 使用情况,包括输入和输出的令牌详情
|
||||||
|
* @param requestId 请求ID,用于跟踪和调试请求
|
||||||
|
*/
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
data class DashScopeResponse(
|
||||||
|
val output: Output?,
|
||||||
|
val usage: Usage?,
|
||||||
|
@JsonProperty("request_id")
|
||||||
|
val requestId: String?
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 输出数据类
|
||||||
|
*
|
||||||
|
* 该类包含Dash服务生成的内容,主要是通过choices列表来提供可能的回复选项
|
||||||
|
*
|
||||||
|
* @param choices 生成内容的选项列表
|
||||||
|
*/
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
data class Output(
|
||||||
|
val choices: List<Choice>?
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 选择项数据类
|
||||||
|
*
|
||||||
|
* 该类表示生成内容的一个选项,包括完成原因和具体的消息内容
|
||||||
|
*
|
||||||
|
* @param finishReason 完成原因,解释生成内容结束的原因
|
||||||
|
* @param message 消息内容,包括角色和具体内容
|
||||||
|
*/
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
data class Choice(
|
||||||
|
@JsonProperty("finish_reason")
|
||||||
|
val finishReason: String?,
|
||||||
|
val message: Message?
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 消息数据类
|
||||||
|
*
|
||||||
|
* 该类表示一条消息,包括消息的角色(如用户、助手)和具体内容
|
||||||
|
* 具体内容通过一个内容列表来表示
|
||||||
|
*
|
||||||
|
* @param role 消息的角色,如用户、助手等
|
||||||
|
* @param content 消息的具体内容列表
|
||||||
|
*/
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
data class Message(
|
||||||
|
val role: String?,
|
||||||
|
val content: List<Content>?
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 内容数据类
|
||||||
|
*
|
||||||
|
* 该类表示消息中的具体内容,目前只支持文本内容
|
||||||
|
*
|
||||||
|
* @param text 文本内容
|
||||||
|
*/
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
data class Content(
|
||||||
|
val text: String?
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用情况数据类
|
||||||
|
*
|
||||||
|
* 该类详细记录了Dash服务的使用情况,包括输入和输出的令牌数量和详情
|
||||||
|
*
|
||||||
|
* @param inputTokensDetails 输入令牌的详情
|
||||||
|
* @param outputTokensDetails 输出令牌的详情
|
||||||
|
* @param inputTokens 输入令牌的数量
|
||||||
|
* @param outputTokens 输出令牌的数量
|
||||||
|
* @param totalTokens 总令牌的数量
|
||||||
|
* @param imageTokens 图像令牌的数量,如果适用
|
||||||
|
*/
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
data class Usage(
|
||||||
|
@JsonProperty("input_tokens_details")
|
||||||
|
val inputTokensDetails: TokenDetail?,
|
||||||
|
@JsonProperty("output_tokens_details")
|
||||||
|
val outputTokensDetails: TokenDetail?,
|
||||||
|
@JsonProperty("input_tokens")
|
||||||
|
val inputTokens: Int?,
|
||||||
|
@JsonProperty("output_tokens")
|
||||||
|
val outputTokens: Int?,
|
||||||
|
@JsonProperty("total_tokens")
|
||||||
|
val totalTokens: Int?,
|
||||||
|
@JsonProperty("image_tokens")
|
||||||
|
val imageTokens: Int?
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 令牌详情数据类
|
||||||
|
*
|
||||||
|
* 该类提供了令牌的详细信息,目前仅支持文本令牌的数量
|
||||||
|
*
|
||||||
|
* @param textTokens 文本令牌的数量
|
||||||
|
*/
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
data class TokenDetail(
|
||||||
|
@JsonProperty("text_tokens")
|
||||||
|
val textTokens: Int?
|
||||||
|
)
|
||||||
@ -1,8 +1,8 @@
|
|||||||
package org.jcnc.llmx.impl.baiLian.service
|
package org.jcnc.llmx.impl.baiLian.service
|
||||||
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import org.jcnc.llmx.core.spi.entities.request.ChatRequest
|
import org.jcnc.llmx.core.common.entities.request.ChatRequest
|
||||||
import org.jcnc.llmx.core.spi.entities.response.ChatResponsePart
|
import org.jcnc.llmx.core.common.entities.response.ChatResponsePart
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -20,4 +20,11 @@ interface BaiLianModelService {
|
|||||||
* @return 返回一个Flow流,包含部分聊天响应,允许逐步处理和消费响应
|
* @return 返回一个Flow流,包含部分聊天响应,允许逐步处理和消费响应
|
||||||
*/
|
*/
|
||||||
fun streamChat(request: ChatRequest): Flow<ChatResponsePart>
|
fun streamChat(request: ChatRequest): Flow<ChatResponsePart>
|
||||||
|
/**
|
||||||
|
* 使用流式多模态聊天交互
|
||||||
|
*
|
||||||
|
* @param request 聊天请求对象,包含用户输入、上下文等信息
|
||||||
|
* @return 返回一个Flow流,包含部分聊天响应,允许逐步处理和消费响应
|
||||||
|
*/
|
||||||
|
fun streamMultimodalityChat(request: ChatRequest) : Flow<ChatResponsePart>
|
||||||
}
|
}
|
||||||
@ -4,11 +4,13 @@ import com.fasterxml.jackson.core.type.TypeReference
|
|||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
import com.gewuyou.forgeboot.core.extension.log
|
import com.gewuyou.forgeboot.core.extension.log
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import org.jcnc.llmx.core.spi.entities.request.ChatRequest
|
import org.jcnc.llmx.core.common.entities.request.ChatRequest
|
||||||
import org.jcnc.llmx.core.spi.entities.response.ChatResponsePart
|
import org.jcnc.llmx.core.common.entities.response.ChatResponsePart
|
||||||
import org.jcnc.llmx.core.spi.entities.response.Usage
|
import org.jcnc.llmx.core.common.entities.response.Usage
|
||||||
|
|
||||||
import org.jcnc.llmx.impl.baiLian.adapter.DashScopeAdapter
|
import org.jcnc.llmx.impl.baiLian.adapter.DashScopeAdapter
|
||||||
import org.jcnc.llmx.impl.baiLian.config.entities.DashScopeProperties
|
import org.jcnc.llmx.impl.baiLian.config.entities.DashScopeProperties
|
||||||
|
import org.jcnc.llmx.impl.baiLian.entities.response.DashScopeResponse
|
||||||
import org.jcnc.llmx.impl.baiLian.service.BaiLianModelService
|
import org.jcnc.llmx.impl.baiLian.service.BaiLianModelService
|
||||||
|
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
@ -47,7 +49,7 @@ class BaiLianModelServiceImpl(
|
|||||||
// 构造输入参数,主要包括用户的prompt
|
// 构造输入参数,主要包括用户的prompt
|
||||||
val inputMap = mutableMapOf("prompt" to request.prompt)
|
val inputMap = mutableMapOf("prompt" to request.prompt)
|
||||||
// 获取会话ID,如果存在,则添加到输入参数中
|
// 获取会话ID,如果存在,则添加到输入参数中
|
||||||
val sessionId = (request.options["session_id"] ?: "").toString()
|
val sessionId = (request.options["session_id"] ?: "")
|
||||||
if (StringUtils.hasText(sessionId)) {
|
if (StringUtils.hasText(sessionId)) {
|
||||||
inputMap["session_id"] = sessionId
|
inputMap["session_id"] = sessionId
|
||||||
}
|
}
|
||||||
@ -123,4 +125,75 @@ class BaiLianModelServiceImpl(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用流式多模态聊天交互
|
||||||
|
*
|
||||||
|
* @param request 聊天请求对象,包含用户输入、上下文等信息
|
||||||
|
* @return 返回一个Flow流,包含部分聊天响应,允许逐步处理和消费响应
|
||||||
|
*/
|
||||||
|
override fun streamMultimodalityChat(request: ChatRequest): Flow<ChatResponsePart> {
|
||||||
|
// 构造请求URL
|
||||||
|
val url = dashScopeProperties.multimodalityUrl
|
||||||
|
log.info("请求URL: $url")
|
||||||
|
// 构造请求头,包括授权信息和内容类型
|
||||||
|
val headers = mapOf(
|
||||||
|
"Authorization" to "Bearer ${dashScopeProperties.apiKey}",
|
||||||
|
"Content-Type" to "application/json",
|
||||||
|
"X-DashScope-SSE" to "enable"
|
||||||
|
)
|
||||||
|
log.info("请求头: $headers")
|
||||||
|
// 构造输入参数,主要包括用户的message
|
||||||
|
val inputMap = mutableMapOf("messages" to request.messages)
|
||||||
|
// 构造参数地图,包括是否包含思考、模型ID、增量输出等
|
||||||
|
val parametersMap = mutableMapOf(
|
||||||
|
"incremental_output" to true,
|
||||||
|
)
|
||||||
|
// 构造请求体,包括输入参数、构造的参数地图和调试信息
|
||||||
|
val body = mapOf(
|
||||||
|
"model" to request.model,
|
||||||
|
"input" to inputMap,
|
||||||
|
"parameters" to parametersMap,
|
||||||
|
)
|
||||||
|
// 发送流式聊天请求,并处理响应
|
||||||
|
return dashScopeAdapter.sendStreamChat(
|
||||||
|
url, headers, body,
|
||||||
|
{ json: String ->
|
||||||
|
val response = objectMapper.readValue(json, DashScopeResponse::class.java)
|
||||||
|
val choices = response
|
||||||
|
.output
|
||||||
|
?.choices
|
||||||
|
?.getOrNull(0)
|
||||||
|
// 提取输出文本
|
||||||
|
val text = choices
|
||||||
|
?.message
|
||||||
|
?.content
|
||||||
|
?.getOrNull(0)
|
||||||
|
?.text
|
||||||
|
?: ""
|
||||||
|
// 判断是否完成(finish_reason 通常为 "stop",但你这里是字符串 "null")
|
||||||
|
val finishReason = choices?.finishReason
|
||||||
|
val done = finishReason == "stop"
|
||||||
|
// 提取使用情况
|
||||||
|
val usage = response.usage
|
||||||
|
val promptTokens = usage?.inputTokens ?: 0
|
||||||
|
val completionTokens = usage?.outputTokens ?: 0
|
||||||
|
val totalTokens = usage?.totalTokens ?: 0
|
||||||
|
ChatResponsePart(
|
||||||
|
content = text,
|
||||||
|
done = done,
|
||||||
|
usage = Usage(
|
||||||
|
promptTokens = promptTokens,
|
||||||
|
completionTokens = completionTokens,
|
||||||
|
totalTokens = totalTokens
|
||||||
|
),
|
||||||
|
other = mapOf(
|
||||||
|
"request_id" to (response.requestId ?: ""),
|
||||||
|
"model" to request.model,
|
||||||
|
"response" to json
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
server:
|
||||||
|
port: 8082
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
server:
|
||||||
|
port: 9003
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
server:
|
||||||
|
port: 9003
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
spring:
|
||||||
|
profiles:
|
||||||
|
active: dev
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
spring:
|
||||||
|
cloud:
|
||||||
|
nacos:
|
||||||
|
ip: 127.0.0.1
|
||||||
|
username: nacos
|
||||||
|
password: L4s6f9y3,
|
||||||
|
server-addr: 49.235.96.75:8848
|
||||||
|
discovery:
|
||||||
|
ip: ${spring.cloud.nacos.ip}
|
||||||
|
username: ${spring.cloud.nacos.username}
|
||||||
|
password: ${spring.cloud.nacos.password}
|
||||||
|
server-addr: ${spring.cloud.nacos.server-addr}
|
||||||
|
group: llmx-${spring.profiles.active}
|
||||||
|
namespace: a17d57ec-4fd9-44c7-a617-7f6003a0b332
|
||||||
|
config:
|
||||||
|
file-extension: yaml
|
||||||
|
namespace: a17d57ec-4fd9-44c7-a617-7f6003a0b332
|
||||||
|
refresh-enabled: true
|
||||||
|
extension-configs:
|
||||||
|
- data-id: ${spring.application.name}-${spring.profiles.active}.yaml
|
||||||
|
refresh: true
|
||||||
|
group: ${spring.application.name}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
spring:
|
||||||
|
cloud:
|
||||||
|
nacos:
|
||||||
|
username: nacos
|
||||||
|
password: L4s6f9y3,
|
||||||
|
server-addr: 49.235.96.75:8848
|
||||||
|
discovery:
|
||||||
|
username: ${spring.cloud.nacos.username}
|
||||||
|
password: ${spring.cloud.nacos.password}
|
||||||
|
server-addr: ${spring.cloud.nacos.server-addr}
|
||||||
|
group: llmx-${spring.profiles.active}
|
||||||
|
namespace: ab34d859-6f1a-4f28-ac6b-27a7410ab27b
|
||||||
|
config:
|
||||||
|
file-extension: yaml
|
||||||
|
namespace: ab34d859-6f1a-4f28-ac6b-27a7410ab27b
|
||||||
|
refresh-enabled: true
|
||||||
|
extension-configs:
|
||||||
|
- data-id: ${spring.application.name}-${spring.profiles.active}.yaml
|
||||||
|
refresh: true
|
||||||
|
group: ${spring.application.name}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
spring:
|
||||||
|
cloud:
|
||||||
|
nacos:
|
||||||
|
username: nacos
|
||||||
|
password: L4s6f9y3,
|
||||||
|
server-addr: llmx-nacos:8848
|
||||||
|
discovery:
|
||||||
|
username: ${spring.cloud.nacos.username}
|
||||||
|
password: ${spring.cloud.nacos.password}
|
||||||
|
server-addr: ${spring.cloud.nacos.server-addr}
|
||||||
|
group: llmx-${spring.profiles.active}
|
||||||
|
namespace: 54a289f7-5f4a-4c83-8a0a-199defa35458
|
||||||
|
config:
|
||||||
|
file-extension: yaml
|
||||||
|
namespace: 54a289f7-5f4a-4c83-8a0a-199defa35458
|
||||||
|
refresh-enabled: true
|
||||||
|
extension-configs:
|
||||||
|
- data-id: ${spring.application.name}-${spring.profiles.active}.yaml
|
||||||
|
refresh: true
|
||||||
|
group: ${spring.application.name}
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
spring:
|
||||||
|
application:
|
||||||
|
name: llmx-impl-bailian
|
||||||
|
profiles:
|
||||||
|
active: dev
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
package com.gewuyou.bailian.util;
|
||||||
|
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base 64 图像
|
||||||
|
*
|
||||||
|
* @author gewuyou
|
||||||
|
* @since 2025-05-08 18:56:02
|
||||||
|
*/
|
||||||
|
public class Base64Image {
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
byte[] imageBytes = Files.readAllBytes(Paths.get("F:/gewuyou/Project/Idea/demo-llm/src/main/resources/2025俏春装/10147009251.jpg"));
|
||||||
|
String base64Image = Base64.getEncoder().encodeToString(imageBytes);
|
||||||
|
String imagePayload = "data:image/jpeg;base64," + base64Image;
|
||||||
|
|
||||||
|
System.out.println(imagePayload); // 你可以把它放到 JSON 中使用
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,85 @@
|
|||||||
|
package com.gewuyou.bailian.util;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.util.Base64;
|
||||||
|
/**
|
||||||
|
* DashScopeLocalImageClient
|
||||||
|
*
|
||||||
|
* @author gewuyou
|
||||||
|
* @since 2025-05-08 18:57:58
|
||||||
|
*/
|
||||||
|
public class DashScopeLocalImageClient {
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
// Step 1: 读取本地图片并转成 base64
|
||||||
|
String imagePath = "F:/gewuyou/Project/Idea/demo-llm/src/main/resources/2025俏春装/10147009251.jpg";
|
||||||
|
byte[] imageBytes = Files.readAllBytes(new File(imagePath).toPath());
|
||||||
|
String base64Image = Base64.getEncoder().encodeToString(imageBytes);
|
||||||
|
String imageDataUrl = "data:image/jpeg;base64," + base64Image;
|
||||||
|
System.out.println(imageDataUrl);
|
||||||
|
// Step 2: 构造请求 JSON
|
||||||
|
String jsonBody = """
|
||||||
|
{
|
||||||
|
"model": "qwen-vl-max-latest",
|
||||||
|
"input": {
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"role": "system",
|
||||||
|
"content": [
|
||||||
|
{ "text": "You are a helpful assistant." }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": [
|
||||||
|
{ "image": "%s" },
|
||||||
|
{ "text": "请给这张图打标签" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"parameters": {
|
||||||
|
"incremental_output": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".formatted(imageDataUrl.replace("\n", ""));
|
||||||
|
|
||||||
|
// Step 3: 发送 POST 请求
|
||||||
|
URL url = new URL("https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation");
|
||||||
|
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||||
|
|
||||||
|
connection.setRequestMethod("POST");
|
||||||
|
connection.setRequestProperty("Authorization", "Bearer sk-78af4dd964a94f4cb373851064dbdc12");
|
||||||
|
connection.setRequestProperty("Content-Type", "application/json");
|
||||||
|
connection.setDoOutput(true);
|
||||||
|
|
||||||
|
try (OutputStream os = connection.getOutputStream()) {
|
||||||
|
os.write(jsonBody.getBytes());
|
||||||
|
os.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 4: 读取响应
|
||||||
|
int responseCode = connection.getResponseCode();
|
||||||
|
System.out.println("Response Code: " + responseCode);
|
||||||
|
|
||||||
|
try (BufferedReader reader = new BufferedReader(
|
||||||
|
new InputStreamReader(responseCode == 200 ?
|
||||||
|
connection.getInputStream() : connection.getErrorStream(), "GBK"))) {
|
||||||
|
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
System.out.println(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
connection.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
3
llmx-impl/llmx-impl-openai/.gitattributes
vendored
Normal file
3
llmx-impl/llmx-impl-openai/.gitattributes
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
/gradlew text eol=lf
|
||||||
|
*.bat text eol=crlf
|
||||||
|
*.jar binary
|
||||||
40
llmx-impl/llmx-impl-openai/.gitignore
vendored
Normal file
40
llmx-impl/llmx-impl-openai/.gitignore
vendored
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
HELP.md
|
||||||
|
.gradle
|
||||||
|
build/
|
||||||
|
!gradle/wrapper/gradle-wrapper.jar
|
||||||
|
!**/src/main/**/build/
|
||||||
|
!**/src/test/**/build/
|
||||||
|
|
||||||
|
### STS ###
|
||||||
|
.apt_generated
|
||||||
|
.classpath
|
||||||
|
.factorypath
|
||||||
|
.project
|
||||||
|
.settings
|
||||||
|
.springBeans
|
||||||
|
.sts4-cache
|
||||||
|
bin/
|
||||||
|
!**/src/main/**/bin/
|
||||||
|
!**/src/test/**/bin/
|
||||||
|
|
||||||
|
### IntelliJ IDEA ###
|
||||||
|
.idea
|
||||||
|
*.iws
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
out/
|
||||||
|
!**/src/main/**/out/
|
||||||
|
!**/src/test/**/out/
|
||||||
|
|
||||||
|
### NetBeans ###
|
||||||
|
/nbproject/private/
|
||||||
|
/nbbuild/
|
||||||
|
/dist/
|
||||||
|
/nbdist/
|
||||||
|
/.nb-gradle/
|
||||||
|
|
||||||
|
### VS Code ###
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
### Kotlin ###
|
||||||
|
.kotlin
|
||||||
5
llmx-impl/llmx-impl-openai/build.gradle.kts
Normal file
5
llmx-impl/llmx-impl-openai/build.gradle.kts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
// 开启LLM实现平台依赖
|
||||||
|
setProperty(ProjectFlags.USE_LLM_IMPL_PLATFORM_DEPENDENCE, true)
|
||||||
|
dependencies {
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
package org.jcnc.llmx.impl.openai
|
||||||
|
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication
|
||||||
|
import org.springframework.boot.runApplication
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
class LlmxImplOpenaiApplication
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
runApplication<LlmxImplOpenaiApplication>(*args)
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
spring.application.name=llmx-impl-openai
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
package org.jcnc.llmx.impl.openai
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest
|
||||||
|
|
||||||
|
@SpringBootTest
|
||||||
|
class LlmxImplOpenaiApplicationTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun contextLoads() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user