基于 Langchain + 开源模型的前端内容创作Agent详细设计方案

基于 Langchain + 开源模型的前端内容创作Agent详细设计方案

基于 Langchain 框架与开源模型,结合此前的全流程创作需求,以下设计一款 模块化、可复用、全链路自动化 的前端内容创作 Agent。设计核心围绕「Langchain 组件解耦 + 开源模型适配 + 全流程闭环」,从架构分层、模块实现、技术选型到部署落地,提供详细可落地的方案。
基于 Langchain + 开源模型的前端内容创作Agent详细设计方案

一、Agent 核心定位与目标

定位

一款 前端技术领域专属的全流程内容创作自动化Agent,基于用户输入的核心关键字,通过 Langchain 实现流程编排、工具调用与记忆管理,结合开源大模型完成「关键字拓展→创作Plan→逻辑框架→博客撰写→ZEEKLOG发布」的端到端闭环,无需人工干预各环节衔接。

核心目标

1. 技术适配:兼容主流开源大模型(Llama 3、Mistral、Qwen等),支持本地/私有部署,避免依赖闭源API;

2. 流程自动化:按固定逻辑链执行全步骤,支持用户中途干预(如修改Plan、框架),兼顾自动化与灵活性;

3. 内容专业性:输出符合前端技术博客规范的内容(含代码片段、逻辑流程图),适配ZEEKLOG平台格式;

4. 可扩展性:支持新增功能模块(如图片生成、代码调试、多平台发布),模块间低耦合。

二、整体架构设计(分层架构)

Agent 采用「核心层→功能模块层→工具层→交互层」的四层架构,基于 Langchain 实现流程串联与状态管理,各层职责清晰、可独立替换。
graph TD
    A[交互层] --> B[核心层(Langchain)]
    B --> C[功能模块层]
    C --> D[工具层]
    D --> E[外部依赖(开源模型/ZEEKLOG接口)]
    
    subgraph 交互层
        A1[用户输入(核心关键字)]
        A2[中间结果确认(Plan/框架)]
        A3[最终输出(博客内容+发布包)]
    end
    
    subgraph 核心层(Langchain)
        B1[记忆模块(ConversationBufferMemory)]
        B2[流程编排(SequentialChain+AgentExecutor)]
        B3[输出解析(PydanticOutputParser)]
        B4[异常处理(RetryChain)]
    end
    
    subgraph 功能模块层
        C1[关键字处理模块]
        C2[创作Plan生成模块]
        C3[逻辑框架搭建模块]
        C4[技术博客撰写模块]
        C5[ZEEKLOG发布适配模块]
    end
    
    subgraph 工具层
        D1[开源模型调用工具(Langchain-HuggingFace)]
        D2[代码格式化工具(Prettier API)]
        D3[ZEEKLOG发布工具(API封装)]
        D4[Mermaid流程图生成工具]
    end
    
    subgraph 外部依赖
        E1[开源大模型(Llama 3/Mistral/Qwen)]
        E2[ZEEKLOG Open API]
        E3[Python/Node.js运行环境]
    end
三、核心技术选型
技术领域 选型方案 核心作用 
框架核心 Langchain (Python/Node.js) 流程编排、工具调用、记忆管理、输出解析 
开源模型 主力:Llama 3-70B(深度任务)轻量:Mistral-7B(快速任务) 自然语言生成、技术内容创作、关键字拓展 
模型部署 Ollama(本地部署)/ vLLM(高性能部署) 提供模型推理服务,兼容Langchain调用 
代码处理 Prettier API + Pygments 代码片段格式化、语法高亮(适配ZEEKLOG) 
接口封装 Requests(Python)/ Axios(Node.js) 封装ZEEKLOG发布API,实现自动化提交 
结构化输出 Pydantic + Langchain OutputParser 强制模型按固定格式输出(如框架结构、Plan字段) 
记忆存储 本地文件(JSON) / Redis 存储用户输入、中间结果(Plan/框架),支持流程回溯 

四、详细模块设计(含Langchain实现)

(一)核心层:Langchain 核心组件配置

核心层是Agent的「大脑」,负责串联所有功能模块、管理流程状态与记忆,关键组件配置如下:

1. 记忆模块(ConversationBufferMemory)

• 作用:存储全流程上下文,包括用户输入的核心关键字、确认后的Plan、框架结构,确保后续模块(如博客撰写)基于统一上下文创作,避免信息断层。

• 存储字段:
memory = ConversationBufferMemory(
    memory_key="chat_history",
    return_messages=True,
    output_key="final_blog"  # 最终输出关联
)
# 存储结构示例(JSON)
{
    "core_keyword": "前端Agent+内容创作",
    "confirmed_plan": {...},  # 用户确认后的创作Plan
    "confirmed_framework": {...},  # 用户确认后的逻辑框架
    "intermediate_steps": ["关键字拓展完成", "Plan已确认"]  # 流程状态
}
2. 流程编排(SequentialChain + AgentExecutor)

采用「线性流程+分支决策」的混合编排模式,核心流程通过 SequentialChain 串联,关键节点(如Plan确认、框架确认)通过 AgentExecutor 实现用户交互决策。
# 1. 子链定义:各功能模块对应的Langchain Chain
keyword_chain = LLMChain(llm=llm, prompt=keyword_prompt, output_key="expanded_keywords")
plan_chain = LLMChain(llm=llm, prompt=plan_prompt, output_key="creation_plan")
framework_chain = LLMChain(llm=llm, prompt=framework_prompt, output_key="blog_framework")
writing_chain = LLMChain(llm=llm, prompt=writing_prompt, output_key="blog_content")
ZEEKLOG_chain = LLMChain(llm=llm, prompt=ZEEKLOG_prompt, output_key="ZEEKLOG_publish_package")

# 2. 主流程链:串联子链,依赖记忆模块
main_chain = SequentialChain(
    chains=[keyword_chain, plan_chain, framework_chain, writing_chain, ZEEKLOG_chain],
    input_variables=["core_keyword"],
    output_variables=["final_blog", "ZEEKLOG_publish_package"],
    memory=memory,
    verbose=True  # 打印流程日志
)

# 3. 决策Agent:处理用户确认环节
confirmation_agent = initialize_agent(
    tools=[confirm_tool],  # 自定义确认工具(接收用户Y/N输入)
    llm=llm,
    agent=AgentType.CONVERSATIONAL_REACT_DESCRIPTION,
    memory=memory,
    verbose=True
)
3. 输出解析(PydanticOutputParser)

为避免开源模型输出格式混乱,每个模块均采用 PydanticOutputParser 强制结构化输出,定义明确的字段约束。

示例:创作Plan的结构化解析器
from pydantic import BaseModel, Field
from langchain.output_parsers import PydanticOutputParser

# 定义Plan的字段结构
class CreationPlan(BaseModel):
    blog_type: str = Field(description="技术博客类型:原理解析/实战教程/踩坑总结/技术前瞻")
    target_audience: str = Field(description="目标受众:入门前端/进阶开发者/可视化从业者等")
    core_highlights: list[str] = Field(description="3-5个核心亮点,如'手把手实现前端Agent'")
    word_count: int = Field(description="预计字数,1500-5000字")
    technical_forms: list[str] = Field(description="技术呈现形式,如代码示例/mermaid流程图")

# 初始化解析器
plan_parser = PydanticOutputParser(pydantic_object=CreationPlan)

# 对应的Prompt模板(包含格式说明)
plan_prompt = PromptTemplate(
    template="基于关键字{expanded_keywords},生成前端技术博客创作Plan,严格按以下格式输出:\n{format_instructions}\n",
    input_variables=["expanded_keywords"],
    partial_variables={"format_instructions": plan_parser.get_format_instructions()}
)
4. 异常处理(RetryChain + 自定义异常工具)

• 模型生成失败:通过 RetryChain 自动重试(最多3次),每次重试优化Prompt(如增加「严格按前端技术规范」提示);

• 接口调用失败(如ZEEKLOG发布):封装异常处理工具,记录失败原因并提示用户手动发布,同时输出可复制的发布包;

• 内容不符合要求:允许用户输入「修改框架」「调整亮点」等指令,Agent通过记忆模块回溯至对应步骤重新执行。

(二)功能模块层:全流程功能实现

模块1:关键字处理模块

• 功能目标:将用户输入的核心关键字,拓展为前端领域相关、层级化的技术词表,确保创作方向精准。

• 输入:用户核心关键字(如「前端Agent+内容创作」)

• 输出:层级化关联词表(主关键字→核心分支→细分知识点)

• 技术实现:

1. 基于Llama 3-7B,通过PromptTemplate定义拓展规则(限定前端领域、覆盖原理/实战/工具等维度);

2. 用PydanticOutputParser定义词表结构,避免杂乱无章;

3. 示例Prompt:
你是前端技术专家,基于核心关键字{core_keyword},按以下规则拓展技术词表:
1. 拓展5-8个关联技术点,必须贴合前端栈(Vue/React/AI+前端/可视化等);
2. 按「主关键字→核心分支→细分知识点」的层级排列;
3. 细分知识点需具体可创作(如「前端Agent的工具调用逻辑」而非「前端Agent原理」);
严格按格式输出:{format_instructions}
4. 输出示例:
{
    "main_keyword": "前端Agent+内容创作",
    "core_branches": [
        {
            "branch": "前端Agent核心原理",
            "sub_points": ["工具调用逻辑", "上下文管理机制", "开源模型适配方案"]
        },
        {
            "branch": "内容创作全流程自动化",
            "sub_points": ["关键字语义拓展", "技术博客框架生成", "ZEEKLOG格式适配"]
        }
    ]
}
模块2:创作Plan生成模块

• 功能目标:基于拓展后的词表,制定明确、可执行的创作计划,避免创作偏离方向。

• 输入:层级化词表

• 输出:结构化创作Plan(含博客类型、受众、亮点、字数、呈现形式)

• 技术实现:

1. 复用上述 CreationPlan 结构化解析器;

2. 结合前端技术博客的传播特点,Prompt中加入「适配ZEEKLOG读者习惯」「突出实战性」等约束;

3. 支持用户干预:生成Plan后,Agent通过 confirmation_agent 询问用户是否确认,接收「修改受众为进阶开发者」等指令后重新生成。

模块3:逻辑框架搭建模块

• 功能目标:输出符合技术博客规范的结构化框架,明确各章节核心内容,为撰写提供骨架。

• 输入:确认后的创作Plan

• 输出:含章节、子标题、核心内容提示的框架(适配ZEEKLOG排版)

• 技术实现:

1. 定义框架结构解析器(Pydantic),包含标题备选、章节列表、每个章节的核心内容;

2. 强制包含「引言→核心技术→实战步骤→总结展望」四大核心模块,深度博客新增「常见问题」「参考文献」;

3. 示例框架输出:
{
    "title_candidates": [
        "前端Agent实战:从零实现内容创作全流程自动化(含ZEEKLOG发布)",
        "基于Langchain+Llama 3的前端内容创作Agent:原理+落地"
    ],
    "chapters": [
        {
            "chapter": "引言",
            "subheadings": ["前端Agent发展现状", "内容创作痛点", "本文核心价值"],
            "core_content": "说明前端Agent在技术创作中的应用场景,本文将实现从关键字到ZEEKLOG发布的闭环"
        },
        {
            "chapter": "核心技术基础",
            "subheadings": ["Langchain流程编排", "Llama 3模型适配", "ZEEKLOG API调用规范"],
            "core_content": "讲解Agent依赖的核心技术,为后续实战铺垫"
        }
    ]
}
模块4:技术博客撰写模块

• 功能目标:基于框架生成完整博客正文,含规范代码片段、流程图描述,适配ZEEKLOG阅读习惯。

• 输入:确认后的逻辑框架、记忆中的词表与Plan

• 输出:Markdown格式的博客正文(含代码高亮、流程图占位符)

• 技术实现:

1. 模型选择:深度内容(如原理解析)用Llama 3-70B,快速创作用Mistral-7B;

2. 代码处理:通过Langchain的 Tool 封装Prettier API,生成代码片段后自动格式化,标注语言类型(如JavaScript/Vue);

3. 流程图处理:在需要流程图的章节,生成Mermaid代码,并标注「此处插入流程图」提示,用户可直接复制到ZEEKLOG渲染;

4. 语言约束:Prompt中加入「避免AI腔、技术术语解释清晰、步骤化描述实战内容」等要求;

5. 示例代码片段输出:
### 2.2 Llama 3模型适配Langchain
以下是通过Langchain调用本地Ollama部署的Llama 3模型的核心代码:
```python
from langchain.llms import Ollama
from langchain.prompts import PromptTemplate

# 初始化Llama 3模型(本地Ollama部署)
llm = Ollama(
    model="llama3:70b",  # 模型名称
    temperature=0.3,  # 降低随机性,确保技术准确性
    base_url="http://localhost:11434"  # Ollama默认地址
)

# 测试模型输出
prompt = PromptTemplate(
    template="解释前端Agent的核心工作流程,不超过200字",
    input_variables=[]
)
response = llm.invoke(prompt.format())
print(response)
模块5:ZEEKLOG发布适配模块

• 功能目标:将博客正文转化为ZEEKLOG发布所需的完整数据包,支持自动化发布或手动复制发布。

• 输入:完整博客正文、创作Plan

• 输出:ZEEKLOG发布数据包(标题、摘要、分类、标签、格式化正文、自动化发布参数)

• 技术实现:

1. 标题优化:基于SEO规则,在标题中融入核心关键字(如「前端Agent+内容创作」),增加「实战」「教程」等流量词;

2. 标签生成:结合拓展词表与ZEEKLOG热门标签(如「Langchain」「前端开发」「开源模型」),生成5-8个标签;

3. 自动化发布:封装ZEEKLOG Open API(需用户提供Cookie/Token鉴权),用Langchain的 Tool 类实现发布函数:
class ZEEKLOGPublishTool(BaseTool):
    name = "ZEEKLOG_publish_tool"
    description = "用于自动化发布博客到ZEEKLOG"

    def _run(self, title: str, content: str, category: str, tags: list[str], summary: str):
        # ZEEKLOG发布API请求逻辑
        url = "https://mp.ZEEKLOG.net/mdeditor/publish"
        headers = {
            "Cookie": "用户的ZEEKLOG Cookie",
            "User-Agent": "Mozilla/5.0..."
        }
        data = {
            "title": title,
            "content": content,
            "category": category,
            "tags": ",".join(tags),
            "summary": summary,
            "status": 1  # 1=发布,0=草稿
        }
        response = requests.post(url, headers=headers, data=data)
        if response.status_code == 200:
            return f"发布成功!博客地址:{response.json()['data']['url']}"
        else:
            raise Exception(f"发布失败:{response.text}")

    def _arun(self, *args, **kwargs):
        raise NotImplementedError("不支持异步调用")
4. 手动发布备选:若API调用失败或用户不愿提供鉴权信息,输出「可直接复制的发布包」,包含格式化后的正文、标题、标签等,用户粘贴到ZEEKLOG编辑器即可。

(三)工具层:外部工具封装

所有外部依赖(模型、API、代码工具)均通过 Langchain 的 Tool 类封装,与功能模块解耦,方便替换:

1. 开源模型工具:封装Ollama/vLLM的调用接口,支持切换模型、调整temperature参数;

2. 代码格式化工具:封装Prettier API,支持JavaScript/TypeScript/Vue等前端语言;

3. Mermaid工具:封装Mermaid渲染逻辑,生成符合ZEEKLOG规范的流程图代码;

4. ZEEKLOG发布工具:如上述示例,封装发布API,支持鉴权、参数校验。

(四)交互层:用户交互设计

1. 输入方式:支持命令行输入、API调用、简单Web界面(可选),核心输入为「前端核心关键字」;

2. 中间确认:关键节点(Plan生成后、框架生成后)自动询问用户是否确认,支持自然语言修改指令(如「把Plan的字数调整为3000字」);

3. 输出形式:

◦ 中间输出:Plan(JSON格式)、框架(Markdown格式);

◦ 最终输出:完整博客正文(Markdown)、ZEEKLOG发布数据包(含自动化发布结果/手动复制包);

4. 异常反馈:若某步骤失败(如模型调用超时),明确提示失败原因,并提供备选方案(如「模型调用失败,请检查Ollama服务是否启动,或切换为Mistral-7B模型」)。

五、工作流执行逻辑(端到端流程)
flowchart TD
    S[用户输入核心关键字] --> A[关键字处理模块]
    A --> A1[生成层级化词表]
    A1 --> B[创作Plan生成模块]
    B --> B1[生成结构化Plan]
    B1 --> C{用户确认Plan?}
    C -- 否 --> B[重新生成Plan]
    C -- 是 --> D[逻辑框架搭建模块]
    D --> D1[生成结构化框架]
    D1 --> E{用户确认框架?}
    E -- 否 --> D[重新生成框架]
    E -- 是 --> F[博客撰写模块]
    F --> F1[生成Markdown正文+格式化代码]
    F1 --> G[ZEEKLOG发布适配模块]
    G --> G1[生成ZEEKLOG发布数据包]
    G1 --> H{用户选择发布方式?}
    H -- 自动化发布 --> H1[调用ZEEKLOG发布工具,返回结果]
    H -- 手动发布 --> H2[输出可复制的发布包]
    H1 --> I[输出最终结果(博客地址/发布包)]
    H2 --> I
六、部署与测试方案

(一)部署环境要求
环境 配置要求 说明 
硬件 CPU:≥8核内存:≥32GB(部署Llama 3-70B需≥64GB)GPU:≥16GB VRAM(可选,加速模型推理) 本地部署推荐用Ollama,自动优化硬件资源 
软件 操作系统:Windows/Linux/macOS运行环境:Python 3.10+/Node.js 18+依赖包:langchain==0.2.0、ollama==0.1.26、requests、pydantic、prettier 用pip/npm安装依赖:pip install langchain ollama requests pydantic 
模型部署 方式1:Ollama(简单部署)命令:ollama pull llama3:70b(拉取模型)方式2:vLLM(高性能部署)命令:python -m vllm.entrypoints.openai.api_server --model meta-llama/Meta-Llama-3-70B-Instruct 确保模型服务地址可被Langchain访问(默认本地地址) 

(二)测试用例

1. 基础功能测试

• 输入关键字:「Vue 3+Composition API+表单校验」

• 预期输出:

◦ 词表:包含「Composition API核心原理」「表单校验实战」「VeeValidate适配」等关联点;

◦ Plan:博客类型为「实战教程」,受众为「进阶前端开发者」,字数2000字;

◦ 框架:包含「引言→Composition API基础→表单校验实现步骤→常见问题→总结」;

◦ 博客正文:含Vue 3代码示例(格式化),步骤化描述表单校验实现;

◦ ZEEKLOG发布包:标题含关键字,标签为「Vue 3」「Composition API」「表单校验」等。

2. 异常处理测试

• 测试场景1:模型服务未启动,调用模型时失败;

• 预期结果:Agent提示「模型调用失败,请检查Ollama服务是否启动」,并提供重试按钮;

• 测试场景2:ZEEKLOG Cookie失效,自动化发布失败;

• 预期结果:Agent提示「发布失败,Cookie可能已失效」,自动切换为输出手动发布包。

3. 扩展性测试

• 测试场景:新增「图片生成工具」(调用开源模型Stable Diffusion生成技术示意图);

• 预期结果:在框架的「核心技术架构」章节,自动生成图片描述并调用图片工具,输出图片链接,嵌入博客正文。

七、扩展方案(可选功能)

1. 多模型切换:在核心层增加「模型路由」,根据任务复杂度自动选择模型(轻量任务用Mistral-7B,深度任务用Llama 3-70B);

2. 多平台发布:新增掘金、知乎专栏等平台的API封装,支持一键多平台发布;

3. 图片生成:集成Stable Diffusion开源模型,自动为博客生成技术架构图、流程图;

4. 代码调试:封装前端代码运行环境(如StackBlitz API),在博客中嵌入可交互的代码示例;

5. 个性化配置:允许用户预设博客风格(如「简洁风」「详细注释风」)、代码高亮主题(适配ZEEKLOG)。
该方案已实现「从关键字到ZEEKLOG发布」的全链路闭环,基于Langchain的组件化设计确保了各模块可独立替换(如替换开源模型、新增工具),开源模型部署方式满足私有化需求。

若需进一步落地,可优先完成:1)部署Ollama与目标开源模型;2)按方案中的代码示例实现核心Chain与Tool;3)测试关键字拓展→Plan生成→框架搭建的基础流程。如需某模块的详细代码(如ZEEKLOG API封装、模型调用适配),或想调整Agent的功能侧重(如强化代码实战性、简化流程),可随时告知我补充优化。

Read more

Web基石:Java Servlet 全面指南:从基础原理到 Spring Boot 实战

Web基石:Java Servlet 全面指南:从基础原理到 Spring Boot 实战

这是一份非常详细、实用、通俗易懂、权威且全面的 Java Servlet 指南,涵盖了其方方面面,包括在 Spring Boot 中的应用,并提供了可直接在 IDE 中运行的最佳实践代码和完整案例。 目录 1. Servlet 概述 * 1.1 什么是 Servlet? * 1.2 为什么需要 Servlet? * 1.3 Servlet 与 CGI 的比较 * 1.4 Servlet 在 Web 应用中的位置 2. Servlet API 核心 * 2.1 javax.servlet 包 (jakarta.servlet)

使用Open WebUI下载的模型文件(Model)默认存放在哪里?

使用Open WebUI下载的模型文件(Model)默认存放在哪里?

🏡作者主页:点击!  🤖Ollama部署LLM专栏:点击! ⏰️创作时间:2025年2月21日21点21分 🀄️文章质量:95分 文章目录 使用CMD安装存放位置 默认存放路径 Open WebUI下载存放位置 默认存放路径 扩展知识 关于 Ollama 核心价值 服务 关于Open WebUI 核心特点 主要功能 使用场景 Open WebUI下载存放位置 在使用Ollama平台进行深度学习和机器学习模型训练时,了解模型文件的存储位置至关重要。这不仅有助于有效地管理和部署模型,还能确保在需要时能够快速访问和更新这些模型文件。本文将详细探讨Ollama下载的模型文件存放在哪里,并提供相关的操作指南和最佳实践 最后感谢大家 希望这篇文章能帮助你! 使用CMD安装存放位置 以下做测试 我们采用哦llama38B模型来测试 输入命令等待安装即可 默认存放路径 C:\Users\Smqnz\.ollama\models\manifests\registry.ollama.ai 不要直接复制粘贴 我的用户名和你的不一样

PowerShell中Invoke-WebRequest的正确使用:避免参数匹配错误

1. 从一次报错说起:为什么我的curl命令在PowerShell里不灵了? 那天我正在调试一个本地API接口,很自然地就在PowerShell里敲下了 curl -X POST http://127.0.0.1:8199/api/post。这命令在Linux的Bash终端里我用了无数次,闭着眼睛都能敲对。结果,PowerShell毫不留情地甩给我一个红字报错:Invoke-WebRequest : 找不到与参数名称“X”匹配的参数。 我当时就愣住了,心想:“-X POST”这不是curl的标准写法吗?怎么到你这儿就不认了?相信很多从Linux/macOS转战Windows,或者刚开始接触PowerShell的朋友,都踩过这个坑。这个错误看似简单,背后却藏着PowerShell设计哲学和命令别名的“小心思”。简单来说,在PowerShell里,curl 并不是你熟悉的那个cURL工具,而是 Invoke-WebRequest 这个PowerShell原生Cmdlet的一个别名。这就好比你在北京叫“师傅”可能是在打招呼,在别的地方可能就是在称呼真正的老师傅,语境完全不同。Invoke-

【Spring 全家桶】Spring MVC 快速入门,开始web 更好上手(下篇) , 万字解析, 建议收藏 ! ! !

【Spring 全家桶】Spring MVC 快速入门,开始web 更好上手(下篇) , 万字解析, 建议收藏 ! ! !

本篇会加入个人的所谓鱼式疯言 ❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言 而是理解过并总结出来通俗易懂的大白话, 小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的. 🤭🤭🤭可能说的不是那么严谨.但小编初心是能让更多人能接受我们这个概念 !!! 引言 Spring MVC 犹如一座桥梁,连接着前端的精彩与后端的强大,它赋予开发者以灵动之笔,在数字化的画布上描绘出绚丽多彩的 Web 世界。在 Spring MVC 的引领下,我们能够驾驭复杂的业务逻辑,实现流畅的用户体验,让技术与创意完美融合,开启无限可能的 Web 开发之旅。 目录 1. 返回响应内容 2. lombok 3. 加法器 一. 返回响应内容 在上篇中,我们学习了如何使用控制层的处理请求相关, 现在我们学习如何处理返回响应内容。 1. 设置状态码 importjakarta.servlet.http.HttpServletResponse;importorg.springframework.stereotype.Controller;importorg.