【AI Agent系列】【MetaGPT】9. 一句话订阅专属信息 - 订阅智能体进阶,实现一个更通用的订阅智能体(2)

文章目录

    • 0. 前置推荐阅读和本文内容
      • 0.1 前置推荐阅读
      • 0.2 本文内容
    • 1. 修改一:直接用大模型获取网页信息,不用爬虫程序
      • 1.1 我们要给大模型什么内容
      • 1.2 提取网页文本信息
      • 1.3 组织Action
      • 1.4 完整代码及细节注释
      • 1.5 可能存在的问题及思考
    • 2. 修改二:解耦RunSubscription和SubscriptionRunner
      • 2.1 思路
      • 2.2 首先将 SubscriptionRunner 移出去
      • 2.3 打通SubRole和SubAction
      • 2.4 触发时间的传递
      • 2.5 完整代码及运行结果

0. 前置推荐阅读和本文内容

0.1 前置推荐阅读

  • 订阅智能体实战

    • 【AI的未来 - AI Agent系列】【MetaGPT】3. 实现一个订阅智能体,订阅消息并打通微信和邮件
    • 【AI Agent系列】【MetaGPT】8. 一句话订阅专属信息 - 订阅智能体进阶,实现一个更通用的订阅智能体
  • ActionNode基础与实战

    • 【AI的未来 - AI Agent系列】【MetaGPT】4. ActionNode从理论到实战

    • 【AI的未来 - AI Agent系列】【MetaGPT】4.1 细说我在ActionNode实战中踩的那些坑

0.2 本文内容

在上篇文章 【AI Agent系列】【MetaGPT】8. 一句话订阅专属信息 - 订阅智能体进阶,实现一个更通用的订阅智能体 中我们实现了一个更通用的订阅智能体,本文在此基础上作一些修改优化。

1. 修改一:直接用大模型获取网页信息,不用爬虫程序

在我们之前实现的通用订阅智能体中,从网页中提取信息的方法都是通过爬虫程序来进行的,那可不可以不用爬虫程序,而是直接借助大模型的能力去总结信息?答案是肯定的,不过存在一些其它问题需要我们来解决。下面是实现过程。

1.1 我们要给大模型什么内容

首先考虑下我们需要给大模型什么内容?

  • url : 需要大模型自己去访问url(调用插件等)
  • html内容
  • 网页中的文本内容

最容易想到的大概也就上面三种内容。给url的话还需要我们去让大模型调用相应的插件,有点复杂,本文暂不考虑。对于html内容,前面我们在利用大模型帮助我们写爬虫程序的时候已经见识到了,内容非常多,一是会严重干扰大模型生成爬虫程序的质量,二是非常容易导致token超限,所以直接用这种数据让大模型总结信息也是不合适也不太可能的。
在这里插入图片描述
那就剩下给大模型【网页中的文本内容】这一条路子了。联想下大模型对文本的总结能力和使用方法,就是给大模型一段文本,然后让大模型总结,是不是觉得这种方法非常可行?下面来看具体做法。

1.2 提取网页文本信息

(1)原来的代码分析

class SubAction(Action):
 	...... 省略 ......
	async def run(self, *args, **kwargs):
	    pages = await WebBrowserEngine().run(*urls)

通过WebBrowserEngine获取到了网页内容。打印出来看一下,大概长下图这样,这些内容都在返回结果pages.inner_text中。
在这里插入图片描述
(2)提取出纯文本信息。
对 pages.inner_text 进行处理,去掉里面的一些特殊符号。可以用下面的代码。

def get_linktext(html_content):
    flag = False
    if len(html_content) > 0:
        html_content = html2text.html2text(html_content)

    html_content = html_content.strip()
    if len(html_content) > 0:
        flag = True
    return flag, html_content

html2text 是一个 Python 库,用于将 HTML 格式的文本转换为纯文本格式。它特别适用于从网页抓取数据,并将这些数据从复杂的 HTML 格式转换为简单的纯文本格式。

来看下提取之后的效果:
在这里插入图片描述

(3)将提取到的文本和用户需求一起给大模型,让大模型总结内容

1.3 组织Action

好了,主要的修改我们已经做完了。下面就是将修改融入到我们之前的代码中。
来看一下我们现在有的元素:

  • [Role] SubscriptionAssistant

    • [Action] ParseSubRequirement : 解析用户需求
    • [Action] RunSubscription :创建并开启订阅智能体
  • [Role] SubRole : 订阅智能体

    • [Action] SubAction

就让SubscriptionAssistant的Action顺序执行就可以了。

class SubscriptionAssistant(Role):
    """Analyze user subscription requirements."""

    name: str = "同学小张的订阅助手"
    profile: str = "Subscription Assistant"
    goal: str = "analyze user subscription requirements to provide personalized subscription services."
    constraints: str = "utilize the same language as the User Requirement"

    def __init__(self, **kwargs) -> None:
        super().__init__(**kwargs)

        self._init_actions([ParseSubRequirement, RunSubscription]) ## 2. 先解析用户需求,然后运行订阅
        self._set_react_mode(react_mode="by_order") ## 按顺序执行

1.4 完整代码及细节注释

from metagpt.actions.action_node import ActionNode
from metagpt.actions.action import Action
import asyncio
from uuid import uuid4
import sys
import aiohttp

## 分析用户的要求语言
LANGUAGE = ActionNode(
    key="language",
    expected_type=str,
    instruction="Provide the language used in the project, typically matching the user's requirement language.",
    example="en_us",
)

## 分析用户的订阅推送时间
CRON_EXPRESSION = ActionNode(
    key="Cron Expression",
    expected_type=str,
    instruction="If the user requires scheduled triggering, please provide the corresponding 5-field cron expression. "
    "Otherwise, leave it blank.",
    example="",
)

## 分析用户订阅的网址URL,可以是列表
CRAWLER_URL_LIST = ActionNode(
    key="Crawler URL List",
    expected_type=list[str],
    instruction="List the URLs user want to crawl. Leave it blank if not provided in the User Requirement.",
    example=["https://example1.com", "https://example2.com"],
)

## 分析用户所需要的网站数据
PAGE_CONTENT_EXTRACTION = ActionNode(
    key="Page Content Extraction",
    expected_type=str,
    instruction="Specify the requirements and tips to extract from the crawled web pages based on User Requirement.",
    example="Retrieve the titles and content of articles published today.",
)

## 分析用户所需要的汇总数据的方式
CRAWL_POST_PROCESSING = ActionNode(
    key="Crawl Post Processing",
    expected_type=str,
    instruction="Specify the processing to be applied to the crawled content, such as summarizing today's news.",
    example="Generate a summary of today's news articles.",
)

## 补充说明,如果url或定时器解析为空,则提示用户补充
INFORMATION_SUPPLEMENT = ActionNode(
    key="Information Supplement",
    expected_type=str,
    instruction="If unable to obtain the Cron Expression, prompt the user to provide the time to receive subscription "
    "messages. If unable to obtain the URL List Crawler, prompt the user to provide the URLs they want to crawl. Keep it "
    "blank if everything is clear",
    example="",
)

NODES = [
    LANGUAGE,
    CRON_EXPRESSION,
    CRAWLER_URL_LIST,
    PAGE_CONTENT_EXTRACTION,
    CRAWL_POST_PROCESSING,
    INFORMATION_SUPPLEMENT,
]

PARSE_SUB_REQUIREMENTS_NODE = ActionNode.from_children("ParseSubscriptionReq", NODES)

## 解析用户的需求的Action
PARSE_SUB_REQUIREMENT_TEMPLATE = """
### User Requirement
{requirements}
"""

SUB_ACTION_TEMPLATE = """
## Requirements
Answer the question based on the provided context {process}. If the question cannot be answered, please summarize the context.

## context
{data}"
"""

class ParseSubRequirement(Action):
    async def run(self, requirements):
        requirements = "\n".join(i.content for i in requirements)
        context = PARSE_SUB_REQUIREMENT_TEMPLATE.format(requirements=requirements)
        node = await PARSE_SUB_REQUIREMENTS_NODE.fill(context=context, llm=self.llm)
        return node ## 3. 返回解析后的用户需求
    
# if __name__ == "__main__":
#     from metagpt.schema import Message
#     asyncio.run(ParseSubRequirement().run([Message(
#         "从36kr创投平台https://pitchhub.36kr.com/financing-flash 爬取所有初创企业融资的信息,获取标题,链接, 时间,总结今天的融资新闻,然后在晚上七点半送给我"
#     )]))

from metagpt.schema import Message
from metagpt.tools.web_browser_engine import WebBrowserEngine
import html2text
from pytz import BaseTzInfo
from typing import Optional
from aiocron import crontab
import os

class CronTrigger:
    def __init__(self, spec: str, tz: Optional[BaseTzInfo] = None) -> None:
        self.crontab = crontab(spec, tz=tz)

    def __aiter__(self):
        return self

    async def __anext__(self):
        await self.crontab.next()
        return Message()


class WxPusherClient:
    def __init__(self, token: Optional[str] = None, base_url: str = "http://wxpusher.zjiecode.com"):
        self.base_url = base_url
        self.token = token or os.environ["WXPUSHER_TOKEN"] # 5.1 从环境变量中获取token,所以你需要在环境变量中配置WXPUSHER_TOKEN或在配置文件中设置WXPUSHER_TOKEN

    async def send_message(
        self,
        content,
        summary: Optional[str] = None,
        content_type: int = 1,
        topic_ids: Optional[list[int]] = None,
        uids: Optional[list[int]] = None,
        verify: bool = False,
        url: Optional[str] = None,
    ):
        payload = {
            "appToken": self.token,
            "content": content,
            "summary": summary,
            "contentType": content_type,
            "topicIds": topic_ids or [],
            # 5.2 从环境变量中获取uids,所以你需要在环境变量中配置WXPUSHER_UIDS
            # uids是你想推送给哪个微信,必须是关注了你这个订阅号的微信才可以知道uid
            "uids": uids or os.environ["WXPUSHER_UIDS"].split(","), 
            "verifyPay": verify,
            "url": url,
        }
        url = f"{self.base_url}/api/send/message"
        return await self._request("POST", url, json=payload)

    async def _request(self, method, url, **kwargs):
        async with aiohttp.ClientSession() as session:
            async with session.request(method, url, **kwargs) as response:
                response.raise_for_status()
                return await response.json()

# 5.3 微信callback wrapper,使用WxPusherClient给指定微信推送消息
async def wxpusher_callback(msg: Message):
    client = WxPusherClient()
    await client.send_message(msg.content, content_type=3)

# 运行订阅智能体的Action
class RunSubscription(Action):
    async def run(self, msgs):
        from metagpt.roles.role import Role
        from metagpt.subscription import SubscriptionRunner

        req = msgs[-1].instruct_content.dict() ## 获取用户需求,注意这里msgs[-1],不是[-2]了,没有code了
        urls = req["Crawler URL List"]
        process = req["Crawl Post Processing"]
        spec = req["Cron Expression"]
        SubAction = self.create_sub_action_cls(urls, process) ## 创建一个Action,urls网页链接、process用户需求的数据
        SubRole = type("SubRole", (Role,), {}) ## 定时触发的Role
        role = SubRole()
        role.init_actions([SubAction])
        runner = SubscriptionRunner()

        callbacks = []
        callbacks.append(wxpusher_callback)

        async def callback(msg):
            print(msg)
            await asyncio.gather(*(call(msg) for call in callbacks)) # 遍历所有回调函数,触发回调,分发消息

        await runner.subscribe(role, CronTrigger(spec), callback)
        await runner.run()

    @staticmethod
    def create_sub_action_cls(urls: list[str], process: str):
        class SubAction(Action):
            
            @staticmethod
            def get_linktext(html_content): ## 提取出网页中的纯文本信息
                flag = False
                if len(html_content) > 0:
                    html_content = html2text.html2text(html_content)

                html_content = html_content.strip()
                if len(html_content) > 0:
                    flag = True
                return flag, html_content
            
            async def run(self, *args, **kwargs):
                pages = await WebBrowserEngine().run(*urls)
                flag, page_content = self.get_linktext(pages.inner_text) ## 这块可能有点bug,没有考虑多个url的情况

                return await self.llm.aask(SUB_ACTION_TEMPLATE.format(process=process, data=page_content))

        return SubAction

# 定义订阅助手角色
from metagpt.roles import Role
from metagpt.actions import UserRequirement
from metagpt.utils.common import any_to_str
class SubscriptionAssistant(Role):
    """Analyze user subscription requirements."""

    name: str = "同学小张的订阅助手"
    profile: str = "Subscription Assistant"
    goal: str = "analyze user subscription requirements to provide personalized subscription services."
    constraints: str = "utilize the same language as the User Requirement"

    def __init__(self, **kwargs) -> None:
        super().__init__(**kwargs)

        self._init_actions([ParseSubRequirement, RunSubscription]) ## 2. 先解析用户需求,然后运行订阅
        self._set_react_mode(react_mode="by_order") ## 按顺序执行
        

if __name__ == "__main__":
    import asyncio
    from metagpt.team import Team

    team = Team()
    team.hire([SubscriptionAssistant()]) ## 从SubscriptionAssistant开始run,这里只有一个角色,其实都不用再使用Team了
    team.run_project("从36kr创投平台https://pitchhub.36kr.com/financing-flash爬取所有初创企业融资的信息,获取标题,链接, 时间,总结今天的融资新闻,然后在10:49送给我")
    asyncio.run(team.run())


  • 运行结果

在这里插入图片描述

1.5 可能存在的问题及思考

(1)网页中文本内容仍然可能有token超限的可能

  • 思考:如果文本太多,可以考虑文本分块给大模型分别总结,然后最后再组合等方式。

(2)Prompt的好坏直接影响最终总结的结果的好坏

2. 修改二:解耦RunSubscription和SubscriptionRunner

目前,订阅智能体是通过RunSubscription运行的,即RunSubscription这个action,不仅创建了订阅智能体代码,并启动了SubscriptionRunner,这会让我们的RunSubscription一直无法退出,请尝试将二者分离,即从RunSubscription分离出AddSubscriptionTask的action,并且让SubscriptionRunner单独运行

2.1 思路

先看下RunSubscription中都做了什么:

  • create_sub_action_cls 创建了SubAction
  • 创建了 SubRole,并添加了 SubAction 作为自身的Action
  • 创建了 SubscriptionRunner ,依赖SubRole,并运行run
  • 添加了 callback

要将 RunSubscriptionSubscriptionRunner分离,需要将 SubscriptionRunner 移出去,而它依赖 SubRoleSubRole又依赖SubAction

一种思路:我们可以让 RunSubscription 只创建SubAction,只要想办法将SubAction传给SubRole,就打通了流程。简单画了个图:

在这里插入图片描述

2.2 首先将 SubscriptionRunner 移出去

我放到了main函数里。其依赖的SubRolecallback,也一并在这里创建了。

if __name__ == "__main__":
    ...... 省略 ......
    role = SubRole()
    runner = SubscriptionRunner()
    
    callbacks = []
    callbacks.append(wxpusher_callback)

    async def callback(msg):
        print(msg)
        await asyncio.gather(*(call(msg) for call in callbacks)) # 遍历所有回调函数,触发回调,分发消息

    async def mainloop():
        await runner.subscribe(role, CronTrigger(role.triggle_time), callback)
        await runner.run()
    asyncio.run(mainloop())

2.3 打通SubRole和SubAction

SubscriptionRunner已经独立run了,下面就是将SubAction加到SubRole里去执行。

这里我将SubRole作为一个参数传递到RunSubscription里,在RunSubscription创建完SubAction之后,通过一个set接口塞给SubRole

class SubRole(Role):
    triggle_time : str = None ## 触发时间
    
    def __init__(self, **kwargs) -> None:
        super().__init__(**kwargs)
    
    def set_actions(self, actions:list): ## 开放一个set接口,接收设置action
        self._init_actions(actions) ## 在这里给role设置actions

class RunSubscription(Action):
    subrole : SubRole = None ## 这里接收外部的SubRole实例,用来后面添加actions
    
    def __init__(self, subrole: SubRole) -> None:
        super().__init__()
        self.subrole = subrole
        
    async def run(self, msgs) -> Action:
        ...... 省略 ......
        subAction = self.create_sub_action_cls(urls, code, process) ## 创建一个Action,urls网页链接、code爬虫代码、process用户需求的数据
        self.subrole.set_actions([subAction]) ## 给SubRole设置一个Action,打通SubRole和SubAction
        self.subrole.triggle_time = spec ## 给SubRole设置一个触发时间
        print("Subscription started end.")
        return spec ## 这里需要返回一个字符串,任意的都行,但不能没有返回

class SubscriptionAssistant(Role):
    ...... 省略 ......

    def __init__(self, subrole:SubRole, **kwargs) -> None: ## 这里接收外部的SubRole实例
        super().__init__(**kwargs)

        self._init_actions([ParseSubRequirement, RunSubscription(subrole)]) ## 将接收的外部SubRole实例传给 RunSubscription

if __name__ == "__main__":
	role = SubRole()
	
	## team.hire([SubscriptionAssistant, CrawlerEngineer()]) ## 1. 从SubscriptionAssistant开始run
	team.hire([SubscriptionAssistant(role), CrawlerEngineer()]) ## 将SubRole实例传递进取

这样在 RunSubscription 创建了SubAction之后,我们的订阅智能体SubRole就有这个SubAction可以执行了。

2.4 触发时间的传递

可能你也发现了,将SubscriptionRunner移出来后,await runner.subscribe(role, CronTrigger(spec), callback)代码中的定时器的spec参数就无法获取到了。所以我也像SubAction传递一样,在SubRole中加了个参数:triggle_time : str = None ## 触发时间,用来记录触发时间。在使用时,直接用role.triggle_time即可。

await runner.subscribe(role, CronTrigger(role.triggle_time), callback)

2.5 完整代码及运行结果

代码修改就以上这么点,比较简单,就不再贴完整代码了。有需要的可以+v jasper_8017要源码,一起交流。

  • 运行结果

在这里插入图片描述
最终订阅的信息并没有想象中的好,只是个demo,要想做成产品,还有很长路要走。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/349035.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

实体识别与分类方法综述

目录 前言1 实体识别简介2 基于模板和规则的方法3 基于序列标注的方法3.1 常见序列标注模型3.2 模型参数估计和学习问题3.3 常见序列预测模型 4. 基于深度学习的实体识别方法5 基于预训练语言模型的实体识别5.1 BERT、GPT等预训练语言模型5.2 解码策略 6 特殊问题与挑战6.1 标签…

视频渲染靠cpu还是显卡 会声会影视频渲染的作用是什么

视频渲染最占用的资源就是CPU,多核心多线程,这样才能渲染快。渲染可以在时间线上实时平滑预览,便于编辑,最终导出成片的时候速度也会快一些,渲染就是对每桢的图像进行重新优化的过程。 渲染的作用主要是能够保证使用者…

C#使用RabbitMQ-2_详解工作队列模式

简介 🍀RabbitMQ中的工作队列模式是指将任务分配给多个消费者并行处理。在工作队列模式中,生产者将任务发送到RabbitMQ交换器,然后交换器将任务路由到一个或多个队列。消费者从队列中获取任务并进行处理。处理完成后,消费者可以向…

outlook如何群发邮件?外贸邮件群发教程?

outlook邮箱群发邮件方法?outlook怎么设置邮件群发? 如果你正在使用Outlook,那么你一定想要知道如何有效地群发邮件。Outlook作为微软办公套件的一部分,不仅功能强大,而且操作简便。下面,蜂邮EDM就来详细讲…

vscode开发FPGA(1)---TEROS_HDL插件报错

一、TerosHDL:modelsim(vlog-66)报错 Error: (vlog-66) Execution of vlib.exe failed 解决办法: 1.新建modelsim工程,并随意编译一个.v文件,将产生的work目录复制到modelsim安装路径下。 2.再将vscode设置verilog>linting>modelsim…

vue3 + jeecgBoot 获取项目IP地址

封装的useGlobSetting 函数 引入并使用 import { useGlobSetting } from //hooks/setting;const glob useGlobSetting();console.log(glob.uploadUrl) //http://192.168.105.57:7900/bs-axfd

docker的资源限制(cgroup)

前瞻 Docker 通过 Cgroup 来控制容器使用的资源配额,包括 CPU、内存、磁盘三大方面, 基本覆盖了常见的资源配额和使用量控制。 Cgroup 是 ControlGroups 的缩写,是 Linux 内核提供的一种可以限制、记录、隔离进程组所使用的物理资源(如 CPU、…

uni-app 接口封装,token过期,自动获取最新的token

一、文件路径截图 2、新建一个文件app.js let hosthttp://172.16.192.40:8083/jeecg-boot/ //本地接口 let myApi {login: ${host}wx/wxUser/login, //登录 } module.exports myApi 3、新建一个文件request.js import myApi from /utils/app.js; export const r…

【云原生】Docker的镜像创建

目录 1.基于现有镜像创建 (1)首先启动一个镜像,在容器里做修改 ​编辑(2)然后将修改后的容器提交为新的镜像,需要使用该容器的 ID 号创建新镜像 实验 2.基于本地模板创建 3&am…

uniapp上传音频文件到服务器

视频教程地址: 【uniapp录音上传组件,将录音上传到django服务器】 https://www.bilibili.com/video/BV1wi4y1p7FL/?share_sourcecopy_web&vd_sourcee66c0e33402a09ca7ae1f0ed3d5ecf7c uniapp 录制音频文件上传到django服务器保存到服务器 &#xf…

svn和git的本质区别是什么

参考: https://blog.csdn.net/feiying0canglang/article/details/126550676 上边图中,跨越了区的箭头,它中间的区数据都会同步。例如:git checkout ,它是将本地仓库数据更新到暂存区和工作区的。\ 理解 gitlab和svn都…

【深度优先搜索】【C++算法】834 树中距离之和

作者推荐 【动态规划】【map】【C算法】1289. 下降路径最小和 II 本文涉及知识点 深度优先搜索 树 图论 LeetCode834 树中距离之和 给定一个无向、连通的树。树中有 n 个标记为 0…n-1 的节点以及 n-1 条边 。 给定整数 n 和数组 edges , edges[i] [ai, bi]表…

【AIGC】Diffusers:训练扩散模型

前言 无条件图像生成是扩散模型的一种流行应用,它生成的图像看起来像用于训练的数据集中的图像。通常,通过在特定数据集上微调预训练模型来获得最佳结果。你可以在HUB找到很多这样的模型,但如果你找不到你喜欢的模型,你可以随时训…

vue常用指令(v-for)

一、v-for 指令 作用: 根据数据生成列表结构 二、代码演示 1、在li标签中获取数组元素 代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-wid…

2024年 复习 HTML5+CSS3+移动web 笔记 之CSS遍

第一天第二天第三天 1.1 引入方式 1.2 选择器 1.3 画盒子 1.4 文字控制 1.5 综合案例 一 新闻详情 2.1 复合选择器 2.2 伪类选择器 2.3 CSS 特性 2.4 Emmet 写法 2.5 背景属性 2.6 显示模式 2.6 综合案例 一 热词 &#xff08;设计稿&#xff1f;&#xff09; 2.7 综合案例 一…

金蝶云星空-表单插件,点击事件(一)

表单插件&#xff0c;点击事件 BarItemClick、AfterBarItemClick 有时候我们在不通的场景中使用到自己的企业的逻辑思维 业务场景&#xff1a;采购订单上&#xff0c;增加一个按钮tbCeShi&#xff0c;添加下面的插件&#xff0c;弹出一个对话框&#xff1b; 添加一个按钮&am…

前端怎么监听手机键盘是否弹起

摘要&#xff1a; 开发移动端中&#xff0c;经常会遇到一些交互需要通过判断手机键盘是否被唤起来做的&#xff0c;说到判断手机键盘弹起和收起&#xff0c;应该都知道&#xff0c;安卓和ios判断手机键盘是否弹起的写法是有所不同的&#xff0c;下面讨论总结一下两端的区别以及…

前端学习生产环境、开发环境、测试环境

1、路径 定义是什么环境 NODE_ENVdevelopment 开发环境 2、.env 端口号 3、.env.development 开发环境 4、.env.production 生产环境 5、.env.test 测试环境 6、如何访问&#xff0c;通过process.env进行访问 学习中.......

SqlAlchemy使用教程(六) -- ORM 表间关系的定义与CRUD操作

SqlAlchemy使用教程(一) 原理与环境搭建SqlAlchemy使用教程(二) 入门示例及编程步骤SqlAlchemy使用教程(三) CoreAPI访问与操作数据库详解SqlAlchemy使用教程(四) MetaData 与 SQL Express Language 的使用SqlAlchemy使用教程(五) ORM API 编程入门 本章内容&#xff0c;稍微有…

SpringMVC-对静态资源的访问

1.工程中加入静态资源 在webapp下创建static文件夹&#xff0c;此文件夹专门放入静态资源 2.使项目可以处理静态资源的请求 在SpringMVC配置文件中添加以下语句 1.引入命名空间 xmlns:mvc"http://www.springframework.org/schema/mvc" xsi:schemaLocation“http…
最新文章