<cite id="ffb66"></cite><cite id="ffb66"><track id="ffb66"></track></cite>
      <legend id="ffb66"><li id="ffb66"></li></legend>
      色婷婷久,激情色播,久久久无码专区,亚洲中文字幕av,国产成人A片,av无码免费,精品久久国产,99视频精品3
      網易首頁 > 網易號 > 正文 申請入駐

      軟件工程原則在多智能體系統中的應用:分層與解耦

      0
      分享至

      ChatGPT 發布之后,AI 智能體的概念就一直牽動著整個行業的想象力。它描繪的場景很誘人:給 AI 系統一個目標,讓它自行拆解問題、調用工具、收集信息,最終綜合出結果。

      圍繞這個概念的框架生態已經相當擁擠了:LangChain、CrewAI、AutoGen、Semantic Kernel、Agent Framework……新框架層出不窮,個個聲稱能簡化智能應用的構建。但大多數還停留在 hello world 級別:一個智能體回答問題,頂多再調一兩個工具。

      構建一個多智能體系統,核心挑戰不在于讓智能體跑起來,因為任何框架都能做到,而在于如何讓系統可維護、可測試、可擴展。本文圍繞一個實際項目(多智能體協作從 YouTube 視頻中提取、摘要和整理信息),探討智能體系統的架構設計。涉及的關鍵問題包括:為什么智能體系統跟其他復雜應用一樣需要分層架構,工具(LLM 接口)和服務(業務邏輯)的分離為何是智能體設計的核心洞見,領域驅動設計的概念如何自然映射到智能體架構,以及編排器模式下四個專業化智能體如何協調工作。

      這個項目基于 Microsoft Agent Framework 構建,這是 Semantic Kernel 和 AutoGen 的繼任者,融合了兩者的優勢。不過具體框架不是重點,后面討論的原則無論用哪個框架都適用。



      架構挑戰

      框架們都擅長幫你快速搭出 demo,但沒有一個在引導你走向可維護、可擴展的架構。比如說各種示例代碼中LLM 調用、工具集成、業務邏輯和編排之間的邊界模糊得一塌糊涂。關注點分離這個概念在軟件工程里存在幾十年了,但在智能體領域,框架們集體選擇了"快速上手"而非架構指導。教程優化的是"看多簡單!"而不是"看多可維護!"

      下面是一個典型的單體寫法的簡化版本,把所有東西混在一起:

      # orchestrator.py - 智能體、工具、提示詞和業務邏輯全部在一起
      def run_research(query: str) -> str:
      # 搜索智能體,工具定義在行內
      def search_youtube(q: str) -> str:
      response = requests.get(f"https://youtube.com/results?q={q}")
      return parse_html_for_videos(response.text)
      search_agent = ChatAgent(
      name="SearchAgent",
      instructions="""You search YouTube. Use search_youtube to find videos.
      Return video IDs and titles as JSON.""",
      tools=[search_youtube]
      )
      # 字幕智能體,有自己的行內工具
      def get_transcript(video_id: str) -> str:
      transcript = YouTubeTranscriptApi.get_transcript(video_id)
      return " ".join([t["text"] for t in transcript])
      transcript_agent = ChatAgent(
      name="TranscriptAgent",
      instructions="Fetch transcripts using get_transcript tool.",
      tools=[get_transcript]
      )
      # 摘要智能體,提示工程嵌入其中
      summarize_agent = ChatAgent(
      name="SummarizeAgent",
      instructions=f"""Summarize cooking content. Focus on:
      - Temperatures and timing
      - Key techniques
      - Pro tips
      Format as markdown."""
      )
      # 編排邏輯與智能體調用交織在一起
      client = AzureOpenAI(api_key=os.environ["KEY"], ...)
      videos = search_agent.run(query, client=client)
      transcripts = []
      for vid in parse_json(videos)[:3]:
      text = transcript_agent.run(f"Get transcript for {vid['id']}", client=client)
      transcripts.append(text)
      summary = summarize_agent.run(f"Summarize:\n{transcripts}", client=client)
      Path(f"./outputs/{query}.md").write_text(summary)
      return summary

      上面代碼拿來做 demo 沒問題,快速驗證想法也完全合適。但問題是如果你要繼續修改呢?

      為什么這是一個架構問題

      LLM 調用工具其實是兩件事:用簡單參數(字符串、數字)調用一個函數,然后解釋返回的字符串結果。

      但實際干活的部分:搜索 YouTube、解析 HTML、處理錯誤要復雜得多。涉及配置、錯誤處理、重試,返回的是帶多個字段的結構化對象。

      這兩件事是不同的關注點,LLM 要的是簡單字符串,應用要的是合理的抽象。把它們攪在一起就像把 SQL 查詢直接寫在視圖層:能跑,但架構上是錯的。

      分離這兩個職責,可測試性、可復用性、代碼清晰度全都跟著出來了。

      如何分離?

      工具 = LLM 接口

      工具是 LLM 和應用之間的薄適配層。接受簡單參數(字符串、數字、布爾值),調用對應的服務,把結果格式化成 LLM 能理解的字符串。無狀態。

      # tools/youtube.py
      async def fetch_video_transcript(
      video_id: Annotated[str, Field(description="YouTube video ID")]
      ) -> str:
      """Fetch the transcript for a YouTube video.
      Returns the full transcript text with video metadata.
      """
      result = await fetch_transcript(video_id) # calls service
      ## Format for LLM
      return f"Transcript for '{result.metadata.title}':\n\n{result.transcript.full_text}"

      工具沒有做的事:沒有配置管理,沒有復雜返回類型,沒有業務邏輯。它只干一件事:調用服務、格式化結果。純粹的適配。

      服務 = 業務邏輯

      服務才是真正實現所在。它們是帶配置的可復用類,返回豐富的領域對象(模型),可以從 CLI、測試、其他服務任何地方調用,可能維護狀態或連接。

      # services/youtube.py
      class YouTubeTranscriptFetcher:
      """Fetches transcripts from YouTube videos."""
      def __init__(self, proxy_url: str | None = None):
      self.proxy_url = proxy_url
      async def fetch(
      self,
      video_id: str,
      languages: list[str] | None = None
      ) -> TranscriptResult:
      """Fetch transcript with full metadata.
      Returns a TranscriptResult containing the transcript text,
      video metadata, and language information.
      """
      # Real implementation with error handling, retries, etc.
      raw_transcript = await self._fetch_from_api(video_id, languages)
      metadata = await self._fetch_metadata(video_id)
      return TranscriptResult(
      metadata=metadata,
      transcript=Transcript(
      full_text=self._format_transcript(raw_transcript),
      segments=raw_transcript,
      language=self._detect_language(raw_transcript),
      ),
      )

      復雜性就該待在這里。配置、緩存、錯誤處理、重試、類型化返回,這些全歸服務管。脫離 LLM,服務照樣能用。

      流程

      LLM 決定獲取字幕時的調用鏈:

      LLM decides to call "fetch_video_transcript"

      tools/youtube.py::fetch_video_transcript(video_id)

      services/youtube.py::YouTubeTranscriptFetcher.fetch(video_id)

      Returns TranscriptResult object

      Tool formats as string for LLM

      為什么這很重要

      先說可復用性。服務可以直接從 CLI、測試腳本、批處理任何入口調用,完全繞過 LLM:

      # 從 CLI 使用,完全繞過智能體
      @click.command()
      def download_transcript(video_id: str, output: str):
      fetcher = YouTubeTranscriptFetcher()
      result = fetcher.fetch(video_id)
      Path(output).write_text(result.transcript.full_text)
      # 在測試中使用,無需模擬 LLM
      def test_fetcher_handles_unavailable_videos():
      fetcher = YouTubeTranscriptFetcher()
      with pytest.raises(TranscriptDisabledError):
      fetcher.fetch("video_with_disabled_transcript")
      # 在批處理中使用
      async def process_videos(video_ids: list[str]):
      fetcher = YouTubeTranscriptFetcher()
      results = await asyncio.gather(*[fetcher.fetch(id) for id in video_ids])
      return results

      再說可測試性。服務返回類型化對象,斷言寫起來干脆利落。工具返回格式化字符串,驗證起來就費勁多了:

      # 測試服務 - 清晰的斷言
      def test_fetcher_returns_transcript():
      result = fetcher.fetch("abc123")
      assert result.transcript.full_text
      assert result.metadata.video_id == "abc123"
      assert result.transcript.language in ["en", "en-US"]
      # 測試工具 - 需要字符串解析
      def test_tool_formats_correctly():
      output = fetch_video_transcript("abc123")
      assert "## " in output # Has title?
      assert "Transcript" in output # Has section header?
      # Much harder to validate structure

      然后是關注點分離。工具代碼管"怎么呈現給 LLM",服務代碼管"怎么真正干活"。YouTube API 改了?只動 services/youtube.py。想換輸出格式?只改工具就可以了。

      分層架構

      工具和服務的分離只是一條邊界。完整的智能體系統需要更多結構。經過反復實驗,最終落地了一個六層架構,每層一個明確的職責。熟悉領域驅動設計的話,應該會覺得眼熟:



      實際代碼中是這樣的:

      # presentation/cli.py - 表示層
      @click.command()
      def search(query: str):
      """Search for videos on YouTube."""
      agent = create_search_agent()
      result = agent.run(query)
      click.echo(result)
      # agents/search.py - 智能體層(僅配置)
      def create_search_agent() -> ChatAgent:
      """Factory function that creates a Search Agent."""
      return ChatAgent(
      chat_client=get_chat_client(),
      name="SearchAgent",
      instructions=SEARCH_AGENT_INSTRUCTIONS,
      tools=[search_youtube_formatted],
      )
      # tools/youtube.py - 工具層(薄 LLM 適配器)
      async def search_youtube_formatted(query: str) -> str:
      """Search YouTube for videos matching the query."""
      results = await search_youtube(query) # calls service
      return format_for_llm(results) # formats for LLM
      # services/youtube.py - 服務層(業務邏輯)
      async def search_youtube(query: str) -> list[VideoResult]:
      """Search YouTube - returns rich domain objects."""
      url = build_search_url(query)
      html = await fetch_html(url) # calls infra
      return parse_video_results(html)
      # models/youtube.py - 模型層(領域對象)
      @dataclass
      class VideoResult:
      video_id: str
      title: str
      channel: str
      # infra/http_client.py - 基礎設施層(HTTP 傳輸)
      async def fetch_html(url: str, timeout: float = 10.0) -> str:
      """Fetch HTML content with browser-like headers."""
      async with httpx.AsyncClient() as client:
      response = await client.get(url, headers=DEFAULT_HEADERS, timeout=timeout)
      response.raise_for_status()
      return response.text

      每層各司其職:智能體配置行為,工具做 LLM 適配,服務實現邏輯,模型定義結構。測試也更直接了:在層邊界 mock,不深入內部。

      DDD 的映射不是硬湊的,它自然浮現,因為智能體系統跟其他復雜應用面對的是同樣一組關注點:



      tools/ 層作為防腐層這個對應關系特別精準。在 DDD 里,防腐層保護領域模型不被外部系統的概念入侵。這里也一樣——它隔離了 LLM 的接口需求,在"LLM 能推理的字符串"和"代碼使用的豐富領域對象"之間做翻譯。

      調用流程嚴格向下。智能體用工具,工具調服務,服務操作模型。這個約束逼著你想清楚每段代碼該放在哪。

      何時需要這種架構

      對簡單項目來說是不是過度設計?算是,但有幾種情況下值得從一開始就這么做:要上生產、在用 AI 編碼助手(GitHub Copilot、Claude Code 這類工具在結構清晰的代碼上表現好得多)、多人協作、需要正經測試、領域本身復雜(多個外部 API、復雜業務邏輯、豐富數據模型),或者預期會持續擴展。

      智能體系統里的"混亂"都是漸進發生的。一開始圖快用內聯工具,后來要復用一個,再后來要測試某個東西,再后來要加錯誤處理。每改一次,代碼就糾纏一分。

      AI 編碼助手時代的架構

      還有一個越來越重要的維度:結構清晰的代碼跟 AI 編碼助手配合得更好。

      GitHub Copilot、Cursor、Claude Code 這些工具已經成了開發工作流的標配。一個很明顯的規律是,面對結構良好的代碼,它們的表現遠勝于面對全新項目或糾纏的代碼庫。配上文檔提供上下文的話效果更好。

      比如讓 Claude Code "實現按最短時長過濾搜索結果的功能",它會精準地找到 services/youtube.py。服務層邊界清晰、接口有類型、模式一致。AI 不需要理解整個系統就能推理出該怎么改。

      如果工具定義散在編排代碼里,AI 就得先搞清楚工具在哪定義、跟智能體怎么耦合、改了會不會影響其他地方、依賴關系怎么走。

      讓代碼對人類可維護的那些架構原則,同時也讓代碼對 AI 助手可導航。清晰的邊界讓 AI 能聚焦單一層而不用理解全棧。一致的模式讓 AI 學會之后可以一致地應用。類型提示不只是文檔,它們是 AI 生成正確代碼的約束。單一職責讓 AI 改一個服務時不用推理多個關注點。

      這不是為了"對 AI 友好"而犧牲設計,而是真正讓代碼對 AI 系統可理解的東西。

      AI 編碼助手越普及,架構紀律就越有價值。從 AI 輔助中獲益最多的永遠是本來就結構良好的代碼庫。混亂的代碼庫只會繼續混亂,因為 AI 會放大已有的模式——不管好壞。

      測試

      分層架構帶來的一個自然好處是可測試性。層間邊界清晰,測試策略就跟著直截了當。

      遵循的原則:在系統邊界 mock,不在內部 mock。

      ┌─────────────────────────────────────────────┐
      │ agents/ → tools/ → services/ │ ← Test with REAL code
      └─────────────────────────────────────────────┘

      ┌─────────────────┐
      │ External APIs │ ← MOCK here
      │ - YouTube API │
      │ - Azure OpenAI │
      └─────────────────┘

      不要 mock 自己的服務。測試 TranscriptSummarizer 時,注入 mock 的 OpenAI 客戶端,但讓服務本身的邏輯真實執行。測試存儲時,用臨時目錄,但跑真正的文件 I/O。

      這樣拿到的是更高的信心(走的是真實代碼路徑),更少脆弱的測試(少維護 mock),還能捕獲純單元測試漏掉的集成 bug。

      領域驅動的組織方式

      有了分層結構,下一個問題是:每個層內部怎么組織代碼?拿 services/ 包舉例,同樣的思路適用于所有層,不過不同層可能會得出不同結論。

      這個地方 DDD 的限界上下文概念直接適用。

      兩個選項:

      選項 A 按功能拆分:

      services/
      ├── search.py # YouTube search
      ├── transcript.py # Transcript fetching
      ├── summarizer.py # AI summarization
      └── storage.py # Persistence

      選項 B 按限界上下文:

      services/
      ├── youtube.py # Search + transcripts (same context)
      ├── summarizer.py # AI summarization
      └── storage.py # Persistence

      選了 B。

      限界上下文

      在領域驅動設計中,限界上下文是一個術語具有一致含義的邊界。"YouTube"就是一個限界上下文——"video_id"指 YouTube 視頻 ID,"channel"指 YouTube 頻道,"transcript"指 YouTube 字幕。

      搜索和字幕獲取共享同一個 API 面、同一組領域概念(視頻、頻道)、同一類錯誤條件(速率限制、視頻不可用)。放在一起可以獲得內聚性(調試字幕問題不用翻多個文件)、可替換性(加 Vimeo 支持?建一個 services/vimeo.py 實現同樣接口,其余系統不用動)、可發現性("YouTube 邏輯在哪?"答案是 services/youtube.py,就這么簡單),以及 AI 可理解性——一致的領域語言讓 AI 助手能共享你的詞匯表,不用猜。

      判定準則

      決定代碼放哪的時候,可以問自己一個問題:"如果把這個外部系統換掉,什么要跟著變?"

      每個領域邊界就是一個潛在的替換點。如果換掉一個外部系統需要改多個文件,邊界很可能劃錯了。

      這個限界上下文原則貫穿了領域層和防腐層——services/、tools/、models/ 里各有一個 youtube.py,組織 YouTube 相關的功能。導航變得可預測:"YouTube 邏輯在哪?"在任何一層找 youtube.py 就行。

      對 AI 輔助開發還有個附帶好處:LLM 需要理解或修改 YouTube 相關代碼時,一致的命名讓它不用猜就能找到正確的文件。而且大一點的內聚模塊不是壞事——模型讀一個文件就有完整上下文,比從一堆小文件里拼信息好得多。

      智能體設計:單一職責

      層結構和領域組織都定了,來看智能體本身。

      每個智能體恰好做一件事:



      看起來也許太死板了——TranscriptAgent 手頭已經有字幕文本了,為什么不順便做個摘要?

      原因在于可預測性和可調試性。出了問題的時候:摘要質量差,查 SummarizeAgent;字幕拉不下來,查 TranscriptAgent;搜索結果不相關,查 SearchAgent。一個問題一個入口。

      為什么不用一個 YouTubeAgent?

      你可能注意到了一個矛盾。剛才主張 services/、tools/、models/ 都按限界上下文組織,每個層都有 youtube.py。那為什么不搞一個同時處理搜索和字幕的 YouTubeAgent?

      因為不同層的組織邏輯不同。領域層(服務、模型)和防腐層(工具)按外部系統劃分,這些層包含"video_id"、"channel"這類領域概念,按限界上下文分組讓系統更容易理解和替換。但智能體是編排層:定義的是任務和角色,不是系統邊界。SearchAgent 的任務是"找視頻",TranscriptAgent 的任務是"拉字幕",它們碰巧用了同一個外部系統。

      沒人會把 SummarizeAgent 叫"AzureOpenAIAgent",雖然它確實用了 Azure OpenAI。智能體的身份取決于它做什么,而非它用了什么。一個任務,一個智能體,出問題時一個要看的地方。

      編排器模式

      四個職責單一的智能體需要協調,這就是 OrchestratorAgent 的工作:

      用戶請求

      編排器(決定做什么)

      ├── "需要搜索" → SearchAgent
      ├── "需要字幕" → TranscriptAgent
      ├── "需要摘要" → SummarizeAgent
      └── "需要保存" → WriterAgent

      編排器維護對話記憶,清楚哪些內容已經緩存(通過上下文注入),把具體工作委托給專家,自己從不直接調 YouTube 或 OpenAI。

      這種分離意味著每個專業智能體都可以獨立測試,輸入輸出清清楚楚。

      智能體

      定義一個智能體出乎意料地簡單:

      #agents/search_agent.py
      SEARCH_AGENT_INSTRUCTIONS = """You are a YouTube Search Agent. Your job is to find relevant YouTube videos based on user queries.
      When asked to search:
      1. Use the search_youtube tool to find videos
      2. Return the results clearly formatted
      3. Highlight which videos seem most relevant to the query
      You only search - you do not fetch transcripts or summarize. Other agents handle those tasks."""
      def create_search_agent() -> ChatAgent:
      """Factory function that creates a Search Agent."""
      return ChatAgent(
      chat_client=get_chat_client(),
      name="SearchAgent",
      instructions=SEARCH_AGENT_INSTRUCTIONS,
      tools=[search_youtube_formatted],
      )

      指令提取成了模塊級常量(也可以從外部文件加載,比如 prompts/search_agent.txt,迭代提示詞時不用碰 Python 代碼)。工具來自 tools/ 層的函數(它們再去調服務)。智能體完全不知道 YouTube API 的存在——它只調工具。

      編排器的樣子

      編排器遵循同樣的模式,只不過它的"工具"是委托給其他智能體:

      class OrchestratorAgent:
      """Coordinates sub-agents for YouTube research tasks."""
      def __init__(self) -> None:
      self._agents: dict[str, ChatAgent] = {}
      # Agent factory registry for lazy initialization
      self._agent_factories = {
      "search": create_search_agent,
      "transcript": create_transcript_agent,
      "summarize": create_summarize_agent,
      "writer": create_writer_agent,
      }
      def _get_agent(self, name: str) -> ChatAgent:
      """Get or create an agent by name (lazy initialization)."""
      if name not in self._agents:
      self._agents[name] = self._agent_factories[name]()
      return self._agents[name]
      async def _delegate(self, agent_name: str, request: str) -> str:
      """Delegate a request to a sub-agent."""
      agent = self._get_agent(agent_name)
      result = await agent.run(request)
      return result.text
      async def ask_search_agent(self, request: str) -> str:
      """Delegate a search request to the Search Agent."""
      return await self._delegate("search", request)
      # ... similar for transcript, summarize, writer
      def get_orchestrator(self) -> ChatAgent:
      return ChatAgent(
      chat_client=get_chat_client(),
      name="Orchestrator",
      instructions=ORCHESTRATOR_INSTRUCTIONS,
      tools=[
      self.ask_search_agent,
      self.ask_transcript_agent,
      self.ask_summarize_agent,
      self.ask_writer_agent,
      ],
      )

      這里用類而不是簡單的工廠函數是刻意的:編排器要維護狀態,具體來說是一個延遲初始化的子智能體緩存。避免每次委托都重建智能體,初始化成本推遲到首次使用。

      編排器的"工具"本質上是委托函數。LLM 決定搜索時調 ask_search_agent,后者運行 SearchAgent 并返回結果。編排器拿到結果,決定下一步。

      這就是中心輻射(hub-and-spoke)模式:

      ┌─────────────┐
      │ Orchestrator│
      │ (LLM) │
      └──────┬──────┘

      ┌────────────┬─────┴─────┬───────────┐
      │ │ │ │
      ▼ ▼ ▼ ▼
      ┌─────────┐ ┌──────────┐ ┌─────────┐ ┌─────────┐
      │ Search │ │Transcript│ │Summarize│ │ Writer │
      │ Agent │ │ Agent │ │ Agent │ │ Agent │
      └─────────┘ └──────────┘ └─────────┘ └─────────┘

      所有交互流經中心。編排器逐步積累上下文,維護完整的對話歷史。

      上下文注入

      一個容易忽略但很關鍵的模式:編排器需要知道哪些字幕已經緩存了,才能做出聰明的決策。Microsoft Agent Framework 提供了 ContextProvider 基類,通過實現 invoking() 方法在每次 LLM 調用之前注入上下文:

      from agent_framework._memory import Context, ContextProvider
      class TranscriptContextProvider(ContextProvider):
      """Provides context about stored transcripts to the orchestrator."""
      async def invoking(self, messages, **kwargs) -> Context:
      """Called before each LLM invocation."""
      video_ids = self._storage.list_videos()
      if not video_ids:
      return Context(instructions="No transcripts currently stored.")
      lines = ["You have these transcripts available:"]
      for vid in video_ids:
      stored = self._storage.load(vid)
      if stored:
      status = "summarized" if stored.summary else "not summarized"
      lines.append(f"- {stored.metadata.title} ({vid}): {status}")
      return Context(instructions="\n".join(lines))

      框架在每次 LLM 請求前調 invoking(),返回的 Context 合并到智能體指令里。

      這跟對話記憶是兩回事,因為對話記憶是用戶和智能體之間的來回對話歷史,框架自動管理,通常走線程或會話機制。傳給 invoking() 的 messages 參數已經包含了這個歷史。

      ContextProvider 解決的是另一個問題:注入對話之外的領域狀態。存儲層把字幕持久化到磁盤了,但 LLM 不知道那邊有啥除非主動告訴它。查詢存儲、格式化成指令,彌合的是應用狀態和 LLM 上下文窗口之間的鴻溝。

      對話記憶回答"聊了什么",領域上下文回答"有什么資源可用"??蚣芄芮罢?,后者得自己負責。

      于是編排器就能做這樣的推理:"用戶要摘要,字幕已經緩存了,跳過獲取直接找 SummarizeAgent。"

      輸出

      最終的 markdown 文件:

      # Pork Loin Roast on a Kamado (YouTube-Technique Guide)
      **Date:** 2025-01-11
      **Source:** YouTube technique summaries (videos linked below)
      ## Key targets (temps & doneness)
      - **Pit / dome temp (indirect smoking):** **250–275°F** (121–135°C)
      - **Internal temp targets (pork loin):**
      - **Pull at 140–145°F** (60–63°C) for juicy slices
      - If you prefer more done: **150°F** (66°C)
      - **Rest:** **10–20 minutes** (loosely tented)
      ## Recommended kamado setups
      ### Setup A — Indirect "smoke-then-finish" (most consistent)
      1. **Charcoal:** quality lump; add 1–3 chunks of mild fruit wood
      2. **Heat deflectors:** installed for indirect cooking
      3. **Target pit temp:** stabilize at **250–275°F**
      ...
      ## Video references
      - **Fork & Embers** — Pork loin roast method
      - **Chuds BBQ** — Temp-control + finishing approach

      多個 YouTube 視頻的信息被綜合成了一份連貫、可直接操作的參考文檔。SearchAgent 找到對的視頻,TranscriptAgent 拿到內容,SummarizeAgent 提煉關鍵信息,WriterAgent 保存結果。各司其職。

      迭代優化

      編排器維護著對話歷史,所以可以接著聊來細化結果:

      User: Can you add a section comparing direct vs indirect cooking methods?
      User: The temperatures seem low - can you check if Chuds mentions a hotter approach?
      User: Save a version without the glaze instructions for my friend who doesn't like sweet.

      后續請求直接復用緩存的字幕,不需要重新從 YouTube 拉取。編排器記得自己有什么,推理還缺什么,按需委托。這個對話循環才是智能體模式真正出彩的地方——系統根據反饋調整,不用每次都從頭來。

      靈活性的代價

      編排器模式有個重要的權衡,跑多幾次才看得出來:方差。

      上面展示的整齊的順序流程只是一種可能的執行路徑。同樣的請求再跑一次,可能走一個完全不同的路線。

      對同一請求做多次基準測試,LLM 調用次數從 17 到 34 不等。同樣的輸入。編排器 LLM 每次做出的戰術決策不一樣:



      開詳細日志就能看到差異:

      # Run A (17 calls) - Minimal approach
      SearchAgent called with: Kamado pork loin Fork and Embers
      SearchAgent called with: Chuds BBQ pork loin kamado
      TranscriptAgent called with: Fetch transcript for video FsbwQI-EI-k...
      TranscriptAgent called with: Fetch transcript for video 2AF1ysZ8eEA...
      TranscriptAgent called with: Fetch transcript for video fI86yXKlnQA...
      WriterAgent called with: Write a markdown file... # Skipped summarization!
      # Run B (25 calls) - Thorough approach
      SearchAgent called with: Find YouTube videos where Fork and Embers...
      SearchAgent called with: Find YouTube videos where Chuds BBQ...
      SearchAgent called with: Find top YouTube videos about cooking pork loin...
      TranscriptAgent called with: ...
      SummarizeAgent called with: From the provided transcripts, extract...
      WriterAgent called with: ...

      Run A 認為 WriterAgent 可以直接從原始字幕綜合出結果。Run B 多走了一步摘要。兩個都給出了有效輸出,但成本和質量可能不同。

      "把 temperature 設成零不就行了?"

      面對方差的第一反應自然是把 LLM temperature 調低,追求確定性行為。測了:



      所有運行都設了固定 seed(42)。

      即使 temperature=0 加固定 seed,調用次數仍有 10 次的波動(25 到 35 次)。不可預測性的根源不是采樣隨機性,而是 LLM 在每次運行中做出了不同的、但都合理的策略選擇:發幾個并行搜索(1、2 還是 3)、按視頻分別摘要還是合并摘要、要不要跳過摘要讓 writer 直接綜合。

      這種方差是架構層面的。要削減它要么把每個智能體的范圍卡得極其嚴格讓決策空間收窄,要么干脆提前規劃好執行路徑,消除運行時決策。后續文章會探討這些替代方案。

      這不是 bug,這是讓 LLM 在運行時決策工作流的固有代價。編排器獲得了隨機應變的靈活性,代價是不可預測性。對于對話式交互場景,這個權衡通常劃得來。對于需要高可預測性的批處理,可能得換別的方法。

      總結

      本文的出發點是想驗證一件事:智能體系統到底能不能像其他嚴肅軟件一樣做架構。編排器模式的探索證明:能。

      方法本身談不上新穎。分層架構、關注點分離、領域驅動設計,全是老話題。不過可以看到它們映射到智能體系統時幾乎是天然契合的。

      工具和服務承擔的是根本不同的職責。工具在 LLM 的世界(簡單參數、字符串輸出)和領域的世界(豐富對象、業務邏輯)之間做翻譯,把它們分清楚,系統就自然變得清晰可測。

      我們可以理解智能體是帶了自然語言接口和 LLM 組件的軟件系統。工程紀律那套東西幾十年了,依然適用,只是得想清楚邊界畫在哪。

      本文代碼:

      https://avoid.overfit.cn/post/feb23ffaa4da461092394e0d1d64db21

      作者:Chris Hughes

      特別聲明:以上內容(如有圖片或視頻亦包括在內)為自媒體平臺“網易號”用戶上傳并發布,本平臺僅提供信息存儲服務。

      Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

      相關推薦
      熱點推薦
      伊朗最大“內鬼”被抓?革命衛隊:勾結以色列,指揮官卡尼被拘!

      伊朗最大“內鬼”被抓?革命衛隊:勾結以色列,指揮官卡尼被拘!

      青青子衿
      2026-03-05 11:57:03
      打瘋了!東契奇首節狂轟22+5三分 生涯30次單節20+升歷史第四

      打瘋了!東契奇首節狂轟22+5三分 生涯30次單節20+升歷史第四

      醉臥浮生
      2026-03-07 12:13:33
      伊拉克庫爾德第一夫人宣言:我們不是任人驅使的炮灰!

      伊拉克庫爾德第一夫人宣言:我們不是任人驅使的炮灰!

      勝研集
      2026-03-06 13:44:23
      廣東一女子不愿上班常年坐街邊,因長得好看被路人投喂:又懶又饞

      廣東一女子不愿上班常年坐街邊,因長得好看被路人投喂:又懶又饞

      明智家庭教育
      2026-03-06 17:19:16
      美以伊軍事沖突最大副作用,是斬斷了俄羅斯的“救命稻草”

      美以伊軍事沖突最大副作用,是斬斷了俄羅斯的“救命稻草”

      廖保平
      2026-03-05 12:08:52
      “不想為以色列賣命”:帝國最后的遮羞布,美式民主終成笑話

      “不想為以色列賣命”:帝國最后的遮羞布,美式民主終成笑話

      怪口歷史的K先生
      2026-03-06 15:22:51
      為何關閉霍爾木茲海峽就能掐全球脖子?因為伊朗原油是全世界最好的

      為何關閉霍爾木茲海峽就能掐全球脖子?因為伊朗原油是全世界最好的

      風向觀察
      2026-03-06 21:31:15
      兩會不到3天,5大好消息傳來!老百姓暗暗叫好:希望國家盡快落實

      兩會不到3天,5大好消息傳來!老百姓暗暗叫好:希望國家盡快落實

      談史論天地
      2026-03-07 06:54:29
      1979年,張國燾凍死在養老院,許世友:除了主席,沒人是他的對手

      1979年,張國燾凍死在養老院,許世友:除了主席,沒人是他的對手

      文史季季紅
      2026-03-05 13:35:03
      寫入教科書的一天:F-35在德黑蘭完成全球首次實戰空對空擊殺

      寫入教科書的一天:F-35在德黑蘭完成全球首次實戰空對空擊殺

      斌聞天下
      2026-03-06 07:30:03
      伊方:因美以襲擊喪生的伊朗人三成為青少年

      伊方:因美以襲擊喪生的伊朗人三成為青少年

      環球網資訊
      2026-03-07 06:39:29
      為什么美國的華人華裔地位那么低 網友從各方面分析 真就那樣

      為什么美國的華人華裔地位那么低 網友從各方面分析 真就那樣

      侃神評故事
      2026-03-06 07:10:03
      我包養過一個女大學生,七年花了一千多萬

      我包養過一個女大學生,七年花了一千多萬

      煙火人間故事匯
      2026-03-06 23:05:03
      性壓抑已經變態至此了?

      性壓抑已經變態至此了?

      黯泉
      2026-03-07 11:28:43
      蘿莉島,是進入核心圈層的投名狀,你猜他們為什么都穿紅皮鞋

      蘿莉島,是進入核心圈層的投名狀,你猜他們為什么都穿紅皮鞋

      百曉生談歷史
      2026-03-05 22:00:08
      一份“煮熟的三文魚”火了,原來低認知的家長,真能搞出人命!

      一份“煮熟的三文魚”火了,原來低認知的家長,真能搞出人命!

      妍妍教育日記
      2026-03-07 08:45:06
      伊朗萬萬沒想到,自家王牌武器遭到破解,美軍多了一張底牌

      伊朗萬萬沒想到,自家王牌武器遭到破解,美軍多了一張底牌

      空天力量
      2026-03-06 13:09:18
      上次被發現還是1911年!上海寶山驚現1只,專家:可能是坐船來的

      上次被發現還是1911年!上海寶山驚現1只,專家:可能是坐船來的

      萬象硬核本尊
      2026-03-06 23:54:22
      女子實名舉報某團外賣:不上大額券就讓我變成“凌晨營業”,你們真黑!

      女子實名舉報某團外賣:不上大額券就讓我變成“凌晨營業”,你們真黑!

      回旋鏢
      2026-03-06 21:13:59
      塔圖姆復出15分12板7助攻凱爾特人大勝獨行俠,布朗24分7板7助

      塔圖姆復出15分12板7助攻凱爾特人大勝獨行俠,布朗24分7板7助

      湖人崛起
      2026-03-07 10:25:09
      2026-03-07 13:43:00
      deephub incentive-icons
      deephub
      CV NLP和數據挖掘知識
      1940文章數 1456關注度
      往期回顧 全部

      科技要聞

      OpenClaw爆火,六位"養蝦人"自述與AI共生

      頭條要聞

      特朗普突然放話"先解決伊朗后解決古巴" 梅西聽懵了

      頭條要聞

      特朗普突然放話"先解決伊朗后解決古巴" 梅西聽懵了

      體育要聞

      塔圖姆歸來:凱爾特人的春之綠

      娛樂要聞

      周杰倫田馥甄的“JH戀” 被扒得底朝天

      財經要聞

      針對"不敢休、不讓休"怪圈 國家出手了

      汽車要聞

      逃離ICU,上汽通用“止血”企穩

      態度原創

      親子
      房產
      游戲
      教育
      藝術

      親子要聞

      六個月寶寶查出散光,原因竟是父母長期身旁玩手機,媽媽懵了:我一直以為他閉著眼就沒事

      房產要聞

      傳統學區房熄火?2月??诙址勘鸬陌鍓K竟然是…

      鍵鼠不是萬能的神!外媒盤點近年適合用手柄玩的游戲

      教育要聞

      兩會速遞|教育部部長:將實施新一輪學生心理健康促進行動

      藝術要聞

      Mark Grantham | 城市街景

      無障礙瀏覽 進入關懷版