鸿蒙专家 发表于 2026-9-1 16:00:00

鸿蒙RN桥接小艺智能体:TurboModule实践与避坑

跨境电商商品运营每天要处理标题优化、五点描述、广告占比、退款率这些事,其实鸿蒙6.0的Agent Framework Kit已经提供了拉起小艺智能体的能力。但我们的App是React Native(RNOH)架构,怎么在RN侧调用这套Kit?只能走TurboModule桥接。我们花了一下午才把链路跑通,中间踩了不少坑,特别是FunctionComponent不能直接在TurboModule里用,最终用startAbility启动一个专门页面来承载。这篇文章把完整的桥接实现和避坑点整理出来。

先看整体架构。小艺智能体模块分三层:JS侧定义类型和API,通过TurboModuleRegistry注册;C++侧用ARK_ASYNC_METHOD_METADATA宏做方法名映射;ArkTS侧调用@kit.AgentFrameworkKit的FunctionController,最终拉起小艺。

前置条件必须确认:设备要鸿蒙6.0(API 20)以上且真机,不支持模拟器;必须联网并登录华为账号;智能体要在小艺开放平台创建并关联应用包名;功能仅中国境内可用。当时我们在模拟器上折腾,isAgentSupport一直返回false,后来查文档才知道不支持模拟器。

JS Spec层用TypeScript定义参数和返回值。这里建议把类型写完全,让JS侧拿到类型提示。注意模块未注册时返回null,调用方要判空。


// NativeXiaoyiAgentModule.ts
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';

export type AgentScene =
| 'listing_optimization'
| 'stock_risk_analysis'
| 'bullet_points_generation'
| 'ad_cost_analysis'
| 'refund_rate_analysis';

export type XiaoyiAgentParams = {
agentId?: string;
scene: AgentScene;
sku: string;
title: string;
category: string;
price?: number;
grossMargin?: number;
stock?: number;
adRatio?: number;
refundRate?: number;
sellingPoints?: string[];
prompt?: string;
};

export type XiaoyiAgentResult = {
success: boolean;
code: string;
message: string;
requestId?: string;
};

export interface Spec extends TurboModule {
isAgentSupported(agentId: string): Promise<boolean>;
openAgent(params: XiaoyiAgentParams): Promise<XiaoyiAgentResult>;
}

export default TurboModuleRegistry.get<Spec>('XiaoyiAgentModule') as Spec | null;


C++侧注册方法映射时,参数个数必须和JS侧一致,否则会夯住。


// XiaoyiAgentModule.cpp
XiaoyiAgentModule::XiaoyiAgentModule(const ArkTSTurboModule::Context ctx, const std::string name)
: ArkTSTurboModule(ctx, name) {
methodMap_ = {
    ARK_ASYNC_METHOD_METADATA(isAgentSupported, 1),
    ARK_ASYNC_METHOD_METADATA(openAgent, 1),
};
}


ArkTS核心实现是桥接的重头。isAgentSupported用FunctionController.isAgentSupport,需要传UIAbilityContext。openAgent里先做参数校验,然后构建完整prompt,最后用startAbility启动承载页,把prompt放进Want参数。注意FunctionController实例要懒加载,并在__onDestroy__里释放。


// XiaoyiAgentModule.ets
import { FunctionController } from '@kit.AgentFrameworkKit';
import { Want } from '@kit.AbilityKit';

export class XiaoyiAgentModule extends UITurboModule {
static readonly NAME = 'XiaoyiAgentModule';
private functionController: FunctionController | null = null;

async isAgentSupported(agentId: string): Promise<boolean> {
    try {
      const controller = this.getOrCreateController();
      const supported = await controller.isAgentSupport(
      this.ctx.uiAbilityContext, agentId
      );
      return supported;
    } catch (err) {
      return false;
    }
}

async openAgent(params: XiaoyiAgentParams): Promise<XiaoyiAgentResult> {
    if (!params || !params.sku || !params.title) {
      return { success: false, code: 'INVALID_PARAMS', message: '缺少必要参数' };
    }
    const fullPrompt = this.buildFullPrompt(params);
    const want: Want = {
      bundleName: this.ctx.uiAbilityContext.abilityInfo.bundleName,
      abilityName: 'XiaoyiAgentAbility',
      parameters: {
      agentId: params.agentId || DEFAULT_AGENT_ID,
      queryText: fullPrompt,
      scene: params.scene,
      },
    };
    await this.ctx.uiAbilityContext.startAbility(want);
    return { success: true, code: 'AGENT_ABILITY_STARTED' };
}
}


为什么不能直接在TurboModule里用FunctionComponent?因为FunctionComponent是ArkUI的@Compoent组件,只能在ArkUI页面里渲染,而TurboModule运行在JS线程或Native线程,跨线程调用直接报类型错误。所以必须创建一个专门的Ability来承载:


// XiaoyiAgentAbility.ets
import { FunctionComponent, FunctionController } from '@kit.AgentFrameworkKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { Want } from '@kit.AbilityKit';

@Entry
@Component
struct XiaoyiAgentAbility {
private controller: FunctionController = new FunctionController();
@State agentId: string = '';
@State queryText: string = '';

aboutToAppear(want: Want) {
    this.agentId = want.parameters?.agentId as string;
    this.queryText = want.parameters?.queryText as string;
}

build() {
    Column() {
      FunctionComponent({
      agentId: this.agentId,
      onError: (err: BusinessError) => {
          console.error(`Agent error: ${err.code}, ${err.message}`);
      },
      options: {
          queryText: this.queryText,
      },
      controller: this.controller,
      })
    }
    .width('100%')
    .height('100%');
}
}


这个承载页必须在module.json5里注册,否则startAbility会报-1错误。


{
"module": {
    "abilities": [
      {
      "name": "XiaoyiAgentAbility",
      "srcEntry": "./ets/turbomodule/XiaoyiAgentAbility.ets",
      "description": "小艺智能体承载页",
      "launchType": "singleton",
      "visible": true,
      "skills": [
          {
            "actions": ["action.system.home"],
            "entities": ["entity.system.home"]
          }
      ]
      }
    ]
}
}


prompt的构建直接影响小艺的理解效果。一开始我们直接序列化JSON,小艺解析很吃力。后来参考开放平台文档,改用自然语言分段格式,而且中文字段的标点要用中文冒号,解析更稳定。


private buildFullPrompt(params: XiaoyiAgentParams): string {
const contextBlock = [
    `SKU:${params.sku}`,
    `标题:${params.title}`,
    `类目:${params.category}`,
    `售价:$${params.price ?? '未知'}`,
    `毛利率:${Math.round(params.grossMargin * 100)}%`,
    `库存:${params.stock ?? '未知'}`,
    `广告占比:${Math.round(params.adRatio * 100)}%`,
    `退款率:${Math.round(params.refundRate * 100)}%`,
    `卖点:${params.sellingPoints?.join(';') || '暂无'}`,
].join('\n');
return `请帮我处理以下跨境电商商品运营任务:\n\n${contextBlock}\n\n任务:${params.prompt}`;
}


我们设计了五个运营场景:listing_optimization、bullet_points_generation、stock_risk_analysis、ad_cost_analysis、refund_rate_analysis。每个场景有对应的prompt模板,例如listing_optimization要求生成英文标题并说明理由,bullet_points_generation要求输出5条英文Bullet Points。调用openAgent时,把场景prompt传给小艺即可。

注册链路也需要三处配合:ArkTS侧在GeneratedPackage里注册XiaoyiAgentModule,C++侧在PackageProvider里push Package,C++源文件要加进CMakeLists。


// GeneratedPackage.ets
,



// PackageProvider.cpp
#include "XiaoyiAgentPackage.h"
packages.push_back(std::make_shared<XiaoyiAgentPackage>(ctx));



# CMakeLists.txt
"./turbomodule/XiaoyiAgentModule.cpp"


再总结几个容易踩的坑。

坑一:FunctionComponent不能直接用在TurboModule里,这是线程模型决定的。错误做法是在openAgent里直接渲染FunctionComponent,正确做法是startAbility到承载页。

坑二:isAgentSupport需要UIAbilityContext。如果context传错,它永远返回false。UITurboModuleContext里已暴露uiAbilityContext,直接用。

坑三:智能体必须在小艺开放平台创建并关联应用包名。只填agentId但不关联应用,拉起一定会失败。

坑四:isAgentSupport失败时不要抛异常。有些场景会catch到异常导致JS侧Promise无法resolve,页面卡死。正确做法是try/catch后返回false,让JS侧做降级提示。

坑五:prompt格式别用JSON,小艺理解能力有限。用自然语言分段,每个字段用中文冒号,效果最好。

最后,isAgentSupport检测是前置步骤,返回true才拉起,false就提示用户登录华为账号或检查网络。整个桥接链路的关键就是理解你的JS方法最终要落到哪个ArkTS能力上,以及哪些组件只能在ArkUI页面里用。希望这份实践能帮到有同样需求的团队。

热心网友7 发表于 2026-9-1 19:00:00

Re: 鸿蒙RN桥接小艺智能体:TurboModule实践与避坑

这个实战分享太及时了,我们团队也正准备在RNOH项目里接小艺智能体,正好卡在TurboModule桥接这块。有几个细节想请教: 1. `startAbility` 启动的承载页,页面内部是怎么拿到 `prompt` 并拉起小艺的?是直接在 `onCreate` 里调 `FunctionController`,还是也要走一层异步初始化? 2. 避坑点里提到 `FunctionComponent` 不能直接在 TurboModule 里用,这个我们一开始完全没想到。请问最终用 `startAbility` 启动承载页面时,承载页本身是用什么写的?ArkUI 的 `@Component` 还是也绕开了函数组件?如果承载页需要被复用,会不会有页面栈管理的问题? 3. 目前看 `openAgent` 是传了 `prompt` 和参数,那返回结果里 `requestId` 是同步拿到的吗?还是说是小艺那边异步回调才有的?如果是异步,RN侧怎么监听结果? 4. `isAgentSupported` 在模拟器上返回 false 这个坑我们也遇到了,后来只能真机测。想确认下是不是必须登录华为账号,还是只要设备网络正常+应用关联好了就行? 5. 最后问下性能方面:每次 `openAgent` 都新建 `FunctionController` 实例吗?看代码是懒加载单例,那 `__onDestroy__

热心网友6 发表于 2026-9-1 19:05:00

Re: 鸿蒙RN桥接小艺智能体:TurboModule实践与避坑

鸿蒙这套桥接方案很实用,尤其对RNOH项目来说。我们团队之前也想接小艺智能体,但一直没理清TurboModule那层调用链,看了这篇文章路径清晰多了。 几个点想再确认下:承载页面的UI是不是还得自己用ArkUI写一套?另外FunctionController懒加载在应用前后台切换时会不会有释放重建的问题?我们在实际场景里对这种生命周期比较敏感。 另外想问下isAgentSupport在模拟器上返回false这个坑,文档里会不会明确标注,还是说只能靠设备实测?这个信息对前期技术调研挺关键的。 整体来说干货密度很高,不过如果能把startAbility那部分跳转参数和返回值的处理再展开讲讲就更好了,我们正好要处理智能体回来之后的场景结果回传。

热心网友3 发表于 2026-9-1 19:10:00

Re: 鸿蒙RN桥接小艺智能体:TurboModule实践与避坑

感谢分享,这篇太实用了!我们最近也在折腾RNOH调系统能力,TurboModule链路确实一堆坑。你提到FunctionComponent不能直接在TurboModule里用,最后用startAbility起承载页解决——这个思路很关键,等于把ArkTS侧的UI组件隔离出去了,绕开了生命周期和上下文的问题。另外模拟器不支持这个坑我们也踩过,isAgentSupport一直false查了半天文档才发现,真机一跑就通了,确认前置条件真的能省不少时间。 还想请教一下,你在ArkTS里构建的fullPrompt具体是怎么传给承载页的?是直接塞进Want参数里,还是传了sku和title让承载页自己拼?如果prompt特别长,Want参数有没有大小限制?后续会不会考虑直接用FunctionController的某种方式传结构化数据?期待你把后半部分也补完,特别是避坑总结那部分。
页: [1]
查看完整版本: 鸿蒙RN桥接小艺智能体:TurboModule实践与避坑