【AI】Agent 全栈进阶|Agent 设计模式

简介: 本文讲解 AI‑Agent 三大常用开发模式:ReAct 边思考边行动,适合路径未知的排查任务;Plan‑and‑Execute 先拆分计划再执行,适配复杂长任务;Reflection 生成后对照标准审查修正,保障交付质量

200x200.png

  ◆ 博主名称: QuZhengRong

  AI俘虏,样式苦手

⭐️ Agent专栏Agent

⭐️ LuckReport专栏LuckReport

⭐️ SpringBoot专栏SpringBoot


目录

resized-6319f7f549f9e9518855fbc2e93461f7.jpg

通常情况下,Agent 很少能仅靠一轮对话就完成任务,例如排查一个经营问题要查多次数据,做一份调研要分几个方向收集材料。当执行任务的步骤变多时,自然而然就会出现一个问题:任务该怎么推进?

设计模式就是对这个问题的回答。Agent 常用的设计模式有三种:ReAct ,Plan-and-Execute ,Reflection 。下面来分别介绍这3个模式

一、ReAct 模式:先观察,后行动

ReAct 模式和人类解决问题的思考方式高度一致,过程如下:

Thought(思考当前缺什么信息)
    ↓
Action(调用工具)
    ↓
Observation(拿到结果)
    ↓
下一轮 Thought,直到证据足够给出结论

ReAct 模式的推进方式是每观察一次数据再决定下一步该怎么做。它适合排查类任务:门店为什么亏损、程序为什么有 BUG。这类任务事先不知道问题在哪,只能走一步看一步

以勇哥说餐饮的场景为例:深圳南山店这个月亏了,让 Agent 排查原因

# ReAct 模式:手写 Thought -> Action -> Observation 循环,演示 Agent 边想边做、看一步走一步
# 主题:勇哥说餐饮——排查"深圳南山店这个月为什么亏了",排查路径不确定,正适合 ReAct
from dotenv import load_dotenv
import os
from langchain.tools import tool
from langchain_openai import ChatOpenAI
from langchain_core.messages import SystemMessage, HumanMessage, ToolMessage

load_dotenv()

# 1. 定义四个排查工具(模拟数据,数据之间留了推理线索,等 Agent 自己串起来)
@tool
def get_month_revenue(store_name: str) -> str:
    """查询指定门店本月的营业额及环比变化。"""
    return f"{store_name}:本月营业额 21 万,环比下降 18%"

@tool
def get_cost_detail(store_name: str) -> str:
    """查询指定门店本月的各项成本明细。"""
    return f"{store_name}:房租 4.5 万、人工 5.2 万、食材 6.8 万、水电杂费 0.8 万"

@tool
def get_month_traffic(store_name: str) -> str:
    """查询指定门店本月的到店客流量及环比变化。"""
    return f"{store_name}:本月到店客流 6200 人次,环比下降 25%"

@tool
def get_nearby_change(store_name: str) -> str:
    """查询指定门店周边最近的经营环境变化。"""
    return f"{store_name}:300 米内新开了 2 家连锁快餐店,其中一家正在做开业大促"

tools = [get_month_revenue, get_cost_detail, get_month_traffic, get_nearby_change]
# 工具名 -> 函数 的映射表,方便按名字调用
tool_map = {
   t.name: t for t in tools}

# 2. System Prompt 是 ReAct 的灵魂
# 强制模型每一步先说思考(Thought)再调工具(Action),拿到观察(Observation)后继续判断
# 并且明确"不要一开始把所有工具都调一遍",这才是看一步走一步
SYSTEM_PROMPT = """你是餐饮老板勇哥的排查助手,任务是排查门店经营问题。

工作方式(ReAct 循环):
1. 每次调用工具前,必须先用一两句话说明这一步的思考:为什么查这个、想验证什么
2. 拿到工具结果后,根据结果决定下一步查什么,禁止一开始就把所有工具全调一遍
3. 证据足够时才给结论,结论必须说明依据了哪些数据

成本判断红线:房租不超营业额 15%,人工不超 20%,食材不超 30%,超标即有问题。"""

# 3. 初始化模型并绑定工具
llm = ChatOpenAI(
    model="qwen3.7-max-2026-06-08",
    api_key=os.getenv("API_KEY"),
    base_url=os.getenv("BASE_URL")
)
llm_with_tools = llm.bind_tools(tools)

def run_react_agent(user_input: str, max_steps: int = 8) -> str:
    """运行 ReAct 循环,打印 Thought/Action/Observation 轨迹,返回最终回答。

    Args:
        user_input: 用户输入的排查任务
        max_steps: 最大步数限制,防止 Agent 跑散后无限调工具

    Returns:
        模型生成的最终回答文本
    """
    messages = [
        SystemMessage(content=SYSTEM_PROMPT),
        HumanMessage(content=user_input),
    ]

    for step in range(1, max_steps + 1):
        # 第一步:模型思考当前缺什么信息,决定要不要调工具
        response = llm_with_tools.invoke(messages)
        messages.append(response)

        # 模型这一步的思考就是 Thought(content 为空说明模型没按规矩说思考,兜底提示一下)
        thought = response.content.strip() if response.content else "(模型未输出文字思考,直接行动)"
        print(f"\n[第 {step} 步 Thought] {thought}")

        # 没有 tool_calls 说明模型认为证据够了,给出最终结论,循环结束
        if not response.tool_calls:
            print(f"\n[最终回答]\n{response.content}")
            return response.content

        # 第二步:执行模型要求的工具(Action),把结果喂回去(Observation)
        for call in response.tool_calls:
            print(f"[Action] {call['name']}({call['args']})")
            result = tool_map[call["name"]].invoke(call["args"])
            print(f"[Observation] {result}")

            # ToolMessage 的 tool_call_id 要和模型给的对应上,模型才知道这是哪个工具的返回
            messages.append(ToolMessage(
                content=str(result),
                tool_call_id=call["id"]
            ))
        # 回到循环顶部:带着 Observation,让模型想下一步

    print("\n达到最大步数,强制结束。")
    return "达到最大步数,强制结束。"

# 4. 跑一个排查任务:亏钱原因事先不知道,只能查一步看一步
if __name__ == "__main__":
    run_react_agent("深圳南山店这个月亏了,帮我排查一下亏在哪。")

运行轨迹:

[第 1 步 Thought] 先查本月营业额,确认亏损幅度和环比变化
[Action] get_month_revenue(store_name='深圳南山店')
[Observation] 深圳南山店:本月营业额 21 万,环比下降 18%

[第 2 步 Thought] 营收降了,查成本明细,看是不是成本占比超标
[Action] get_cost_detail(store_name='深圳南山店')
[Observation] 房租 4.5 万、人工 5.2 万、食材 6.8 万、水电杂费 0.8 万

[第 3 步 Thought] 食材 6.8/21≈32% 超了 30% 红线,但营收本身也在降,查客流确认原因
[Action] get_month_traffic(store_name='深圳南山店')
[Observation] 本月到店客流 6200 人次,环比下降 25%

[第 4 步 Thought] 客流掉了四分之一,查周边环境有没有变化
[Action] get_nearby_change(store_name='深圳南山店')
[Observation] 300 米内新开了 2 家连锁快餐店,其中一家正在做开业大促

[最终回答] 亏损主因是客流下降 25% 导致营业额环比降 18%,周边新开竞对分流是直接原因;同时食材成本占比约 32% 超过 30% 红线,成本端也有问题……

任务推进顺序:查了营收才想查成本,算出占比超标才想查客流,看到客流下降才想查周边。每一步都由上一步的结果来决定。

React 模式的一些注意点:

1、必须有 max_steps 最长步数。ReAct 容易跑偏,忘掉原始目标、在失败结果里反复重试、信息足够了还继续查。没有步数上限,一旦跑偏就会持续消耗 token 。

2、System Prompt 里禁止一开始把所有工具全调一遍。不约束的话,模型经常第一轮就调完四个工具,逐步推理就失去意义了。

3、要求结论说明依据了哪些数据。这是防幻觉的措施,强制模型基于 Observation 分析。

二、Plan-and-Execute 模式:先拆分任务,再按 计划执行

Plan-and-Execute 把执行任务的过程分成两个部分:Planner 先把任务拆成步骤列表,Executor 按计划逐步执行,最后汇总结果。过程如下

用户目标
    ↓
Planner 拆成步骤列表(结构化输出,可解析)
    ↓
Executor 逐步执行,每步可调工具,记录每步结果
    ↓
带着全部步骤结果生成最终交付物

ReAct 是一边想一边做,Plan-and-Execute 是先规划后执行。Executor 执行任务时始终看得见完整计划,不容易分散模型注意力。

举一个分步调研的例子:勇哥要在深圳开一家 100 平米的新面馆,让 Agent 做开店前调研并输出简报。

# Plan-and-Execute 模式:先让 Planner 拆任务,再让 Executor 逐步执行,演示"先规划后执行"
# 主题:勇哥说餐饮——勇哥要在深圳开新面馆,让 Agent 先拆调研步骤,再逐步执行输出简报
from dotenv import load_dotenv
import os
from langchain.tools import tool
from langchain_openai import ChatOpenAI
from langchain_core.messages import SystemMessage, HumanMessage, ToolMessage
from pydantic import BaseModel, Field

load_dotenv()

llm = ChatOpenAI(
    model="qwen3.7-max-2026-06-08",
    api_key=os.getenv("API_KEY"),
    base_url=os.getenv("BASE_URL")
)

# 1. Planner 的输出结构:用结构化输出保证计划可解析、可逐条执行
# 没有结构化约束,模型可能把计划写成一大段散文,程序没法按步骤推进
class Plan(BaseModel):
    steps: list[str] = Field(description="按顺序执行的调研步骤,每步一句话,控制在 3 到 5 步")

# 2. 定义四个调研工具(模拟数据),供 Executor 执行步骤时调用
@tool
def search_market(city: str, category: str) -> str:
    """查询指定城市某品类餐饮的整体市场行情。"""
    return f"{city}{category}:市场偏饱和,客单价 25-35 元,新店半年存活率约 55%"

@tool
def estimate_startup_cost(city: str, category: str, area: int) -> str:
    """按城市、品类、面积测算开店启动成本。"""
    return f"{city} {area} 平米{category}:启动成本约 38 万(转让费 8 万 + 装修 15 万 + 设备 10 万 + 首批物料 5 万)"

@tool
def get_rent(district: str) -> str:
    """查询指定商圈的铺面租金水平。"""
    return f"{district}:临街铺面月租约 300 元/平米,100 平米门店月租约 3 万"

@tool
def check_competition(district: str, category: str) -> str:
    """查询指定商圈某品类的竞争情况。"""
    return f"{district}:500 米内已有 4 家同类门店,其中 2 家是连锁品牌,竞争激烈"

tools = [search_market, estimate_startup_cost, get_rent, check_competition]
tool_map = {
   t.name: t for t in tools}
# Executor 用带工具的模型;Planner 不带工具,纯思考拆任务,这就是两个角色的分工
llm_with_tools = llm.bind_tools(tools)

def make_plan(goal: str) -> list[str]:
    """调用 Planner 把总目标拆成有序步骤列表。

    Args:
        goal: 用户的总目标描述

    Returns:
        按顺序执行的步骤列表
    """
    planner_prompt = f"""你是餐饮调研团队的规划员(Planner),负责把老板的目标拆成可执行的调研步骤。
要求:步骤不超过 5 步,每步一句话,按逻辑排序,最后一步必须是"汇总以上信息输出简报"。

老板的目标:{goal}"""
    # with_structured_output 强制模型按 Plan 结构输出,返回值直接是 Plan 对象
    plan = llm.with_structured_output(Plan).invoke(planner_prompt)
    return plan.steps

def execute_step(goal: str, steps: list[str], step_index: int, done_results: list[str], max_iter: int = 4) -> str:
    """执行计划中的单个步骤,步骤内允许调用工具,返回该步骤的执行结果。

    Args:
        goal: 总目标,让 Executor 知道自己在为哪个目标服务
        steps: 完整计划步骤列表
        step_index: 当前要执行的步骤下标
        done_results: 已完成步骤的结果列表
        max_iter: 单步内最大工具调用轮数,防跑偏

    Returns:
        当前步骤的执行结果文本
    """
    # 把总目标、完整计划、已完成结果、当前步骤一起塞给 Executor
    # 这是 Plan-and-Execute 的关键:执行时始终看得见全局计划,不容易走散
    done_summary = "\n".join(f"步骤{i+1}结果:{r}" for i, r in enumerate(done_results)) or "(还没执行任何步骤)"
    system_prompt = f"""你是餐饮调研团队的执行员(Executor),只负责执行指定步骤,不要做整份简报。
可以调用工具获取数据,拿到数据后用两三句话总结本步骤结论。

总目标:{goal}
完整计划:
{chr(10).join(f"{i+1}. {s}" for i, s in enumerate(steps))}

已完成步骤结果:
{done_summary}

当前要执行的步骤:{steps[step_index]}"""

    messages = [SystemMessage(content=system_prompt)]
    for _ in range(max_iter):
        response = llm_with_tools.invoke(messages)
        messages.append(response)

        # 不再调工具,说明本步骤执行完毕
        if not response.tool_calls:
            return response.content

        # 执行工具并把结果喂回去
        for call in response.tool_calls:
            result = tool_map[call["name"]].invoke(call["args"])
            messages.append(ToolMessage(content=str(result), tool_call_id=call["id"]))

    return "本步骤执行超时,跳过。"

def run_plan_and_execute(goal: str) -> str:
    """跑完整 Plan-and-Execute 流程:规划 -> 逐步执行 -> 汇总简报。

    Args:
        goal: 用户的总目标描述

    Returns:
        最终生成的调研简报文本
    """
    # 第一步:Planner 拆任务
    steps = make_plan(goal)
    print("=== Planner 生成的计划 ===")
    for i, s in enumerate(steps):
        print(f"{i + 1}. {s}")

    # 第二步:Executor 逐步执行,记录每步结果
    done_results: list[str] = []
    print("\n=== Executor 开始逐步执行 ===")
    for i in range(len(steps) - 1):
        print(f"\n--- 执行步骤 {i + 1}/{len(steps)}:{steps[i]} ---")
        result = execute_step(goal, steps, i, done_results)
        done_results.append(result)
        print(f"步骤结果:{result}")

    # 第三步:带着全部步骤结果生成最终简报(最后一步是汇总)
    print(f"\n--- 执行步骤 {len(steps)}/{len(steps)}:{steps[-1]} ---")
    evidence = "\n".join(f"步骤{i+1}结果:{r}" for i, r in enumerate(done_results))
    final_prompt = f"""你是餐饮老板勇哥的调研助手,基于以下调研结果写一份开店简报。
要求:结论在前,数据在后,最后给出明确的"开/不开"建议和理由。语气直接,不绕弯子。

总目标:{goal}

调研结果:
{evidence}"""
    report = llm.invoke(final_prompt).content
    print(f"\n=== 最终简报 ===\n{report}")
    return report

# 3. 跑一个复杂多步任务:如果用 ReAct 边想边做,这种任务容易走散,先规划就稳很多
if __name__ == "__main__":
    run_plan_and_execute("勇哥计划在深圳开一家 100 平米的新面馆,帮我做开店前调研并输出简报。")

运行后先看到 Planner 拆出的计划,再逐步执行:

=== Planner 生成的计划 ===
1. 调研深圳面馆市场的整体行情和存活率
2. 测算 100 平米面馆的启动成本
3. 调研目标商圈的租金水平和竞争情况
4. 汇总以上信息输出简报

=== Executor 开始逐步执行 ===
--- 执行步骤 1/4:调研深圳面馆市场的整体行情和存活率 ---
[调用 search_market(city='深圳', category='面馆')]
步骤结果:深圳面馆市场偏饱和,客单价 25-35 元,新店半年存活率约 55%,进入门槛不低。

--- 执行步骤 2/4:测算 100 平米面馆的启动成本 ---
[调用 estimate_startup_cost(city='深圳', category='面馆', area=100)]
步骤结果:启动成本约 38 万,转让费是大头之一,回本压力不小。

=== 最终简报 ===
结论:不建议现在贸然入场。市场偏饱和、半年存活率仅 55%;启动成本 38 万;目标商圈已有 4 家同类门店……

Plan-and-Execute 模式的一些注意点:

1、计划用结构化输出。Plan 用 Pydantic 约束,返回值直接是步骤列表,程序才能 for 循环逐步推进

2、执行每一步时,把完整计划和已完成结果都放进上下文。Executor 始终知道当前步骤和总目标的关系,这是它比 ReAct 抗迷失的原因。

3、计划应该可调整。这个例子里计划是固定的,真实任务里经常出现查完发现假设不成立的情况。成熟的做法是把计划当成可更新的任务清单:每完成一步评估一次,假设不成立就重新规划后续步骤。

三、Reflection 模式:交付前按标准检查

Reflection 的做法是增加一个明确的检查环节:先产出结果,再对照验收标准检查,发现问题就修正,校验通过才交付。核心在验收标准上——有明确的标准,检查才有依据。过程如下:

Generator(生成器):产出初稿
    ↓
Reviewer(审查器):对照验收标准逐条检查,输出结构化的问题清单
    ↓
Revisor(修正器):按问题清单修正
    ↓
未通过就再来一轮,通过才交付

Reflection 模式适合有明确验收标准的任务:代码要能通过单测,JSON 要符合 Schema,文案要满足固定要求。

举一个文档生成的例子:让 Agent 给餐饮新人写一份开店前必读提醒。初稿生成时只给了笼统要求(语气亲切、不用列数字),审查环节对照勇哥的四条硬标准检查。这模拟的是实际开发里常见的问题:生成时的要求和验收时的标准不一致。

# Reflection 模式:生成 -> 按明确标准审查 -> 修正,演示交付前的检查环节
# 主题:勇哥说餐饮——给粉丝写"开店前必读提醒",初稿没对齐勇哥的标准,被审查环节拦下来修正
from dotenv import load_dotenv
import os
from langchain_openai import ChatOpenAI
from pydantic import BaseModel, Field

load_dotenv()

llm = ChatOpenAI(
    model="qwen3.7-max-2026-06-08",
    api_key=os.getenv("API_KEY"),
    base_url=os.getenv("BASE_URL")
)

# 1. 审查结果的输出结构:passed 决定是否放行,issues 是具体问题清单
# Reflection 的核心不是让模型说"我再检查一下",而是把检查结果变成程序可判断的结构
class ReviewResult(BaseModel):
    passed: bool = Field(description="初稿是否全部满足审查标准")
    issues: list[str] = Field(description="不满足标准的具体问题列表,全部满足时为空列表")

# 2. 勇哥的验收标准:检查必须有明确抓手,没有标准的反思就是自我安慰
REVIEW_STANDARDS = """1. 必须包含三条成本红线:房租不超营业额 15%、人工不超 20%、食材不超 30%
2. 必须包含一条明确的止损建议(连续亏损多久该怎么办)
3. 语气必须直接、不留情面,符合勇哥"刀子嘴"人设,不能出现"建议您酌情考虑"这类客服腔
4. 不得出现任何具体品牌名,避免拉踩惹麻烦"""

def generate_draft(task: str) -> str:
    """生成初稿。故意不对齐验收标准,模拟真实开发里"生成和验收两张皮"的场景。

    Args:
        task: 内容生成任务描述

    Returns:
        模型生成的初稿文本
    """
    # 注意:这里刻意只给了笼统要求(语气亲切、不用列数字)
    # 初稿大概率过不了审查,正好演示 Reflection 怎么把问题拦下来
    prompt = f"""请完成以下写作任务,300 字左右,语气亲切温和,读起来舒服即可,不用列具体数字。

任务:{task}"""
    return llm.invoke(prompt).content

def review_draft(task: str, draft: str) -> ReviewResult:
    """按验收标准审查初稿,返回结构化审查结果。

    Args:
        task: 原始写作任务
        draft: 待审查的初稿文本

    Returns:
        ReviewResult 对象:passed 是否通过,issues 问题列表
    """
    reviewer_prompt = f"""你是严格的审稿人,对照下面的验收标准逐条审查初稿,一条都不能放过。

写作任务:{task}

验收标准:
{REVIEW_STANDARDS}

待审查初稿:
{draft}"""
    # with_structured_output 保证审查结果可被程序判断,而不是一段没法用的自由文本
    return llm.with_structured_output(ReviewResult).invoke(reviewer_prompt)

def revise_draft(task: str, draft: str, issues: list[str]) -> str:
    """按审查发现的问题修正初稿。

    Args:
        task: 原始写作任务
        draft: 未通过审查的初稿文本
        issues: 审查环节发现的问题列表

    Returns:
        修正后的终稿文本
    """
    issue_list = "\n".join(f"- {issue}" for issue in issues)
    prompt = f"""你是文章修改助手,按照审查问题逐条修正下面的初稿,其他内容保持不变。

写作任务:{task}

验收标准:
{REVIEW_STANDARDS}

审查发现的问题:
{issue_list}

待修正初稿:
{draft}"""
    return llm.invoke(prompt).content

def run_reflection(task: str, max_rounds: int = 3) -> str:
    """跑完整 Reflection 流程:生成 -> 审查 -> 修正,未通过可多轮修正。

    Args:
        task: 内容生成任务描述
        max_rounds: 最大审查修正轮数,防止无限循环

    Returns:
        通过审查的终稿文本
    """
    # 第一步:生成初稿
    draft = generate_draft(task)
    print("=== 初稿(未对齐标准随手生成)===")
    print(draft)

    # 第二步:按标准审查,发现问题就修正,直到通过或达到轮数上限
    for round_no in range(1, max_rounds + 1):
        review = review_draft(task, draft)
        print(f"\n=== 第 {round_no} 轮审查 ===")
        print(f"是否通过:{'通过' if review.passed else '不通过'}")
        for issue in review.issues:
            print(f"发现问题:{issue}")

        # 全部标准满足,放行交付
        if review.passed:
            print("\n审查通过,交付终稿。")
            return draft

        # 第三步:按问题清单修正,进入下一轮审查
        draft = revise_draft(task, draft, review.issues)
        print(f"\n=== 修正后稿件(第 {round_no} 轮)===")
        print(draft)

    print("\n达到最大审查轮数,按当前版本交付。")
    return draft

# 3. 跑一次完整流程:初稿是"温和客服腔",审查按勇哥的四条硬标准把它拦下来
if __name__ == "__main__":
    run_reflection("给第一次开餐饮店的新人写一份《开店前必读提醒》")

运行效果:

=== 初稿(未对齐标准随手生成)===
亲爱的朋友,开店是一件令人期待的事情呢~建议您提前做好市场调研,
合理规划预算,保持良好的心态,相信用心经营一定会收获回报的哦……

=== 第 1 轮审查 ===
是否通过:不通过
发现问题:未包含三条成本红线数字(房租 15%、人工 20%、食材 30%)
发现问题:缺少明确的止损建议
发现问题:语气亲切温和,出现"建议您""呢~"等客服腔,不符合勇哥直接不留情面的人设

=== 修正后稿件(第 1 轮)===
开店前先记住三条红线:房租别超营业额 15%,人工别超 20%,食材别超 30%,
超了就是在给房东和员工打工。连续三个月亏损看不到拐点,趁早关店止损,
别死在"再坚持一下"……

=== 第 2 轮审查 ===
是否通过:通过

审查通过,交付终稿。

初稿是温和的客服腔,四条标准都不满足,被审查环节拦下,修正一轮后通过。如果没有校验环节,初稿就直接交付出去了。

Reflection 模式的一些注意点:

1、审查结果用结构化输出。ReviewResult 把是否通过变成程序能判断的布尔值,问题清单直接传给修正环节。审查器输出自由文本的话,循环逻辑写不起来。

2、修正时带上问题清单和原标准。

3、审查标准来自业务。这四条标准是根据餐饮行业规则定的,模型只负责执行检查,不能指望模型自己生成验收标准。

三种模式在实际项目里经常组合:Planner 拆任务保证不漏项,执行时用 ReAct 根据结果决定下一步,交付前用 Reflection 检查。

四、总结

  1. ReAct:Thought → Action → Observation 循环,边想边做,适合路径不确定的排查类任务,必须配步数限制。
  2. Plan-and-Execute:Planner 拆任务,Executor 逐步执行,适合复杂多步任务,计划要可调整。
  3. Reflection:生成、审查、修正三步,交付前按标准检查,验收标准要明确,审查结果要结构化。

三种模式都包含以下几个步骤: 工具调用 、结构化输出、循环控制。区别只是花心思的环节不同:ReAct 在每一步的决策上,Plan-and-Execute 在开工前的拆解上,Reflection 在交付前的检查上。

下一篇讲 Agent 记忆 机制。任务一长、对话一多,Agent 就开始丢失信息,所以它需要一套自己的记忆系统。

五、LuckReport 项目推荐

在这里插入图片描述

导航:LuckReport专栏

1、项目简介

Luck-Report 是一款基于开源项目 UReport2 重构的 Java 高性能报表引擎,通过迭代单元格可以实现任意复杂的中国式报表。相较于 UReport2,在技术架构上进行了全新升级,后端基于 SpringBoot 框架开发、前端采用 Vue 框架构建,技术选型贴合当下主流项目开发标准,可精准适配各类实际开发需求。

Luck-Report 提供了全新的基于网页的报表设计器,可以在 Chrome、Firefox、Edge 等各种主流浏览器运行(IE 浏览器除外)。使用 Luck-Report,打开浏览器即可完成各种复杂报表的设计制作。

Luck-Report 基于 Apache-2.0 开源协议 开源

2、在线体验

相关文章
人工智能 缓存 前端开发
11700 59
人工智能 JavaScript 开发工具
4677 17
Web App开发 人工智能 API
1179 1
开发工具 Swift git
1890 6
人工智能 Java BI
1303 1
人工智能 JavaScript 测试技术
2145 2
人工智能 JavaScript 测试技术
1097 4
缓存 JavaScript Shell
2055 3

热门文章

最新文章