From eb6f417a93fe1977db4a291e3939dc37e2a030fc Mon Sep 17 00:00:00 2001 From: C9luster <138663536+C9luster@users.noreply.github.com> Date: Mon, 9 Sep 2024 12:05:40 +0800 Subject: [PATCH 01/10] test-sdk-test (#503) * test-sdk-test * test-sdk-test * test-sdk-test * test-sdk-test * test-sdk-test * test-sdk-test * test-sdk-test * test-sdk-test --------- Co-authored-by: yinjiaqi --- .github/workflows/python-package.yml | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 3271e2ea0..40c23f981 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -76,14 +76,28 @@ jobs: git fetch upstream git remote -v git status - changed_files=$(git diff --name-only --diff-filter=ACMRT master -- '*.py' '*.sh') - echo "发生更改的py/sh文件为:" + + # 找到当前分支与 upstream/master 的共同祖先提交 + merge_base=$(git merge-base HEAD upstream/master) + echo "merge_base=$merge_base" + + # 比较当前分支与 merge_base 之间的差异 + changed_files=$(git diff --name-only --diff-filter=ACMRT $merge_base) + changed_files_py_sh=$(git diff --name-only --diff-filter=ACMRT $merge_base -- '*.py' '*.sh') + + echo "发生更改的文件为:" echo "$changed_files" - if [ -n "$changed_files" ]; then + + echo "发生更改的py/sh文件为:" + + if [ -n "$changed_files_py_sh" ]; then export APPBUILDER_PYTHON_TESTS=True + echo "$changed_files_py_sh" else export APPBUILDER_PYTHON_TESTS=False + echo "没有检测到Python或Shell文件被更改" fi + echo "APPBUILDER_PYTHON_TESTS=$APPBUILDER_PYTHON_TESTS" >> $GITHUB_ENV pwd - name: Install dependencies From 16d14d06c94b528ea312384e4bb4733b43d35be6 Mon Sep 17 00:00:00 2001 From: ide-rea <30512600+ide-rea@users.noreply.github.com> Date: Mon, 9 Sep 2024 12:16:38 +0800 Subject: [PATCH 02/10] =?UTF-8?q?=E4=BC=98=E5=8C=96AgentRuntime=E4=BB=A3?= =?UTF-8?q?=E7=A0=81&=E8=A1=A5=E5=85=85=E5=8D=95=E6=B5=8B=20(#497)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 优化AgentRuntime代码&补充单测 * 优化AgentRuntime代码&补充单测 * 触发单测 --- appbuilder/core/agent.py | 74 ++++++------ appbuilder/tests/test_core_agent.py | 172 ++++++++++++++++++++-------- 2 files changed, 166 insertions(+), 80 deletions(-) diff --git a/appbuilder/core/agent.py b/appbuilder/core/agent.py index e5723409c..9732554ba 100644 --- a/appbuilder/core/agent.py +++ b/appbuilder/core/agent.py @@ -322,8 +322,9 @@ def warp(): user_id = request.headers.get("X-Appbuilder-User-Id", None) init_context(session_id=session_id, request_id=request_id, user_id=user_id) - logging.debug( - f"[request_id={request_id}, session_id={session_id}] message={message}, stream={stream}, data={data}") + logging.info( + f"request_id={request_id}, session_id={session_id}] message={message}," + f" stream={stream}, data={data}, start run...") def gen_sse_resp(): with app.app_context(): @@ -332,7 +333,16 @@ def gen_sse_resp(): while retry_count < MAX_RETRY_COUNT: try: answer = self.chat(message, stream, **data) + except Exception as e: # 调用chat方法报错,直接返回 + code = 500 if not hasattr(e, "code") else e.code + err_resp = {"code": code, "message": "InternalServerError", "result": None} + logging.error( + f"request_id={request_id}, session_id={session_id}, err={e}, execute self.chat failed", exc_info=True) + yield "data: " + json.dumps(err_resp, ensure_ascii=False) + "\n\n" + return + else: # 调用chat方法成功,开始生成流式事件 content_iterator = iter(answer.content) + answer.content = None result = None try: for sub_content in content_iterator: @@ -349,10 +359,21 @@ def gen_sse_resp(): received_first_packet = True except Exception as e: retry_count += 1 - if not received_first_packet: + logging.error( + f"[request_id={request_id}, session_id={session_id}] err={e}, " + f"retry_count={retry_count}", exc_info=True) + # 如果未收到首包且重试次数小于最大重试次数,则尝试重新执行一次chat方法 + if not received_first_packet and retry_count < MAX_RETRY_COUNT: continue - else: - raise e + else: # 其它情况返回 + logging.error( + f"[request_id={request_id}, session_id={session_id}] err={e}, " + f"retry_count={retry_count}, received_first_packet={received_first_packet}" + , exc_info=True) + code = 500 if not hasattr(e, "code") else e.code + err_resp = {"code": code, "message": "InternalServerError", "result": None} + yield "data: " + json.dumps(err_resp, ensure_ascii=False) + "\n\n" + return result.content = "" yield "data: " + json.dumps({ "code": 0, "message": "", @@ -362,41 +383,30 @@ def gen_sse_resp(): "answer_message": json.loads(result.json(exclude_none=True)) } }, ensure_ascii=False) + "\n\n" + logging.info( + f"request_id={request_id}, session_id={session_id}]" + f"retry_count={retry_count}, success response", exc_info=True) self.user_session._post_append() - break - except Exception as e: - code = 500 if not hasattr( - e, "code") else e.code - err_resp = {"code": code, - "message": "InternalServerError", "result": None} - logging.error( - f"[request_id={request_id}, session_id={session_id}] err={e}", exc_info=True) - yield "data: " + json.dumps(err_resp, ensure_ascii=False) + "\n\n" + return # 正常返回 - try: - if stream: - return Response(stream_with_context(gen_sse_resp()), 200, - {'Content-Type': 'text/event-stream; charset=utf-8'}, - ) - else: + if stream: # 流式 + return Response(stream_with_context(gen_sse_resp()), 200, + {'Content-Type': 'text/event-stream; charset=utf-8'}) + if not stream: # 非流式 + try: answer = self.chat(message, stream, **data) - blocking_result = json.loads( - copy.deepcopy(answer).json(exclude_none=True)) - logging.info( - f"[request_id={request_id}, session_id={session_id}] blocking_result={blocking_result}") + blocking_result = json.loads(copy.deepcopy(answer).json(exclude_none=True)) + logging.debug(f"[request_id={request_id}, session_id={session_id}] blocking_result={blocking_result}") self.user_session._post_append() return { "code": 0, "message": "", "result": {"session_id": session_id, "answer_message": blocking_result} } - except Exception as e: - logging.error( - f"[request_id={request_id}, session_id={session_id}] err={e}", exc_info=True) - raise e - except Exception as e: - logging.error( - f"[request_id={request_id}, session_id={session_id}] err={e}", exc_info=True) - raise e + except Exception as e: + logging.error( + f"[request_id={request_id}, session_id={session_id}] err={e}", exc_info=True) + code = 500 if not hasattr(e, "code") else e.code + return {"code": code, "message": "InternalServerError", "result": None} app.add_url_rule(url_rule, 'chat', warp, methods=['POST']) return app diff --git a/appbuilder/tests/test_core_agent.py b/appbuilder/tests/test_core_agent.py index dccaf1a80..23de1a4a7 100644 --- a/appbuilder/tests/test_core_agent.py +++ b/appbuilder/tests/test_core_agent.py @@ -11,63 +11,139 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +import json import os import unittest -import json -import subprocess -import asyncio +import random +from appbuilder.core.components.llms.style_writing import StyleWriting from appbuilder.core.agent import AgentRuntime from appbuilder.core.component import Component +from appbuilder.core.message import Message +from appbuilder.utils.sse_util import SSEClient + + +def generate_event(case): + # 模拟正常事件 + if case == "normal": + yield "event1" + yield "event2" + yield "event3" + # 模拟首包一定概率出错 + elif case == "head_may_failed": + num = random.randint(1, 100) + if num < 20: + raise Exception("事件生成报错") + else: + yield "event1" + # 模型中包出错 + elif case == "middle_failed": + yield "event1" + raise Exception("事件生成报错") + # 模拟首包总是报错 + elif case == "head_always_failed": + raise Exception("事件生成报错") + + +# 模拟流式事件生成报错 +class FakeComponent1(Component): + def run(self, message, stream, **kwargs): + # 模拟流式调用 + if stream: + case = kwargs["case"] + return Message(content=generate_event(case)) + else: + return Message(content="result") + + +# 模拟组件内部执行报错 +class FakeComponent2(Component): + def run(self, message, stream, **kwargs): + # 内部执行报错 + raise Exception("内部执行报错") + -@unittest.skipUnless(os.getenv("TEST_CASE", "UNKNOWN") == "CPU_PARALLEL", "") class TestCoreAgent(unittest.TestCase): def setUp(self): - self.appbuilder_run_chainlit=os.getenv('APPBUILDER_RUN_CHAINLIT') - - def tearDown(self): - if self.appbuilder_run_chainlit is None: - os.unsetenv('APPBUILDER_RUN_CHAINLIT') - else: - os.environ['APPBUILDER_RUN_CHAINLIT'] = self.appbuilder_run_chainlit - - def test_core_agent_create_flask(self): - # test_core_agent_create_flask_app - component = Component() + pass + + def test_core_agent_create_flask1(self): + component = FakeComponent1() + # agent = AgentRuntime(component=StyleWriting(model="eb")) agent = AgentRuntime(component=component) - subprocess.check_call(['pip', 'uninstall', 'flask', '-y']) - with self.assertRaises(ImportError): - agent.create_flask_app() - subprocess.check_call(['pip', 'install', 'flask==2.3.2']) - subprocess.check_call(['pip', 'install', 'flask-restful==0.3.9']) - subprocess.check_call(['pip', 'install', 'werkzeug']) + app = agent.create_flask_app() - - # 附加测试core/component,相关代码还未编写 - def test_core_component(self): - component = Component() - component.http_client - with self.assertRaises(NotImplementedError): - component.run() - component.batch() - component._trace() - component._debug() - component.tool_desc() - component.tool_name() - component.tool_eval() - # 运行asyncio - asyncio.run(component.arun()) - asyncio.run(component.abatch()) - # test_tool_eval - component.manifests=[1,2,3] - with self.assertRaises(NotImplementedError): - component.tool_eval() - # test self._http_client is None - component._http_client = None - component.http_client - - - + client = app.test_client() + payload = { + "stream": False, + "message": "message", + } + # 非流式请求 + rsp = client.post("http://127.0.0.1:8080/chat", json=payload) + assert (rsp.json["code"] == 0) + + # 流式请求 + for case in ["normal", "head_may_failed", "middle_failed", "head_always_failed"]: + payload = { + "stream": True, + "message": "message", + "case": case + } + if case == "normal": + rsp = client.post("http://127.0.0.1:8080/chat", json=payload) + data_chunks = rsp.data.splitlines(keepends=True) + for event in SSEClient(data_chunks).events(): + d = json.loads(event.data) + self.assertEqual(d["code"], 0) + if case == "head_may_failed": + for i in range(5): + rsp = client.post("http://127.0.0.1:8080/chat", json=payload) + data_chunks = rsp.data.splitlines(keepends=True) + for event in SSEClient(data_chunks).events(): + d = json.loads(event.data) + self.assertEqual(d["code"], 0) + if case == "middle_failed": + rsp = client.post("http://127.0.0.1:8080/chat", json=payload) + data_chunks = rsp.data.splitlines(keepends=True) + i = 0 + for event in SSEClient(data_chunks).events(): + d = json.loads(event.data) + if i == 0: + self.assertEqual(d["code"], 0) + if i == 1: + self.assertNotEqual(d["code"], 0) + i += 1 + + if case == "head_always_failed": + rsp = client.post("http://127.0.0.1:8080/chat", json=payload) + data_chunks = rsp.data.splitlines(keepends=True) + for event in SSEClient(data_chunks).events(): + self.assertNotEqual(d["code"], 0) + + def test_core_agent_create_flask2(self): + component = FakeComponent2() + agent = AgentRuntime(component=component) + app = agent.create_flask_app() + client = app.test_client() + payload = { + "stream": False, + "message": "message", + } + # 非流式请求 + rsp = client.post("http://127.0.0.1:8080/chat", json=payload) + assert (rsp.json["code"] != 0) + + payload = { + "stream": True, + "message": "message", + } + # 流式请求 + rsp = client.post("http://127.0.0.1:8080/chat", json=payload) + data_chunks = rsp.data.splitlines(keepends=True) + for event in SSEClient(data_chunks).events(): + d = json.loads(event.data) + self.assertNotEqual(d["code"], 0) + + if __name__ == '__main__': unittest.main() - From 6e77d394c2bfe2679ac6fcfd448cfcc8c8c2d5f4 Mon Sep 17 00:00:00 2001 From: userpj Date: Tue, 10 Sep 2024 11:01:07 +0800 Subject: [PATCH 03/10] =?UTF-8?q?=E8=A7=84=E8=8C=83=E5=8C=96go=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=20(#506)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go/appbuilder/agent_builder.go | 11 +- go/appbuilder/agent_builder_data.go | 7 +- go/appbuilder/app_builder_client.go | 34 ++- go/appbuilder/app_builder_client_data.go | 19 +- go/appbuilder/app_builder_client_test.go | 8 +- go/appbuilder/config.go | 275 +++++++++++------------ go/appbuilder/dataset.go | 13 +- go/appbuilder/dataset_data.go | 2 +- go/appbuilder/go.mod | 13 +- go/appbuilder/go.sum | 17 -- go/appbuilder/knowledge_base.go | 35 ++- go/appbuilder/knowledge_base_data.go | 2 +- go/appbuilder/rag_data.go | 3 +- go/appbuilder/util.go | 4 +- 14 files changed, 204 insertions(+), 239 deletions(-) diff --git a/go/appbuilder/agent_builder.go b/go/appbuilder/agent_builder.go index bd6a5012a..a78791f1d 100644 --- a/go/appbuilder/agent_builder.go +++ b/go/appbuilder/agent_builder.go @@ -26,7 +26,6 @@ import ( "os" "path/filepath" "time" - "io/ioutil" ) // Deprecated: 请使用AppBuilderClient 代替 AgentBuilder @@ -74,11 +73,11 @@ func (t *AgentBuilder) CreateConversation() (string, error) { if err != nil { return "", fmt.Errorf("requestID=%s, err=%v", requestID, err) } - data, err = ioutil.ReadAll(resp.Body) + data, err = io.ReadAll(resp.Body) if err != nil { return "", fmt.Errorf("requestID=%s, err=%v", requestID, err) } - rsp := make(map[string]interface{}) + rsp := make(map[string]any) if err := json.Unmarshal(data, &rsp); err != nil { return "", fmt.Errorf("requestID=%s, err=%v", requestID, err) } @@ -126,11 +125,11 @@ func (t *AgentBuilder) UploadLocalFile(conversationID string, filePath string) ( if err != nil { return "", fmt.Errorf("requestID=%s, err=%v", requestID, err) } - body, err := ioutil.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) if err != nil { return "", fmt.Errorf("requestID=%s, err=%v", requestID, err) } - rsp := make(map[string]interface{}) + rsp := make(map[string]any) if err := json.Unmarshal(body, &rsp); err != nil { return "", fmt.Errorf("requestID=%s, err=%v", requestID, err) } @@ -145,7 +144,7 @@ func (t *AgentBuilder) Run(conversationID string, query string, fileIDS []string if len(conversationID) == 0 { return nil, errors.New("conversationID mustn't be empty") } - m := map[string]interface{}{"app_id": t.appID, + m := map[string]any{"app_id": t.appID, "conversation_id": conversationID, "query": query, "file_ids": fileIDS, diff --git a/go/appbuilder/agent_builder_data.go b/go/appbuilder/agent_builder_data.go index fffe93eeb..e810154d1 100644 --- a/go/appbuilder/agent_builder_data.go +++ b/go/appbuilder/agent_builder_data.go @@ -20,7 +20,6 @@ import ( "io" "reflect" "strings" - "io/ioutil" ) func (t *AgentBuilderAnswer) transform(inp *AgentBuilderRawResponse) { @@ -59,11 +58,11 @@ type AgentBuilderStreamIterator struct { func (t *AgentBuilderStreamIterator) Next() (*AgentBuilderAnswer, error) { data, err := t.r.ReadMessageLine() - if err != nil && !(err ==io.EOF) { + if err != nil && !(err == io.EOF) { t.body.Close() return nil, fmt.Errorf("requestID=%s, err=%v", t.requestID, err) } - if err != nil && err ==io.EOF { + if err != nil && err == io.EOF { t.body.Close() return nil, err } @@ -89,7 +88,7 @@ type AgentBuilderOnceIterator struct { } func (t *AgentBuilderOnceIterator) Next() (*AgentBuilderAnswer, error) { - data, err := ioutil.ReadAll(t.body) + data, err := io.ReadAll(t.body) if err != nil { return nil, fmt.Errorf("requestID=%s, err=%v", t.requestID, err) } diff --git a/go/appbuilder/app_builder_client.go b/go/appbuilder/app_builder_client.go index 760a7c00f..93548dedc 100644 --- a/go/appbuilder/app_builder_client.go +++ b/go/appbuilder/app_builder_client.go @@ -28,7 +28,6 @@ import ( "path/filepath" "strconv" "time" - "io/ioutil" ) func GetAppList(req GetAppListRequest, config *SDKConfig) ([]App, error) { @@ -44,7 +43,7 @@ func GetAppList(req GetAppListRequest, config *SDKConfig) ([]App, error) { header.Set("Content-Type", "application/json") request.Header = header - reqMap := make(map[string]interface{}) + reqMap := make(map[string]any) reqJson, _ := json.Marshal(req) json.Unmarshal(reqJson, &reqMap) params := url.Values{} @@ -75,7 +74,7 @@ func GetAppList(req GetAppListRequest, config *SDKConfig) ([]App, error) { if err != nil { return nil, fmt.Errorf("requestID=%s, err=%v", requestID, err) } - data, err := ioutil.ReadAll(resp.Body) + data, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("requestID=%s, err=%v", requestID, err) } @@ -109,11 +108,11 @@ type AppBuilderClient struct { // 在 AppBuilderClient 结构体中添加 Getter 方法 func (t *AppBuilderClient) GetSdkConfig() *SDKConfig { - return t.sdkConfig + return t.sdkConfig } func (t *AppBuilderClient) GetClient() HTTPClient { - return t.client + return t.client } type HTTPClient interface { @@ -148,7 +147,7 @@ func (t *AppBuilderClient) CreateConversation() (string, error) { if err != nil { return "", fmt.Errorf("requestID=%s, err=%v", requestID, err) } - rsp := make(map[string]interface{}) + rsp := make(map[string]any) if err := json.Unmarshal(data, &rsp); err != nil { return "", fmt.Errorf("requestID=%s, err=%v", requestID, err) } @@ -159,7 +158,6 @@ func (t *AppBuilderClient) CreateConversation() (string, error) { return val.(string), nil } - func (t *AppBuilderClient) UploadLocalFile(conversationID string, filePath string) (string, error) { var data bytes.Buffer w := multipart.NewWriter(&data) @@ -197,11 +195,11 @@ func (t *AppBuilderClient) UploadLocalFile(conversationID string, filePath strin if err != nil { return "", fmt.Errorf("requestID=%s, err=%v", requestID, err) } - body, err := ioutil.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) if err != nil { return "", fmt.Errorf("requestID=%s, err=%v", requestID, err) } - rsp := make(map[string]interface{}) + rsp := make(map[string]any) if err := json.Unmarshal(body, &rsp); err != nil { return "", fmt.Errorf("requestID=%s, err=%v", requestID, err) } @@ -216,12 +214,12 @@ func (t *AppBuilderClient) Run(conversationID string, query string, fileIDS []st if len(conversationID) == 0 { return nil, errors.New("conversationID mustn't be empty") } - m := map[string]interface{}{ - "app_id": t.appID, - "conversation_id": conversationID, - "query": query, - "file_ids": fileIDS, - "stream": stream, + m := map[string]any{ + "app_id": t.appID, + "conversation_id": conversationID, + "query": query, + "file_ids": fileIDS, + "stream": stream, } request := http.Request{} @@ -237,7 +235,7 @@ func (t *AppBuilderClient) Run(conversationID string, query string, fileIDS []st request.Header = header data, _ := json.Marshal(m) request.Body = NopCloser(bytes.NewReader(data)) - request.ContentLength = int64(len(data)) //手动设置长度 + request.ContentLength = int64(len(data)) // 手动设置长度 t.sdkConfig.BuildCurlCommand(&request) @@ -276,8 +274,8 @@ func (t *AppBuilderClient) RunWithToolCall(req AppBuilderClientRunRequest) (AppB request.Header = header data, _ := json.Marshal(req) request.Body = NopCloser(bytes.NewReader(data)) - request.ContentLength = int64(len(data)) //手动设置长度 - + request.ContentLength = int64(len(data)) // 手动设置长度 + t.sdkConfig.BuildCurlCommand(&request) resp, err := t.client.Do(&request) if err != nil { diff --git a/go/appbuilder/app_builder_client_data.go b/go/appbuilder/app_builder_client_data.go index ceb5a7557..0ef8830ec 100644 --- a/go/appbuilder/app_builder_client_data.go +++ b/go/appbuilder/app_builder_client_data.go @@ -20,7 +20,6 @@ import ( "io" "reflect" "strings" - "io/ioutil" ) const ( @@ -60,9 +59,9 @@ type Tool struct { } type Function struct { - Name string `json:"name"` - Description string `json:"description"` - Parameters map[string]interface{} `json:"parameters"` + Name string `json:"name"` + Description string `json:"description"` + Parameters map[string]any `json:"parameters"` } type ToolOutput struct { @@ -111,7 +110,7 @@ type Event struct { EventType string ContentType string Usage Usage - Detail interface{} // 将any替换为interface{} + Detail any // 将any替换为interface{} ToolCalls []ToolCall } @@ -123,7 +122,7 @@ type ToolCall struct { type FunctionCallOption struct { Name string `json:"name"` - Arguments map[string]interface{} `json:"arguments"` + Arguments map[string]any `json:"arguments"` } type TextDetail struct { @@ -153,7 +152,7 @@ type Reference struct { } type FunctionCallDetail struct { - Text interface{} `json:"text"` + Text any `json:"text"` Image string `json:"image"` Audio string `json:"audio"` Video string `json:"video"` @@ -226,7 +225,7 @@ func (t *AppBuilderClientAnswer) transform(inp *AppBuilderClientRawResponse) { Usage: c.Usage, Detail: c.Outputs, ToolCalls: c.ToolCalls} - //这部分新改的 + // 这部分新改的 tp, ok := TypeToStruct[ev.ContentType] if !ok { tp = reflect.TypeOf(DefaultDetail{}) @@ -234,7 +233,7 @@ func (t *AppBuilderClientAnswer) transform(inp *AppBuilderClientRawResponse) { v := reflect.New(tp) _ = json.Unmarshal(c.Outputs, v.Interface()) ev.Detail = v.Elem().Interface() - //这部分新改的 + // 这部分新改的 t.Events = append(t.Events, ev) } } @@ -285,7 +284,7 @@ type AppBuilderClientOnceIterator struct { } func (t *AppBuilderClientOnceIterator) Next() (*AppBuilderClientAnswer, error) { - data, err := ioutil.ReadAll(t.body) + data, err := io.ReadAll(t.body) if err != nil { return nil, fmt.Errorf("requestID=%s, err=%v", t.requestID, err) } diff --git a/go/appbuilder/app_builder_client_test.go b/go/appbuilder/app_builder_client_test.go index a0062fe03..2405ddc1e 100644 --- a/go/appbuilder/app_builder_client_test.go +++ b/go/appbuilder/app_builder_client_test.go @@ -86,17 +86,17 @@ func TestAppBuilderClientRunWithToolCall(t *testing.T) { t.Fatalf("create conversation failed: %v", err) } - parameters := make(map[string]interface{}) + parameters := make(map[string]any) - location := make(map[string]interface{}) + location := make(map[string]any) location["type"] = "string" location["description"] = "省,市名,例如:河北省" - unit := make(map[string]interface{}) + unit := make(map[string]any) unit["type"] = "string" unit["enum"] = []string{"摄氏度", "华氏度"} - properties := make(map[string]interface{}) + properties := make(map[string]any) properties["location"] = location properties["unit"] = unit diff --git a/go/appbuilder/config.go b/go/appbuilder/config.go index 12e717c92..c33e55cf5 100644 --- a/go/appbuilder/config.go +++ b/go/appbuilder/config.go @@ -15,188 +15,187 @@ package appbuilder import ( - "fmt" - "io" - "log" - "net/http" - "net/url" - "os" - "path" - "strings" - "io/ioutil" - "github.com/google/uuid" + "fmt" + "io" + "log" + "net/http" + "net/url" + "os" + "path" + "strings" + + "github.com/google/uuid" ) const ( - GatewayURL = "GATEWAY_URL" - GatewayURLV2 = "GATEWAY_URL_V2" - SecretKey = "APPBUILDER_TOKEN" - ConsoleOpenAPIVersion = "CONSOLE_OPENAPI_VERSION" - ConsoleOpenAPIPrefix = "CONSOLE_OPENAPI_PREFIX" - SecretKeyPrefix = "SECRET_KEY_PREFIX" - - DefaultSecretKeyPrefix = "Bearer" - DefaultGatewayURL = "https://appbuilder.baidu.com" - DefaultGatewayURLV2 = "https://qianfan.baidubce.com" - DefaultConsoleOpenAPIVersion = "/v2" - DefaultConsoleOpenAPIPrefix = "" + GatewayURL = "GATEWAY_URL" + GatewayURLV2 = "GATEWAY_URL_V2" + SecretKey = "APPBUILDER_TOKEN" + ConsoleOpenAPIVersion = "CONSOLE_OPENAPI_VERSION" + ConsoleOpenAPIPrefix = "CONSOLE_OPENAPI_PREFIX" + SecretKeyPrefix = "SECRET_KEY_PREFIX" + + DefaultSecretKeyPrefix = "Bearer" + DefaultGatewayURL = "https://appbuilder.baidu.com" + DefaultGatewayURLV2 = "https://qianfan.baidubce.com" + DefaultConsoleOpenAPIVersion = "/v2" + DefaultConsoleOpenAPIPrefix = "" ) type SDKConfig struct { - GatewayURL string - GatewayURLV2 string - ConsoleOpenAPIVersion string - ConsoleOpenAPIPrefix string - SecretKey string - HTTPClient HTTPClient // custom HTTP Client, optional - logger *log.Logger + GatewayURL string + GatewayURLV2 string + ConsoleOpenAPIVersion string + ConsoleOpenAPIPrefix string + SecretKey string + HTTPClient HTTPClient // custom HTTP Client, optional + logger *log.Logger } func NewSDKConfig(gatewayURL, secretKey string) (*SDKConfig, error) { - gatewayURL = getEnvWithDefault(GatewayURL, gatewayURL, DefaultGatewayURL) - gatewayURLV2 := getEnvWithDefault(GatewayURLV2, "", DefaultGatewayURLV2) - openAPIVersion := getEnvWithDefault(ConsoleOpenAPIVersion, "", DefaultConsoleOpenAPIVersion) - openAPIPrefix := getEnvWithDefault(ConsoleOpenAPIPrefix, "", DefaultConsoleOpenAPIPrefix) - - secretKey = getEnvWithDefault(SecretKey, secretKey, "") - if len(secretKey) == 0 { - log.Println("Error: secret key is empty") - } - secretKeyPrefix := getEnvWithDefault(SecretKeyPrefix, "", DefaultSecretKeyPrefix) - if !strings.HasPrefix(secretKey, secretKeyPrefix) { - secretKey = secretKeyPrefix + " " + secretKey - } - - sdkConfig := &SDKConfig{ - GatewayURL: gatewayURL, - GatewayURLV2: gatewayURLV2, - ConsoleOpenAPIVersion: openAPIVersion, - ConsoleOpenAPIPrefix: openAPIPrefix, - SecretKey: secretKey, - } - - logFile := os.Getenv("APPBUILDER_LOGFILE") - if len(logFile) > 0 { - f, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) - if err != nil { - sdkConfig.logger = log.New(os.Stdout, "", log.LstdFlags) - } else { - sdkConfig.logger = log.New(f, "", log.LstdFlags) - } - } else { - sdkConfig.logger = log.New(os.Stdout, "", log.LstdFlags) - } - - return sdkConfig, nil + gatewayURL = getEnvWithDefault(GatewayURL, gatewayURL, DefaultGatewayURL) + gatewayURLV2 := getEnvWithDefault(GatewayURLV2, "", DefaultGatewayURLV2) + openAPIVersion := getEnvWithDefault(ConsoleOpenAPIVersion, "", DefaultConsoleOpenAPIVersion) + openAPIPrefix := getEnvWithDefault(ConsoleOpenAPIPrefix, "", DefaultConsoleOpenAPIPrefix) + + secretKey = getEnvWithDefault(SecretKey, secretKey, "") + if len(secretKey) == 0 { + log.Println("Error: secret key is empty") + } + secretKeyPrefix := getEnvWithDefault(SecretKeyPrefix, "", DefaultSecretKeyPrefix) + if !strings.HasPrefix(secretKey, secretKeyPrefix) { + secretKey = secretKeyPrefix + " " + secretKey + } + + sdkConfig := &SDKConfig{ + GatewayURL: gatewayURL, + GatewayURLV2: gatewayURLV2, + ConsoleOpenAPIVersion: openAPIVersion, + ConsoleOpenAPIPrefix: openAPIPrefix, + SecretKey: secretKey, + } + + logFile := os.Getenv("APPBUILDER_LOGFILE") + if len(logFile) > 0 { + f, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) + if err != nil { + sdkConfig.logger = log.New(os.Stdout, "", log.LstdFlags) + } else { + sdkConfig.logger = log.New(f, "", log.LstdFlags) + } + } else { + sdkConfig.logger = log.New(os.Stdout, "", log.LstdFlags) + } + + return sdkConfig, nil } func getEnvWithDefault(key, paramValue, defaultValue string) string { - if paramValue != "" { - return paramValue - } - - v := os.Getenv(key) - if v == "" { - return defaultValue - } - return v + if paramValue != "" { + return paramValue + } + + v := os.Getenv(key) + if v == "" { + return defaultValue + } + return v } func (t *SDKConfig) AuthHeader() http.Header { - header := t.authHeader() - header.Set("X-Appbuilder-Authorization", t.SecretKey) - t.logger.Printf("Auth Header %v", header) - return header + header := t.authHeader() + header.Set("X-Appbuilder-Authorization", t.SecretKey) + t.logger.Printf("Auth Header %v", header) + return header } // AuthHeaderV2 适配OpenAPI,当前仅AgentBuilder使用 func (t *SDKConfig) AuthHeaderV2() http.Header { - header := t.authHeader() - header.Set("Authorization", t.SecretKey) - t.logger.Printf("Auth Header %v", header) - return header + header := t.authHeader() + header.Set("Authorization", t.SecretKey) + t.logger.Printf("Auth Header %v", header) + return header } func (t *SDKConfig) authHeader() http.Header { - header := make(http.Header) - platform := os.Getenv("APPBUILDER_SDK_PLATFORM") - if platform == "" { - platform = "unknown" - } - header.Set("X-Appbuilder-Origin", "appbuilder_sdk") - header.Set("X-Appbuilder-Sdk-Config", "{\"appbuilder_sdk_version\":\"0.9.0\",\"appbuilder_sdk_language\":\"go\",\"appbuilder_sdk_platform\":\""+platform+"\"}") - header.Set("X-Appbuilder-Request-Id", uuid.New().String()) - return header + header := make(http.Header) + platform := os.Getenv("APPBUILDER_SDK_PLATFORM") + if platform == "" { + platform = "unknown" + } + header.Set("X-Appbuilder-Origin", "appbuilder_sdk") + header.Set("X-Appbuilder-Sdk-Config", "{\"appbuilder_sdk_version\":\"0.9.0\",\"appbuilder_sdk_language\":\"go\",\"appbuilder_sdk_platform\":\""+platform+"\"}") + header.Set("X-Appbuilder-Request-Id", uuid.New().String()) + return header } func (t *SDKConfig) ServiceURL(suffix string) (*url.URL, error) { - absolutePath := t.GatewayURL - if !strings.HasSuffix(absolutePath, "/") { - absolutePath += "/" - } - if strings.HasPrefix(suffix, "/") { - suffix = strings.TrimPrefix(suffix, "/") - } - absolutePath += suffix - - return t.formatURL(absolutePath, suffix) + absolutePath := t.GatewayURL + if !strings.HasSuffix(absolutePath, "/") { + absolutePath += "/" + } + if strings.HasPrefix(suffix, "/") { + suffix = strings.TrimPrefix(suffix, "/") + } + absolutePath += suffix + + return t.formatURL(absolutePath, suffix) } - // ServiceURLV2 适配OpenAPI,当前仅AppbuilderClient使用 func (t *SDKConfig) ServiceURLV2(suffix string) (*url.URL, error) { - suffix = path.Join(t.ConsoleOpenAPIPrefix, t.ConsoleOpenAPIVersion, suffix) - return t.formatURL(t.GatewayURLV2, suffix) + suffix = path.Join(t.ConsoleOpenAPIPrefix, t.ConsoleOpenAPIVersion, suffix) + return t.formatURL(t.GatewayURLV2, suffix) } func (t *SDKConfig) formatURL(absolutePath, suffix string) (*url.URL, error) { - t.logger.Printf("Service URL %s", absolutePath) - url, err := url.Parse(absolutePath) - if err != nil { - return nil, err - } + t.logger.Printf("Service URL %s", absolutePath) + url, err := url.Parse(absolutePath) + if err != nil { + return nil, err + } - endpoint, err := url.Parse(suffix) - if err != nil { - return nil, err - } + endpoint, err := url.Parse(suffix) + if err != nil { + return nil, err + } - url = url.ResolveReference(endpoint) + url = url.ResolveReference(endpoint) - return url, nil + return url, nil } type nopCloser struct { - io.Reader + io.Reader } func (nopCloser) Close() error { return nil } func NopCloser(r io.Reader) io.ReadCloser { - return nopCloser{r} + return nopCloser{Reader: r} } func (t *SDKConfig) BuildCurlCommand(req *http.Request) { - curlCmd := fmt.Sprintf("curl -X %s -L '%v' \\\n", req.Method, req.URL.String()) - - for k, v := range req.Header { - header := fmt.Sprintf("-H '%v: %v' \\\n", k, v[0]) - curlCmd = fmt.Sprintf("%v %v", curlCmd, header) - } - - if req.Method == "POST" { - bodyBytes, err := ioutil.ReadAll(req.Body) - if err != nil { - t.logger.Println("Failed to read request body:", err) - return - } - req.Body.Close() - req.Body = NopCloser(strings.NewReader(string(bodyBytes))) - - body := fmt.Sprintf("-d '%v'", string(bodyBytes)) - curlCmd = fmt.Sprintf("%v %v", curlCmd, body) - } else if req.Method == "GET" || req.Method == "DELETE" { - curlCmd = strings.TrimSuffix(curlCmd, " \\\n") - } - fmt.Println("\n" + curlCmd + "\n") + curlCmd := fmt.Sprintf("curl -X %s -L '%v' \\\n", req.Method, req.URL.String()) + + for k, v := range req.Header { + header := fmt.Sprintf("-H '%v: %v' \\\n", k, v[0]) + curlCmd = fmt.Sprintf("%v %v", curlCmd, header) + } + + if req.Method == "POST" { + bodyBytes, err := io.ReadAll(req.Body) + if err != nil { + t.logger.Println("Failed to read request body:", err) + return + } + req.Body.Close() + req.Body = NopCloser(strings.NewReader(string(bodyBytes))) + + body := fmt.Sprintf("-d '%v'", string(bodyBytes)) + curlCmd = fmt.Sprintf("%v %v", curlCmd, body) + } else if req.Method == "GET" || req.Method == "DELETE" { + curlCmd = strings.TrimSuffix(curlCmd, " \\\n") + } + fmt.Println("\n" + curlCmd + "\n") } diff --git a/go/appbuilder/dataset.go b/go/appbuilder/dataset.go index cbcbefda9..5c11010de 100644 --- a/go/appbuilder/dataset.go +++ b/go/appbuilder/dataset.go @@ -25,7 +25,6 @@ import ( "os" "path/filepath" "time" - "io/ioutil" ) // Deprecated: 已废弃,请使用 NewKnowledgeBase @@ -69,7 +68,7 @@ func (t *Dataset) Create(name string) (string, error) { if err != nil { return "", fmt.Errorf("requestID=%s, err=%v", requestID, err) } - data, err = ioutil.ReadAll(resp.Body) + data, err = io.ReadAll(resp.Body) if err != nil { return "", fmt.Errorf("requestID=%s, err=%v", requestID, err) } @@ -144,7 +143,7 @@ func (t *Dataset) uploadLocalFile(localFilePath string) (string, error) { if err != nil { return "", fmt.Errorf("requestID=%s, err=%v", requestID, err) } - respData, err := ioutil.ReadAll(resp.Body) + respData, err := io.ReadAll(resp.Body) if err != nil { return "", fmt.Errorf("requestID=%s, err=%v", requestID, err) } @@ -170,7 +169,7 @@ func (t *Dataset) addFileToDataset(datasetID string, fileID []string) ([]string, request.Method = "POST" header.Set("Content-Type", "application/json") request.Header = header - m := map[string]interface{}{ + m := map[string]any{ "file_ids": fileID, "dataset_id": datasetID} data, _ := json.Marshal(m) @@ -184,7 +183,7 @@ func (t *Dataset) addFileToDataset(datasetID string, fileID []string) ([]string, if err != nil { return nil, fmt.Errorf("requestID=%s, err=%v", requestID, err) } - respData, err := ioutil.ReadAll(resp.Body) + respData, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("requestID=%s, err=%v", requestID, err) } @@ -209,7 +208,7 @@ func (t *Dataset) ListDocument(datasetID string, page int, limit int, keyword st request.Method = "POST" header.Set("Content-Type", "application/json") request.Header = header - m := map[string]interface{}{ + m := map[string]any{ "dataset_id": datasetID, "page": page, "limit": limit, @@ -227,7 +226,7 @@ func (t *Dataset) ListDocument(datasetID string, page int, limit int, keyword st if err != nil { return nil, fmt.Errorf("requestID=%s, err=%v", requestID, err) } - respData, err := ioutil.ReadAll(resp.Body) + respData, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("requestID=%s, err=%v", requestID, err) } diff --git a/go/appbuilder/dataset_data.go b/go/appbuilder/dataset_data.go index 25e4bf807..895d3f23e 100644 --- a/go/appbuilder/dataset_data.go +++ b/go/appbuilder/dataset_data.go @@ -17,7 +17,7 @@ package appbuilder type DatasetResponse struct { Code int `json:"code"` Message string `json:"message"` - Result map[string]interface{} `json:"result"` + Result map[string]any `json:"result"` } type DatasetBindResponse struct { diff --git a/go/appbuilder/go.mod b/go/appbuilder/go.mod index fa6ef46ca..998b916d1 100644 --- a/go/appbuilder/go.mod +++ b/go/appbuilder/go.mod @@ -1,14 +1,5 @@ module github.com/baidubce/app-builder/go/appbuilder -go 1.19 +go 1.18 -require ( - github.com/google/uuid v1.6.0 - github.com/rs/zerolog v1.33.0 -) - -require ( - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - golang.org/x/sys v0.21.0 // indirect -) +require github.com/google/uuid v1.6.0 diff --git a/go/appbuilder/go.sum b/go/appbuilder/go.sum index a79978bb9..7790d7c3e 100644 --- a/go/appbuilder/go.sum +++ b/go/appbuilder/go.sum @@ -1,19 +1,2 @@ -github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= -github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/go/appbuilder/knowledge_base.go b/go/appbuilder/knowledge_base.go index 7828350c7..61ba25fb5 100644 --- a/go/appbuilder/knowledge_base.go +++ b/go/appbuilder/knowledge_base.go @@ -27,7 +27,6 @@ import ( "path/filepath" "strconv" "time" - "io/ioutil" "github.com/google/uuid" ) @@ -75,7 +74,7 @@ func (t *KnowledgeBase) CreateDocument(req CreateDocumentRequest) (CreateDocumen if err != nil { return CreateDocumentResponse{}, fmt.Errorf("requestID=%s, err=%v", requestID, err) } - data, err = ioutil.ReadAll(resp.Body) + data, err = io.ReadAll(resp.Body) if err != nil { return CreateDocumentResponse{}, fmt.Errorf("requestID=%s, err=%v", requestID, err) } @@ -99,7 +98,7 @@ func (t *KnowledgeBase) DeleteDocument(req DeleteDocumentRequest) error { return err } - reqMap := make(map[string]interface{}) + reqMap := make(map[string]any) reqJson, _ := json.Marshal(req) json.Unmarshal(reqJson, &reqMap) params := url.Values{} @@ -141,7 +140,7 @@ func (t *KnowledgeBase) GetDocumentList(req GetDocumentListRequest) (*GetDocumen return nil, err } - reqMap := make(map[string]interface{}) + reqMap := make(map[string]any) reqJson, _ := json.Marshal(req) json.Unmarshal(reqJson, &reqMap) params := url.Values{} @@ -175,7 +174,7 @@ func (t *KnowledgeBase) GetDocumentList(req GetDocumentListRequest) (*GetDocumen if err != nil { return nil, fmt.Errorf("requestID=%s, err=%v", requestID, err) } - respData, err := ioutil.ReadAll(resp.Body) + respData, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("requestID=%s, err=%v", requestID, err) } @@ -222,7 +221,7 @@ func (t *KnowledgeBase) UploadFile(localFilePath string) (string, error) { if err != nil { return "", fmt.Errorf("requestID=%s, err=%v", requestID, err) } - respData, err := ioutil.ReadAll(resp.Body) + respData, err := io.ReadAll(resp.Body) if err != nil { return "", fmt.Errorf("requestID=%s, err=%v", requestID, err) } @@ -263,7 +262,7 @@ func (t *KnowledgeBase) CreateKnowledgeBase(req KnowledgeBaseDetail) (KnowledgeB if err != nil { return KnowledgeBaseDetail{}, fmt.Errorf("requestID=%s, err=%v", requestID, err) } - data, err = ioutil.ReadAll(resp.Body) + data, err = io.ReadAll(resp.Body) if err != nil { return KnowledgeBaseDetail{}, fmt.Errorf("requestID=%s, err=%v", requestID, err) } @@ -300,7 +299,7 @@ func (t *KnowledgeBase) GetKnowledgeBaseDetail(knowledgeBaseID string) (Knowledg if err != nil { return KnowledgeBaseDetail{}, fmt.Errorf("requestID=%s, err=%v", requestID, err) } - data, err = ioutil.ReadAll(resp.Body) + data, err = io.ReadAll(resp.Body) if err != nil { return KnowledgeBaseDetail{}, fmt.Errorf("requestID=%s, err=%v", requestID, err) } @@ -335,7 +334,7 @@ func (t *KnowledgeBase) GetKnowledgeBaseList(req GetKnowledgeBaseListRequest) (G if err != nil { return GetKnowledgeBaseListResponse{}, fmt.Errorf("requestID=%s, err=%v", requestID, err) } - data, err = ioutil.ReadAll(resp.Body) + data, err = io.ReadAll(resp.Body) if err != nil { return GetKnowledgeBaseListResponse{}, fmt.Errorf("requestID=%s, err=%v", requestID, err) } @@ -373,7 +372,7 @@ func (t *KnowledgeBase) ModifyKnowledgeBase(req ModifyKnowlegeBaseRequest) error if err != nil { return fmt.Errorf("requestID=%s, err=%v", requestID, err) } - _, err = ioutil.ReadAll(resp.Body) + _, err = io.ReadAll(resp.Body) if err != nil { return fmt.Errorf("requestID=%s, err=%v", requestID, err) } @@ -417,7 +416,7 @@ func (t *KnowledgeBase) deleteKnowledgeBase(knowledgeBaseID string, clientToken if err != nil { return fmt.Errorf("requestID=%s, err=%v", requestID, err) } - _, err = ioutil.ReadAll(resp.Body) + _, err = io.ReadAll(resp.Body) if err != nil { return fmt.Errorf("requestID=%s, err=%v", requestID, err) } @@ -451,7 +450,7 @@ func (t *KnowledgeBase) CreateDocuments(req CreateDocumentsRequest) error { if err != nil { return fmt.Errorf("requestID=%s, err=%v", requestID, err) } - _, err = ioutil.ReadAll(resp.Body) + _, err = io.ReadAll(resp.Body) if err != nil { return fmt.Errorf("requestID=%s, err=%v", requestID, err) } @@ -508,7 +507,7 @@ func (t *KnowledgeBase) UploadDocuments(localFilePath string, req CreateDocument if err != nil { return fmt.Errorf("requestID=%s, err=%v", requestID, err) } - _, err = ioutil.ReadAll(resp.Body) + _, err = io.ReadAll(resp.Body) if err != nil { return fmt.Errorf("requestID=%s, err=%v", requestID, err) } @@ -542,7 +541,7 @@ func (t *KnowledgeBase) CreateChunk(req CreateChunkRequest) (string, error) { if err != nil { return "", fmt.Errorf("requestID=%s, err=%v", requestID, err) } - data, err = ioutil.ReadAll(resp.Body) + data, err = io.ReadAll(resp.Body) if err != nil { return "", fmt.Errorf("requestID=%s, err=%v", requestID, err) } @@ -581,7 +580,7 @@ func (t *KnowledgeBase) ModifyChunk(req ModifyChunkRequest) error { if err != nil { return fmt.Errorf("requestID=%s, err=%v", requestID, err) } - data, err = ioutil.ReadAll(resp.Body) + data, err = io.ReadAll(resp.Body) if err != nil { return fmt.Errorf("requestID=%s, err=%v", requestID, err) } @@ -631,7 +630,7 @@ func (t *KnowledgeBase) deleteChunk(chunkID string, clientToken string) error { if err != nil { return fmt.Errorf("requestID=%s, err=%v", requestID, err) } - data, err = ioutil.ReadAll(resp.Body) + data, err = io.ReadAll(resp.Body) if err != nil { return fmt.Errorf("requestID=%s, err=%v", requestID, err) } @@ -670,7 +669,7 @@ func (t *KnowledgeBase) DescribeChunk(chunkID string) (DescribeChunkResponse, er if err != nil { return DescribeChunkResponse{}, fmt.Errorf("requestID=%s, err=%v", requestID, err) } - data, err = ioutil.ReadAll(resp.Body) + data, err = io.ReadAll(resp.Body) if err != nil { return DescribeChunkResponse{}, fmt.Errorf("requestID=%s, err=%v", requestID, err) } @@ -706,7 +705,7 @@ func (t *KnowledgeBase) DescribeChunks(req DescribeChunksRequest) (DescribeChunk if err != nil { return DescribeChunksResponse{}, fmt.Errorf("requestID=%s, err=%v", requestID, err) } - data, err = ioutil.ReadAll(resp.Body) + data, err = io.ReadAll(resp.Body) if err != nil { return DescribeChunksResponse{}, fmt.Errorf("requestID=%s, err=%v", requestID, err) } diff --git a/go/appbuilder/knowledge_base_data.go b/go/appbuilder/knowledge_base_data.go index b97eb13da..518499d0a 100644 --- a/go/appbuilder/knowledge_base_data.go +++ b/go/appbuilder/knowledge_base_data.go @@ -65,7 +65,7 @@ type GetDocumentListResponse struct { type Document struct { ID string `json:"id"` Name string `json:"name"` - CreatedAt interface{} `json:"created_at"` + CreatedAt any `json:"created_at"` WordCount int64 `json:"word_count"` Enabled bool `json:"enabled"` Meta DocumentMeta `json:"meta"` diff --git a/go/appbuilder/rag_data.go b/go/appbuilder/rag_data.go index 7a5780a4c..d76416d34 100644 --- a/go/appbuilder/rag_data.go +++ b/go/appbuilder/rag_data.go @@ -19,7 +19,6 @@ import ( "fmt" "io" "strings" - "io/ioutil" ) type RAGRunResponse struct { @@ -109,7 +108,7 @@ func (t *RAGOnceIterator) Next() (*RAGAnswer, error) { if t.eoi { return nil, io.EOF } - data, err := ioutil.ReadAll(t.body) + data, err := io.ReadAll(t.body) if err != nil { return nil, fmt.Errorf("requestID=%s, err=%v", t.requestID, err) } diff --git a/go/appbuilder/util.go b/go/appbuilder/util.go index 1d1fa6d47..26d6f2c22 100644 --- a/go/appbuilder/util.go +++ b/go/appbuilder/util.go @@ -18,8 +18,8 @@ import ( "bufio" "errors" "fmt" + "io" "net/http" - "io/ioutil" ) type SSEEvent struct { @@ -34,7 +34,7 @@ func checkHTTPResponse(rsp *http.Response) (string, error) { return requestID, nil } - data, err := ioutil.ReadAll(rsp.Body) + data, err := io.ReadAll(rsp.Body) if err != nil { return requestID, err } From 52b7b6767f47f687880ecde8234fb92ef032b302 Mon Sep 17 00:00:00 2001 From: Chengmo Date: Tue, 10 Sep 2024 20:17:07 +0800 Subject: [PATCH 04/10] Update Tool Call cookbook (#508) * base code * update console dataset * update * add base code * update link * update * update --- README.md | 1 + cookbooks/README.md | 3 +- .../end2end_application/agent/tool_call.ipynb | 782 ++++++++++++++++++ .../{ => rag}/console_dataset.ipynb | 0 .../dataset\347\244\272\344\276\213.png" | Bin docs/quick_start/changelog.md | 2 +- 6 files changed, 786 insertions(+), 2 deletions(-) create mode 100644 cookbooks/end2end_application/agent/tool_call.ipynb rename cookbooks/end2end_application/{ => rag}/console_dataset.ipynb (100%) rename "cookbooks/end2end_application/image/dataset\347\244\272\344\276\213.png" => "cookbooks/end2end_application/rag/image/dataset\347\244\272\344\276\213.png" (100%) diff --git a/README.md b/README.md index 9add32a99..61888f8ee 100644 --- a/README.md +++ b/README.md @@ -263,6 +263,7 @@ Hook: | 基础能力组件 | [基础组件服务化](/cookbooks/components/agent_runtime.ipynb) | 基础组件可通过flask实现服务化部署 或 通过chainlit实现可交互的前端部署,集成到您的系统中 | | 流程编排 | [Assistant SDK](/cookbooks/pipeline/assistant_function_call.ipynb) | 学习如何纯代码态搭建一个Agent应用,并实现自定义工作流程及FunctionCall | | 端到端应用 | [AppBuilder Client SDK](/cookbooks/agent_builder.ipynb) | 使用AppBuilder网页端创建并发布一个Agent应用后,通过AppBuilderClient SDK集成到你的系统中 | +| 端到端应用 | [通过AppBuilder-ToolCall功能实现端云组件联动的Agent](/cookbooks/end2end_application/agent/tool_call.ipynb) | 学习Agent、FunctionCall的知识,并构造调用本地组件的Agent | | 端到端应用 | [简历筛选小助手](/cookbooks/end2end_application/rag/rag.ipynb) | 通过对本地简历库的简历进行解析、切片、创建索引,实现基于JD进行简历筛选,并对筛选的Top1简历进行总结 | | 端到端应用 | [企业级问答系统](/cookbooks/end2end_application/rag/qa_system_2_dialogue.ipynb) | 学习如何通过SDK与网页平台搭配,实现离线知识库生产与在线问答 | | 进阶应用 | [使用appbuilder_bce_deploy部署公有云服务](/cookbooks/advanced_application/cloud_deploy.ipynb) | 一键将自己的服务部署到百度智能云,部署后可以自动生成公网ip,联动工作流的API节点 | diff --git a/cookbooks/README.md b/cookbooks/README.md index d799811e2..3fc0c2c10 100644 --- a/cookbooks/README.md +++ b/cookbooks/README.md @@ -28,7 +28,8 @@ - [RAG应用-问答助手](/cookbooks/end2end_application/rag/console_rag.ipynb) - [RAG应用-企业问答系统-离线知识生产](/cookbooks/end2end_application/rag/qa_system_1_dataset.ipynb) - [RAG应用-企业问答系统-在线问答流程](/cookbooks/end2end_application/rag/qa_system_2_dialogue.ipynb) -- [知识库操作助手](/cookbooks/end2end_application/console_dataset.ipynb) +- [知识库操作助手](/cookbooks/end2end_application/rag/console_dataset.ipynb) +- [通过AppBuilder-ToolCall功能实现端云组件联动的Agent](/cookbooks/end2end_application/agent/tool_call.ipynb) ### 进阶应用 - [公有云部署](/cookbooks/advanced_application/cloud_deploy.ipynb) diff --git a/cookbooks/end2end_application/agent/tool_call.ipynb b/cookbooks/end2end_application/agent/tool_call.ipynb new file mode 100644 index 000000000..0946ed830 --- /dev/null +++ b/cookbooks/end2end_application/agent/tool_call.ipynb @@ -0,0 +1,782 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\"drawing\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 前言 - 学习本项目你可以获得什么\n", + "- 理论学习:了解AIAgent的基础知识\n", + "- 上手实操:深入了解Agent中的FunctionCall运行流程\n", + "- 上手实操:入门百度智能云千帆AppBuilder,在十分钟内打造一个个性化AIAgent\n", + "- 上手实操:使用AppBuilder-SDK打造一个端云组件联动的进阶Agent" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. 项目背景\n", + "\n", + "### 1.1、 什么是AppBuilder\n", + "[百度智能云千帆AppBuilder](https://appbuilder.cloud.baidu.com/)(以下简称AppBuilder)是基于大模型搭建AI原生应用的工作台,旨在降低AI原生应用的开发门槛,赋能开发者和企业快速实现应用搭建。\n", + "\n", + "平台提供了RAG(检索增强生成)、Agent(智能体)等应用框架,内置了文档问答、表格问答、多轮对话、生成创作等多种应用组件,还包括百度搜索和百度地图等特色组件,以及文本处理、图像处理和语音处理等传统AI组件,支持零代码、低代码、全代码三种开发方式,满足不同开发能力的开发者和企业的场景需求。\n", + "\n", + "### 1.2、 什么是AppBuilder-SDK\n", + "\n", + "[百度智能云千帆AppBuilder-SDK](https://github.com/baidubce/app-builder)(以下简称AB-SDK),百度智能云千帆AppBuilder-SDK是百度智能云千帆AppBuilder面向AI原生应用开发者提供的一站式开发平台的客户端SDK。\n", + "\n", + "\"drawing\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 2. 项目介绍 - 通过ToolCall实现端云组件联动的Agent\n", + "### 2.1、 什么是Agent\n", + "\n", + "AIAgent是能够感知环境,基于目标进行决策并执行动作的智能化应用。不同于传统人工智能应用(主要指以规则引擎、机器学习、深度学习等技术为核心)和RPA机器人,AIAgent能够基于目标和对现状能力的认知,在环境约束中,依赖特定资源和现有工具,找到行动规则并将行动拆解为必要的步骤,自主执行步骤,达成目标。\n", + "\n", + "AIAgent具备三个核心能力:独立思考、自主执行、持续迭代。\n", + "- 独立思考是指AlAgent能够根据给定任务目标和约束条件,进行任务规划和问题拆解,形成执行步骤(即工作流);\n", + "- 自主执行是指AlAgent能够调取各类组件和工具,按照执行步骤依次执行,实现任务目标;\n", + "- 持续选代是指AlAgent能够自动记录任务目标、工作流和执行结果,基于结果反馈,沉淀专家知识和案例。\n", + "\n", + "AICopilot、AIAgent、大模型等名词在各类文章上经常混淆,此处简要说明下三者的区别。大模型一般是指大模型技术,AlAgent和Al Copilot是基于大模型技术的智能化应用,AlAgent和AlCopilot在功能和场景上存在差别。\n", + "\n", + "自主性是AIAgent和AI Copilot之间最大的区别。AI Copilot是“副驾驶”,只是提供建议而非决策,AIAgent是“主驾驶”,需要真正做出决策并开展行动。\n", + "\n", + "\"drawing\"\n", + "\n", + "### 2.2、 什么是ToolCall\n", + "\n", + "解释该问题,需要了解以下的知识点:`Agent工具` -> `FunctionCall` - `ToolCall`\n", + "\n", + "AIAgent 有四大核心组件:记忆、规划、工具和执行。其中工具部分,与我们的开发关系最密切,在各类Agent开发平台/工具中,常被称为“组件”、\"插件\"、\"能力\"等.\n", + "\n", + "关于Agent的工具的定义与分类,如下图~\n", + "\n", + "\"drawing\"\n", + "\n", + "Agent使用工具的流程,一般称为`FunctionCall`,最早由OpenAI提出,并在[Assistant API](https://platform.openai.com/docs/assistants/overview)中广泛应用。\n", + "\n", + "\n", + "ToolCall,则是AppBuilder平台提出的一种进阶的FunctionCall,本质与OpenAI的FunctionCall一致,但具有以下两个特点:\n", + "\n", + "- **端云组件联动**: Agent 调用工具时,可以同时调用云端和本地组件。\n", + "\n", + "- **组件类型泛化**: AppBuilder在未来会支持多种类型组件,已经超出了Function的含义,例如数据库、记忆库、工作流等等\n", + "\n", + "\n", + "\n", + "### 2.3、 什么是端云组件联动,要解决什么问题\n", + "\n", + "我们首先从工具的执行位置出发展开~ 在使用如AppBuilder / Coze 等平台开发Agent时,我们可以使用很多平台组件广场中,官方提供的组件,这里组件开箱即用,非常方便。\n", + "\n", + "\"drawing\"\n", + "\n", + "但是存在一个问题,基于平台云端组件开发的应用,无法调用内网/局域网/私域的知识与能力,也无法与本地的工具进行联动,限制了Agent的灵活性。\n", + "\n", + "我们在解决实际业务问题时,常遇到需要访问内网链接API或本地/硬件功能的FunctionCall需求,AppBuilder ToolCall可以解决这个问题:\n", + "\n", + "* 1、用户可注册一个本地运行的组件到已发布的应用\n", + "* 2、由AppBuilder-Agent的云端思考模型进行规划和参数生成\n", + "* 3、用户基于生成的参数调用本地组件,并再上传运行结果\n", + "* 4、以此实现将本地组件能力嵌入到应用整体流程\n", + "\n", + "\n", + "\"drawing\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 3、ToolCall(FunctionCall)基础知识介绍\n", + "\n", + "### 3.1、Agent是如何调用Tool的\n", + "\n", + "我们可以将Agent的黑箱拆解为以下几个部分:\n", + "1. Agent的背景信息\n", + "2. Agent的输入信息\n", + "3. Agent的思考过程\n", + "4. Agent触发组件调用\n", + "5. Agent基于组件输出反思总结\n", + "\n", + "#### Agent的背景信息包含以下几个部分\n", + "- 角色定义描述(Prompt):定义Agent的角色\n", + "- 能力描述(Prompt):定义Agent可以干什么\n", + "- 工具描述(JsonSchema/Str):将工具的输入和输出,按照规范,定义为一段字符串,作为最终大模型Prompt的一部分\n", + "\n", + "#### Agent的输入信息包含以下几个部分\n", + "- 用户输入(Query/Prompt):用户输入的文本\n", + "- 对话相关的文件(File/Url):与本地对话相关的文件路径\n", + "\n", + "#### Agent的思考过程\n", + "AppBuilder-Agent会将背景信息与输入信息,拼接为最终的Prompt,然后调用大模型推理。\n", + "\n", + "Prompt的一个简单且直观的例子是:\n", + "\n", + "你是`{角色定义描述}`,你可以做以下事情:`{能力描述}`,你可以使用这些工具:`{工具描述-description}`,工具依赖的输入是:`{工具描述-paramters-properties-name}`,这些输入的格式分别是`{工具描述-paramters-properties-type}`。现在用户的问题是`{用户输入}`,与该问题相关的文件是`{对话相关的文件}`,请你解决用户的这个问题。\n", + "\n", + "#### Agent触发组件调用\n", + "\n", + "如果用户的query和组件能够解决的问题匹配,那么大模型就会尝试根据prompt里给出的工具的描述,从query中提炼出该次调用工具所需的参数,生成一个ToolCall命令,交给执行组件的模块去执行。\n", + "\n", + "例如,我们给出的组件能力是\"查找公司内指定人员的信息\",函数的参数名为\"name\"。当用户输入\"查找张三的信息\",大模型会从query中提炼出参数\"name=张三\"这个信息。\n", + "\n", + "\"drawing\"\n", + "\n", + "#### Agent基于组件输出反思总结\n", + "\n", + "组件运行模块执行组件后,会给出字符串形式的结果给到Agent,Agent会再次将结果拼接为Prompt,然后调用大模型推理。判断用户的需求是否已经解决。如果解决了,则经过一个对话模块,总结用户的需求,并生成一个对话记录。如果未解决,则继续调用大模型推理,尝试调用更多的工具,直到用户的需求被解决。\n", + "\n", + "### 3.2、开发者如何命令Agent调用本地Tool\n", + "\n", + "我们以AppBuilder-SDK中的AppBuilder-Client的基础代码为例,介绍开发者应该如何使用ToolCall功能\n", + "\n", + "\n", + "```python\n", + "import appbuilder\n", + "\n", + "# 实例化AppBuilderClient\n", + "app_client = appbuilder.AppBuilderClient(app_id)\n", + "conversation_id = app_client.create_conversation()\n", + "\n", + "# 第一次对话,输入原始的query 和 工具描述\n", + "message_1 = app_client.run(\n", + " conversation_id=conversation_id,\n", + " query=\"请问张三同学的生日是哪天?\",\n", + " tools=tools\n", + ")\n", + "tool_call = message_1.content.events[-1].tool_calls[-1]\n", + "tool_call_id = tool_call.id\n", + "\n", + "# 第二次对话,在本地执行组件后,上传组件的运行结果\n", + "tool_call_result = \"张三同学的生日是2008年8月8日\"\n", + "message_2 = app_client.run(\n", + " conversation_id=conversation_id,\n", + " tool_outputs=[{\n", + " \"tool_call_id\": tool_call_id,\n", + " \"output\": tool_call_result\n", + " }]\n", + ")\n", + "print(message_2.content)\n", + "```\n", + "\n", + "其中`AppBuilderClient`的`run`方法是核心,我们展开该函数的定义和参数介绍:\n", + "\n", + "`AppBuilderClient().run() -> Message`\n", + "\n", + "```python\n", + "def run(self, conversation_id: str,\n", + " query: str = \"\",\n", + " file_ids: list = [],\n", + " stream: bool = False,\n", + " tools: list[data_class.Tool] = None,\n", + " tool_outputs: list[data_class.ToolOutput] = None,\n", + " **kwargs\n", + " ) -> Message:\n", + " r\"\"\"\n", + " 参数:\n", + " query (str: 必须): query内容\n", + " conversation_id (str, 必须): 唯一会话ID,如需开始新的会话,请使用self.create_conversation创建新的会话\n", + " file_ids(list[str], 可选):\n", + " stream (bool, 可选): 为True时,流式返回,需要将message.content.answer拼接起来才是完整的回答;为False时,对应非流式返回\n", + " tools(list[data_class.Tools], 可选): 一个Tools组成的列表,其中每个Tools对应一个工具的配置, 默认为None\n", + " tool_outputs(list[data_class.ToolOutput], 可选): 工具输出列表,格式为list[ToolOutput], ToolOutputd内容为本地的工具执行结果,以自然语言/json dump str描述,默认为None\n", + " 返回: message (obj: `Message`): 对话结果.\n", + " \"\"\"\n", + " pass\n", + "```\n", + "\n", + "\n", + "| 参数名称 | 参数类型 | 是否必须 | 描述 | 示例值 |\n", + "| --------------- | ---------------- | -------- | ------------------------------------------------------------ | ----------------- |\n", + "| conversation_id | String | 是 | 会话ID | |\n", + "| query | String | 否 | query问题内容 | \"今天天气怎么样?\" |\n", + "| file_ids | list[String] | 否 | 对话可引用的文档ID | |\n", + "| stream | Bool | 否 | 为true时则流式返回,为false时则一次性返回所有内容, 推荐设为true,降低首token时延 | False |\n", + "| tools | List[Tool] | 否 | 一个列表,其中每个字典对应一个工具的配置 | |\n", + "| tools[0] | Tool | 否 | 工具配置 | |\n", + "| +type | String | 否 | 枚举:
**file_retrieval**: 知识库检索工具能够理解文档内容,支持用户针对文档内容的问答。
**code_interpreter**: 代码解释器, 代码解释器能够生成并执行代码,从而协助用户解决复杂问题,涵盖科学计算(包括普通数学计算题)、数据可视化、文件编辑处理(图片、PDF文档、视频、音频等)、文件格式转换(如WAV、MP3、text、SRT、PNG、jpg、MP4、GIF、MP3等)、数据分析&清洗&处理(文件以excel、csv格式为主)、机器学习&深度学习建模&自然语言处理等多个领域。
**function**: 支持fucntion call模式调用工具 | |\n", + "| +function | Function | 否 | Function工具描述
仅当**type为**`**function**` 时需要且必须填写 | |\n", + "| ++name | String | 否 | 函数名
只允许数字、大小写字母和中划线和下划线,最大长度为64个字符。一次运行中唯一。 | |\n", + "| ++description | String | 否 | 工具描述 | |\n", + "| ++parameters | Dict | 否 | 工具参数, json_schema格式 | |\n", + "| tool_outputs | List[ToolOutput] | 否 | 内容为本地的工具执行结果,以自然语言/json dump str描述 | |\n", + "| tool_outputs[0] | ToolOutput | 否 | 工具执行结果 | |\n", + "| +tool_call_id | String | 否 | 工具调用ID | |\n", + "| +output | String | 否 | 工具输出 | |\n", + "\n", + "`Tool`与`Function`是本地组件的描述,类型为object,其定义如下:\n", + "\n", + "```python\n", + "class Tool(BaseModel):\n", + " type: str = \"function\"\n", + " function: Function = Field(..., description=\"工具信息\")\n", + "\n", + "class Function(BaseModel):\n", + " name: str = Field(..., description=\"工具名称\")\n", + " description: str = Field(..., description=\"工具描述\")\n", + " parameters: dict = Field(..., description=\"工具参数, json_schema格式\")\n", + "```\n", + "\n", + "`ToolOutput`是本地组件的执行结果,需要再次上传到Agent,参与思考,类型为object,其定义如下:\n", + "```python\n", + "class ToolOutput(BaseModel):\n", + " tool_call_id: str = Field(..., description=\"工具调用ID\")\n", + " output: str = Field(..., description=\"工具输出\")\n", + "\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 4、ToolCall的第一个例子\n", + "\n", + "我们继续以上文中提到的查找张三生日为例,看一下完整的流程是怎么样的\n", + "\n", + "### 前置工作,在AppBuilder平台上创建一个白板应用(可以跳过)\n", + "\n", + "网页链接:https://appbuilder.cloud.baidu.com/\n", + "\n", + "注册后,进入控制台:https://console.bce.baidu.com/ai_apaas/dialogHome\n", + "\n", + "点击左上角的【创建应用】-> 【AI自动配置】,我们输入以下Prompt,自动生成Agent:`你是智能问题解决者,自动集成多种工具组件,解决用户各类问题`\n", + "\n", + "\"drawing\"\n", + "\n", + "最终生成的Agent长这个样子:\n", + "\n", + "\"drawing\"\n", + "\n", + "而后点击【发布】,分别在控制台的左侧【个人空间】获取`app_id`,在【我的密钥】获取`APPBUILDER_TOEN`后,就可以开始后续的操作了。\n", + "\n", + "当然,下面的示例代码中,我们已经提供了可以直接运行的试用Token与App,你可以直接上手运行" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "vscode": { + "languageId": "plaintext" + } + }, + "outputs": [], + "source": [ + "import os\n", + "import appbuilder\n", + "\n", + "# AppBuilder Token,此处为试用Token,速度Quota有限制,正式使用替换为您个人的Token\n", + "os.environ[\"APPBUILDER_TOKEN\"] = \"bce-v3/ALTAK-n5AYUIUJMarF7F7iFXVeK/1bf65eed7c8c7efef9b11388524fa1087f90ea58\"\n", + "\n", + "# 应用为:智能问题解决者\n", + "app_id = \"b9473e78-754b-463a-916b-f0a9097a8e5f\"\n", + "app_client = appbuilder.AppBuilderClient(app_id)\n", + "conversation_id = app_client.create_conversation()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "首次提问一个问题,应用不具备该能力,通过回答可以印证\n", + "\n", + "- 由于并没有关于张三同学的信息,所以Agent无法实现查询" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "vscode": { + "languageId": "plaintext" + } + }, + "outputs": [], + "source": [ + "message_1 = app_client.run(\n", + " conversation_id=conversation_id,\n", + " query=\"请问本公司的张三同学的生日是哪天?\",\n", + ")\n", + "print(\"Agent第一次回答: {}\".format(message_1.content.answer))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**output**\n", + "```\n", + "Agent第一次回答: 为了回答这个问题,我们首先需要明确几个关键点:\n", + "\n", + "1. **问题理解**:\n", + " - 需要确定的是“张三同学的生日”。\n", + "\n", + "2. **工具选择**:\n", + " - 由于问题涉及的是特定个人的信息(张三的生日),这通常不是通过工具或系统查询能得到的,而是需要通过公司内部的人事记录或直接询问张三本人来获取。\n", + "\n", + "3. **解决方案生成**:\n", + " - **步骤一**:首先,尝试访问公司的人事系统或员工档案,看是否有张三的生日信息记录。\n", + " - **步骤二**:如果人事系统或员工档案中没有相关信息,或者你不具备访问权限,那么可以考虑直接询问张三本人或其同事,看是否有人知道他的生日。\n", + " - **步骤三**:如果以上方法都不可行,还可以尝试联系公司的人力资源部门,看他们是否能提供相关信息。\n", + "\n", + "4. **注意事项**:\n", + " - 在尝试获取张三的生日信息时,要确保遵守公司的隐私政策和相关法律法规,不要侵犯张三的隐私权。\n", + " - 如果张三不愿意透露他的生日信息,应尊重他的选择,并停止进一步询问。\n", + "\n", + "5. **可能遇到的问题**:\n", + " - 人事系统或员工档案中可能没有张三的生日信息。\n", + " - 张三或其同事可能不愿意透露生日信息。\n", + " - 人力资源部门可能因隐私政策而无法提供相关信息。\n", + "\n", + "综上所述,要确定张三的生日,最直接且尊重隐私的方法是直接询问张三本人,或者通过公司正式渠道(如人力资源部门)在遵守隐私政策的前提下进行查询。\n", + "```\n", + "\n", + "\n", + "##### 赋予应用一个本地查询组件能力\n", + "\n", + "- 这里我们使用info_dict模拟一个数据库查询的返回结果" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "vscode": { + "languageId": "plaintext" + } + }, + "outputs": [], + "source": [ + "def get_person_infomation(name: str):\n", + " info_dict = {\n", + " \"张三\": \"1980年1月1日\",\n", + " \"李四\": \"1975年12月31日\",\n", + " \"刘伟\": \"1990年12月30日\"\n", + " }\n", + "\n", + " if name in info_dict:\n", + " return f\"您要查找的{name}的生日是:{info_dict[name]}\"\n", + " else:\n", + " return f\"您要查找的{name}的信息我们暂未收录,请联系管理员添加。\"\n", + " \n", + "# 创建工具的描述:json_schema格式\n", + "tools = [\n", + " {\n", + " \"type\": \"function\",\n", + "\"function\": {\n", + " \"name\": \"get_person_infomation\",\n", + " \"description\": \"查找公司内指定人员的信息\",\n", + " \"parameters\": {\n", + " \"type\": \"object\",\n", + " \"properties\": {\n", + " \"name\": {\n", + " \"type\": \"string\",\n", + " \"description\": \"人员名称,例如:张三、李四\",\n", + " },\n", + " },\n", + " \"required\": [\"name\"],\n", + " },\n", + "},\n", + " }\n", + "]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- 现在我们已经完成了本地tool组件的设计,接下来我们将tool的功能赋予Client应用" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "vscode": { + "languageId": "plaintext" + } + }, + "outputs": [], + "source": [ + "message_2 = app_client.run(\n", + " conversation_id=conversation_id,\n", + " query=\"请问本公司的张三同学的生日是哪天?\",\n", + " tools=tools\n", + ")\n", + "print(\"Agent的中间思考过程:\")\n", + "print(message_2.content.events[-1].model_dump_json(indent=4))\n", + "print(\"Agent思考结束,等待我们上传本地结果\\n\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**output**\n", + "这部分输出为Client应用的思考过程\n", + "```\n", + "Agent的中间思考过程:\n", + "{\n", + " \"code\": 0,\n", + " \"message\": \"\",\n", + " \"status\": \"interrupt\",\n", + " \"event_type\": \"Interrupt\",\n", + " \"content_type\": \"contexts\",\n", + " \"detail\": {\n", + " \"text\": {\n", + " \"function_call\": {\n", + " \"thought\": \"用户想要查询公司内张三同学的生日信息,这个需求很明确,且背景信息也足够。我可以使用get_person_infomation工具来查找张三的生日信息。\",\n", + " \"name\": \"get_person_infomation\",\n", + " \"arguments\": {\n", + " \"name\": \"张三\"\n", + " },\n", + " \"usage\": {\n", + " \"prompt_tokens\": 697,\n", + " \"completion_tokens\": 87,\n", + " \"total_tokens\": 784,\n", + " \"name\": \"ERNIE-4.0-Turbo-8K\",\n", + " \"type\": \"plan\"\n", + " },\n", + " \"tool_call_id\": \"c23309f7-e24a-4476-85e2-3ef9cfd4f6ed\"\n", + " },\n", + " \"used_tool\": []\n", + "...\n", + " ]\n", + "}\n", + "Agent思考结束,等待我们上传本地结果\n", + "```\n", + "\n", + "- 大模型下发了调用本地函数的参数,我们使用这个参数调用本地函数" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "vscode": { + "languageId": "plaintext" + } + }, + "outputs": [], + "source": [ + "tool_call = message_2.content.events[-1].tool_calls[-1]\n", + "tool_call_id = tool_call.id\n", + "tool_call_argument = tool_call.function.arguments\n", + "local_func_result = get_person_infomation(**tool_call_argument)\n", + "print(\"local_func_result: {}\\n\".format(local_func_result))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**output**\n", + "```\n", + "local_func_result: 您要查找的张三的生日是:1980年1月1日\n", + "```\n", + "\n", + "- 向应用返回本地运行的结果,完成本地函数toolcall调用" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "vscode": { + "languageId": "plaintext" + } + }, + "outputs": [], + "source": [ + "message_3 = app_client.run(\n", + " conversation_id=conversation_id,\n", + " tool_outputs=[{\n", + " \"tool_call_id\": tool_call_id,\n", + " \"output\": local_func_result\n", + " }]\n", + ")\n", + "print(\"Agent 拥有了本地函数调用能力后,回答是: {}\".format(message_3.content.answer))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**output**\n", + "```\n", + "Agent的中间思考过程:\n", + "{\n", + " \"code\": 0,\n", + " \"message\": \"\",\n", + " \"status\": \"interrupt\",\n", + " \"event_type\": \"Interrupt\",\n", + " \"content_type\": \"contexts\",\n", + " \"detail\": {\n", + " \"text\": {\n", + " \"function_call\": {\n", + " \"thought\": \"用户想要查询公司内张三同学的生日信息,这个需求很明确,且背景信息也足够。我可以使用get_person_infomation工具来查找张三的生日信息。\",\n", + " \"name\": \"get_person_infomation\",\n", + " \"arguments\": {\n", + " \"name\": \"张三\"\n", + " },\n", + " \"usage\": {\n", + " \"prompt_tokens\": 697,\n", + " \"completion_tokens\": 87,\n", + " \"total_tokens\": 784,\n", + " \"name\": \"ERNIE-4.0-Turbo-8K\",\n", + " \"type\": \"plan\"\n", + " },\n", + " \"tool_call_id\": \"c23309f7-e24a-4476-85e2-3ef9cfd4f6ed\"\n", + " },\n", + " \"used_tool\": []\n", + "...\n", + " ]\n", + "}\n", + "Agent思考结束,等待我们上传本地结果\n", + "\n", + "Output is truncated. View as a scrollable element or open in a text editor. Adjust cell output settings...\n", + "\n", + "local_func_result: 您要查找的张三的生日是:1980年1月1日\n", + "\n", + "Agent 拥有了本地函数调用能力后,回答是: # 解决方案\n", + "\n", + "## 问题分析\n", + "\n", + "用户想要查询公司内张三同学的生日信息。这是一个明确且具体的需求,我们可以通过`get_person_infomation`工具来获取这一信息。\n", + "\n", + "## 工具运用\n", + "\n", + "1. **工具选择**:`get_person_infomation`\n", + "2. **参数设置**:\n", + "\n", + "\t* `name`:张三\n", + "\n", + "3. **执行结果**:张三的生日是1980年1月1日。\n", + "\n", + "## 解决方案步骤\n", + "\n", + "1. 使用`get_person_infomation`工具,并设置参数`name`为“张三”。\n", + "2. 等待工具执行,并获取张三的生日信息。\n", + "3. 将获取到的生日信息(1980年1月1日)告知用户。\n", + "\n", + "## 注意事项\n", + "\n", + "* 确保在使用`get_person_infomation`工具时,输入的姓名与公司内部记录的姓名完全一致,以避免查询错误。\n", + "* 如果工具返回“未找到”或类似结果,请检查姓名是否有误或联系公司人事部门确认信息。\n", + "\n", + "通过上述步骤,我们可以准确地回答用户的问题,并提供张三的生日信息。\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 5、ToolCal第二个例子-调用本地工具并且代码更简洁\n", + "\n", + "我们可以使用AppBuilderClient应用来执行tool_call操作,完成指定的命令,但是需要自己配置client的思考与运行流程,较为繁琐。SDK提供了使用AppBuilderEventHandler简化tool_call操作的功能\n", + "\n", + "##### 配置运行环境&导入Client应用" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import appbuilder\n", + "\n", + "\n", + "# AppBuilder Token,此处为试用Token\n", + "os.environ[\"APPBUILDER_TOKEN\"] = \"bce-v3/ALTAK-n5AYUIUJMarF7F7iFXVeK/1bf65eed7c8c7efef9b11388524fa1087f90ea58\"\n", + "\n", + "# 应用为:智能问题解决者\n", + "app_id = \"b9473e78-754b-463a-916b-f0a9097a8e5f\"\n", + "app_client = appbuilder.AppBuilderClient(app_id)\n", + "conversation_id = app_client.create_conversation()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### 继承AppBuilderEventHandler类,并实现针对各类型event的处理方法" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from appbuilder.core.console.appbuilder_client.event_handler import AppBuilderEventHandler\n", + "class MyEventHandler(AppBuilderEventHandler):\n", + " def execute_local_command(self, cmd: str):\n", + " import subprocess\n", + " try:\n", + " result = subprocess.check_output(cmd, shell=True).decode(\"utf-8\")\n", + " if result.strip() == \"\":\n", + " return \"命令执行成功,无返回值\"\n", + " return result\n", + " except Exception as e:\n", + " return str(e)\n", + " \n", + " def interrupt(self, run_context, run_response):\n", + " thought = run_context.current_thought\n", + " # 绿色打印\n", + " print(\"\\033[1;32m\", \"-> Agent 中间思考: \", thought, \"\\033[0m\")\n", + "\n", + " tool_output = []\n", + " for tool_call in run_context.current_tool_calls:\n", + " tool_call_id = tool_call.id\n", + " tool_res = self.execute_local_command(\n", + " **tool_call.function.arguments)\n", + " # 蓝色打印\n", + " print(\"\\033[1;34m\", \"-> 本地ToolCall结果: \\n\", tool_res, \"\\033[0m\\n\")\n", + " tool_output.append(\n", + " {\n", + " \"tool_call_id\": tool_call_id,\n", + " \"output\": tool_res\n", + " }\n", + " )\n", + " return tool_output\n", + " \n", + " def success(self, run_context, run_response):\n", + " print(\"\\n\\033[1;31m\",\"-> Agent 非流式回答: \\n\", run_response.answer, \"\\033[0m\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### 定义本地的tools工具\n", + "\n", + "通过`subprocess.check_output`方法,可以在终端中执行命令,并返回执行结果" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "tools = [\n", + " {\n", + " \"type\": \"function\",\n", + " \"function\": {\n", + " \"name\": \"execute_local_command\",\n", + " \"description\": \"可以在bash环境中,执行输入的指令,注意,一次只能执行一个原子命令。例如:ls\",\n", + " \"parameters\": {\n", + " \"type\": \"object\",\n", + " \"properties\": {\n", + " \"cmd\": {\n", + " \"type\": \"string\",\n", + " \"description\": \"需要执行的指令\",\n", + " },\n", + " },\n", + " \"required\": [\"cmd\"],\n", + " },\n", + " },\n", + " }\n", + "]\n", + "\n", + "with app_client.run_with_handler(\n", + " conversation_id = conversation_id,\n", + " query = \"请问当前文件夹下有哪些文件?如果没有test.txt文件,请新建一个test.txt文件,内容为:Hello World!\",\n", + " tools = tools,\n", + " event_handler = MyEventHandler(),\n", + " ) as run:\n", + " run.until_done()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**output**\n", + "```\n", + " -> Agent 中间思考: 首先,我需要使用execute_local_command工具来执行'ls'命令,列出当前文件夹下的所有文件。然后,我需要检查输出中是否存在test.txt文件。如果不存在,我将再次使用execute_local_command工具来执行'echo \"Hello World\" > test.txt'命令,以创建并写入test.txt文件。 \n", + " -> 本地ToolCall结果: \n", + " multi_tool_call.ipynb\n", + "multi_tool_call.py\n", + "multi_tool_call_with_handler.ipynb\n", + "multi_tool_call_with_handler.py\n", + "sdk_ knowledgebase.ipynb\n", + "sdk_trace.ipynb\n", + "simple_tool_call.ipynb\n", + "simple_tool_call.py\n", + "tmp.log\n", + "黑神话(悟空).pdf\n", + " \n", + "\n", + " -> Agent 中间思考: 根据execute_local_command工具的返回结果,当前文件夹下并没有test.txt文件。因此,我需要使用execute_local_command工具来执行'echo \"Hello World\" > test.txt'命令,以创建并写入test.txt文件。 \n", + " -> 本地ToolCall结果: \n", + " 命令执行成功,无返回值 \n", + "\n", + "\n", + " -> Agent 非流式回答: \n", + " 当前文件夹下的文件包括:\n", + "\n", + "- multi_tool_call.ipynb\n", + "- multi_tool_call.py\n", + "- multi_tool_call_with_handler.ipynb\n", + "...\n", + "- tmp.log\n", + "- 黑神话(悟空).pdf\n", + "\n", + "经过检查,发现当前文件夹下**不存在**test.txt文件。因此,已经为您新建了一个test.txt文件,并写入了内容“Hello World!”。 \n", + "```\n", + "\n", + "- 使用AppBuilderEventHandler架构可以简化client的交互方式" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 6、项目总结\n", + "\n", + "本项目通过多个知识点的学习,以及两个使用AppBuilder-SDK的实操,最终完成了一个支持ToolCall AIAgent的构建。\n", + "\n", + "- 理论学习:了解AIAgent的基础知识\n", + "- 上手实操:深入了解Agent中的FunctionCall运行流程\n", + "- 上手实操:入门百度智能云千帆AppBuilder,在十分钟内打造一个个性化AIAgent\n", + "- 上手实操:使用AppBuilder-SDK打造一个端云组件联动的进阶Agent\n", + "\n", + "\n", + "希望您可以不吝`Star`,给`AppBuilder-SDK`一些鼓励,期待您的`PR`,一起共建AIAgent生态。\n", + "\n", + "Github地址:https://github.com/baidubce/app-builder\n", + "\n", + "\"drawing\"\n", + "\n", + "最后,您也可以进入`AppBuilder-SDK`的WX交流群,和大家一起交流AppBuilder使用及开发心得。\n", + "\n", + "\"drawing\"" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/cookbooks/end2end_application/console_dataset.ipynb b/cookbooks/end2end_application/rag/console_dataset.ipynb similarity index 100% rename from cookbooks/end2end_application/console_dataset.ipynb rename to cookbooks/end2end_application/rag/console_dataset.ipynb diff --git "a/cookbooks/end2end_application/image/dataset\347\244\272\344\276\213.png" "b/cookbooks/end2end_application/rag/image/dataset\347\244\272\344\276\213.png" similarity index 100% rename from "cookbooks/end2end_application/image/dataset\347\244\272\344\276\213.png" rename to "cookbooks/end2end_application/rag/image/dataset\347\244\272\344\276\213.png" diff --git a/docs/quick_start/changelog.md b/docs/quick_start/changelog.md index f62c9d471..2b8584cda 100644 --- a/docs/quick_start/changelog.md +++ b/docs/quick_start/changelog.md @@ -9,7 +9,7 @@ * 模型列表获取:与千帆大模型平台模型名打通,可动态获取当前账号模型名,并在组件中使用[获取模型列表](/docs/basic_module/get_model_list.md) * 可通过官方镜像开发和运行实例代码[二次开发](/docs/develop_guide/README.md) * **2024.02.27 v0.4.0版本发布** [Release Note](https://github.com/baidubce/app-builder/releases/tag/0.4.0) - * AppBuilder Console SDK发布[知识集合Cookbook](/cookbooks/end2end_application/console_dataset.ipynb),[RAG调用Cookbook](/cookbooks/end2end_application/rag/rag.ipynb) + * AppBuilder Console SDK发布[知识集合Cookbook](/cookbooks/end2end_application/rag/console_dataset.ipynb),[RAG调用Cookbook](/cookbooks/end2end_application/rag/rag.ipynb) * 大模型组件新增:Excel2Figure(基于Excel信息画图表) * AI能力引擎组件新增&更新:植物识别、动物识别、表格文字识别V2、手写文字识别、二维码识别、身份证混贴识别、文档矫正识别、图像内容理解、流式TTS * AgentRuntime:新增[Cookbook](/cookbooks/components/agent_runtime.ipynb) From 8241d1be1b3f2cbfddcfa6ebc5ce681e900307b9 Mon Sep 17 00:00:00 2001 From: Chengmo Date: Tue, 10 Sep 2024 20:24:54 +0800 Subject: [PATCH 05/10] Update doc for version 0.9.4 (#500) * update version * update --- README.md | 2 +- README_en.md | 2 +- README_ja.md | 2 +- appbuilder/__init__.py | 2 +- docs/quick_start/changelog.md | 5 ++++- go/appbuilder/config.go | 2 +- .../baidubce/appbuilder/base/utils/http/HttpClient.java | 8 ++++---- setup.py | 2 +- 8 files changed, 14 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 61888f8ee..df505893a 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ AppBuilder-SDK不仅提供了百度智能云提供的基础能力组件,同时 ## 如何安装AppBuilder-SDK -#### 百度智能云千帆AppBuilder-SDK 最新版本 0.9.3 (2024-08-20) +#### 百度智能云千帆AppBuilder-SDK 最新版本 0.9.4 (2024-09-10) 百度智能云千帆AppBuilder-SDK 更新记录&最新特性请查阅我们的[版本说明](/docs/quick_start/changelog.md) diff --git a/README_en.md b/README_en.md index e776fce7c..3a2eccccd 100644 --- a/README_en.md +++ b/README_en.md @@ -47,7 +47,7 @@ Baidu AI Cloud Qianfan AppBuilder-SDK offers the following essential features fo ## How to install? -#### The latest version of Baidu AI Cloud Qianfan AppBuilder SDK is 0.9.3 (2024-08-20) +#### The latest version of Baidu AI Cloud Qianfan AppBuilder SDK is 0.9.4 (2024-09-10) Baidu AI Cloud Qianfan AppBuilder SDK ReleaseNote please refer to our [version description](/docs/quick_start/changelog.md) diff --git a/README_ja.md b/README_ja.md index 87e5f6566..6a92fb442 100644 --- a/README_ja.md +++ b/README_ja.md @@ -44,7 +44,7 @@ Baidu AI Cloud Qianfan AppBuilder-SDKは、AIアプリケーション開発者 ## どのようにインストールしますか? -#### Baidu AI Cloud Qianfan AppBuilder SDKの最新バージョンは0.9.3(2024-08-20)です +#### Baidu AI Cloud Qianfan AppBuilder SDKの最新バージョンは0.9.4(2024-09-10)です Baidu AI Cloud Qianfan AppBuilder SDKのリリースノートについては、[バージョン説明](/docs/quick_start/changelog.md)をご覧ください。 diff --git a/appbuilder/__init__.py b/appbuilder/__init__.py index 68619946b..e823832f8 100644 --- a/appbuilder/__init__.py +++ b/appbuilder/__init__.py @@ -13,7 +13,7 @@ # limitations under the License. -__version__ = '0.9.3' +__version__ = '0.9.4' import os import sys diff --git a/docs/quick_start/changelog.md b/docs/quick_start/changelog.md index 2b8584cda..4b54b44d1 100644 --- a/docs/quick_start/changelog.md +++ b/docs/quick_start/changelog.md @@ -51,4 +51,7 @@ * 新增`PPTGenerationFromFile`、`PPTGenerationFromInstruction`、`PPTGenerationFromPaper`三个组件 * **2024.08.20 v0.9.3版本发布** [ReleaseNote](https://github.com/baidubce/app-builder/releases/tag/0.9.3) * Text2Image组件接口及效果更新 - * ImageUnderstand组件接口及效果更新 \ No newline at end of file + * ImageUnderstand组件接口及效果更新 +* **2024.09.04 v0.9.4版本发布** [ReleaseNote](https://github.com/baidubce/app-builder/releases/tag/0.9.4) + * AppBuilderClient新增tool_choice / end_user_id功能 + * 增加VScode setting,优化开发者使用体验 \ No newline at end of file diff --git a/go/appbuilder/config.go b/go/appbuilder/config.go index c33e55cf5..c43cbe913 100644 --- a/go/appbuilder/config.go +++ b/go/appbuilder/config.go @@ -124,7 +124,7 @@ func (t *SDKConfig) authHeader() http.Header { platform = "unknown" } header.Set("X-Appbuilder-Origin", "appbuilder_sdk") - header.Set("X-Appbuilder-Sdk-Config", "{\"appbuilder_sdk_version\":\"0.9.0\",\"appbuilder_sdk_language\":\"go\",\"appbuilder_sdk_platform\":\""+platform+"\"}") + header.Set("X-Appbuilder-Sdk-Config", "{\"appbuilder_sdk_version\":\"0.9.4\",\"appbuilder_sdk_language\":\"go\",\"appbuilder_sdk_platform\":\""+platform+"\"}") header.Set("X-Appbuilder-Request-Id", uuid.New().String()) return header } diff --git a/java/src/main/java/com/baidubce/appbuilder/base/utils/http/HttpClient.java b/java/src/main/java/com/baidubce/appbuilder/base/utils/http/HttpClient.java index 73f4f188a..34880ca3f 100644 --- a/java/src/main/java/com/baidubce/appbuilder/base/utils/http/HttpClient.java +++ b/java/src/main/java/com/baidubce/appbuilder/base/utils/http/HttpClient.java @@ -105,7 +105,7 @@ public ClassicHttpRequest createPostRequest(String url, HttpEntity entity) { ? System.getenv("APPBUILDER_SDK_PLATFORM") : "unknown"; httpPost.setHeader("X-Appbuilder-Sdk-Config", - "{\"appbuilder_sdk_version\":\"0.9.2\",\"appbuilder_sdk_language\":\"java\",\"appbuilder_sdk_platform\":\"" + "{\"appbuilder_sdk_version\":\"0.9.4\",\"appbuilder_sdk_language\":\"java\",\"appbuilder_sdk_platform\":\"" + platform + "\"}"); httpPost.setHeader("X-Appbuilder-Request-Id", java.util.UUID.randomUUID().toString()); httpPost.setEntity(entity); @@ -134,7 +134,7 @@ public ClassicHttpRequest createPostRequestV2(String url, HttpEntity entity) { ? System.getenv("APPBUILDER_SDK_PLATFORM") : "unknown"; httpPost.setHeader("X-Appbuilder-Sdk-Config", - "{\"appbuilder_sdk_version\":\"0.9.2\",\"appbuilder_sdk_language\":\"java\",\"appbuilder_sdk_platform\":\"" + "{\"appbuilder_sdk_version\":\"0.9.4\",\"appbuilder_sdk_language\":\"java\",\"appbuilder_sdk_platform\":\"" + platform + "\"}"); httpPost.setHeader("X-Appbuilder-Request-Id", java.util.UUID.randomUUID().toString()); httpPost.setEntity(entity); @@ -158,7 +158,7 @@ public ClassicHttpRequest createGetRequestV2(String url, Map map ? System.getenv("APPBUILDER_SDK_PLATFORM") : "unknown"; httpGet.setHeader("X-Appbuilder-Sdk-Config", - "{\"appbuilder_sdk_version\":\"0.9.2\",\"appbuilder_sdk_language\":\"java\",\"appbuilder_sdk_platform\":\"" + "{\"appbuilder_sdk_version\":\"0.9.4\",\"appbuilder_sdk_language\":\"java\",\"appbuilder_sdk_platform\":\"" + platform + "\"}"); httpGet.setHeader("X-Appbuilder-Request-Id", java.util.UUID.randomUUID().toString()); String headers = "headers: \n"; @@ -181,7 +181,7 @@ public ClassicHttpRequest createDeleteRequestV2(String url, Map ? System.getenv("APPBUILDER_SDK_PLATFORM") : "unknown"; httpDelete.setHeader("X-Appbuilder-Sdk-Config", - "{\"appbuilder_sdk_version\":\"0.9.2\",\"appbuilder_sdk_language\":\"java\",\"appbuilder_sdk_platform\":\"" + "{\"appbuilder_sdk_version\":\"0.9.4\",\"appbuilder_sdk_language\":\"java\",\"appbuilder_sdk_platform\":\"" + platform + "\"}"); httpDelete.setHeader("X-Appbuilder-Request-Id", java.util.UUID.randomUUID().toString()); String headers = "headers: \n"; diff --git a/setup.py b/setup.py index 5d72a0360..85f7e550c 100755 --- a/setup.py +++ b/setup.py @@ -39,7 +39,7 @@ setup( name="appbuilder-sdk", # NOTE(chengmo): 修改此版本号时,请注意同时修改 __init__.py 中的 __version__ - version="0.9.3", + version="0.9.4", author="dongdaxiang", author_email="dongdaxiang@baidu.com", packages=packages, From 83f6615f4d56859b5f08addcb3a7b1ed5a2d4217 Mon Sep 17 00:00:00 2001 From: lolee_k Date: Tue, 10 Sep 2024 20:31:39 +0800 Subject: [PATCH 06/10] =?UTF-8?q?DocParser=E6=8A=A5=E9=94=99=E4=BC=98?= =?UTF-8?q?=E5=8C=96=20(#510)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/components/doc_parser/doc_parser.py | 69 +++++++++++++++---- 1 file changed, 55 insertions(+), 14 deletions(-) diff --git a/appbuilder/core/components/doc_parser/doc_parser.py b/appbuilder/core/components/doc_parser/doc_parser.py index a18cf0102..b26cdc6e0 100644 --- a/appbuilder/core/components/doc_parser/doc_parser.py +++ b/appbuilder/core/components/doc_parser/doc_parser.py @@ -26,7 +26,9 @@ from appbuilder.utils.logger_util import logger from appbuilder.core._client import HTTPClient from appbuilder.core.components.doc_parser.base import ParserConfig, ParseResult -from appbuilder.utils.trace.tracer_wrapper import components_run_trace, components_run_stream_trace +from appbuilder.utils.trace.tracer_wrapper import ( + components_run_trace, +) class DocParser(Component): @@ -46,6 +48,7 @@ class DocParser(Component): parse_result = parser(msg) """ + name: str = "doc_parser" tool_desc: Dict[str, Any] = {"description": "parse document content"} base_url: str = "/v1/bce/xmind/parser" @@ -61,16 +64,24 @@ def make_parse_result(self, response: Dict): """ 将解析结果的内容转化成ParseResult的结构 """ - para_nodes = response["para_nodes"] if response["para_nodes"] is not None else [] + para_nodes = ( + response["para_nodes"] if response["para_nodes"] is not None else [] + ) catalog = response["catalog"] if response["catalog"] is not None else [] pdf_data = response["pdf_data"] title_node_ids = [title["node_id"] for title in catalog] if catalog else [] page_contents = [] for content in response["file_content"]: - page_content = {"page_num": content["page_num"], "page_width": int(content["page_size"]["width"]), - "page_height": int(content["page_size"]["height"]), "page_angle": int(content["page_angle"]), - "page_type": content["page_content"]["type"], "page_layouts": [], "page_titles": [], - "page_tables": []} + page_content = { + "page_num": content["page_num"], + "page_width": int(content["page_size"]["width"]), + "page_height": int(content["page_size"]["height"]), + "page_angle": int(content["page_angle"]), + "page_type": content["page_content"]["type"], + "page_layouts": [], + "page_titles": [], + "page_tables": [], + } for layout_item in content["page_content"]["layout"]: if layout_item["node_id"] in title_node_ids: continue @@ -81,8 +92,16 @@ def make_parse_result(self, response: Dict): table_row = [] for i in range(len(layout_item["matrix"])): cell_index = layout_item["matrix"][i] - row_markdown = "|" + "|".join( - [layout_item["children"][index]["text"] for index in set(cell_index)]) + "|" + row_markdown = ( + "|" + + "|".join( + [ + layout_item["children"][index]["text"] + for index in set(cell_index) + ] + ) + + "|" + ) if i != len(layout_item["matrix"]) - 1: row_markdown += "\n" table_row.append(row_markdown) @@ -94,9 +113,18 @@ def make_parse_result(self, response: Dict): for title in catalog: page_num = title["position"][0]["pageno"] page_contents[page_num]["page_titles"].append( - {"text": title["text"], "type": title["level"], "box": title["position"][0]["box"], - "node_id": title["node_id"]}) - parse_result = {"para_node_tree": para_nodes, "page_contents": page_contents, "pdf_data": pdf_data} + { + "text": title["text"], + "type": title["level"], + "box": title["position"][0]["box"], + "node_id": title["node_id"], + } + ) + parse_result = { + "para_node_tree": para_nodes, + "page_contents": page_contents, + "pdf_data": pdf_data, + } # parse_result = ParseResult.parse_obj(parse_result) return parse_result @@ -123,13 +151,26 @@ def run(self, input_message: Message, return_raw=False) -> Message: payload = json.dumps({"file_list": [param]}) headers = self.http_client.auth_header() headers["Content-Type"] = "application/json" - response = self.http_client.session.post(url=self.http_client.service_url(self.base_url), headers=headers, data=payload) + response = self.http_client.session.post( + url=self.http_client.service_url(self.base_url), + headers=headers, + data=payload, + ) self.http_client.check_response_header(response) self.http_client.check_response_json(response.json()) + request_id = self.http_client.response_request_id(response) response = response.json() if response["error_code"] != 0: - logger.error("doc parser service log_id {} err {}".format(response["log_id"], response["error_msg"])) - raise AppBuilderServerException(response["error_msg"]) + logger.error( + "doc parser service log_id {} err {}".format( + response["log_id"], response["error_msg"] + ) + ) + raise AppBuilderServerException( + request_id=request_id, + service_err_code=response["error_code"], + service_err_message=response["error_msg"], + ) parse_result = self.make_parse_result(response["result"]["result_list"][0]) if return_raw: parse_result["raw"] = response From e7fa14f6e7afdf509b30b1f95f5839492d11f1c7 Mon Sep 17 00:00:00 2001 From: Herbert <49505737+HerbertArthur@users.noreply.github.com> Date: Wed, 11 Sep 2024 15:15:49 +0800 Subject: [PATCH 07/10] =?UTF-8?q?=E6=96=B0=E5=A2=9ERerank=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=20(#502)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- appbuilder/__init__.py | 2 + .../components/retriever/reranker/README.md | 83 +++++++++++ .../components/retriever/reranker/__init__.py | 0 .../components/retriever/reranker/rerank.py | 129 ++++++++++++++++++ appbuilder/tests/test_rerank.py | 37 +++++ 5 files changed, 251 insertions(+) create mode 100644 appbuilder/core/components/retriever/reranker/README.md create mode 100644 appbuilder/core/components/retriever/reranker/__init__.py create mode 100644 appbuilder/core/components/retriever/reranker/rerank.py create mode 100644 appbuilder/tests/test_rerank.py diff --git a/appbuilder/__init__.py b/appbuilder/__init__.py index e823832f8..e5e4e998c 100644 --- a/appbuilder/__init__.py +++ b/appbuilder/__init__.py @@ -90,6 +90,7 @@ def get_default_header(): from .core.components.retriever.baidu_vdb.baiduvdb_retriever import BaiduVDBVectorStoreIndex from .core.components.retriever.baidu_vdb.baiduvdb_retriever import BaiduVDBRetriever from .core.components.retriever.baidu_vdb.baiduvdb_retriever import TableParams +from .core.components.retriever.reranker.rerank import Reranker from .core.components.ppt_generation_from_instruction.component import PPTGenerationFromInstruction from .core.components.ppt_generation_from_paper.component import PPTGenerationFromPaper from .core.components.ppt_generation_from_file.component import PPTGenerationFromFile @@ -184,6 +185,7 @@ def get_default_header(): "BaiduVDBVectorStoreIndex", "BaiduVDBRetriever", "TableParams", + "Reranker", "HallucinationDetection", 'DishRecognition', diff --git a/appbuilder/core/components/retriever/reranker/README.md b/appbuilder/core/components/retriever/reranker/README.md new file mode 100644 index 000000000..3e6c4b03d --- /dev/null +++ b/appbuilder/core/components/retriever/reranker/README.md @@ -0,0 +1,83 @@ +# 文本精排(Reranker) + +## 简介 +文本精排能力,将Query召回到的N个候选文本段落进行精排;保证与Query相关程度越高的文本段落排序越靠前,提升检索效果。 + +### 功能介绍 +文本精排(Reranker)用于检索排序,输入为Query和Top K个段落,输出为每个段落的排序得分;Query相关程度越高的文本段落排序越靠前,用于提升检索效果。 + +### 特色优势 +- 高效准确:基于开源模型[ +bce-reranker](https://huggingface.co/maidalun1020/bce-reranker-base_v1)的能力,提供高效且准确的内容检索功能。[百度云推理服务Api](https://cloud.baidu.com/doc/WENXINWORKSHOP/s/xlu216rqn) + +### 应用场景 +检索排序场景 + + +## 基本用法 + +以下是有关如何开始使用BESRetriever的代码示例: + +```python +import os +import appbuilder +from appbuilder import Message + +# 请前往千帆AppBuilder官网创建密钥,流程详见:https://cloud.baidu.com/doc/AppBuilder/s/Olq6grrt6#1%E3%80%81%E5%88%9B%E5%BB%BA%E5%AF%86%E9%92%A5 +os.environ["APPBUILDER_TOKEN"] = '...' + +reranker = appbuilder.Reranker() +ranked_1 = reranker("你好", ["他也好", "hello?"]) +print(ranked_1) + +# 使用上游的Message作为输入的代码示例 +ranked_2 = reranker(appbuilder.Message("你好"), appbuilder.Message(["他也好", "hello?"])) +print(ranked_2) +``` + +## 参数说明 +### 初始化参数说明: + +| 参数名称 | 参数类型 |是否必须 | 描述 | 示例值 | +|---------|--------|--------|------------------|---------------| +| model | str |是 | 指定底座模型的类型。当前仅支持 bce-reranker-base 作为可选值。若不指定,默认值为 bce-reranker-base。 | bce-reranker-base | + + +### 调用参数: + +| 参数名称 | 参数类型 | 是否必须 | 描述 | 示例值 | +|---------|--------|--------|------------------|---------------| +| query | str |是 | 精排Query,长度小于1600。 | "你好" | +| texts | List[str] | 是 | 精排输入段落,会对列表里的所有内容排序,最大长度为50. | ["你好", "世界"] | + + +### 响应示例 +#### 输入 +```python +query="你好", text=["他也好", "hello?"] +``` + +#### 响应 +```json +[ + { + "document": "hello?", + "relevance_score": 0.5651187300682068, + "index": 1 + }, + { + "document": "他也好", + "relevance_score": 0.47729530930519104, + "index": 0 + } +] +``` + + +### 错误码 + +无 + +## 更新记录和贡献 + +* reranker-base (2024-08) diff --git a/appbuilder/core/components/retriever/reranker/__init__.py b/appbuilder/core/components/retriever/reranker/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/appbuilder/core/components/retriever/reranker/rerank.py b/appbuilder/core/components/retriever/reranker/rerank.py new file mode 100644 index 000000000..d2b45f4fd --- /dev/null +++ b/appbuilder/core/components/retriever/reranker/rerank.py @@ -0,0 +1,129 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2024 Baidu, Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" Reranker 文本精排 +""" +from typing import Union, List + +from appbuilder.core.message import Message +from appbuilder.core.component import ComponentArguments, Component, Message +from appbuilder.core._exception import AppBuilderServerException, ModelNotSupportedException +from appbuilder.utils.trace.tracer_wrapper import components_run_trace + + +class RerankerArgs(ComponentArguments): + """配置""" + + text: Union[Message[str], str] + + +class Reranker(Component): + """ Reranker + + Examples: + + .. code-block:: python + + import os + import appbuilder + from appbuilder import Message + + os.environ["APPBUILDER_TOKEN"] = '...' + + reranker = appbuilder.Reranker() + ranked_1 = reranker("你好", ["他也好", "hello?"]) + print(ranked_1) + """ + name: str = "reranker" + version: str = "v1" + + meta = RerankerArgs + base_urls = { + 'bce-reranker-base' : "/api/v1/component/component/bce_reranker_base" + } + accepted_models = list(base_urls.keys()) + + def __init__(self, model="bce-reranker-base"): + """Reranker""" + + if model not in self.accepted_models: + raise ModelNotSupportedException(f"Model {model} not supported, only support {self.accepted_models}") + + if model in self.base_urls: + self.base_url = self.base_urls[model] + else: + raise ModelNotSupportedException(f"Model {model} is not yet supported, only support {self.base_urls.keys()}") + + super().__init__(self.meta) + + def _check_response_json(self, data: dict): + """ + check_response_json + """ + + self.http_client.check_response_json(data) + if "error_code" in data and "error_msg" in data: + raise AppBuilderServerException( + service_err_code=data['error_code'], + service_err_message=data['error_msg'], + ) + + def _request(self, payload: dict) -> dict: + """ + request to gateway + """ + headers = self.http_client.auth_header() + headers["Content-Type"] = "application/json" + resp = self.http_client.session.post( + url=self.http_client.service_url(self.base_url, "/"), + headers=headers, + json=payload, + ) + self.http_client.check_response_header(resp) + self._check_response_json(resp.json()) + + return resp.json() + + def _batch(self, query, texts: List[str]) -> List[dict]: + """ + batch run implement + """ + if len(texts) > 50: + raise ValueError(f'Rerank texts max nums must be lower than 50, but got {len(texts)}') + for v in texts: + if not isinstance(v, str): + raise ValueError(f'Rerank texts must be str, but got {v}') + + params = { + "inputs": { + "query": query, + "texts": texts + } + } + result = self._request(params) + result = result["result"] + return result + + @components_run_trace + def run(self, query: Union[Message[str], str], + texts: Union[Message[List[str]], List[str]]) -> Message[List[dict]]: + """ + run + """ + _query = query if isinstance(query, str) else query.content + _texts = texts if isinstance(texts, List) else texts.content + + return Message(self._batch(_query, _texts)) diff --git a/appbuilder/tests/test_rerank.py b/appbuilder/tests/test_rerank.py new file mode 100644 index 000000000..f0045112e --- /dev/null +++ b/appbuilder/tests/test_rerank.py @@ -0,0 +1,37 @@ +""" +test rerank +""" +import os +import time + +import unittest + +import appbuilder + + +@unittest.skipUnless(os.getenv("TEST_CASE", "UNKNOWN") == "CPU_SERIAL", "") +class TestReranker(unittest.TestCase): + + def setUp(self): + self.reranker = appbuilder.Reranker() + + def test_run(self): + ranked_1 = self.reranker("你好", ["他好", "hello?"]) + time.sleep(1) + ranked_2 = self.reranker(appbuilder.Message("你好"), appbuilder.Message(["他好", "hello?"])) + + self.assertEqual(ranked_1.content[0]["relevance_score"], ranked_2.content[0]["relevance_score"]) + self.assertEqual(ranked_1.content[1]["relevance_score"], ranked_2.content[1]["relevance_score"]) + + def test_not_support_model(self): + try: + _ = appbuilder.Reranker(model="foo") + except Exception as e: + from appbuilder.core._exception import ModelNotSupportedException + assert isinstance(e, ModelNotSupportedException) + msg = str(e) + assert "Model foo not supported" in msg + + +if __name__ == '__main__': + unittest.main() From 2aa73f0888d038a82ead38053ad483674205b855 Mon Sep 17 00:00:00 2001 From: userpj Date: Wed, 11 Sep 2024 15:24:33 +0800 Subject: [PATCH 08/10] AppbuilderClient.Run support ToolChoice && add end_user_id (#494) * AppbuilderClient support ToolChoice * add go&&java * update * AppbuilderClient.run support end_user_id * fix bug * update * update * update * update --- .../appbuilder_client/appbuilder_client.py | 32 ++++++----- .../console/appbuilder_client/data_class.py | 29 +++++++++- go/appbuilder/app_builder_client_data.go | 12 ++++ .../AppBuilderClientRunRequest.java | 57 +++++++++++++++++++ 4 files changed, 114 insertions(+), 16 deletions(-) diff --git a/appbuilder/core/console/appbuilder_client/appbuilder_client.py b/appbuilder/core/console/appbuilder_client/appbuilder_client.py index d781fc0bc..69f8cfc62 100644 --- a/appbuilder/core/console/appbuilder_client/appbuilder_client.py +++ b/appbuilder/core/console/appbuilder_client/appbuilder_client.py @@ -169,7 +169,7 @@ def upload_local_file(self, conversation_id, local_file_path: str) -> str: if len(conversation_id) == 0: raise ValueError("conversation_id is empty, you can run self.create_conversation to get a conversation_id") - + filepath = os.path.abspath(local_file_path) if not os.path.exists(filepath): raise FileNotFoundError(f"{filepath} does not exist") @@ -196,24 +196,28 @@ def run(self, conversation_id: str, stream: bool = False, tools: list[data_class.Tool] = None, tool_outputs: list[data_class.ToolOutput] = None, + tool_choice: data_class.ToolChoice = None, + end_user_id: str = None, **kwargs ) -> Message: r""" - 参数: - query (str: 必须): query内容 - conversation_id (str, 必须): 唯一会话ID,如需开始新的会话,请使用self.create_conversation创建新的会话 - file_ids(list[str], 可选): - stream (bool, 可选): 为True时,流式返回,需要将message.content.answer拼接起来才是完整的回答;为False时,对应非流式返回 - tools(list[data_class.Tools], 可选): 一个Tools组成的列表,其中每个Tools对应一个工具的配置, 默认为None - tool_outputs(list[data_class.ToolOutput], 可选): 工具输出列表,格式为list[ToolOutput], ToolOutputd内容为本地的工具执行结果,以自然语言/json dump str描述,默认为None - 返回: message (obj: `Message`): 对话结果. + 参数: + query (str: 必须): query内容 + conversation_id (str, 必须): 唯一会话ID,如需开始新的会话,请使用self.create_conversation创建新的会话 + file_ids(list[str], 可选): + stream (bool, 可选): 为True时,流式返回,需要将message.content.answer拼接起来才是完整的回答;为False时,对应非流式返回 + tools(list[data_class.Tools], 可选): 一个Tools组成的列表,其中每个Tools对应一个工具的配置, 默认为None + tool_outputs(list[data_class.ToolOutput], 可选): 工具输出列表,格式为list[ToolOutput], ToolOutputd内容为本地的工具执行结果,以自然语言/json dump str描述,默认为None + tool_choice(data_class.ToolChoice, 可选): 控制大模型使用组件的方式,默认为None + end_user_id (str, 可选): 用户ID,用于区分不同用户 + 返回: message (obj: `Message`): 对话结果. """ if len(conversation_id) == 0: raise ValueError( "conversation_id is empty, you can run self.create_conversation to get a conversation_id" ) - + if query == "" and (tool_outputs is None or len(tool_outputs) == 0): raise ValueError("AppBuilderClient Run API: query and tool_outputs cannot both be empty") @@ -224,7 +228,9 @@ def run(self, conversation_id: str, stream=True if stream else False, file_ids=file_ids, tools=tools, - tool_outputs=tool_outputs + tool_outputs=tool_outputs, + tool_choice=tool_choice, + end_user_id=end_user_id, ) headers = self.http_client.auth_header_v2() @@ -244,7 +250,7 @@ def run(self, conversation_id: str, out = data_class.AppBuilderClientAnswer() _transform(resp, out) return Message(content=out) - + def run_with_handler(self, conversation_id: str, query: str = "", @@ -263,7 +269,7 @@ def run_with_handler(self, stream=stream, **kwargs ) - + return event_handler @staticmethod diff --git a/appbuilder/core/console/appbuilder_client/data_class.py b/appbuilder/core/console/appbuilder_client/data_class.py index 4ed8e7a1b..a191c4918 100644 --- a/appbuilder/core/console/appbuilder_client/data_class.py +++ b/appbuilder/core/console/appbuilder_client/data_class.py @@ -22,7 +22,7 @@ class Function(BaseModel): name: str = Field(..., description="工具名称") description: str = Field(..., description="工具描述") parameters: dict = Field(..., description="工具参数, json_schema格式") - + class Tool(BaseModel): type: str = "function" function: Function = Field(..., description="工具信息") @@ -40,6 +40,28 @@ class ToolCall(BaseModel): type: str = Field("function", description="需要输出的工具调用的类型。就目前而言,这始终是function") function: FunctionCallDetail = Field(..., description="函数定义") + +class ToolChoiceFunction(BaseModel): + name: str = Field( + ..., + description="组件的英文名称(唯一标识),用户通过工作流完成自定义组件后,可在个人空间-组件下查看组件英文名称", + ) + input: dict = Field( + ..., + description="当组件没有入参或者必填的入参只有一个时可省略,必填的入参只有一个且省略时,使用query字段的值作为入参", + ) + + +class ToolChoice(BaseModel): + type: str = Field( + ..., + description="auto/function,auto表示由LLM自动判断调什么组件;function表示由用户指定调用哪个组件", + ) + function: Optional[ToolChoiceFunction] = Field( + ..., description="当type为function时,需要指定调用哪个组件" + ) + + class AppBuilderClientRequest(BaseModel): """会话请求参数 属性: @@ -56,6 +78,8 @@ class AppBuilderClientRequest(BaseModel): app_id: str tools: Optional[list[Tool]] = None tool_outputs: Optional[list[ToolOutput]] = None + tool_choice: Optional[ToolChoice] = None + end_user_id: Optional[str] = None class Usage(BaseModel): @@ -107,7 +131,7 @@ class AppBuilderClientResponse(BaseModel): message_id: str = "" is_completion: Optional[bool] = False content: list[OriginalEvent] = [] - + class TextDetail(BaseModel): """content_type=text,详情内容 @@ -286,4 +310,3 @@ class AppBuilderClientAppListResponse(BaseModel): request_id: str = Field("", description="请求ID") data: Optional[list[AppOverview]] = Field( [], description="应用概览列表") - \ No newline at end of file diff --git a/go/appbuilder/app_builder_client_data.go b/go/appbuilder/app_builder_client_data.go index 0ef8830ec..45f000432 100644 --- a/go/appbuilder/app_builder_client_data.go +++ b/go/appbuilder/app_builder_client_data.go @@ -48,9 +48,11 @@ type AppBuilderClientRunRequest struct { AppID string `json:"app_id"` Query string `json:"query"` Stream bool `json:"stream"` + EndUserID string `json:"end_user_id"` ConversationID string `json:"conversation_id"` Tools []Tool `json:"tools"` ToolOutputs []ToolOutput `json:"tool_outputs"` + ToolChoice ToolChoice `json:"tool_choice"` } type Tool struct { @@ -69,6 +71,16 @@ type ToolOutput struct { Output string `json:"output" description:"工具输出"` } +type ToolChoice struct { + Type string `json:"type"` + Function ToolChoiceFunction `json:"function"` +} + +type ToolChoiceFunction struct { + Name string `json:"name"` + Input map[string]interface{} `json:"input"` +} + type AgentBuilderRawResponse struct { RequestID string `json:"request_id"` Date string `json:"date"` diff --git a/java/src/main/java/com/baidubce/appbuilder/model/appbuilderclient/AppBuilderClientRunRequest.java b/java/src/main/java/com/baidubce/appbuilder/model/appbuilderclient/AppBuilderClientRunRequest.java index 7706fc746..54d0e1efe 100644 --- a/java/src/main/java/com/baidubce/appbuilder/model/appbuilderclient/AppBuilderClientRunRequest.java +++ b/java/src/main/java/com/baidubce/appbuilder/model/appbuilderclient/AppBuilderClientRunRequest.java @@ -10,9 +10,13 @@ public class AppBuilderClientRunRequest { private boolean stream; @SerializedName("conversation_id") private String conversationID; + @SerializedName("end_user_id") + private String endUserId; private Tool[] tools; @SerializedName("tool_outputs") private ToolOutput[] ToolOutputs; + @SerializedName("tool_choice") + private ToolChoice ToolChoice; public String getAppId() { return appId; @@ -46,6 +50,14 @@ public void setConversationID(String conversationID) { this.conversationID = conversationID; } + public String getEndUserId() { + return endUserId; + } + + public void setEndUserId(String endUserId) { + this.endUserId = endUserId; + } + public Tool[] getTools() { return tools; } @@ -62,6 +74,14 @@ public void setToolOutputs(ToolOutput[] toolOutputs) { this.ToolOutputs = toolOutputs; } + public ToolChoice getToolChoice() { + return ToolChoice; + } + + public void setToolChoice(ToolChoice toolChoice) { + this.ToolChoice = toolChoice; + } + public static class Tool { private String type; private Function function; @@ -79,6 +99,7 @@ public Function getFunction() { return function; } + public static class Function { private String name; private String description; @@ -122,4 +143,40 @@ public String getOutput() { return output; } } + + public static class ToolChoice { + private String type; + private Function function; + + public ToolChoice(String type, Function function) { + this.type=type; + this.function=function; + } + + public String getType() { + return type; + } + + public Function getFunction() { + return function; + } + + public static class Function { + private String name; + private Map input; + + public Function(String name, Map input) { + this.name = name; + this.input = input; + } + + public String getName() { + return name; + } + + public Map getInput() { + return input; + } + } + } } From a6b22ec4e07522e41c9d352024dd3b4dd4c8b6db Mon Sep 17 00:00:00 2001 From: userpj Date: Wed, 11 Sep 2024 16:04:45 +0800 Subject: [PATCH 09/10] =?UTF-8?q?=E5=A2=9E=E5=8A=A0ToolChoice=20java&&go?= =?UTF-8?q?=E5=8D=95=E6=B5=8B=20(#511)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go/appbuilder/app_builder_client_data.go | 4 +- go/appbuilder/app_builder_client_test.go | 54 +++++++++++++++++-- .../appbuilder/AppBuilderClientTest.java | 42 +++++++++++---- 3 files changed, 86 insertions(+), 14 deletions(-) diff --git a/go/appbuilder/app_builder_client_data.go b/go/appbuilder/app_builder_client_data.go index 45f000432..2b5724438 100644 --- a/go/appbuilder/app_builder_client_data.go +++ b/go/appbuilder/app_builder_client_data.go @@ -48,11 +48,11 @@ type AppBuilderClientRunRequest struct { AppID string `json:"app_id"` Query string `json:"query"` Stream bool `json:"stream"` - EndUserID string `json:"end_user_id"` + EndUserID *string `json:"end_user_id"` ConversationID string `json:"conversation_id"` Tools []Tool `json:"tools"` ToolOutputs []ToolOutput `json:"tool_outputs"` - ToolChoice ToolChoice `json:"tool_choice"` + ToolChoice *ToolChoice `json:"tool_choice"` } type Tool struct { diff --git a/go/appbuilder/app_builder_client_test.go b/go/appbuilder/app_builder_client_test.go index 2405ddc1e..b70e43dc7 100644 --- a/go/appbuilder/app_builder_client_test.go +++ b/go/appbuilder/app_builder_client_test.go @@ -69,13 +69,12 @@ func TestNewAppBuilderClient(t *testing.T) { func TestAppBuilderClientRunWithToolCall(t *testing.T) { os.Setenv("APPBUILDER_LOGLEVEL", "DEBUG") os.Setenv("APPBUILDER_LOGFILE", "") - os.Setenv("GATEWAY_URL_V2", "https://apaas-api-sandbox.baidu-int.com/") - config, err := NewSDKConfig("", "bce-v3/ALTAK-vGrDN4BvjP15rDrXBI9OC/6d435ece62ed09b396e1b051bd87869c11861332") + config, err := NewSDKConfig("", "") if err != nil { t.Fatalf("new http client config failed: %v", err) } - appID := "4d4b1b27-d607-4d2a-9002-206134217a9f" + appID := "" client, err := NewAppBuilderClient(appID, config) if err != nil { t.Fatalf("new AgentBuidler instance failed") @@ -160,3 +159,52 @@ func TestAppBuilderClientRunWithToolCall(t *testing.T) { fmt.Println("----------------answer-------------------") fmt.Println(totalAnswer) } + +func TestAppBuilderClientRunToolChoice(t *testing.T) { + os.Setenv("APPBUILDER_LOGLEVEL", "DEBUG") + os.Setenv("APPBUILDER_LOGFILE", "") + config, err := NewSDKConfig("", "") + if err != nil { + t.Fatalf("new http client config failed: %v", err) + } + + appID := "" + client, err := NewAppBuilderClient(appID, config) + if err != nil { + t.Fatalf("new AgentBuidler instance failed") + } + + conversationID, err := client.CreateConversation() + if err != nil { + t.Fatalf("create conversation failed: %v", err) + } + + input := make(map[string]interface{}) + input["city"] = "北京" + end_user_id := "go_user_id_0" + i, err := client.RunWithToolCall(AppBuilderClientRunRequest{ + ConversationID: conversationID, + AppID: appID, + Query: "你能干什么", + EndUserID: &end_user_id, + Stream: false, + ToolChoice: &ToolChoice{ + Type: "function", + Function: ToolChoiceFunction{ + Name: "WeatherQuery", + Input: input, + }, + }, + }) + + if err != nil { + fmt.Println("run failed: ", err) + } + + for answer, err := i.Next(); err == nil; answer, err = i.Next() { + for _, ev := range answer.Events { + evJSON, _ := json.Marshal(ev) + fmt.Println(string(evJSON)) + } + } +} diff --git a/java/src/test/java/com/baidubce/appbuilder/AppBuilderClientTest.java b/java/src/test/java/com/baidubce/appbuilder/AppBuilderClientTest.java index 409c48845..df96628ee 100644 --- a/java/src/test/java/com/baidubce/appbuilder/AppBuilderClientTest.java +++ b/java/src/test/java/com/baidubce/appbuilder/AppBuilderClientTest.java @@ -9,7 +9,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import javax.tools.Tool; import com.baidubce.appbuilder.model.appbuilderclient.AppBuilderClientIterator; import com.baidubce.appbuilder.model.appbuilderclient.AppBuilderClientResult; import com.baidubce.appbuilder.model.appbuilderclient.AppListRequest; @@ -100,11 +99,10 @@ public void AppBuilderClientRunFuncTest() throws IOException, AppBuilderServerEx required.add("location"); parameters.put("required", required); - AppBuilderClientRunRequest.Tool.Function func = - new AppBuilderClientRunRequest.Tool.Function(name, desc, parameters); - AppBuilderClientRunRequest.Tool tool = - new AppBuilderClientRunRequest.Tool("function", func); - request.setTools(new AppBuilderClientRunRequest.Tool[] {tool}); + AppBuilderClientRunRequest.Tool.Function func = new AppBuilderClientRunRequest.Tool.Function(name, desc, + parameters); + AppBuilderClientRunRequest.Tool tool = new AppBuilderClientRunRequest.Tool("function", func); + request.setTools(new AppBuilderClientRunRequest.Tool[] { tool }); AppBuilderClientIterator itor = builder.run(request); assertTrue(itor.hasNext()); @@ -119,9 +117,8 @@ public void AppBuilderClientRunFuncTest() throws IOException, AppBuilderServerEx request2.setAppId(appId); request2.setConversationID(conversationId); - AppBuilderClientRunRequest.ToolOutput output = - new AppBuilderClientRunRequest.ToolOutput(ToolCallID, "北京今天35度"); - request2.setToolOutputs(new AppBuilderClientRunRequest.ToolOutput[] {output}); + AppBuilderClientRunRequest.ToolOutput output = new AppBuilderClientRunRequest.ToolOutput(ToolCallID, "北京今天35度"); + request2.setToolOutputs(new AppBuilderClientRunRequest.ToolOutput[] { output }); AppBuilderClientIterator itor2 = builder.run(request2); assertTrue(itor2.hasNext()); while (itor2.hasNext()) { @@ -129,4 +126,31 @@ public void AppBuilderClientRunFuncTest() throws IOException, AppBuilderServerEx System.out.println(result); } } + + @Test + public void AppBuilderClientRunToolChoiceTest() throws IOException, AppBuilderServerException { + AppBuilderClient builder = new AppBuilderClient(appId); + String conversationId = builder.createConversation(); + assertNotNull(conversationId); + + AppBuilderClientRunRequest request = new AppBuilderClientRunRequest(); + request.setAppId(appId); + request.setConversationID(conversationId); + request.setQuery("你能干什么"); + request.setStream(false); + request.setEndUserId("java_test_user_0"); + Map input = new HashMap<>(); + input.put("city", "北京"); + AppBuilderClientRunRequest.ToolChoice.Function func = new AppBuilderClientRunRequest.ToolChoice.Function( + "WeatherQuery", input); + AppBuilderClientRunRequest.ToolChoice choice = new AppBuilderClientRunRequest.ToolChoice("function", func); + request.setToolChoice(choice); + + AppBuilderClientIterator itor = builder.run(request); + assertTrue(itor.hasNext()); + while (itor.hasNext()) { + AppBuilderClientResult result = itor.next(); + System.out.println(result); + } + } } From cb57e6bcdaa60f6881e664b475d738fbbba43414 Mon Sep 17 00:00:00 2001 From: userpj Date: Fri, 13 Sep 2024 11:40:32 +0800 Subject: [PATCH 10/10] =?UTF-8?q?jar=E5=8C=85=E3=80=81=E9=95=9C=E5=83=8F?= =?UTF-8?q?=E5=8D=87=E7=BA=A7=E5=88=B00.9.4=20(#514)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- appbuilder/utils/bce_deploy.py | 4 ++-- docs/develop_guide/README.md | 2 +- docs/quick_start/install.md | 10 +++++----- java/pom.xml | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/appbuilder/utils/bce_deploy.py b/appbuilder/utils/bce_deploy.py index bb7d4f2de..435cd4435 100644 --- a/appbuilder/utils/bce_deploy.py +++ b/appbuilder/utils/bce_deploy.py @@ -113,8 +113,8 @@ def build_user_data(self): + f"rm {self.tar_file_name}\\n" + f"chmod a+x {self.run_script_name}\\n" + "yum install -y docker\\n" - + "docker pull registry.baidubce.com/appbuilder/appbuilder-sdk-cloud:0.9.2\\n" - + f"docker run -itd --net=host -v /root/test:{workspace} --name appbuilder-sdk registry.baidubce.com/appbuilder/appbuilder-sdk-cloud:0.9.2 {workspace}/{self.run_script_name}" + + "docker pull registry.baidubce.com/appbuilder/appbuilder-sdk-cloud:0.9.4\\n" + + f"docker run -itd --net=host -v /root/test:{workspace} --name appbuilder-sdk registry.baidubce.com/appbuilder/appbuilder-sdk-cloud:0.9.4{workspace}/{self.run_script_name}" ) return user_data diff --git a/docs/develop_guide/README.md b/docs/develop_guide/README.md index 71688691c..4a2bb8b32 100644 --- a/docs/develop_guide/README.md +++ b/docs/develop_guide/README.md @@ -10,7 +10,7 @@ 当前已集成Python版本AppBuilder-SDK 0.7.1及相关依赖,方便开发者融入个人已有的大模型应用程序。此部分仍在不断建设中。 二次开发可以采用官方提供的开发镜像,便于快速安装各种依赖库。 ``` shell -docker pull registry.baidubce.com/appbuilder/appbuilder-sdk-devel:0.9.2 +docker pull registry.baidubce.com/appbuilder/appbuilder-sdk-devel:0.9.4 ``` ### 消息(Message) diff --git a/docs/quick_start/install.md b/docs/quick_start/install.md index 0c9ff55fc..fb1e0803a 100644 --- a/docs/quick_start/install.md +++ b/docs/quick_start/install.md @@ -16,20 +16,20 @@ pip install --upgrade appbuilder-sdk com.baidubce appbuilder - 0.9.2 + 0.9.4 ``` #### Gradle 对于Kotlin DSL,在build.gradle.kts的dependencies中添加依赖 ```kotlin -implementation("com.baidubce:appbuilder:0.9.2") +implementation("com.baidubce:appbuilder:0.9.4") ``` 对于Groovy DSL,在build.gradle的dependencies中添加依赖 ```groovy -implementation 'com.baidubce:appbuilder:0.9.2' +implementation 'com.baidubce:appbuilder:0.9.4' ``` #### 本地导入 -点击[链接](https://repo1.maven.org/maven2/com/baidubce/appbuilder/0.9.2/appbuilder-0.9.2.jar) 下载Jar包,将Jar包导入到项目目录下。 +点击[链接](https://repo1.maven.org/maven2/com/baidubce/appbuilder/0.9.4/appbuilder-0.9.4.jar) 下载Jar包,将Jar包导入到项目目录下。 ### Go (仅支持调用端到端应用) > 支持Go 1.18.1以上版本 @@ -40,5 +40,5 @@ go get github.com/baidubce/app-builder/go/appbuilder ### Docker (当前仅集成了Python版本AppBuilder-SDK) ``` shell -docker pull registry.baidubce.com/appbuilder/appbuilder-sdk-devel:0.9.2 +docker pull registry.baidubce.com/appbuilder/appbuilder-sdk-devel:0.9.4 ``` diff --git a/java/pom.xml b/java/pom.xml index 0689b3c6c..9ba6f8e69 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -6,7 +6,7 @@ com.baidubce appbuilder - 0.9.2 + 0.9.4 jar app-builder