conversations_router.py 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845
  1. import logging
  2. import textwrap
  3. from typing import Optional
  4. from uuid import UUID
  5. from fastapi import Body, Depends, Path, Query
  6. from fastapi.background import BackgroundTasks
  7. from fastapi.responses import FileResponse
  8. from core.base import Message, R2RException
  9. from core.base.api.models import (
  10. GenericBooleanResponse,
  11. WrappedBooleanResponse,
  12. WrappedConversationMessagesResponse,
  13. WrappedConversationResponse,
  14. WrappedConversationsResponse,
  15. WrappedMessageResponse,
  16. )
  17. from ...abstractions import R2RProviders, R2RServices
  18. from .base_router import BaseRouterV3
  19. logger = logging.getLogger()
  20. class ConversationsRouter(BaseRouterV3):
  21. def __init__(
  22. self,
  23. providers: R2RProviders,
  24. services: R2RServices,
  25. ):
  26. super().__init__(providers, services)
  27. def _setup_routes(self):
  28. @self.router.post(
  29. "/conversations",
  30. summary="Create a new conversation",
  31. dependencies=[Depends(self.rate_limit_dependency)],
  32. openapi_extra={
  33. "x-codeSamples": [
  34. {
  35. "lang": "Python",
  36. "source": textwrap.dedent(
  37. """
  38. from r2r import R2RClient
  39. client = R2RClient()
  40. # when using auth, do client.login(...)
  41. result = client.conversations.create()
  42. """
  43. ),
  44. },
  45. {
  46. "lang": "JavaScript",
  47. "source": textwrap.dedent(
  48. """
  49. const { r2rClient } = require("r2r-js");
  50. const client = new r2rClient();
  51. function main() {
  52. const response = await client.conversations.create();
  53. }
  54. main();
  55. """
  56. ),
  57. },
  58. {
  59. "lang": "CLI",
  60. "source": textwrap.dedent(
  61. """
  62. r2r conversations create
  63. """
  64. ),
  65. },
  66. {
  67. "lang": "cURL",
  68. "source": textwrap.dedent(
  69. """
  70. curl -X POST "https://api.example.com/v3/conversations" \\
  71. -H "Authorization: Bearer YOUR_API_KEY"
  72. """
  73. ),
  74. },
  75. ]
  76. },
  77. )
  78. @self.base_endpoint
  79. async def create_conversation(
  80. name: Optional[str] = Body(
  81. None, description="The name of the conversation", embed=True
  82. ),
  83. auth_user=Depends(self.providers.auth.auth_wrapper()),
  84. ) -> WrappedConversationResponse:
  85. """
  86. Create a new conversation.
  87. This endpoint initializes a new conversation for the authenticated user.
  88. """
  89. user_id = auth_user.id
  90. return await self.services.management.create_conversation(
  91. user_id=user_id,
  92. name=name,
  93. )
  94. @self.router.get(
  95. "/conversations",
  96. summary="List conversations",
  97. dependencies=[Depends(self.rate_limit_dependency)],
  98. openapi_extra={
  99. "x-codeSamples": [
  100. {
  101. "lang": "Python",
  102. "source": textwrap.dedent(
  103. """
  104. from r2r import R2RClient
  105. client = R2RClient()
  106. # when using auth, do client.login(...)
  107. result = client.conversations.list(
  108. offset=0,
  109. limit=10,
  110. )
  111. """
  112. ),
  113. },
  114. {
  115. "lang": "JavaScript",
  116. "source": textwrap.dedent(
  117. """
  118. const { r2rClient } = require("r2r-js");
  119. const client = new r2rClient();
  120. function main() {
  121. const response = await client.conversations.list();
  122. }
  123. main();
  124. """
  125. ),
  126. },
  127. {
  128. "lang": "CLI",
  129. "source": textwrap.dedent(
  130. """
  131. r2r conversations list
  132. """
  133. ),
  134. },
  135. {
  136. "lang": "cURL",
  137. "source": textwrap.dedent(
  138. """
  139. curl -X GET "https://api.example.com/v3/conversations?offset=0&limit=10" \\
  140. -H "Authorization: Bearer YOUR_API_KEY"
  141. """
  142. ),
  143. },
  144. ]
  145. },
  146. )
  147. @self.base_endpoint
  148. async def list_conversations(
  149. ids: list[str] = Query(
  150. [],
  151. description="A list of conversation IDs to retrieve. If not provided, all conversations will be returned.",
  152. ),
  153. offset: int = Query(
  154. 0,
  155. ge=0,
  156. description="Specifies the number of objects to skip. Defaults to 0.",
  157. ),
  158. limit: int = Query(
  159. 100,
  160. ge=1,
  161. le=1000,
  162. description="Specifies a limit on the number of objects to return, ranging between 1 and 100. Defaults to 100.",
  163. ),
  164. auth_user=Depends(self.providers.auth.auth_wrapper()),
  165. ) -> WrappedConversationsResponse:
  166. """
  167. List conversations with pagination and sorting options.
  168. This endpoint returns a paginated list of conversations for the authenticated user.
  169. """
  170. requesting_user_id = (
  171. None if auth_user.is_superuser else [auth_user.id]
  172. )
  173. conversation_uuids = [
  174. UUID(conversation_id) for conversation_id in ids
  175. ]
  176. conversations_response = (
  177. await self.services.management.conversations_overview(
  178. offset=offset,
  179. limit=limit,
  180. conversation_ids=conversation_uuids,
  181. user_ids=requesting_user_id,
  182. )
  183. )
  184. return conversations_response["results"], { # type: ignore
  185. "total_entries": conversations_response["total_entries"]
  186. }
  187. @self.router.post(
  188. "/conversations/export",
  189. summary="Export conversations to CSV",
  190. dependencies=[Depends(self.rate_limit_dependency)],
  191. openapi_extra={
  192. "x-codeSamples": [
  193. {
  194. "lang": "Python",
  195. "source": textwrap.dedent(
  196. """
  197. from r2r import R2RClient
  198. client = R2RClient("http://localhost:7272")
  199. # when using auth, do client.login(...)
  200. response = client.conversations.export(
  201. output_path="export.csv",
  202. columns=["id", "created_at"],
  203. include_header=True,
  204. )
  205. """
  206. ),
  207. },
  208. {
  209. "lang": "JavaScript",
  210. "source": textwrap.dedent(
  211. """
  212. const { r2rClient } = require("r2r-js");
  213. const client = new r2rClient("http://localhost:7272");
  214. function main() {
  215. await client.conversations.export({
  216. outputPath: "export.csv",
  217. columns: ["id", "created_at"],
  218. includeHeader: true,
  219. });
  220. }
  221. main();
  222. """
  223. ),
  224. },
  225. {
  226. "lang": "CLI",
  227. "source": textwrap.dedent(
  228. """
  229. """
  230. ),
  231. },
  232. {
  233. "lang": "cURL",
  234. "source": textwrap.dedent(
  235. """
  236. curl -X POST "http://127.0.0.1:7272/v3/conversations/export" \
  237. -H "Authorization: Bearer YOUR_API_KEY" \
  238. -H "Content-Type: application/json" \
  239. -H "Accept: text/csv" \
  240. -d '{ "columns": ["id", "created_at"], "include_header": true }' \
  241. --output export.csv
  242. """
  243. ),
  244. },
  245. ]
  246. },
  247. )
  248. @self.base_endpoint
  249. async def export_conversations(
  250. background_tasks: BackgroundTasks,
  251. columns: Optional[list[str]] = Body(
  252. None, description="Specific columns to export"
  253. ),
  254. filters: Optional[dict] = Body(
  255. None, description="Filters to apply to the export"
  256. ),
  257. include_header: Optional[bool] = Body(
  258. True, description="Whether to include column headers"
  259. ),
  260. auth_user=Depends(self.providers.auth.auth_wrapper()),
  261. ) -> FileResponse:
  262. """
  263. Export conversations as a downloadable CSV file.
  264. """
  265. if not auth_user.is_superuser:
  266. raise R2RException(
  267. "Only a superuser can export data.",
  268. 403,
  269. )
  270. csv_file_path, temp_file = (
  271. await self.services.management.export_conversations(
  272. columns=columns,
  273. filters=filters,
  274. include_header=include_header,
  275. )
  276. )
  277. background_tasks.add_task(temp_file.close)
  278. return FileResponse(
  279. path=csv_file_path,
  280. media_type="text/csv",
  281. filename="documents_export.csv",
  282. )
  283. @self.router.post(
  284. "/conversations/export_messages",
  285. summary="Export messages to CSV",
  286. dependencies=[Depends(self.rate_limit_dependency)],
  287. openapi_extra={
  288. "x-codeSamples": [
  289. {
  290. "lang": "Python",
  291. "source": textwrap.dedent(
  292. """
  293. from r2r import R2RClient
  294. client = R2RClient("http://localhost:7272")
  295. # when using auth, do client.login(...)
  296. response = client.conversations.export_messages(
  297. output_path="export.csv",
  298. columns=["id", "created_at"],
  299. include_header=True,
  300. )
  301. """
  302. ),
  303. },
  304. {
  305. "lang": "JavaScript",
  306. "source": textwrap.dedent(
  307. """
  308. const { r2rClient } = require("r2r-js");
  309. const client = new r2rClient("http://localhost:7272");
  310. function main() {
  311. await client.conversations.exportMessages({
  312. outputPath: "export.csv",
  313. columns: ["id", "created_at"],
  314. includeHeader: true,
  315. });
  316. }
  317. main();
  318. """
  319. ),
  320. },
  321. {
  322. "lang": "CLI",
  323. "source": textwrap.dedent(
  324. """
  325. """
  326. ),
  327. },
  328. {
  329. "lang": "cURL",
  330. "source": textwrap.dedent(
  331. """
  332. curl -X POST "http://127.0.0.1:7272/v3/conversations/export_messages" \
  333. -H "Authorization: Bearer YOUR_API_KEY" \
  334. -H "Content-Type: application/json" \
  335. -H "Accept: text/csv" \
  336. -d '{ "columns": ["id", "created_at"], "include_header": true }' \
  337. --output export.csv
  338. """
  339. ),
  340. },
  341. ]
  342. },
  343. )
  344. @self.base_endpoint
  345. async def export_messages(
  346. background_tasks: BackgroundTasks,
  347. columns: Optional[list[str]] = Body(
  348. None, description="Specific columns to export"
  349. ),
  350. filters: Optional[dict] = Body(
  351. None, description="Filters to apply to the export"
  352. ),
  353. include_header: Optional[bool] = Body(
  354. True, description="Whether to include column headers"
  355. ),
  356. auth_user=Depends(self.providers.auth.auth_wrapper()),
  357. ) -> FileResponse:
  358. """
  359. Export conversations as a downloadable CSV file.
  360. """
  361. if not auth_user.is_superuser:
  362. raise R2RException(
  363. "Only a superuser can export data.",
  364. 403,
  365. )
  366. csv_file_path, temp_file = (
  367. await self.services.management.export_messages(
  368. columns=columns,
  369. filters=filters,
  370. include_header=include_header,
  371. )
  372. )
  373. background_tasks.add_task(temp_file.close)
  374. return FileResponse(
  375. path=csv_file_path,
  376. media_type="text/csv",
  377. filename="documents_export.csv",
  378. )
  379. @self.router.get(
  380. "/conversations/{id}",
  381. summary="Get conversation details",
  382. dependencies=[Depends(self.rate_limit_dependency)],
  383. openapi_extra={
  384. "x-codeSamples": [
  385. {
  386. "lang": "Python",
  387. "source": textwrap.dedent(
  388. """
  389. from r2r import R2RClient
  390. client = R2RClient()
  391. # when using auth, do client.login(...)
  392. result = client.conversations.get(
  393. "123e4567-e89b-12d3-a456-426614174000"
  394. )
  395. """
  396. ),
  397. },
  398. {
  399. "lang": "JavaScript",
  400. "source": textwrap.dedent(
  401. """
  402. const { r2rClient } = require("r2r-js");
  403. const client = new r2rClient();
  404. function main() {
  405. const response = await client.conversations.retrieve({
  406. id: "123e4567-e89b-12d3-a456-426614174000",
  407. });
  408. }
  409. main();
  410. """
  411. ),
  412. },
  413. {
  414. "lang": "CLI",
  415. "source": textwrap.dedent(
  416. """
  417. r2r conversations retrieve 123e4567-e89b-12d3-a456-426614174000
  418. """
  419. ),
  420. },
  421. {
  422. "lang": "cURL",
  423. "source": textwrap.dedent(
  424. """
  425. curl -X GET "https://api.example.com/v3/conversations/123e4567-e89b-12d3-a456-426614174000" \\
  426. -H "Authorization: Bearer YOUR_API_KEY"
  427. """
  428. ),
  429. },
  430. ]
  431. },
  432. )
  433. @self.base_endpoint
  434. async def get_conversation(
  435. id: UUID = Path(
  436. ..., description="The unique identifier of the conversation"
  437. ),
  438. auth_user=Depends(self.providers.auth.auth_wrapper()),
  439. ) -> WrappedConversationMessagesResponse:
  440. """
  441. Get details of a specific conversation.
  442. This endpoint retrieves detailed information about a single conversation identified by its UUID.
  443. """
  444. requesting_user_id = (
  445. None if auth_user.is_superuser else [auth_user.id]
  446. )
  447. conversation = await self.services.management.get_conversation(
  448. conversation_id=id,
  449. user_ids=requesting_user_id,
  450. )
  451. return conversation
  452. @self.router.post(
  453. "/conversations/{id}",
  454. summary="Update conversation",
  455. dependencies=[Depends(self.rate_limit_dependency)],
  456. openapi_extra={
  457. "x-codeSamples": [
  458. {
  459. "lang": "Python",
  460. "source": textwrap.dedent(
  461. """
  462. from r2r import R2RClient
  463. client = R2RClient()
  464. # when using auth, do client.login(...)
  465. result = client.conversations.update("123e4567-e89b-12d3-a456-426614174000", "new_name")
  466. """
  467. ),
  468. },
  469. {
  470. "lang": "JavaScript",
  471. "source": textwrap.dedent(
  472. """
  473. const { r2rClient } = require("r2r-js");
  474. const client = new r2rClient();
  475. function main() {
  476. const response = await client.conversations.update({
  477. id: "123e4567-e89b-12d3-a456-426614174000",
  478. name: "new_name",
  479. });
  480. }
  481. main();
  482. """
  483. ),
  484. },
  485. {
  486. "lang": "CLI",
  487. "source": textwrap.dedent(
  488. """
  489. r2r conversations delete 123e4567-e89b-12d3-a456-426614174000
  490. """
  491. ),
  492. },
  493. {
  494. "lang": "cURL",
  495. "source": textwrap.dedent(
  496. """
  497. curl -X POST "https://api.example.com/v3/conversations/123e4567-e89b-12d3-a456-426614174000" \
  498. -H "Authorization: Bearer YOUR_API_KEY" \
  499. -H "Content-Type: application/json" \
  500. -d '{"name": "new_name"}'
  501. """
  502. ),
  503. },
  504. ]
  505. },
  506. )
  507. @self.base_endpoint
  508. async def update_conversation(
  509. id: UUID = Path(
  510. ...,
  511. description="The unique identifier of the conversation to delete",
  512. ),
  513. name: str = Body(
  514. ...,
  515. description="The updated name for the conversation",
  516. embed=True,
  517. ),
  518. auth_user=Depends(self.providers.auth.auth_wrapper()),
  519. ) -> WrappedConversationResponse:
  520. """
  521. Update an existing conversation.
  522. This endpoint updates the name of an existing conversation identified by its UUID.
  523. """
  524. return await self.services.management.update_conversation(
  525. conversation_id=id,
  526. name=name,
  527. )
  528. @self.router.delete(
  529. "/conversations/{id}",
  530. summary="Delete conversation",
  531. dependencies=[Depends(self.rate_limit_dependency)],
  532. openapi_extra={
  533. "x-codeSamples": [
  534. {
  535. "lang": "Python",
  536. "source": textwrap.dedent(
  537. """
  538. from r2r import R2RClient
  539. client = R2RClient()
  540. # when using auth, do client.login(...)
  541. result = client.conversations.delete("123e4567-e89b-12d3-a456-426614174000")
  542. """
  543. ),
  544. },
  545. {
  546. "lang": "JavaScript",
  547. "source": textwrap.dedent(
  548. """
  549. const { r2rClient } = require("r2r-js");
  550. const client = new r2rClient();
  551. function main() {
  552. const response = await client.conversations.delete({
  553. id: "123e4567-e89b-12d3-a456-426614174000",
  554. });
  555. }
  556. main();
  557. """
  558. ),
  559. },
  560. {
  561. "lang": "CLI",
  562. "source": textwrap.dedent(
  563. """
  564. r2r conversations delete 123e4567-e89b-12d3-a456-426614174000
  565. """
  566. ),
  567. },
  568. {
  569. "lang": "cURL",
  570. "source": textwrap.dedent(
  571. """
  572. curl -X DELETE "https://api.example.com/v3/conversations/123e4567-e89b-12d3-a456-426614174000" \\
  573. -H "Authorization: Bearer YOUR_API_KEY"
  574. """
  575. ),
  576. },
  577. ]
  578. },
  579. )
  580. @self.base_endpoint
  581. async def delete_conversation(
  582. id: UUID = Path(
  583. ...,
  584. description="The unique identifier of the conversation to delete",
  585. ),
  586. auth_user=Depends(self.providers.auth.auth_wrapper()),
  587. ) -> WrappedBooleanResponse:
  588. """
  589. Delete an existing conversation.
  590. This endpoint deletes a conversation identified by its UUID.
  591. """
  592. requesting_user_id = (
  593. None if auth_user.is_superuser else [auth_user.id]
  594. )
  595. await self.services.management.delete_conversation(
  596. conversation_id=id,
  597. user_ids=requesting_user_id,
  598. )
  599. return GenericBooleanResponse(success=True) # type: ignore
  600. @self.router.post(
  601. "/conversations/{id}/messages",
  602. summary="Add message to conversation",
  603. dependencies=[Depends(self.rate_limit_dependency)],
  604. openapi_extra={
  605. "x-codeSamples": [
  606. {
  607. "lang": "Python",
  608. "source": textwrap.dedent(
  609. """
  610. from r2r import R2RClient
  611. client = R2RClient()
  612. # when using auth, do client.login(...)
  613. result = client.conversations.add_message(
  614. "123e4567-e89b-12d3-a456-426614174000",
  615. content="Hello, world!",
  616. role="user",
  617. parent_id="parent_message_id",
  618. metadata={"key": "value"}
  619. )
  620. """
  621. ),
  622. },
  623. {
  624. "lang": "JavaScript",
  625. "source": textwrap.dedent(
  626. """
  627. const { r2rClient } = require("r2r-js");
  628. const client = new r2rClient();
  629. function main() {
  630. const response = await client.conversations.addMessage({
  631. id: "123e4567-e89b-12d3-a456-426614174000",
  632. content: "Hello, world!",
  633. role: "user",
  634. parentId: "parent_message_id",
  635. });
  636. }
  637. main();
  638. """
  639. ),
  640. },
  641. {
  642. "lang": "cURL",
  643. "source": textwrap.dedent(
  644. """
  645. curl -X POST "https://api.example.com/v3/conversations/123e4567-e89b-12d3-a456-426614174000/messages" \\
  646. -H "Authorization: Bearer YOUR_API_KEY" \\
  647. -H "Content-Type: application/json" \\
  648. -d '{"content": "Hello, world!", "parent_id": "parent_message_id", "metadata": {"key": "value"}}'
  649. """
  650. ),
  651. },
  652. ]
  653. },
  654. )
  655. @self.base_endpoint
  656. async def add_message(
  657. id: UUID = Path(
  658. ..., description="The unique identifier of the conversation"
  659. ),
  660. content: str = Body(
  661. ..., description="The content of the message to add"
  662. ),
  663. role: str = Body(
  664. ..., description="The role of the message to add"
  665. ),
  666. parent_id: Optional[UUID] = Body(
  667. None, description="The ID of the parent message, if any"
  668. ),
  669. metadata: Optional[dict[str, str]] = Body(
  670. None, description="Additional metadata for the message"
  671. ),
  672. auth_user=Depends(self.providers.auth.auth_wrapper()),
  673. ) -> WrappedMessageResponse:
  674. """
  675. Add a new message to a conversation.
  676. This endpoint adds a new message to an existing conversation.
  677. """
  678. if content == "":
  679. raise R2RException("Content cannot be empty", status_code=400)
  680. if role not in ["user", "assistant", "system"]:
  681. raise R2RException("Invalid role", status_code=400)
  682. message = Message(role=role, content=content)
  683. return await self.services.management.add_message(
  684. conversation_id=id,
  685. content=message,
  686. parent_id=parent_id,
  687. metadata=metadata,
  688. )
  689. @self.router.post(
  690. "/conversations/{id}/messages/{message_id}",
  691. summary="Update message in conversation",
  692. dependencies=[Depends(self.rate_limit_dependency)],
  693. openapi_extra={
  694. "x-codeSamples": [
  695. {
  696. "lang": "Python",
  697. "source": textwrap.dedent(
  698. """
  699. from r2r import R2RClient
  700. client = R2RClient()
  701. # when using auth, do client.login(...)
  702. result = client.conversations.update_message(
  703. "123e4567-e89b-12d3-a456-426614174000",
  704. "message_id_to_update",
  705. content="Updated content"
  706. )
  707. """
  708. ),
  709. },
  710. {
  711. "lang": "JavaScript",
  712. "source": textwrap.dedent(
  713. """
  714. const { r2rClient } = require("r2r-js");
  715. const client = new r2rClient();
  716. function main() {
  717. const response = await client.conversations.updateMessage({
  718. id: "123e4567-e89b-12d3-a456-426614174000",
  719. messageId: "message_id_to_update",
  720. content: "Updated content",
  721. });
  722. }
  723. main();
  724. """
  725. ),
  726. },
  727. {
  728. "lang": "cURL",
  729. "source": textwrap.dedent(
  730. """
  731. curl -X POST "https://api.example.com/v3/conversations/123e4567-e89b-12d3-a456-426614174000/messages/message_id_to_update" \\
  732. -H "Authorization: Bearer YOUR_API_KEY" \\
  733. -H "Content-Type: application/json" \\
  734. -d '{"content": "Updated content"}'
  735. """
  736. ),
  737. },
  738. ]
  739. },
  740. )
  741. @self.base_endpoint
  742. async def update_message(
  743. id: UUID = Path(
  744. ..., description="The unique identifier of the conversation"
  745. ),
  746. message_id: UUID = Path(
  747. ..., description="The ID of the message to update"
  748. ),
  749. content: Optional[str] = Body(
  750. None, description="The new content for the message"
  751. ),
  752. metadata: Optional[dict[str, str]] = Body(
  753. None, description="Additional metadata for the message"
  754. ),
  755. auth_user=Depends(self.providers.auth.auth_wrapper()),
  756. ) -> WrappedMessageResponse:
  757. """
  758. Update an existing message in a conversation.
  759. This endpoint updates the content of an existing message in a conversation.
  760. """
  761. return await self.services.management.edit_message(
  762. message_id=message_id,
  763. new_content=content,
  764. additional_metadata=metadata,
  765. )